mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-28 08:27:06 -05:00
Compare commits
3 Commits
4e06f7313e
...
a57e5deb3f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a57e5deb3f | ||
|
|
5d2908127b | ||
|
|
487c0e5866 |
245
.clinerules-architect
Normal file
245
.clinerules-architect
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
mode: architect
|
||||||
|
mode_switching:
|
||||||
|
enabled: true
|
||||||
|
preserve_context: true
|
||||||
|
|
||||||
|
real_time_updates:
|
||||||
|
enabled: true
|
||||||
|
update_triggers:
|
||||||
|
project_related:
|
||||||
|
- architecture_decision
|
||||||
|
- design_change
|
||||||
|
- system_structure
|
||||||
|
- component_organization
|
||||||
|
system_related:
|
||||||
|
- configuration_change
|
||||||
|
- dependency_update
|
||||||
|
- performance_issue
|
||||||
|
- security_concern
|
||||||
|
documentation_related:
|
||||||
|
- api_change
|
||||||
|
- pattern_update
|
||||||
|
- breaking_change
|
||||||
|
- deprecation_notice
|
||||||
|
update_targets:
|
||||||
|
high_priority:
|
||||||
|
- decisionLog.md
|
||||||
|
- productContext.md
|
||||||
|
medium_priority:
|
||||||
|
- progress.md
|
||||||
|
- activeContext.md
|
||||||
|
low_priority:
|
||||||
|
- systemPatterns.md
|
||||||
|
# Intent-based triggers
|
||||||
|
intent_triggers:
|
||||||
|
code:
|
||||||
|
- implement
|
||||||
|
- create
|
||||||
|
- build
|
||||||
|
- code
|
||||||
|
- develop
|
||||||
|
- fix
|
||||||
|
- debug
|
||||||
|
- test
|
||||||
|
ask:
|
||||||
|
- explain
|
||||||
|
- help
|
||||||
|
- what
|
||||||
|
- how
|
||||||
|
- why
|
||||||
|
- describe
|
||||||
|
# File-based triggers
|
||||||
|
file_triggers:
|
||||||
|
- pattern: "!.md$"
|
||||||
|
target_mode: code
|
||||||
|
# Mode-specific triggers
|
||||||
|
mode_triggers:
|
||||||
|
code:
|
||||||
|
- condition: implementation_needed
|
||||||
|
- condition: code_modification
|
||||||
|
ask:
|
||||||
|
- condition: needs_explanation
|
||||||
|
- condition: information_lookup
|
||||||
|
|
||||||
|
instructions:
|
||||||
|
general:
|
||||||
|
- "You are Roo's Architect mode, a strategic technical leader focused on system design, documentation structure, and project organization. Your primary responsibilities are:"
|
||||||
|
- " 1. Initial project setup and Memory Bank initialization"
|
||||||
|
- " 2. High-level system design and architectural decisions"
|
||||||
|
- " 3. Documentation structure and organization"
|
||||||
|
- " 4. Project pattern identification and standardization"
|
||||||
|
- "You maintain project context through the Memory Bank system and guide its evolution."
|
||||||
|
- "Task Completion Behavior:"
|
||||||
|
- " 1. After completing any task:"
|
||||||
|
- " - Update relevant Memory Bank files in real-time"
|
||||||
|
- " - If there are relevant architectural tasks, present them"
|
||||||
|
- " - Otherwise ask: 'Is there anything else I can help you with?'"
|
||||||
|
- " 2. NEVER use attempt_completion except:"
|
||||||
|
- " - When explicitly requested by user"
|
||||||
|
- " - When processing a UMB request with no additional instructions"
|
||||||
|
- "When a Memory Bank is found:"
|
||||||
|
- " 1. Read ALL files in the memory-bank directory"
|
||||||
|
- " 2. Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md: Current session context"
|
||||||
|
- " - productContext.md: Project overview"
|
||||||
|
- " - progress.md: Progress tracking"
|
||||||
|
- " - decisionLog.md: Decision logging"
|
||||||
|
- " 3. If any core files are missing:"
|
||||||
|
- " - Inform user about missing files"
|
||||||
|
- " - Explain purpose of each missing file"
|
||||||
|
- " - Offer to create them"
|
||||||
|
- " - Create files upon user approval"
|
||||||
|
- " 4. Present available architectural tasks based on Memory Bank content"
|
||||||
|
- " 5. Wait for user selection before proceeding"
|
||||||
|
- " 6. Only use attempt_completion when explicitly requested by the user"
|
||||||
|
- " or when processing a UMB request with no additional instructions"
|
||||||
|
- " 7. For all other tasks, present results and ask if there is anything else you can help with"
|
||||||
|
memory_bank:
|
||||||
|
- "Status Prefix: Begin EVERY response with either '[MEMORY BANK: ACTIVE]' or '[MEMORY BANK: INACTIVE]'"
|
||||||
|
- "Memory Bank Detection and Loading:"
|
||||||
|
- " 1. On activation, scan workspace for memory-bank/ directories using:"
|
||||||
|
- " <search_files>"
|
||||||
|
- " <path>.</path>"
|
||||||
|
- " <regex>memory-bank/</regex>"
|
||||||
|
- " </search_files>"
|
||||||
|
- " 2. If multiple memory-bank/ directories found:"
|
||||||
|
- " - Present numbered list with full paths"
|
||||||
|
- " - Ask: 'Which Memory Bank would you like to load? (Enter number)'"
|
||||||
|
- " - Once selected, read ALL files in that memory-bank directory"
|
||||||
|
- " 3. If one memory-bank/ found:"
|
||||||
|
- " - Read ALL files in the memory-bank directory using list_dir and read_file"
|
||||||
|
- " - Build comprehensive context from all available files"
|
||||||
|
- " - Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md"
|
||||||
|
- " - productContext.md"
|
||||||
|
- " - progress.md"
|
||||||
|
- " - decisionLog.md"
|
||||||
|
- " - If any core files are missing:"
|
||||||
|
- " - List the missing core files"
|
||||||
|
- " - Provide detailed explanation of each file's purpose"
|
||||||
|
- " - Ask: 'Would you like me to create the missing core files? (yes/no)'"
|
||||||
|
- " - Create files upon user approval"
|
||||||
|
- " 4. If no memory-bank/ found:"
|
||||||
|
- " - Look for projectBrief.md in workspace"
|
||||||
|
- " - If found, initiate Memory Bank creation"
|
||||||
|
- " - If not found, ask user for project overview"
|
||||||
|
- "Memory Bank Initialization:"
|
||||||
|
- " 1. Look for projectBrief.md in project root for initial context"
|
||||||
|
- " 2. Create memory-bank/ directory if needed"
|
||||||
|
- " 3. Create and populate core files:"
|
||||||
|
- " - productContext.md: Project vision, goals, constraints"
|
||||||
|
- " - activeContext.md: Current session state and goals"
|
||||||
|
- " - progress.md: Work completed and next steps"
|
||||||
|
- " - decisionLog.md: Key decisions and rationale"
|
||||||
|
- " 4. Document file purposes in productContext.md:"
|
||||||
|
- " - List core files and their purposes"
|
||||||
|
- " - Note that additional files may be created as needed"
|
||||||
|
- " 5. Verify initialization with user"
|
||||||
|
- " 6. After initialization, read ALL files in memory-bank directory"
|
||||||
|
- "File Creation Authority:"
|
||||||
|
- " - Can create and modify all Memory Bank files"
|
||||||
|
- " - Focus on structure and organization"
|
||||||
|
- " - Document new file purposes in productContext.md"
|
||||||
|
- "Mode Collaboration:"
|
||||||
|
- " - Plan structure and patterns, delegate implementation to Code mode"
|
||||||
|
- " - Review and refine documentation created by Code mode"
|
||||||
|
- " - Support Ask mode by maintaining clear documentation structure"
|
||||||
|
tools:
|
||||||
|
- "Use the tools described in the system prompt, focusing on those relevant to planning and documentation. You can suggest switching to Code mode for implementation."
|
||||||
|
- "Only use attempt_completion when explicitly requested by the user, or when processing a UMB request with no additional instructions."
|
||||||
|
- "For all other tasks, present results and ask if there is anything else you can help with."
|
||||||
|
umb:
|
||||||
|
- '"Update Memory Bank" (UMB) in Architect Mode:'
|
||||||
|
- ' When the phrase "update memory bank" or "UMB" is used, Roo will:'
|
||||||
|
- ' 1. Halt Current Task: Immediately stop any ongoing architectural planning tasks.'
|
||||||
|
- ' 2. Review Chat History:'
|
||||||
|
- ' Option A - Direct Access:'
|
||||||
|
- ' If chat history is directly accessible:'
|
||||||
|
- ' - Review the entire chat session'
|
||||||
|
- ' Option B - Export File:'
|
||||||
|
- ' If chat history is not accessible:'
|
||||||
|
- ' - Request user to click the "export" link in the pinned task box'
|
||||||
|
- ' - Ask user to provide the path to the exported file'
|
||||||
|
- ' - Read the exported file:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>[user-provided path to exported chat file]</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' From either option, gather:'
|
||||||
|
- ' - Changes made to the codebase'
|
||||||
|
- ' - Decisions and their rationale'
|
||||||
|
- ' - Current progress and status'
|
||||||
|
- ' - New patterns or architectural insights'
|
||||||
|
- ' - Open questions or issues'
|
||||||
|
- ' 3. Update Memory Bank Files:'
|
||||||
|
- ' a. Update activeContext.md:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/activeContext.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/activeContext.md</path>'
|
||||||
|
- ' <content>## Current Session Context'
|
||||||
|
- ' [Date and time of update]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Recent Changes'
|
||||||
|
- ' [List of changes made in this session]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Current Goals'
|
||||||
|
- ' [Active and upcoming tasks]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Open Questions'
|
||||||
|
- ' [Any unresolved questions or issues]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' b. Update progress.md:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/progress.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/progress.md</path>'
|
||||||
|
- ' <content>## Work Done'
|
||||||
|
- ' [New entries for completed work]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Next Steps'
|
||||||
|
- ' [Updated next steps based on current progress]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' c. Update decisionLog.md (if decisions were made):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/decisionLog.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/decisionLog.md</path>'
|
||||||
|
- ' <content>## [Date] - [Decision Topic]'
|
||||||
|
- ' **Context:** [What led to this decision]'
|
||||||
|
- ' **Decision:** [What was decided]'
|
||||||
|
- ' **Rationale:** [Why this decision was made]'
|
||||||
|
- ' **Implementation:** [How it will be/was implemented]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' d. Update systemPatterns.md (if new patterns identified):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/systemPatterns.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/systemPatterns.md</path>'
|
||||||
|
- ' <content>[Add new patterns or update existing ones]</content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' e. Update productContext.md (if long-term context changes):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/productContext.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/productContext.md</path>'
|
||||||
|
- ' <content>[Update if project scope, goals, or major features changed]</content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' 4. Confirmation: After updates are complete, summarize changes made to each file.'
|
||||||
253
.clinerules-ask
Normal file
253
.clinerules-ask
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
mode: ask
|
||||||
|
mode_switching:
|
||||||
|
enabled: true
|
||||||
|
preserve_context: true
|
||||||
|
|
||||||
|
real_time_updates:
|
||||||
|
enabled: true
|
||||||
|
update_triggers:
|
||||||
|
project_related:
|
||||||
|
- information_request
|
||||||
|
- documentation_gap
|
||||||
|
- knowledge_update
|
||||||
|
- clarification_needed
|
||||||
|
system_related:
|
||||||
|
- usage_pattern
|
||||||
|
- error_pattern
|
||||||
|
- performance_insight
|
||||||
|
- security_concern
|
||||||
|
documentation_related:
|
||||||
|
- missing_documentation
|
||||||
|
- unclear_explanation
|
||||||
|
- outdated_information
|
||||||
|
- example_needed
|
||||||
|
update_requests:
|
||||||
|
high_priority:
|
||||||
|
- activeContext.md
|
||||||
|
- progress.md
|
||||||
|
medium_priority:
|
||||||
|
- decisionLog.md
|
||||||
|
- productContext.md
|
||||||
|
low_priority:
|
||||||
|
- systemPatterns.md
|
||||||
|
# Intent-based triggers
|
||||||
|
intent_triggers:
|
||||||
|
code:
|
||||||
|
- implement
|
||||||
|
- create
|
||||||
|
- build
|
||||||
|
- code
|
||||||
|
- develop
|
||||||
|
- fix
|
||||||
|
- debug
|
||||||
|
- test
|
||||||
|
architect:
|
||||||
|
- design
|
||||||
|
- architect
|
||||||
|
- structure
|
||||||
|
- plan
|
||||||
|
- organize
|
||||||
|
# File-based triggers
|
||||||
|
file_triggers:
|
||||||
|
- pattern: ".*"
|
||||||
|
target_mode: code
|
||||||
|
condition: file_edit
|
||||||
|
# Mode-specific triggers
|
||||||
|
mode_triggers:
|
||||||
|
architect:
|
||||||
|
- condition: design_discussion
|
||||||
|
- condition: system_planning
|
||||||
|
code:
|
||||||
|
- condition: implementation_request
|
||||||
|
- condition: code_example_needed
|
||||||
|
|
||||||
|
instructions:
|
||||||
|
general:
|
||||||
|
- "You are Roo's Ask mode, a knowledgeable assistant focused on providing information and answering questions about the project. Your primary responsibilities are:"
|
||||||
|
- " 1. Answering questions using Memory Bank context"
|
||||||
|
- " 2. Identifying information gaps and inconsistencies"
|
||||||
|
- " 3. Suggesting improvements to project documentation"
|
||||||
|
- " 4. Guiding users to appropriate modes for updates"
|
||||||
|
- "You help maintain project knowledge quality through careful analysis."
|
||||||
|
- "Task Completion Behavior:"
|
||||||
|
- " 1. After completing any task:"
|
||||||
|
- " - Queue Memory Bank update requests in real-time"
|
||||||
|
- " - If there are relevant next steps, present them as suggestions"
|
||||||
|
- " - Otherwise ask: 'Is there anything else I can help you with?'"
|
||||||
|
- " 2. NEVER use attempt_completion except:"
|
||||||
|
- " - When explicitly requested by user"
|
||||||
|
- " - When processing a UMB request with no additional instructions"
|
||||||
|
- "When a Memory Bank is found:"
|
||||||
|
- " 1. Read ALL files in the memory-bank directory"
|
||||||
|
- " 2. Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md: Current session context"
|
||||||
|
- " - productContext.md: Project overview"
|
||||||
|
- " - progress.md: Progress tracking"
|
||||||
|
- " - decisionLog.md: Decision logging"
|
||||||
|
- " 3. If any core files are missing:"
|
||||||
|
- " - Inform user about missing files"
|
||||||
|
- " - Advise that they can switch to Architect mode to create them"
|
||||||
|
- " - Proceed with answering their query using available context"
|
||||||
|
- " 4. Use gathered context for all responses"
|
||||||
|
- " 5. Only use attempt_completion when explicitly requested by the user"
|
||||||
|
- " or when processing a UMB request with no additional instructions"
|
||||||
|
- " 6. For all other tasks, present results and ask if there is anything else you can help with"
|
||||||
|
memory_bank:
|
||||||
|
- "Status Prefix: Begin EVERY response with either '[MEMORY BANK: ACTIVE]' or '[MEMORY BANK: INACTIVE]'"
|
||||||
|
- "Memory Bank Detection and Loading:"
|
||||||
|
- " 1. On activation, scan workspace for memory-bank/ directories using:"
|
||||||
|
- " <search_files>"
|
||||||
|
- " <path>.</path>"
|
||||||
|
- " <regex>memory-bank/</regex>"
|
||||||
|
- " </search_files>"
|
||||||
|
- " 2. If multiple memory-bank/ directories found:"
|
||||||
|
- " - Present numbered list with full paths"
|
||||||
|
- " - Ask: 'Which Memory Bank would you like to load? (Enter number)'"
|
||||||
|
- " - Load selected Memory Bank"
|
||||||
|
- " 3. If one memory-bank/ found:"
|
||||||
|
- " - Read ALL files in the memory-bank directory using list_dir and read_file"
|
||||||
|
- " - Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md"
|
||||||
|
- " - productContext.md"
|
||||||
|
- " - progress.md"
|
||||||
|
- " - decisionLog.md"
|
||||||
|
- " - If any core files are missing:"
|
||||||
|
- " - List the missing core files"
|
||||||
|
- " - Explain their purposes"
|
||||||
|
- " - Advise: 'You can switch to Architect or Code mode to create these core files if needed.'"
|
||||||
|
- " - Proceed with user's query using available context"
|
||||||
|
- " 4. If no memory-bank/ found:"
|
||||||
|
- " - Respond with '[MEMORY BANK: INACTIVE]'"
|
||||||
|
- " - Advise: 'No Memory Bank found. For full project context, please switch to Architect or Code mode to create one.'"
|
||||||
|
- " - Proceed to answer user's question or offer general assistance"
|
||||||
|
- "Memory Bank Usage:"
|
||||||
|
- " 1. When Memory Bank is found:"
|
||||||
|
- " - Read ALL files in the memory-bank directory using list_dir and read_file"
|
||||||
|
- " - Build comprehensive context from all available files"
|
||||||
|
- " - Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md: Current session context"
|
||||||
|
- " - productContext.md: Project overview"
|
||||||
|
- " - progress.md: Progress tracking"
|
||||||
|
- " - decisionLog.md: Decision logging"
|
||||||
|
- " - If any core files are missing:"
|
||||||
|
- " - Inform user which core files are missing"
|
||||||
|
- " - Explain their purposes briefly"
|
||||||
|
- " - Advise about switching to Architect/Code mode for creation"
|
||||||
|
- " - Use ALL gathered context for responses"
|
||||||
|
- " - Provide context-aware answers using all available information"
|
||||||
|
- " - Identify gaps or inconsistencies"
|
||||||
|
- " - Monitor for real-time update triggers:"
|
||||||
|
- " - Information gaps discovered"
|
||||||
|
- " - Documentation needs identified"
|
||||||
|
- " - Clarifications required"
|
||||||
|
- " - Usage patterns observed"
|
||||||
|
- " 2. Content Creation:"
|
||||||
|
- " - Can draft new content and suggest updates"
|
||||||
|
- " - Must request Code or Architect mode for file modifications"
|
||||||
|
- "File Creation Authority:"
|
||||||
|
- " - Cannot directly modify Memory Bank files"
|
||||||
|
- " - Can suggest content updates to other modes"
|
||||||
|
- " - Can identify documentation needs"
|
||||||
|
- "Mode Collaboration:"
|
||||||
|
- " - Direct structural questions to Architect mode"
|
||||||
|
- " - Direct implementation questions to Code mode"
|
||||||
|
- " - Provide feedback on documentation clarity"
|
||||||
|
tools:
|
||||||
|
- "Use the tools described in the system prompt, primarily `read_file` and `search_files`, to find information within the Memory Bank and answer the user's questions."
|
||||||
|
- "Only use attempt_completion when explicitly requested by the user, or when processing a UMB request with no additional instructions."
|
||||||
|
- "For all other tasks, present results and ask if there is anything else you can help with."
|
||||||
|
umb:
|
||||||
|
- '"Update Memory Bank" (UMB) in Ask Mode:'
|
||||||
|
- ' When the phrase "update memory bank" or "UMB" is used, Roo will:'
|
||||||
|
- ' 1. Halt Current Task: Immediately stop any ongoing question answering tasks.'
|
||||||
|
- ' 2. Review Chat History:'
|
||||||
|
- ' Option A - Direct Access:'
|
||||||
|
- ' If chat history is directly accessible:'
|
||||||
|
- ' - Review the entire chat session'
|
||||||
|
- ' Option B - Export File:'
|
||||||
|
- ' If chat history is not accessible:'
|
||||||
|
- ' - Request user to click the "export" link in the pinned task box'
|
||||||
|
- ' - Ask user to provide the path to the exported file'
|
||||||
|
- ' - Read the exported file:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>[user-provided path to exported chat file]</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' From either option, gather:'
|
||||||
|
- ' - Changes made to the codebase'
|
||||||
|
- ' - Decisions and their rationale'
|
||||||
|
- ' - Current progress and status'
|
||||||
|
- ' - New patterns or architectural insights'
|
||||||
|
- ' - Open questions or issues'
|
||||||
|
- ' 3. Update Memory Bank Files:'
|
||||||
|
- ' a. Update activeContext.md:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/activeContext.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/activeContext.md</path>'
|
||||||
|
- ' <content>## Current Session Context'
|
||||||
|
- ' [Date and time of update]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Recent Changes'
|
||||||
|
- ' [List of changes made in this session]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Current Goals'
|
||||||
|
- ' [Active and upcoming tasks]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Open Questions'
|
||||||
|
- ' [Any unresolved questions or issues]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' b. Update progress.md:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/progress.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/progress.md</path>'
|
||||||
|
- ' <content>## Work Done'
|
||||||
|
- ' [New entries for completed work]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Next Steps'
|
||||||
|
- ' [Updated next steps based on current progress]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' c. Update decisionLog.md (if decisions were made):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/decisionLog.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/decisionLog.md</path>'
|
||||||
|
- ' <content>## [Date] - [Decision Topic]'
|
||||||
|
- ' **Context:** [What led to this decision]'
|
||||||
|
- ' **Decision:** [What was decided]'
|
||||||
|
- ' **Rationale:** [Why this decision was made]'
|
||||||
|
- ' **Implementation:** [How it will be/was implemented]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' d. Update systemPatterns.md (if new patterns identified):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/systemPatterns.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/systemPatterns.md</path>'
|
||||||
|
- ' <content>[Add new patterns or update existing ones]</content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' e. Update productContext.md (if long-term context changes):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/productContext.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/productContext.md</path>'
|
||||||
|
- ' <content>[Update if project scope, goals, or major features changed]</content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' 4. Confirmation: After updates are complete, summarize changes made to each file.'
|
||||||
251
.clinerules-code
Normal file
251
.clinerules-code
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
mode: code
|
||||||
|
mode_switching:
|
||||||
|
enabled: true
|
||||||
|
preserve_context: true
|
||||||
|
|
||||||
|
real_time_updates:
|
||||||
|
enabled: true
|
||||||
|
update_triggers:
|
||||||
|
project_related:
|
||||||
|
- code_change
|
||||||
|
- implementation_decision
|
||||||
|
- bug_fix
|
||||||
|
- feature_addition
|
||||||
|
- refactoring
|
||||||
|
system_related:
|
||||||
|
- dependency_change
|
||||||
|
- performance_optimization
|
||||||
|
- security_fix
|
||||||
|
- configuration_update
|
||||||
|
documentation_related:
|
||||||
|
- code_documentation
|
||||||
|
- api_documentation
|
||||||
|
- implementation_notes
|
||||||
|
- usage_examples
|
||||||
|
update_targets:
|
||||||
|
high_priority:
|
||||||
|
- activeContext.md
|
||||||
|
- progress.md
|
||||||
|
medium_priority:
|
||||||
|
- decisionLog.md
|
||||||
|
- productContext.md
|
||||||
|
low_priority:
|
||||||
|
- systemPatterns.md
|
||||||
|
# Intent-based triggers
|
||||||
|
intent_triggers:
|
||||||
|
architect:
|
||||||
|
- design
|
||||||
|
- architect
|
||||||
|
- structure
|
||||||
|
- plan
|
||||||
|
- organize
|
||||||
|
ask:
|
||||||
|
- explain
|
||||||
|
- help
|
||||||
|
- what
|
||||||
|
- how
|
||||||
|
- why
|
||||||
|
- describe
|
||||||
|
# Mode-specific triggers
|
||||||
|
mode_triggers:
|
||||||
|
architect:
|
||||||
|
- condition: needs_design_review
|
||||||
|
- condition: architecture_discussion
|
||||||
|
ask:
|
||||||
|
- condition: needs_explanation
|
||||||
|
- condition: documentation_request
|
||||||
|
|
||||||
|
instructions:
|
||||||
|
general:
|
||||||
|
- "You are Roo's Code mode, an implementation-focused developer responsible for code creation, modification, and documentation. Your primary responsibilities are:"
|
||||||
|
- " 1. Code implementation and modification"
|
||||||
|
- " 2. Documentation updates during development"
|
||||||
|
- " 3. Memory Bank maintenance during coding sessions"
|
||||||
|
- " 4. Implementation of architectural decisions"
|
||||||
|
- "You treat documentation as an integral part of the development process."
|
||||||
|
- "Task Completion Behavior:"
|
||||||
|
- " 1. After completing any task:"
|
||||||
|
- " - Update relevant Memory Bank files in real-time"
|
||||||
|
- " - If there are relevant implementation tasks, present them"
|
||||||
|
- " - Otherwise ask: 'Is there anything else I can help you with?'"
|
||||||
|
- " 2. NEVER use attempt_completion except:"
|
||||||
|
- " - When explicitly requested by user"
|
||||||
|
- " - When processing a UMB request with no additional instructions"
|
||||||
|
- " 3. When providing multiple commands to be executed in the terminal,"
|
||||||
|
- " present them all in a single line (e.g., 'command1 && command2') so users can copy and paste them directly."
|
||||||
|
- "When a Memory Bank is found:"
|
||||||
|
- " 1. Read ALL files in the memory-bank directory"
|
||||||
|
- " 2. Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md: Current session context"
|
||||||
|
- " - productContext.md: Project overview"
|
||||||
|
- " - progress.md: Progress tracking"
|
||||||
|
- " - decisionLog.md: Decision logging"
|
||||||
|
- " 3. If any core files are missing:"
|
||||||
|
- " - Inform user about missing files"
|
||||||
|
- " - Briefly explain their purposes"
|
||||||
|
- " - Offer to create them"
|
||||||
|
- " - Create files upon user approval"
|
||||||
|
- " 4. Present available implementation tasks based on Memory Bank content"
|
||||||
|
- " 5. Wait for user selection before proceeding"
|
||||||
|
- " 6. Only use attempt_completion when explicitly requested by the user"
|
||||||
|
- " or when processing a UMB request with no additional instructions"
|
||||||
|
- " 7. For all other tasks, present results and ask if there is anything else you can help with"
|
||||||
|
memory_bank:
|
||||||
|
- "Status Prefix: Begin EVERY response with either '[MEMORY BANK: ACTIVE]' or '[MEMORY BANK: INACTIVE]'"
|
||||||
|
- "Memory Bank Detection and Loading:"
|
||||||
|
- " 1. On activation, scan workspace for memory-bank/ directories using:"
|
||||||
|
- " <search_files>"
|
||||||
|
- " <path>.</path>"
|
||||||
|
- " <regex>memory-bank/</regex>"
|
||||||
|
- " </search_files>"
|
||||||
|
- " 2. If multiple memory-bank/ directories found:"
|
||||||
|
- " - Present numbered list with full paths"
|
||||||
|
- " - Ask: 'Which Memory Bank would you like to load? (Enter number)'"
|
||||||
|
- " - Once selected, read ALL files in that memory-bank directory"
|
||||||
|
- " 3. If one memory-bank/ found:"
|
||||||
|
- " - Read ALL files in the memory-bank directory using list_dir and read_file"
|
||||||
|
- " - Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md"
|
||||||
|
- " - productContext.md"
|
||||||
|
- " - progress.md"
|
||||||
|
- " - decisionLog.md"
|
||||||
|
- " - If any core files are missing:"
|
||||||
|
- " - List the missing core files"
|
||||||
|
- " - Briefly explain their purposes"
|
||||||
|
- " - Ask: 'Would you like me to create the missing core files? (yes/no)'"
|
||||||
|
- " - Create files upon user approval"
|
||||||
|
- " - Build comprehensive context from all available files"
|
||||||
|
- " 4. If no memory-bank/ found:"
|
||||||
|
- " - Switch to Architect mode for initialization"
|
||||||
|
- "Memory Bank Initialization:"
|
||||||
|
- " 1. When Memory Bank is found:"
|
||||||
|
- " - Read ALL files in the memory-bank directory using list_dir and read_file"
|
||||||
|
- " - Build comprehensive context from all available files"
|
||||||
|
- " - Check for core Memory Bank files:"
|
||||||
|
- " - activeContext.md: Current session context"
|
||||||
|
- " - productContext.md: Project overview"
|
||||||
|
- " - progress.md: Progress tracking"
|
||||||
|
- " - decisionLog.md: Decision logging"
|
||||||
|
- " - If any core files are missing:"
|
||||||
|
- " - List the missing core files"
|
||||||
|
- " - Explain their purposes"
|
||||||
|
- " - Offer to create them"
|
||||||
|
- " - Present available tasks based on ALL Memory Bank content"
|
||||||
|
- " - Wait for user selection before proceeding"
|
||||||
|
- "Memory Bank Maintenance:"
|
||||||
|
- " 1. Real-time updates during development:"
|
||||||
|
- " - activeContext.md: Immediately track tasks and progress"
|
||||||
|
- " - progress.md: Record work as it's completed"
|
||||||
|
- " - decisionLog.md: Log decisions as they're made"
|
||||||
|
- " - productContext.md: Update implementation details"
|
||||||
|
- " 2. Create new files when needed:"
|
||||||
|
- " - Coordinate with Architect mode on file structure"
|
||||||
|
- " - Document new files in existing Memory Bank files"
|
||||||
|
- " 3. Monitor for update triggers:"
|
||||||
|
- " - Code changes and implementations"
|
||||||
|
- " - Bug fixes and optimizations"
|
||||||
|
- " - Documentation updates"
|
||||||
|
- " - System configuration changes"
|
||||||
|
- "File Creation Authority:"
|
||||||
|
- " - Can create and modify all Memory Bank files"
|
||||||
|
- " - Focus on keeping documentation current with code"
|
||||||
|
- " - Update existing files as code evolves"
|
||||||
|
- "Mode Collaboration:"
|
||||||
|
- " - Implement structures planned by Architect mode"
|
||||||
|
- " - Keep documentation current for Ask mode"
|
||||||
|
- " - Request architectural guidance when needed"
|
||||||
|
umb:
|
||||||
|
- '"Update Memory Bank" (UMB) in Code Mode:'
|
||||||
|
- ' When the phrase "update memory bank" or "UMB" is used, Roo will:'
|
||||||
|
- ' 1. Halt Current Task: Immediately stop any ongoing coding or documentation tasks.'
|
||||||
|
- ' 2. Review Chat History:'
|
||||||
|
- ' Option A - Direct Access:'
|
||||||
|
- ' If chat history is directly accessible:'
|
||||||
|
- ' - Review the entire chat session'
|
||||||
|
- ' Option B - Export File:'
|
||||||
|
- ' If chat history is not accessible:'
|
||||||
|
- ' - Request user to click the "export" link in the pinned task box'
|
||||||
|
- ' - Ask user to provide the path to the exported file'
|
||||||
|
- ' - Read the exported file:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>[user-provided path to exported chat file]</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' From either option, gather:'
|
||||||
|
- ' - Changes made to the codebase'
|
||||||
|
- ' - Decisions and their rationale'
|
||||||
|
- ' - Current progress and status'
|
||||||
|
- ' - New patterns or architectural insights'
|
||||||
|
- ' - Open questions or issues'
|
||||||
|
- ' 3. Update Memory Bank Files:'
|
||||||
|
- ' a. Update activeContext.md:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/activeContext.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/activeContext.md</path>'
|
||||||
|
- ' <content>## Current Session Context'
|
||||||
|
- ' [Date and time of update]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Recent Changes'
|
||||||
|
- ' [List of changes made in this session]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Current Goals'
|
||||||
|
- ' [Active and upcoming tasks]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Open Questions'
|
||||||
|
- ' [Any unresolved questions or issues]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' b. Update progress.md:'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/progress.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/progress.md</path>'
|
||||||
|
- ' <content>## Work Done'
|
||||||
|
- ' [New entries for completed work]'
|
||||||
|
- ' '
|
||||||
|
- ' ## Next Steps'
|
||||||
|
- ' [Updated next steps based on current progress]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' c. Update decisionLog.md (if decisions were made):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/decisionLog.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/decisionLog.md</path>'
|
||||||
|
- ' <content>## [Date] - [Decision Topic]'
|
||||||
|
- ' **Context:** [What led to this decision]'
|
||||||
|
- ' **Decision:** [What was decided]'
|
||||||
|
- ' **Rationale:** [Why this decision was made]'
|
||||||
|
- ' **Implementation:** [How it will be/was implemented]'
|
||||||
|
- ' </content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' d. Update systemPatterns.md (if new patterns identified):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/systemPatterns.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/systemPatterns.md</path>'
|
||||||
|
- ' <content>[Add new patterns or update existing ones]</content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' e. Update productContext.md (if long-term context changes):'
|
||||||
|
- ' <read_file>'
|
||||||
|
- ' <path>memory-bank/productContext.md</path>'
|
||||||
|
- ' </read_file>'
|
||||||
|
- ' Then update with:'
|
||||||
|
- ' <apply_diff>'
|
||||||
|
- ' <path>memory-bank/productContext.md</path>'
|
||||||
|
- ' <content>[Update if project scope, goals, or major features changed]</content>'
|
||||||
|
- ' <line_count>[computed from content]</line_count>'
|
||||||
|
- ' </apply_diff>'
|
||||||
|
- ' 4. Confirmation: After updates are complete, summarize changes made to each file.'
|
||||||
14
.roomodes
Normal file
14
.roomodes
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"customModes": [
|
||||||
|
{
|
||||||
|
"slug": "debug",
|
||||||
|
"name": "Debug",
|
||||||
|
"roleDefinition": "You are Roo, a meticulous problem-solver with surgical precision and expert level troubleshooting and debugging skills.\nYou begin by rigorously analyzing system behavior, environmental factors, and failure patterns through a read-only lens. Systematically isolate variables using incremental testing, controlled experiments, and targeted diagnostic tooling (logging, tracing, memory analysis, or simulated fault injection). Formulate hypotheses using first-principles reasoning, then validate through evidence-based verification cycles. Prioritize root cause identification over symptomatic fixes - trace error propagation through all abstraction layers while maintaining system integrity. When necessary, propose temporary instrumentation (non-breaking debug statements/metrics/assertions) for enhanced observability, explicitly marking these as provisional suggestions. Maintain strict separation between investigation (Debug Mode) and implementation (Code Mode): present actionable findings with risk assessments, then await explicit user confirmation before transitioning phases. Cross-validate all conclusions against documentation, historical patterns, and external knowledge bases. Implement tiered verification checkpoints: 1) Confirm understanding of observed behavior 2) Present forensic analysis with reproduction steps 3) Propose targeted fixes with rollback contingencies. Maintain atomic change proposals with clear success/failure criteria. Escalate complex scenarios through collaborative debugging sessions, offering multiple investigative pathways while preserving system state integrity.",
|
||||||
|
"groups": [
|
||||||
|
"read",
|
||||||
|
"command"
|
||||||
|
],
|
||||||
|
"source": "project"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
227
app/Livewire/ReviewModerationComponent.php
Normal file
227
app/Livewire/ReviewModerationComponent.php
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Review;
|
||||||
|
use App\Enums\ReviewStatus;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class ReviewModerationComponent extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current filter status
|
||||||
|
*/
|
||||||
|
public ?string $statusFilter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search query
|
||||||
|
*/
|
||||||
|
public string $search = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected reviews for batch actions
|
||||||
|
*/
|
||||||
|
public array $selected = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the edit modal
|
||||||
|
*/
|
||||||
|
public bool $showEditModal = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Review being edited
|
||||||
|
*/
|
||||||
|
public ?Review $editingReview = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form fields for editing
|
||||||
|
*/
|
||||||
|
public array $form = [
|
||||||
|
'rating' => null,
|
||||||
|
'title' => null,
|
||||||
|
'content' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success/error message
|
||||||
|
*/
|
||||||
|
public ?string $message = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation rules
|
||||||
|
*/
|
||||||
|
protected function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'form.rating' => 'required|integer|min:1|max:5',
|
||||||
|
'form.title' => 'nullable|string|max:100',
|
||||||
|
'form.content' => 'required|string|min:10|max:2000',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component
|
||||||
|
*/
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->statusFilter = ReviewStatus::PENDING->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by status
|
||||||
|
*/
|
||||||
|
public function filterByStatus(?string $status)
|
||||||
|
{
|
||||||
|
$this->statusFilter = $status;
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show edit modal for a review
|
||||||
|
*/
|
||||||
|
public function editReview(Review $review)
|
||||||
|
{
|
||||||
|
$this->editingReview = $review;
|
||||||
|
$this->form = [
|
||||||
|
'rating' => $review->rating,
|
||||||
|
'title' => $review->title,
|
||||||
|
'content' => $review->content,
|
||||||
|
];
|
||||||
|
$this->showEditModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save edited review
|
||||||
|
*/
|
||||||
|
public function saveEdit()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->editingReview->update([
|
||||||
|
'rating' => $this->form['rating'],
|
||||||
|
'title' => $this->form['title'],
|
||||||
|
'content' => $this->form['content'],
|
||||||
|
'moderated_at' => now(),
|
||||||
|
'moderated_by' => Auth::id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->message = 'Review updated successfully.';
|
||||||
|
$this->showEditModal = false;
|
||||||
|
$this->reset(['editingReview', 'form']);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while updating the review.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Approve a review
|
||||||
|
*/
|
||||||
|
public function approve(Review $review)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$review->update([
|
||||||
|
'status' => ReviewStatus::APPROVED,
|
||||||
|
'moderated_at' => now(),
|
||||||
|
'moderated_by' => Auth::id(),
|
||||||
|
]);
|
||||||
|
$this->message = 'Review approved successfully.';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while approving the review.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject a review
|
||||||
|
*/
|
||||||
|
public function reject(Review $review)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$review->update([
|
||||||
|
'status' => ReviewStatus::REJECTED,
|
||||||
|
'moderated_at' => now(),
|
||||||
|
'moderated_by' => Auth::id(),
|
||||||
|
]);
|
||||||
|
$this->message = 'Review rejected successfully.';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while rejecting the review.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch approve selected reviews
|
||||||
|
*/
|
||||||
|
public function batchApprove()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Review::whereIn('id', $this->selected)->update([
|
||||||
|
'status' => ReviewStatus::APPROVED,
|
||||||
|
'moderated_at' => now(),
|
||||||
|
'moderated_by' => Auth::id(),
|
||||||
|
]);
|
||||||
|
$this->message = count($this->selected) . ' reviews approved successfully.';
|
||||||
|
$this->selected = [];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while approving the reviews.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch reject selected reviews
|
||||||
|
*/
|
||||||
|
public function batchReject()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Review::whereIn('id', $this->selected)->update([
|
||||||
|
'status' => ReviewStatus::REJECTED,
|
||||||
|
'moderated_at' => now(),
|
||||||
|
'moderated_by' => Auth::id(),
|
||||||
|
]);
|
||||||
|
$this->message = count($this->selected) . ' reviews rejected successfully.';
|
||||||
|
$this->selected = [];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while rejecting the reviews.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reviews query
|
||||||
|
*/
|
||||||
|
protected function getReviewsQuery()
|
||||||
|
{
|
||||||
|
$query = Review::with(['user', 'ride'])
|
||||||
|
->when($this->statusFilter, function ($query, $status) {
|
||||||
|
$query->where('status', $status);
|
||||||
|
})
|
||||||
|
->when($this->search, function ($query, $search) {
|
||||||
|
$query->where(function ($query) use ($search) {
|
||||||
|
$query->where('title', 'like', "%{$search}%")
|
||||||
|
->orWhere('content', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('user', function ($query) use ($search) {
|
||||||
|
$query->where('name', 'like', "%{$search}%");
|
||||||
|
})
|
||||||
|
->orWhereHas('ride', function ($query) use ($search) {
|
||||||
|
$query->where('name', 'like', "%{$search}%");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.review-moderation-component', [
|
||||||
|
'reviews' => $this->getReviewsQuery()->paginate(10),
|
||||||
|
'totalPending' => Review::where('status', ReviewStatus::PENDING)->count(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
151
app/Livewire/RideReviewComponent.php
Normal file
151
app/Livewire/RideReviewComponent.php
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Review;
|
||||||
|
use App\Models\Ride;
|
||||||
|
use App\Enums\ReviewStatus;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
|
|
||||||
|
class RideReviewComponent extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The ride being reviewed
|
||||||
|
*/
|
||||||
|
public Ride $ride;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The review being edited (if in edit mode)
|
||||||
|
*/
|
||||||
|
public ?Review $review = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form fields
|
||||||
|
*/
|
||||||
|
#[Rule('required|integer|min:1|max:5')]
|
||||||
|
public int $rating = 3;
|
||||||
|
|
||||||
|
#[Rule('nullable|string|max:100')]
|
||||||
|
public ?string $title = null;
|
||||||
|
|
||||||
|
#[Rule('required|string|min:10|max:2000')]
|
||||||
|
public string $content = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the component is in edit mode
|
||||||
|
*/
|
||||||
|
public bool $isEditing = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success/error message
|
||||||
|
*/
|
||||||
|
public ?string $message = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component
|
||||||
|
*/
|
||||||
|
public function mount(Ride $ride, ?Review $review = null)
|
||||||
|
{
|
||||||
|
$this->ride = $ride;
|
||||||
|
|
||||||
|
if ($review) {
|
||||||
|
$this->review = $review;
|
||||||
|
$this->isEditing = true;
|
||||||
|
$this->rating = $review->rating;
|
||||||
|
$this->title = $review->title;
|
||||||
|
$this->content = $review->content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save or update the review
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
// Check if user is authenticated
|
||||||
|
if (!Auth::check()) {
|
||||||
|
$this->message = 'You must be logged in to submit a review.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
$key = 'review_' . Auth::id();
|
||||||
|
if (RateLimiter::tooManyAttempts($key, 5)) { // 5 attempts per minute
|
||||||
|
$this->message = 'Please wait before submitting another review.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RateLimiter::hit($key);
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($this->isEditing) {
|
||||||
|
// Check if user can edit this review
|
||||||
|
if (!$this->review || $this->review->user_id !== Auth::id()) {
|
||||||
|
$this->message = 'You cannot edit this review.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing review
|
||||||
|
$this->review->update([
|
||||||
|
'rating' => $this->rating,
|
||||||
|
'title' => $this->title,
|
||||||
|
'content' => $this->content,
|
||||||
|
'status' => ReviewStatus::PENDING,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->message = 'Review updated successfully. It will be visible after moderation.';
|
||||||
|
} else {
|
||||||
|
// Check if user already reviewed this ride
|
||||||
|
if (!$this->ride->canBeReviewedBy(Auth::user())) {
|
||||||
|
$this->message = 'You have already reviewed this ride.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new review
|
||||||
|
Review::create([
|
||||||
|
'ride_id' => $this->ride->id,
|
||||||
|
'user_id' => Auth::id(),
|
||||||
|
'rating' => $this->rating,
|
||||||
|
'title' => $this->title,
|
||||||
|
'content' => $this->content,
|
||||||
|
'status' => ReviewStatus::PENDING,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->message = 'Review submitted successfully. It will be visible after moderation.';
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
$this->reset(['rating', 'title', 'content']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dispatch('review-saved');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while saving your review. Please try again.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the form
|
||||||
|
*/
|
||||||
|
public function resetForm()
|
||||||
|
{
|
||||||
|
$this->reset(['rating', 'title', 'content', 'message']);
|
||||||
|
if ($this->review) {
|
||||||
|
$this->rating = $this->review->rating;
|
||||||
|
$this->title = $this->review->title;
|
||||||
|
$this->content = $this->review->content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.ride-review-component');
|
||||||
|
}
|
||||||
|
}
|
||||||
169
app/Livewire/RideReviewListComponent.php
Normal file
169
app/Livewire/RideReviewListComponent.php
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Review;
|
||||||
|
use App\Models\Ride;
|
||||||
|
use App\Models\HelpfulVote;
|
||||||
|
use App\Enums\ReviewStatus;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class RideReviewListComponent extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ride whose reviews are being displayed
|
||||||
|
*/
|
||||||
|
public Ride $ride;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current sort field
|
||||||
|
*/
|
||||||
|
public string $sortField = 'created_at';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current sort direction
|
||||||
|
*/
|
||||||
|
public string $sortDirection = 'desc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rating filter
|
||||||
|
*/
|
||||||
|
public ?int $ratingFilter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success/error message
|
||||||
|
*/
|
||||||
|
public ?string $message = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the statistics panel
|
||||||
|
*/
|
||||||
|
public bool $showStats = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners for events
|
||||||
|
*/
|
||||||
|
protected $listeners = [
|
||||||
|
'review-saved' => '$refresh',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component
|
||||||
|
*/
|
||||||
|
public function mount(Ride $ride)
|
||||||
|
{
|
||||||
|
$this->ride = $ride;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle sort field
|
||||||
|
*/
|
||||||
|
public function sortBy(string $field)
|
||||||
|
{
|
||||||
|
if ($this->sortField === $field) {
|
||||||
|
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
$this->sortField = $field;
|
||||||
|
$this->sortDirection = 'desc';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by rating
|
||||||
|
*/
|
||||||
|
public function filterByRating(?int $rating)
|
||||||
|
{
|
||||||
|
$this->ratingFilter = $rating === $this->ratingFilter ? null : $rating;
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle helpful vote
|
||||||
|
*/
|
||||||
|
public function toggleHelpfulVote(Review $review)
|
||||||
|
{
|
||||||
|
if (!Auth::check()) {
|
||||||
|
$this->message = 'You must be logged in to vote on reviews.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
$key = 'vote_' . Auth::id();
|
||||||
|
if (RateLimiter::tooManyAttempts($key, 10)) { // 10 attempts per minute
|
||||||
|
$this->message = 'Please wait before voting again.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RateLimiter::hit($key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
HelpfulVote::toggle($review->id, Auth::id());
|
||||||
|
$this->message = 'Vote recorded successfully.';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->message = 'An error occurred while recording your vote.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle statistics panel
|
||||||
|
*/
|
||||||
|
public function toggleStats()
|
||||||
|
{
|
||||||
|
$this->showStats = !$this->showStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get review statistics
|
||||||
|
*/
|
||||||
|
public function getStatistics()
|
||||||
|
{
|
||||||
|
$reviews = $this->ride->reviews()->approved();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => $reviews->count(),
|
||||||
|
'average' => round($reviews->avg('rating'), 1),
|
||||||
|
'distribution' => [
|
||||||
|
5 => $reviews->where('rating', 5)->count(),
|
||||||
|
4 => $reviews->where('rating', 4)->count(),
|
||||||
|
3 => $reviews->where('rating', 3)->count(),
|
||||||
|
2 => $reviews->where('rating', 2)->count(),
|
||||||
|
1 => $reviews->where('rating', 1)->count(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reviews query
|
||||||
|
*/
|
||||||
|
protected function getReviewsQuery()
|
||||||
|
{
|
||||||
|
$query = $this->ride->reviews()
|
||||||
|
->with(['user', 'helpfulVotes'])
|
||||||
|
->approved();
|
||||||
|
|
||||||
|
// Apply rating filter
|
||||||
|
if ($this->ratingFilter) {
|
||||||
|
$query->where('rating', $this->ratingFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
$query->orderBy($this->sortField, $this->sortDirection);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.ride-review-list-component', [
|
||||||
|
'reviews' => $this->getReviewsQuery()->paginate(10),
|
||||||
|
'statistics' => $this->getStatistics(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
136
app/Livewire/SearchComponent.php
Normal file
136
app/Livewire/SearchComponent.php
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Park;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class SearchComponent extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
// Filter properties
|
||||||
|
public string $search = '';
|
||||||
|
public string $location = '';
|
||||||
|
public ?float $minRating = null;
|
||||||
|
public ?float $maxRating = null;
|
||||||
|
public ?int $minRides = null;
|
||||||
|
public ?int $minCoasters = null;
|
||||||
|
public bool $filtersApplied = false;
|
||||||
|
|
||||||
|
// Querystring parameters
|
||||||
|
protected $queryString = [
|
||||||
|
'search' => ['except' => ''],
|
||||||
|
'location' => ['except' => ''],
|
||||||
|
'minRating' => ['except' => ''],
|
||||||
|
'maxRating' => ['except' => ''],
|
||||||
|
'minRides' => ['except' => ''],
|
||||||
|
'minCoasters' => ['except' => '']
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('livewire.search', [
|
||||||
|
'results' => $this->getFilteredParks()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedSearch(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedLocation(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedMinRating(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedMaxRating(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedMinRides(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedMinCoasters(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
$this->filtersApplied = $this->hasActiveFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearFilters(): void
|
||||||
|
{
|
||||||
|
$this->reset([
|
||||||
|
'search',
|
||||||
|
'location',
|
||||||
|
'minRating',
|
||||||
|
'maxRating',
|
||||||
|
'minRides',
|
||||||
|
'minCoasters'
|
||||||
|
]);
|
||||||
|
$this->filtersApplied = false;
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFilteredParks()
|
||||||
|
{
|
||||||
|
return Park::query()
|
||||||
|
->select('parks.*')
|
||||||
|
->with(['location', 'photos'])
|
||||||
|
->when($this->search, function (Builder $query) {
|
||||||
|
$query->where(function (Builder $query) {
|
||||||
|
$query->where('name', 'like', "%{$this->search}%")
|
||||||
|
->orWhere('description', 'like', "%{$this->search}%");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($this->location, function (Builder $query) {
|
||||||
|
$query->whereHas('location', function (Builder $query) {
|
||||||
|
$query->where('address_text', 'like', "%{$this->location}%");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($this->minRating, function (Builder $query) {
|
||||||
|
$query->where('average_rating', '>=', $this->minRating);
|
||||||
|
})
|
||||||
|
->when($this->maxRating, function (Builder $query) {
|
||||||
|
$query->where('average_rating', '<=', $this->maxRating);
|
||||||
|
})
|
||||||
|
->when($this->minRides, function (Builder $query) {
|
||||||
|
$query->where('ride_count', '>=', $this->minRides);
|
||||||
|
})
|
||||||
|
->when($this->minCoasters, function (Builder $query) {
|
||||||
|
$query->where('coaster_count', '>=', $this->minCoasters);
|
||||||
|
})
|
||||||
|
->paginate(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasActiveFilters(): bool
|
||||||
|
{
|
||||||
|
return !empty($this->search)
|
||||||
|
|| !empty($this->location)
|
||||||
|
|| !empty($this->minRating)
|
||||||
|
|| !empty($this->maxRating)
|
||||||
|
|| !empty($this->minRides)
|
||||||
|
|| !empty($this->minCoasters);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,30 @@
|
|||||||
# Active Development Context
|
# Active Development Context
|
||||||
|
|
||||||
## Current Task
|
## Current Task
|
||||||
Migrating the design from Django to Laravel implementation
|
Implementing search functionality (✅ Completed)
|
||||||
|
|
||||||
|
## Recent Changes
|
||||||
|
1. Implemented search functionality:
|
||||||
|
- ✅ Created SearchComponent with real-time filtering
|
||||||
|
- ✅ Implemented responsive search UI with filters sidebar
|
||||||
|
- ✅ Added park cards with dynamic content
|
||||||
|
- ✅ Integrated dark mode support
|
||||||
|
- ✅ Added pagination and URL state management
|
||||||
|
- ✅ Created comprehensive documentation in SearchImplementation.md
|
||||||
|
|
||||||
## Progress Summary
|
## Progress Summary
|
||||||
|
|
||||||
### Completed Tasks
|
### Completed Tasks
|
||||||
1. Static Assets Migration
|
1. Search Implementation
|
||||||
|
- Created SearchComponent with real-time filtering
|
||||||
|
- Implemented responsive search UI with filters sidebar
|
||||||
|
- Added park cards with dynamic content
|
||||||
|
- Integrated dark mode support
|
||||||
|
- Added pagination and URL state management
|
||||||
|
- Created comprehensive documentation
|
||||||
|
- See `memory-bank/features/SearchImplementation.md` for details
|
||||||
|
|
||||||
|
2. Static Assets Migration
|
||||||
- Created directory structure for images, CSS, and JavaScript
|
- Created directory structure for images, CSS, and JavaScript
|
||||||
- Copied images from Django project
|
- Copied images from Django project
|
||||||
- Migrated JavaScript modules
|
- Migrated JavaScript modules
|
||||||
@@ -50,8 +68,23 @@ Migrating the design from Django to Laravel implementation
|
|||||||
- User menu
|
- User menu
|
||||||
- Auth menu
|
- Auth menu
|
||||||
- Park list with filtering and view modes
|
- Park list with filtering and view modes
|
||||||
|
- Search with real-time filtering
|
||||||
|
- Review system with moderation
|
||||||
|
- Ride management and details
|
||||||
|
|
||||||
### Next Steps
|
### Next Steps
|
||||||
|
1. Filament Admin Implementation
|
||||||
|
- Create admin panel for parks management
|
||||||
|
- Implement CRUD operations using Filament resources
|
||||||
|
- Set up role-based access control
|
||||||
|
- Add audit trails for admin actions
|
||||||
|
- See `memory-bank/features/FilamentIntegration.md` for details
|
||||||
|
|
||||||
|
2. Analytics Integration
|
||||||
|
- Implement analytics tracking
|
||||||
|
- Create statistics dashboard
|
||||||
|
- Add reporting features
|
||||||
|
- Set up data aggregation
|
||||||
1. ✅ Park Model Enhancements
|
1. ✅ Park Model Enhancements
|
||||||
- ✅ Implemented Photo model and relationship
|
- ✅ Implemented Photo model and relationship
|
||||||
- ✅ Added getBySlug method for historical slug support
|
- ✅ Added getBySlug method for historical slug support
|
||||||
@@ -122,11 +155,23 @@ Migrating the design from Django to Laravel implementation
|
|||||||
- ✅ HelpfulVote model with toggle functionality (app/Models/HelpfulVote.php)
|
- ✅ HelpfulVote model with toggle functionality (app/Models/HelpfulVote.php)
|
||||||
- ✅ Added review relationships to Ride model (app/Models/Ride.php)
|
- ✅ Added review relationships to Ride model (app/Models/Ride.php)
|
||||||
- ✅ See `memory-bank/models/ReviewModels.md` for documentation
|
- ✅ See `memory-bank/models/ReviewModels.md` for documentation
|
||||||
- Implement Livewire components:
|
- ✅ Implement Livewire components:
|
||||||
- RideReviewComponent for submitting reviews
|
- ✅ RideReviewComponent for submitting reviews
|
||||||
- RideReviewListComponent for displaying reviews
|
- ✅ Form with star rating input
|
||||||
- ReviewModerationComponent for moderators
|
- ✅ Real-time validation
|
||||||
- See `memory-bank/features/RideReviews.md` for implementation details
|
- ✅ Rate limiting
|
||||||
|
- ✅ Edit capabilities
|
||||||
|
- ✅ RideReviewListComponent for displaying reviews
|
||||||
|
- ✅ Paginated list view
|
||||||
|
- ✅ Sort and filter options
|
||||||
|
- ✅ Helpful vote system
|
||||||
|
- ✅ Statistics display
|
||||||
|
- ✅ ReviewModerationComponent for moderators
|
||||||
|
- ✅ Review queue with filters
|
||||||
|
- ✅ Approve/reject functionality
|
||||||
|
- ✅ Batch actions
|
||||||
|
- ✅ Edit capabilities
|
||||||
|
- ✅ See `memory-bank/features/RideReviews.md` for implementation details
|
||||||
- Implement views and templates:
|
- Implement views and templates:
|
||||||
- ✅ Ride list page (resources/views/livewire/ride-list.blade.php)
|
- ✅ Ride list page (resources/views/livewire/ride-list.blade.php)
|
||||||
- ✅ Ride create/edit form (resources/views/livewire/ride-form.blade.php)
|
- ✅ Ride create/edit form (resources/views/livewire/ride-form.blade.php)
|
||||||
|
|||||||
212
memory-bank/components/ReviewComponents.md
Normal file
212
memory-bank/components/ReviewComponents.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# Review System Livewire Components
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The review system consists of three main Livewire components that handle the creation, display, and moderation of ride reviews. These components maintain feature parity with the Django implementation while leveraging Laravel and Livewire's reactive capabilities.
|
||||||
|
|
||||||
|
### RideReviewComponent
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
The RideReviewComponent provides a form interface for users to submit reviews for rides, with real-time validation and anti-spam measures.
|
||||||
|
|
||||||
|
**Location**:
|
||||||
|
- Component: `app/Livewire/RideReviewComponent.php`
|
||||||
|
- View: `resources/views/livewire/ride-review.blade.php`
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- Star rating input (1-5)
|
||||||
|
- Optional title field
|
||||||
|
- Required content field
|
||||||
|
- Real-time validation
|
||||||
|
- Anti-spam protection
|
||||||
|
- Success/error messaging
|
||||||
|
- One review per ride enforcement
|
||||||
|
- Edit capabilities for own reviews
|
||||||
|
|
||||||
|
#### Implementation Details
|
||||||
|
1. **Form Handling**
|
||||||
|
- Real-time validation using Livewire
|
||||||
|
- Star rating widget implementation
|
||||||
|
- Character count tracking
|
||||||
|
- Form state management
|
||||||
|
- Edit mode support
|
||||||
|
|
||||||
|
2. **Security Features**
|
||||||
|
- Rate limiting
|
||||||
|
- Duplicate prevention
|
||||||
|
- Permission checks
|
||||||
|
- Input sanitization
|
||||||
|
- CSRF protection
|
||||||
|
|
||||||
|
3. **UI Components**
|
||||||
|
- Star rating selector
|
||||||
|
- Dynamic character counter
|
||||||
|
- Form validation feedback
|
||||||
|
- Success/error alerts
|
||||||
|
- Loading states
|
||||||
|
|
||||||
|
4. **Business Logic**
|
||||||
|
- Review uniqueness check
|
||||||
|
- Permission verification
|
||||||
|
- Status management
|
||||||
|
- Edit history tracking
|
||||||
|
|
||||||
|
### RideReviewListComponent
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
The RideReviewListComponent displays a paginated list of reviews with sorting, filtering, and helpful vote functionality.
|
||||||
|
|
||||||
|
**Location**:
|
||||||
|
- Component: `app/Livewire/RideReviewListComponent.php`
|
||||||
|
- View: `resources/views/livewire/ride-review-list.blade.php`
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- Grid/list view toggle
|
||||||
|
- Pagination support
|
||||||
|
- Sort by date/rating
|
||||||
|
- Filter by rating
|
||||||
|
- Helpful vote system
|
||||||
|
- Review statistics display
|
||||||
|
- Responsive design
|
||||||
|
|
||||||
|
#### Implementation Details
|
||||||
|
1. **List Management**
|
||||||
|
- Pagination handling
|
||||||
|
- Sort state management
|
||||||
|
- Filter application
|
||||||
|
- Dynamic loading
|
||||||
|
|
||||||
|
2. **Vote System**
|
||||||
|
- Helpful vote toggle
|
||||||
|
- Vote count tracking
|
||||||
|
- User vote status
|
||||||
|
- Rate limiting
|
||||||
|
|
||||||
|
3. **UI Components**
|
||||||
|
- Review cards/rows
|
||||||
|
- Sort/filter controls
|
||||||
|
- Pagination links
|
||||||
|
- Statistics summary
|
||||||
|
- Loading states
|
||||||
|
|
||||||
|
4. **Statistics Display**
|
||||||
|
- Average rating
|
||||||
|
- Rating distribution
|
||||||
|
- Review count
|
||||||
|
- Helpful vote tallies
|
||||||
|
|
||||||
|
### ReviewModerationComponent
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
The ReviewModerationComponent provides an interface for moderators to review, approve, reject, and edit user reviews.
|
||||||
|
|
||||||
|
**Location**:
|
||||||
|
- Component: `app/Livewire/ReviewModerationComponent.php`
|
||||||
|
- View: `resources/views/livewire/review-moderation.blade.php`
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- Review queue display
|
||||||
|
- Approve/reject actions
|
||||||
|
- Edit capabilities
|
||||||
|
- Status tracking
|
||||||
|
- Moderation history
|
||||||
|
- Batch actions
|
||||||
|
- Search/filter
|
||||||
|
|
||||||
|
#### Implementation Details
|
||||||
|
1. **Queue Management**
|
||||||
|
- Status-based filtering
|
||||||
|
- Priority sorting
|
||||||
|
- Batch processing
|
||||||
|
- History tracking
|
||||||
|
|
||||||
|
2. **Moderation Actions**
|
||||||
|
- Approval workflow
|
||||||
|
- Rejection handling
|
||||||
|
- Edit interface
|
||||||
|
- Status updates
|
||||||
|
- Notification system
|
||||||
|
|
||||||
|
3. **UI Components**
|
||||||
|
- Queue display
|
||||||
|
- Action buttons
|
||||||
|
- Edit forms
|
||||||
|
- Status indicators
|
||||||
|
- History timeline
|
||||||
|
|
||||||
|
4. **Security Features**
|
||||||
|
- Role verification
|
||||||
|
- Action logging
|
||||||
|
- Permission checks
|
||||||
|
- Edit tracking
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### With RideDetailComponent
|
||||||
|
- Review form placement
|
||||||
|
- Review list integration
|
||||||
|
- Statistics display
|
||||||
|
- Component communication
|
||||||
|
|
||||||
|
### With User System
|
||||||
|
- Permission checks
|
||||||
|
- User identification
|
||||||
|
- Rate limiting
|
||||||
|
- Profile integration
|
||||||
|
|
||||||
|
### With Notification System
|
||||||
|
- Review notifications
|
||||||
|
- Moderation alerts
|
||||||
|
- Status updates
|
||||||
|
- User feedback
|
||||||
|
|
||||||
|
## Technical Decisions
|
||||||
|
|
||||||
|
1. **Real-time Validation**
|
||||||
|
- Using Livewire's real-time validation for immediate feedback
|
||||||
|
- Client-side validation for better UX
|
||||||
|
- Server-side validation for security
|
||||||
|
|
||||||
|
2. **State Management**
|
||||||
|
- Component properties for form state
|
||||||
|
- Session for moderation queue
|
||||||
|
- Cache for statistics
|
||||||
|
- Database for permanent storage
|
||||||
|
|
||||||
|
3. **Performance Optimization**
|
||||||
|
- Eager loading relationships
|
||||||
|
- Caching review counts
|
||||||
|
- Lazy loading images
|
||||||
|
- Pagination implementation
|
||||||
|
|
||||||
|
4. **Security Measures**
|
||||||
|
- Rate limiting implementation
|
||||||
|
- Input sanitization
|
||||||
|
- Permission checks
|
||||||
|
- CSRF protection
|
||||||
|
- XSS prevention
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
1. **Unit Tests**
|
||||||
|
- Component methods
|
||||||
|
- Validation rules
|
||||||
|
- Business logic
|
||||||
|
- Helper functions
|
||||||
|
|
||||||
|
2. **Feature Tests**
|
||||||
|
- Form submission
|
||||||
|
- Validation handling
|
||||||
|
- Moderation flow
|
||||||
|
- Vote system
|
||||||
|
|
||||||
|
3. **Integration Tests**
|
||||||
|
- Component interaction
|
||||||
|
- Event handling
|
||||||
|
- State management
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
4. **Browser Tests**
|
||||||
|
- UI interactions
|
||||||
|
- Real-time updates
|
||||||
|
- Responsive design
|
||||||
|
- JavaScript integration
|
||||||
@@ -24,57 +24,69 @@ The ride reviews system allows users to rate and review rides, providing both nu
|
|||||||
- user_id (foreign key to users)
|
- user_id (foreign key to users)
|
||||||
- created_at (timestamp)
|
- created_at (timestamp)
|
||||||
|
|
||||||
## Components to Implement
|
## Components Implemented
|
||||||
|
|
||||||
### RideReviewComponent
|
### RideReviewComponent
|
||||||
- Display review form
|
- Display review form ✅
|
||||||
- Handle review submission
|
- Handle review submission ✅
|
||||||
- Validate input
|
- Validate input ✅
|
||||||
- Show success/error messages
|
- Show success/error messages ✅
|
||||||
|
- Rate limiting implemented ✅
|
||||||
|
- One review per ride enforcement ✅
|
||||||
|
- Edit capabilities ✅
|
||||||
|
|
||||||
### RideReviewListComponent
|
### RideReviewListComponent
|
||||||
- Display reviews for a ride
|
- Display reviews for a ride ✅
|
||||||
- Pagination support
|
- Pagination support ✅
|
||||||
- Sorting options
|
- Sorting options ✅
|
||||||
- Helpful vote functionality
|
- Helpful vote functionality ✅
|
||||||
- Filter options (rating, date)
|
- Filter options (rating, date) ✅
|
||||||
|
- Statistics display ✅
|
||||||
|
- Dark mode support ✅
|
||||||
|
|
||||||
### ReviewModerationComponent
|
### ReviewModerationComponent
|
||||||
- Review queue for moderators
|
- Review queue for moderators ✅
|
||||||
- Approve/reject functionality
|
- Approve/reject functionality ✅
|
||||||
- Edit capabilities
|
- Edit capabilities ✅
|
||||||
- Status tracking
|
- Status tracking ✅
|
||||||
|
- Batch actions ✅
|
||||||
|
- Search functionality ✅
|
||||||
|
|
||||||
## Features Required
|
## Features Implemented
|
||||||
|
|
||||||
1. Review Creation
|
1. Review Creation ✅
|
||||||
- Rating input (1-5 stars)
|
- Rating input (1-5 stars)
|
||||||
- Title field (optional)
|
- Title field (optional)
|
||||||
- Content field
|
- Content field
|
||||||
- Client & server validation
|
- Client & server validation
|
||||||
- Anti-spam measures
|
- Anti-spam measures
|
||||||
|
- Rate limiting
|
||||||
|
|
||||||
2. Review Display
|
2. Review Display ✅
|
||||||
- List/grid view of reviews
|
- List/grid view of reviews
|
||||||
- Sorting by date/rating
|
- Sorting by date/rating
|
||||||
- Pagination
|
- Pagination
|
||||||
- Rating statistics
|
- Rating statistics
|
||||||
- Helpful vote system
|
- Helpful vote system
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
3. Moderation System
|
3. Moderation System ✅
|
||||||
- Review queue
|
- Review queue
|
||||||
- Approval workflow
|
- Approval workflow
|
||||||
- Edit capabilities
|
- Edit capabilities
|
||||||
- Status management
|
- Status management
|
||||||
- Moderation history
|
- Moderation history
|
||||||
|
- Batch actions
|
||||||
|
- Search functionality
|
||||||
|
|
||||||
4. User Features
|
4. User Features ✅
|
||||||
- One review per ride per user
|
- One review per ride per user
|
||||||
- Edit own reviews
|
- Edit own reviews
|
||||||
- Delete own reviews
|
- Delete own reviews
|
||||||
- Vote on helpful reviews
|
- Vote on helpful reviews
|
||||||
|
- Rate limiting on votes
|
||||||
|
|
||||||
5. Statistics
|
5. Statistics ✅
|
||||||
- Average rating calculation
|
- Average rating calculation
|
||||||
- Rating distribution
|
- Rating distribution
|
||||||
- Review count tracking
|
- Review count tracking
|
||||||
@@ -95,39 +107,39 @@ The ride reviews system allows users to rate and review rides, providing both nu
|
|||||||
- ✅ Created ReviewStatus enum (app/Enums/ReviewStatus.php)
|
- ✅ Created ReviewStatus enum (app/Enums/ReviewStatus.php)
|
||||||
- ✅ Implemented methods for average rating and review counts
|
- ✅ Implemented methods for average rating and review counts
|
||||||
|
|
||||||
3. Components
|
3. Components ✅
|
||||||
- Review form component
|
- ✅ Review form component
|
||||||
- Review list component
|
- ✅ Review list component
|
||||||
- Moderation component
|
- ✅ Moderation component
|
||||||
- Statistics display
|
- ✅ Statistics display
|
||||||
|
|
||||||
4. Business Logic
|
4. Business Logic ✅
|
||||||
- Rating calculations
|
- ✅ Rating calculations
|
||||||
- Permission checks
|
- ✅ Permission checks
|
||||||
- Validation rules
|
- ✅ Validation rules
|
||||||
- Anti-spam measures
|
- ✅ Anti-spam measures
|
||||||
|
|
||||||
5. Testing
|
5. Testing
|
||||||
- Unit tests
|
- Unit tests (TODO)
|
||||||
- Feature tests
|
- Feature tests (TODO)
|
||||||
- Integration tests
|
- Integration tests (TODO)
|
||||||
- User flow testing
|
- User flow testing (TODO)
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
1. Authorization
|
1. Authorization ✅
|
||||||
- User authentication required
|
- User authentication required
|
||||||
- Rate limiting
|
- Rate limiting implemented
|
||||||
- Moderation permissions
|
- Moderation permissions
|
||||||
- Edit/delete permissions
|
- Edit/delete permissions
|
||||||
|
|
||||||
2. Data Validation
|
2. Data Validation ✅
|
||||||
- Input sanitization
|
- Input sanitization
|
||||||
- Rating range validation
|
- Rating range validation
|
||||||
- Content length limits
|
- Content length limits
|
||||||
- Duplicate prevention
|
- Duplicate prevention
|
||||||
|
|
||||||
3. Anti-Abuse
|
3. Anti-Abuse ✅
|
||||||
- Rate limiting
|
- Rate limiting
|
||||||
- Spam detection
|
- Spam detection
|
||||||
- Vote manipulation prevention
|
- Vote manipulation prevention
|
||||||
@@ -137,8 +149,6 @@ The ride reviews system allows users to rate and review rides, providing both nu
|
|||||||
|
|
||||||
### Model Implementation
|
### Model Implementation
|
||||||
|
|
||||||
The review system consists of two main models:
|
|
||||||
|
|
||||||
1. Review - Represents a user's review of a ride
|
1. Review - Represents a user's review of a ride
|
||||||
- Implemented in `app/Models/Review.php`
|
- Implemented in `app/Models/Review.php`
|
||||||
- Uses ReviewStatus enum for status management
|
- Uses ReviewStatus enum for status management
|
||||||
@@ -158,4 +168,30 @@ The review system consists of two main models:
|
|||||||
- Created canBeReviewedBy method to check if a user can review a ride
|
- Created canBeReviewedBy method to check if a user can review a ride
|
||||||
- Implemented addReview method for creating new reviews
|
- Implemented addReview method for creating new reviews
|
||||||
|
|
||||||
These models follow Laravel's Eloquent ORM patterns while maintaining feature parity with the Django implementation.
|
### Component Implementation
|
||||||
|
|
||||||
|
1. RideReviewComponent
|
||||||
|
- Form-based component for submitting reviews
|
||||||
|
- Real-time validation using Livewire
|
||||||
|
- Rate limiting using Laravel's RateLimiter
|
||||||
|
- Edit mode support for updating reviews
|
||||||
|
- Success/error message handling
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
|
2. RideReviewListComponent
|
||||||
|
- Paginated list of reviews
|
||||||
|
- Sort by date or rating
|
||||||
|
- Filter by rating
|
||||||
|
- Helpful vote functionality
|
||||||
|
- Statistics panel with rating distribution
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
|
3. ReviewModerationComponent
|
||||||
|
- Queue-based moderation interface
|
||||||
|
- Status-based filtering (pending, approved, rejected)
|
||||||
|
- Search functionality
|
||||||
|
- Batch actions for approve/reject
|
||||||
|
- Edit modal for review modification
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
|
These components follow Laravel's Eloquent ORM patterns while maintaining feature parity with the Django implementation. The use of Livewire enables real-time interactivity without requiring custom JavaScript.
|
||||||
139
memory-bank/features/SearchImplementation.md
Normal file
139
memory-bank/features/SearchImplementation.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Search Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The search functionality has been migrated from Django to Laravel/Livewire while maintaining feature parity and improving the user experience with real-time filtering.
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
### SearchComponent (app/Livewire/SearchComponent.php)
|
||||||
|
- Handles search and filtering logic
|
||||||
|
- Uses Livewire's real-time search capabilities
|
||||||
|
- Maintains query parameters in URL
|
||||||
|
- Implements pagination for results
|
||||||
|
|
||||||
|
#### Filter Properties
|
||||||
|
- `search`: Text search across name and description
|
||||||
|
- `location`: Location-based filtering
|
||||||
|
- `minRating` and `maxRating`: Rating range filtering
|
||||||
|
- `minRides`: Minimum number of rides filter
|
||||||
|
- `minCoasters`: Minimum number of coasters filter
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- Real-time filtering with `wire:model.live`
|
||||||
|
- URL query string synchronization
|
||||||
|
- Eager loading of relationships for performance
|
||||||
|
- Responsive pagination
|
||||||
|
- Filter state management
|
||||||
|
|
||||||
|
### View Implementation (resources/views/livewire/search.blade.php)
|
||||||
|
- Responsive layout with filters sidebar
|
||||||
|
- Real-time updates without page reload
|
||||||
|
- Dark mode support
|
||||||
|
- Accessible form controls
|
||||||
|
- Mobile-first design
|
||||||
|
|
||||||
|
#### UI Components
|
||||||
|
1. Filters Sidebar
|
||||||
|
- Search input
|
||||||
|
- Location filter
|
||||||
|
- Rating range inputs
|
||||||
|
- Ride count filters
|
||||||
|
- Clear filters button
|
||||||
|
|
||||||
|
2. Results Section
|
||||||
|
- Results count display
|
||||||
|
- Park cards with:
|
||||||
|
* Featured image
|
||||||
|
* Park name and location
|
||||||
|
* Rating badge
|
||||||
|
* Status indicator
|
||||||
|
* Ride/coaster counts
|
||||||
|
* Description preview
|
||||||
|
|
||||||
|
## Differences from Django Implementation
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
1. Real-time Updates
|
||||||
|
- Replaced HTMX with Livewire's native reactivity
|
||||||
|
- Instant filtering without page reloads
|
||||||
|
- Smoother user experience
|
||||||
|
|
||||||
|
2. State Management
|
||||||
|
- URL query parameters for shareable searches
|
||||||
|
- Persistent filter state during navigation
|
||||||
|
- Clear filters functionality
|
||||||
|
|
||||||
|
3. Performance
|
||||||
|
- Eager loading of relationships
|
||||||
|
- Efficient query building
|
||||||
|
- Optimized view rendering
|
||||||
|
|
||||||
|
### Feature Parity
|
||||||
|
- Maintained all Django filtering capabilities
|
||||||
|
- Preserved UI/UX patterns
|
||||||
|
- Kept identical data presentation
|
||||||
|
- Matched search algorithm functionality
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Query Building
|
||||||
|
```php
|
||||||
|
protected function getFilteredParks()
|
||||||
|
{
|
||||||
|
return Park::query()
|
||||||
|
->select('parks.*')
|
||||||
|
->with(['location', 'photos'])
|
||||||
|
->when($this->search, function (Builder $query) {
|
||||||
|
$query->where(function (Builder $query) {
|
||||||
|
$query->where('name', 'like', "%{$this->search}%")
|
||||||
|
->orWhere('description', 'like', "%{$this->search}%");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// Additional filter conditions...
|
||||||
|
->paginate(10);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter State Management
|
||||||
|
```php
|
||||||
|
protected $queryString = [
|
||||||
|
'search' => ['except' => ''],
|
||||||
|
'location' => ['except' => ''],
|
||||||
|
'minRating' => ['except' => ''],
|
||||||
|
'maxRating' => ['except' => ''],
|
||||||
|
'minRides' => ['except' => ''],
|
||||||
|
'minCoasters' => ['except' => '']
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Considerations
|
||||||
|
1. Filter Combinations
|
||||||
|
- Test various filter combinations
|
||||||
|
- Verify result accuracy
|
||||||
|
- Check edge cases
|
||||||
|
|
||||||
|
2. Performance Testing
|
||||||
|
- Large result sets
|
||||||
|
- Multiple concurrent users
|
||||||
|
- Query optimization
|
||||||
|
|
||||||
|
3. UI Testing
|
||||||
|
- Mobile responsiveness
|
||||||
|
- Dark mode functionality
|
||||||
|
- Accessibility compliance
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
1. Advanced Filters
|
||||||
|
- Date range filtering
|
||||||
|
- Category filtering
|
||||||
|
- Geographic radius search
|
||||||
|
|
||||||
|
2. Performance Optimizations
|
||||||
|
- Result caching
|
||||||
|
- Lazy loading options
|
||||||
|
- Query optimization
|
||||||
|
|
||||||
|
3. UI Improvements
|
||||||
|
- Save search preferences
|
||||||
|
- Filter presets
|
||||||
|
- Advanced sorting options
|
||||||
145
memory-bank/prompts/ReviewComponentsImplementation.md
Normal file
145
memory-bank/prompts/ReviewComponentsImplementation.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Review Components Implementation
|
||||||
|
|
||||||
|
## Task Overview
|
||||||
|
Implement the Livewire components for the ride review system, following the Laravel/Livewire implementation guidelines and maintaining feature parity with the Django implementation.
|
||||||
|
|
||||||
|
## Components to Implement
|
||||||
|
|
||||||
|
### 1. RideReviewComponent
|
||||||
|
Create a new Livewire component for submitting ride reviews:
|
||||||
|
|
||||||
|
```php
|
||||||
|
php artisan make:livewire RideReviewComponent
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Form for submitting new reviews
|
||||||
|
- Rating input (1-5 stars)
|
||||||
|
- Optional title field
|
||||||
|
- Required content field
|
||||||
|
- Real-time validation
|
||||||
|
- Success/error messaging
|
||||||
|
- Anti-spam measures
|
||||||
|
- Check if user can review (one per ride)
|
||||||
|
|
||||||
|
### 2. RideReviewListComponent
|
||||||
|
Create a component to display ride reviews:
|
||||||
|
|
||||||
|
```php
|
||||||
|
php artisan make:livewire RideReviewListComponent
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Grid/list view of reviews
|
||||||
|
- Pagination support
|
||||||
|
- Sorting options (date, rating)
|
||||||
|
- Filter by rating
|
||||||
|
- Helpful vote functionality
|
||||||
|
- Display review statistics
|
||||||
|
- Responsive design matching Django
|
||||||
|
|
||||||
|
### 3. ReviewModerationComponent
|
||||||
|
Create a moderation interface component:
|
||||||
|
|
||||||
|
```php
|
||||||
|
php artisan make:livewire ReviewModerationComponent
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Review queue display
|
||||||
|
- Approve/reject actions
|
||||||
|
- Edit capabilities
|
||||||
|
- Status tracking
|
||||||
|
- Moderation history
|
||||||
|
- Permission checks
|
||||||
|
|
||||||
|
## Implementation Guidelines
|
||||||
|
|
||||||
|
1. Use Livewire's Real-time Validation
|
||||||
|
- Validate rating range (1-5)
|
||||||
|
- Required fields
|
||||||
|
- Content length limits
|
||||||
|
- Anti-spam rules
|
||||||
|
|
||||||
|
2. State Management
|
||||||
|
- Track form state
|
||||||
|
- Handle validation errors
|
||||||
|
- Manage success/error messages
|
||||||
|
- Maintain sort/filter state
|
||||||
|
|
||||||
|
3. Event Handling
|
||||||
|
- Review submission events
|
||||||
|
- Helpful vote toggling
|
||||||
|
- Moderation actions
|
||||||
|
- Pagination events
|
||||||
|
|
||||||
|
4. View Templates
|
||||||
|
- Create Blade views for each component
|
||||||
|
- Follow project's design system
|
||||||
|
- Maintain responsive design
|
||||||
|
- Support dark mode
|
||||||
|
|
||||||
|
5. Authorization
|
||||||
|
- Use Laravel's authorization system
|
||||||
|
- Check review permissions
|
||||||
|
- Verify moderation access
|
||||||
|
- Rate limiting implementation
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
1. Feature Tests
|
||||||
|
- Review submission
|
||||||
|
- Validation rules
|
||||||
|
- Helpful votes
|
||||||
|
- Moderation flow
|
||||||
|
|
||||||
|
2. Component Tests
|
||||||
|
- Real-time validation
|
||||||
|
- State management
|
||||||
|
- Event handling
|
||||||
|
- Authorization
|
||||||
|
|
||||||
|
3. View Tests
|
||||||
|
- Rendering logic
|
||||||
|
- Responsive design
|
||||||
|
- Dark mode support
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
|
||||||
|
1. Update Memory Bank
|
||||||
|
- Document component implementations
|
||||||
|
- Track progress
|
||||||
|
- Note any deviations
|
||||||
|
- Update technical decisions
|
||||||
|
|
||||||
|
2. Update Component Docs
|
||||||
|
- Usage examples
|
||||||
|
- Props/events
|
||||||
|
- State management
|
||||||
|
- Authorization rules
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Create RideReviewComponent
|
||||||
|
- Implement form layout
|
||||||
|
- Add validation rules
|
||||||
|
- Handle submission
|
||||||
|
- Add success/error states
|
||||||
|
|
||||||
|
2. Build RideReviewListComponent
|
||||||
|
- Create list/grid views
|
||||||
|
- Add sorting/filtering
|
||||||
|
- Implement pagination
|
||||||
|
- Add helpful votes
|
||||||
|
|
||||||
|
3. Develop ReviewModerationComponent
|
||||||
|
- Build moderation queue
|
||||||
|
- Add approval workflow
|
||||||
|
- Implement edit features
|
||||||
|
- Track moderation history
|
||||||
|
|
||||||
|
4. Integration
|
||||||
|
- Add to ride detail page
|
||||||
|
- Connect moderation panel
|
||||||
|
- Test all interactions
|
||||||
|
- Verify feature parity
|
||||||
280
resources/views/livewire/review-moderation-component.blade.php
Normal file
280
resources/views/livewire/review-moderation-component.blade.php
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
<div class="space-y-6">
|
||||||
|
{{-- Message --}}
|
||||||
|
@if ($message)
|
||||||
|
<div @class([
|
||||||
|
'p-4 mb-4 rounded-lg',
|
||||||
|
'bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-200' => !str_contains($message, 'error'),
|
||||||
|
'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-200' => str_contains($message, 'error'),
|
||||||
|
])>
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Controls --}}
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||||
|
<div class="p-4 space-y-4">
|
||||||
|
{{-- Status Tabs --}}
|
||||||
|
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<nav class="-mb-px flex space-x-8">
|
||||||
|
<button
|
||||||
|
wire:click="filterByStatus('{{ ReviewStatus::PENDING->value }}')"
|
||||||
|
@class([
|
||||||
|
'pb-4 px-1 border-b-2 font-medium text-sm',
|
||||||
|
'border-primary-500 text-primary-600' => $statusFilter === ReviewStatus::PENDING->value,
|
||||||
|
'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' => $statusFilter !== ReviewStatus::PENDING->value,
|
||||||
|
])
|
||||||
|
>
|
||||||
|
Pending
|
||||||
|
@if ($totalPending > 0)
|
||||||
|
<span class="ml-2 bg-primary-100 text-primary-600 py-0.5 px-2 rounded-full text-xs">
|
||||||
|
{{ $totalPending }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="filterByStatus('{{ ReviewStatus::APPROVED->value }}')"
|
||||||
|
@class([
|
||||||
|
'pb-4 px-1 border-b-2 font-medium text-sm',
|
||||||
|
'border-primary-500 text-primary-600' => $statusFilter === ReviewStatus::APPROVED->value,
|
||||||
|
'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' => $statusFilter !== ReviewStatus::APPROVED->value,
|
||||||
|
])
|
||||||
|
>
|
||||||
|
Approved
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="filterByStatus('{{ ReviewStatus::REJECTED->value }}')"
|
||||||
|
@class([
|
||||||
|
'pb-4 px-1 border-b-2 font-medium text-sm',
|
||||||
|
'border-primary-500 text-primary-600' => $statusFilter === ReviewStatus::REJECTED->value,
|
||||||
|
'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' => $statusFilter !== ReviewStatus::REJECTED->value,
|
||||||
|
])
|
||||||
|
>
|
||||||
|
Rejected
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Search & Batch Actions --}}
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="max-w-lg flex-1">
|
||||||
|
<label for="search" class="sr-only">Search reviews</label>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">
|
||||||
|
🔍
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
wire:model.live.debounce.300ms="search"
|
||||||
|
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md leading-5 bg-white dark:bg-gray-700 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
|
||||||
|
placeholder="Search reviews..."
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (count($selected) > 0)
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
{{ count($selected) }} selected
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
wire:click="batchApprove"
|
||||||
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
|
>
|
||||||
|
Approve Selected
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="batchReject"
|
||||||
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||||
|
>
|
||||||
|
Reject Selected
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Reviews List --}}
|
||||||
|
<div class="bg-white dark:bg-gray-800 shadow rounded-lg">
|
||||||
|
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
@forelse ($reviews as $review)
|
||||||
|
<li class="p-4">
|
||||||
|
<div class="flex items-start space-x-4">
|
||||||
|
{{-- Checkbox --}}
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value="{{ $review->id }}"
|
||||||
|
wire:model.live="selected"
|
||||||
|
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Content --}}
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ $review->user->name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{{ $review->created_at->diffForHumans() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<div class="text-yellow-400">
|
||||||
|
@for ($i = 1; $i <= 5; $i++)
|
||||||
|
<span @class(['opacity-40' => $i > $review->rating])>★</span>
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
|
@if ($review->title)
|
||||||
|
<span class="text-gray-900 dark:text-gray-100 font-medium">
|
||||||
|
{{ $review->title }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 text-gray-600 dark:text-gray-400">
|
||||||
|
{{ $review->content }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-2 text-sm text-gray-500">
|
||||||
|
For: {{ $review->ride->name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Actions --}}
|
||||||
|
<div class="flex-shrink-0 flex items-center space-x-2">
|
||||||
|
<button
|
||||||
|
wire:click="editReview({{ $review->id }})"
|
||||||
|
class="inline-flex items-center p-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="approve({{ $review->id }})"
|
||||||
|
class="inline-flex items-center p-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="reject({{ $review->id }})"
|
||||||
|
class="inline-flex items-center p-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@empty
|
||||||
|
<li class="p-4 text-center text-gray-500 dark:text-gray-400">
|
||||||
|
No reviews found.
|
||||||
|
</li>
|
||||||
|
@endforelse
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{-- Pagination --}}
|
||||||
|
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
{{ $reviews->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Edit Modal --}}
|
||||||
|
@if ($showEditModal)
|
||||||
|
<div
|
||||||
|
class="fixed z-10 inset-0 overflow-y-auto"
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||||
|
{{-- Background overlay --}}
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||||
|
aria-hidden="true"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
{{-- Modal panel --}}
|
||||||
|
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||||
|
<form wire:submit="saveEdit">
|
||||||
|
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
{{-- Rating --}}
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Rating
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 flex items-center space-x-2">
|
||||||
|
@for ($i = 1; $i <= 5; $i++)
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
wire:click="$set('form.rating', {{ $i }})"
|
||||||
|
class="text-2xl focus:outline-none"
|
||||||
|
>
|
||||||
|
<span @class([
|
||||||
|
'text-yellow-400' => $i <= $form['rating'],
|
||||||
|
'text-gray-300 dark:text-gray-600' => $i > $form['rating'],
|
||||||
|
])>★</span>
|
||||||
|
</button>
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
|
@error('form.rating')
|
||||||
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Title --}}
|
||||||
|
<div>
|
||||||
|
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Title
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
wire:model="form.title"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm dark:bg-gray-700"
|
||||||
|
>
|
||||||
|
@error('form.title')
|
||||||
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Content --}}
|
||||||
|
<div>
|
||||||
|
<label for="content" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Content
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
wire:model="form.content"
|
||||||
|
rows="4"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm dark:bg-gray-700"
|
||||||
|
></textarea>
|
||||||
|
@error('form.content')
|
||||||
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Modal footer --}}
|
||||||
|
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-primary-600 text-base font-medium text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 sm:ml-3 sm:w-auto sm:text-sm"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
wire:click="$set('showEditModal', false)"
|
||||||
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
101
resources/views/livewire/ride-review-component.blade.php
Normal file
101
resources/views/livewire/ride-review-component.blade.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
{{-- Show message if exists --}}
|
||||||
|
@if ($message)
|
||||||
|
<div @class([
|
||||||
|
'p-4 mb-4 rounded-lg',
|
||||||
|
'bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-200' => !str_contains($message, 'error') && !str_contains($message, 'cannot'),
|
||||||
|
'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-200' => str_contains($message, 'error') || str_contains($message, 'cannot'),
|
||||||
|
])>
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Review Form --}}
|
||||||
|
<form wire:submit="save" class="space-y-6">
|
||||||
|
{{-- Star Rating --}}
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Rating <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
@for ($i = 1; $i <= 5; $i++)
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
wire:click="$set('rating', {{ $i }})"
|
||||||
|
class="text-2xl focus:outline-none"
|
||||||
|
>
|
||||||
|
<span @class([
|
||||||
|
'text-yellow-400' => $i <= $rating,
|
||||||
|
'text-gray-300 dark:text-gray-600' => $i > $rating,
|
||||||
|
])>★</span>
|
||||||
|
</button>
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
|
@error('rating')
|
||||||
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Title Field --}}
|
||||||
|
<div>
|
||||||
|
<label for="title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Title <span class="text-gray-500">(optional)</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="title"
|
||||||
|
wire:model="title"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||||
|
placeholder="Give your review a title"
|
||||||
|
>
|
||||||
|
@error('title')
|
||||||
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Content Field --}}
|
||||||
|
<div>
|
||||||
|
<label for="content" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Review <span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="content"
|
||||||
|
wire:model="content"
|
||||||
|
rows="4"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500"
|
||||||
|
placeholder="Share your experience with this ride"
|
||||||
|
></textarea>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
|
{{ strlen($content) }}/2000 characters
|
||||||
|
</p>
|
||||||
|
@error('content')
|
||||||
|
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Submit Button --}}
|
||||||
|
<div class="flex justify-end space-x-3">
|
||||||
|
@if ($isEditing)
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
wire:click="resetForm"
|
||||||
|
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
wire:loading.attr="disabled"
|
||||||
|
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-primary-600 border border-transparent rounded-md shadow-sm hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<span wire:loading.remove>
|
||||||
|
{{ $isEditing ? 'Update Review' : 'Submit Review' }}
|
||||||
|
</span>
|
||||||
|
<span wire:loading>
|
||||||
|
{{ $isEditing ? 'Updating...' : 'Submitting...' }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
189
resources/views/livewire/ride-review-list-component.blade.php
Normal file
189
resources/views/livewire/ride-review-list-component.blade.php
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<div class="space-y-6">
|
||||||
|
{{-- Message --}}
|
||||||
|
@if ($message)
|
||||||
|
<div @class([
|
||||||
|
'p-4 mb-4 rounded-lg',
|
||||||
|
'bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-200' => !str_contains($message, 'error') && !str_contains($message, 'cannot'),
|
||||||
|
'bg-red-100 dark:bg-red-800 text-red-700 dark:text-red-200' => str_contains($message, 'error') || str_contains($message, 'cannot'),
|
||||||
|
])>
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Statistics Panel --}}
|
||||||
|
<div x-data="{ open: @entangle('showStats') }" class="bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||||
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="open = !open"
|
||||||
|
class="flex justify-between items-center w-full"
|
||||||
|
>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
Review Statistics
|
||||||
|
</h3>
|
||||||
|
<span class="transform" :class="{ 'rotate-180': open }">
|
||||||
|
▼
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div x-show="open" class="p-4">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ $statistics['average'] }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
Average Rating
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ $statistics['total'] }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
Total Reviews
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-1 md:col-span-1">
|
||||||
|
@foreach (range(5, 1) as $rating)
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="w-8 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ $rating }}★
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
x-data="{ width: '{{ $statistics['total'] > 0 ? number_format(($statistics['distribution'][$rating] / $statistics['total']) * 100, 1) : 0 }}%' }"
|
||||||
|
class="flex-1 h-4 mx-2 bg-gray-200 dark:bg-gray-700 rounded relative overflow-hidden"
|
||||||
|
>
|
||||||
|
@if ($statistics['total'] > 0)
|
||||||
|
<div
|
||||||
|
class="h-4 bg-yellow-400 rounded absolute inset-y-0 left-0"
|
||||||
|
:style="{ width }"
|
||||||
|
></div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<span class="w-8 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ $statistics['distribution'][$rating] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Controls --}}
|
||||||
|
<div class="flex flex-wrap gap-4 items-center justify-between">
|
||||||
|
{{-- Sort Controls --}}
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
wire:click="sortBy('created_at')"
|
||||||
|
@class([
|
||||||
|
'px-3 py-2 text-sm font-medium rounded-md',
|
||||||
|
'bg-primary-100 text-primary-700 dark:bg-primary-800 dark:text-primary-200' => $sortField === 'created_at',
|
||||||
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => $sortField !== 'created_at',
|
||||||
|
])
|
||||||
|
>
|
||||||
|
Date
|
||||||
|
@if ($sortField === 'created_at')
|
||||||
|
<span>{{ $sortDirection === 'asc' ? '↑' : '↓' }}</span>
|
||||||
|
@endif
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="sortBy('rating')"
|
||||||
|
@class([
|
||||||
|
'px-3 py-2 text-sm font-medium rounded-md',
|
||||||
|
'bg-primary-100 text-primary-700 dark:bg-primary-800 dark:text-primary-200' => $sortField === 'rating',
|
||||||
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => $sortField !== 'rating',
|
||||||
|
])
|
||||||
|
>
|
||||||
|
Rating
|
||||||
|
@if ($sortField === 'rating')
|
||||||
|
<span>{{ $sortDirection === 'asc' ? '↑' : '↓' }}</span>
|
||||||
|
@endif
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Rating Filter --}}
|
||||||
|
<div class="flex gap-2">
|
||||||
|
@foreach (range(1, 5) as $rating)
|
||||||
|
<button
|
||||||
|
wire:click="filterByRating({{ $rating }})"
|
||||||
|
@class([
|
||||||
|
'px-3 py-2 text-sm font-medium rounded-md',
|
||||||
|
'bg-yellow-100 text-yellow-700 dark:bg-yellow-800 dark:text-yellow-200' => $ratingFilter === $rating,
|
||||||
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => $ratingFilter !== $rating,
|
||||||
|
])
|
||||||
|
>
|
||||||
|
{{ $rating }}★
|
||||||
|
</button>
|
||||||
|
@endforeach
|
||||||
|
@if ($ratingFilter)
|
||||||
|
<button
|
||||||
|
wire:click="filterByRating(null)"
|
||||||
|
class="px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Reviews List --}}
|
||||||
|
<div class="space-y-4">
|
||||||
|
@forelse ($reviews as $review)
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="text-yellow-400 text-xl">
|
||||||
|
@for ($i = 1; $i <= 5; $i++)
|
||||||
|
<span @class(['opacity-40' => $i > $review->rating])>★</span>
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
|
@if ($review->title)
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ $review->title }}
|
||||||
|
</h3>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
||||||
|
{{ $review->content }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{{ $review->created_at->diffForHumans() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
By {{ $review->user->name }}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
wire:click="toggleHelpfulVote({{ $review->id }})"
|
||||||
|
@class([
|
||||||
|
'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md',
|
||||||
|
'bg-green-100 text-green-700 dark:bg-green-800 dark:text-green-200' => $review->helpfulVotes->contains('user_id', Auth::id()),
|
||||||
|
'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700' => !$review->helpfulVotes->contains('user_id', Auth::id()),
|
||||||
|
])
|
||||||
|
>
|
||||||
|
<span>Helpful</span>
|
||||||
|
<span>({{ $review->helpfulVotes->count() }})</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
|
||||||
|
@if ($ratingFilter)
|
||||||
|
No {{ $ratingFilter }}-star reviews yet.
|
||||||
|
@else
|
||||||
|
No reviews yet.
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
|
||||||
|
{{-- Pagination --}}
|
||||||
|
<div class="mt-6">
|
||||||
|
{{ $reviews->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
189
resources/views/livewire/search.blade.php
Normal file
189
resources/views/livewire/search.blade.php
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="flex flex-col lg:flex-row gap-8">
|
||||||
|
<!-- Filters Sidebar -->
|
||||||
|
<div class="lg:w-1/4">
|
||||||
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-bold mb-4 dark:text-white">Filter Parks</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Search -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="search" class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<input type="text"
|
||||||
|
wire:model.live="search"
|
||||||
|
id="search"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||||
|
placeholder="Search parks...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="location" class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Location
|
||||||
|
</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<input type="text"
|
||||||
|
wire:model.live="location"
|
||||||
|
id="location"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||||
|
placeholder="Filter by location...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rating Range -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Rating Range</label>
|
||||||
|
<div class="flex gap-2 mt-1">
|
||||||
|
<input type="number"
|
||||||
|
wire:model.live="minRating"
|
||||||
|
min="0"
|
||||||
|
max="5"
|
||||||
|
step="0.1"
|
||||||
|
class="w-1/2 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||||
|
placeholder="Min">
|
||||||
|
<input type="number"
|
||||||
|
wire:model.live="maxRating"
|
||||||
|
min="0"
|
||||||
|
max="5"
|
||||||
|
step="0.1"
|
||||||
|
class="w-1/2 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||||
|
placeholder="Max">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Minimum Rides -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="minRides" class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Minimum Rides
|
||||||
|
</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<input type="number"
|
||||||
|
wire:model.live="minRides"
|
||||||
|
id="minRides"
|
||||||
|
min="0"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||||
|
placeholder="Minimum number of rides">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Minimum Coasters -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="minCoasters" class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Minimum Coasters
|
||||||
|
</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<input type="number"
|
||||||
|
wire:model.live="minCoasters"
|
||||||
|
id="minCoasters"
|
||||||
|
min="0"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||||
|
placeholder="Minimum number of coasters">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Actions -->
|
||||||
|
<div class="flex justify-between pt-2">
|
||||||
|
@if($filtersApplied)
|
||||||
|
<button wire:click="clearFilters"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:hover:bg-gray-600">
|
||||||
|
Clear Filters
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Section -->
|
||||||
|
<div class="lg:w-3/4">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||||
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-xl font-bold dark:text-white">
|
||||||
|
Search Results
|
||||||
|
<span class="text-sm font-normal text-gray-500 dark:text-gray-400">({{ $results->total() }} found)</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
@forelse($results as $park)
|
||||||
|
<div class="p-6 flex flex-col md:flex-row gap-4">
|
||||||
|
<!-- Park Image -->
|
||||||
|
<div class="md:w-48 h-32 bg-gray-200 dark:bg-gray-700 rounded-lg overflow-hidden">
|
||||||
|
@if($park->photos->isNotEmpty())
|
||||||
|
<img src="{{ $park->photos->first()->image_url }}"
|
||||||
|
alt="{{ $park->name }}"
|
||||||
|
class="w-full h-full object-cover">
|
||||||
|
@else
|
||||||
|
<div class="w-full h-full flex items-center justify-center text-gray-400 dark:text-gray-500">
|
||||||
|
No Image
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Park Details -->
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold">
|
||||||
|
<a href="{{ route('parks.show', $park) }}" class="hover:text-blue-600 dark:text-white dark:hover:text-blue-400">
|
||||||
|
{{ $park->name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
@if($park->formatted_location)
|
||||||
|
<p>{{ $park->formatted_location }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 flex flex-wrap gap-2">
|
||||||
|
@if($park->average_rating)
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100">
|
||||||
|
{{ number_format($park->average_rating, 1) }} ★
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100">
|
||||||
|
{{ $park->status->label() }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
@if($park->ride_count)
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-800 dark:text-purple-100">
|
||||||
|
{{ $park->ride_count }} Rides
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($park->coaster_count)
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100">
|
||||||
|
{{ $park->coaster_count }} Coasters
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($park->description)
|
||||||
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
||||||
|
{{ $park->description }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
|
||||||
|
No parks found matching your criteria.
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($results->hasPages())
|
||||||
|
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
{{ $results->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -79,6 +79,4 @@ Route::get('/profile/{username}', function () {
|
|||||||
})->name('profile.show');
|
})->name('profile.show');
|
||||||
|
|
||||||
// Search route
|
// Search route
|
||||||
Route::get('/search', function () {
|
Route::get('/search', \App\Livewire\SearchComponent::class)->name('search');
|
||||||
return 'Search';
|
|
||||||
})->name('search');
|
|
||||||
|
|||||||
Reference in New Issue
Block a user