diff --git a/README.md b/README.md index 0e1211217..6a9b7536b 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,108 @@ -# [your app name here] +# πŸ¦Έβ€β™‚οΈ HabitHero -CodePath WEB103 Final Project +### CodePath WEB103 Final Project -Designed and developed by: [your names here] +**Designed and developed by:** +Ricardo Beale, Vitaliy Prymak, Om Patki -πŸ”— Link to deployed app: +πŸ”— **Link to deployed app:** [HabitHero](https://habitss-cxf1.onrender.com) -## About +--- -### Description and Purpose +## 🧩 About -[text goes here] +### πŸ“ Description and Purpose +HabitHero is a fullstack web app that helps users build and track positive habits through small daily goals. +Each completed habit helps the user’s β€œhero” grow stronger, motivating consistency and self-improvement. +The app allows users to add, edit, and delete habits, view progress, and earn virtual achievements for streaks. -### Inspiration +### πŸ’‘ Inspiration +We were inspired by apps like **Habitica** and **Duolingo** that combine productivity with fun. +Many people struggle to stay consistent, so we wanted to create something that makes tracking habits more engaging β€” turning discipline into a game. -[text goes here] +--- -## Tech Stack +## βš™οΈ Tech Stack +- **Frontend:** React, React Router, Tailwind CSS +- **Backend:** Express.js, Node.js, PostgreSQL -Frontend: +--- +## 🌟 Basic Features +### πŸͺͺ Authentication βœ… +Users will be able to create an account, log in, and securely access their personalized habit data. -Backend: +![](gifs/Authentication.gif) -## Features +### 🧠 Habit Dashboard with Habit Cards βœ… +Users can view all their habits in one place with progress tracking and streak counts. Each habit will be displayed as a card showing its name, progress, category, and completion status, allowing users to easily track and manage their habits. -### [Name of Feature 1] +![](gifs/Habit-Dashboard.gif) -[short description goes here] +### βž• Add / Edit / Delete Habits βœ… +Users can create new habits, update them, or remove them easily from their dashboard. -[gif goes here] +![](gifs/Add-Edit-Delete-Habits.gif) -### [Name of Feature 2] +--- +## πŸ”₯ Bonus (Stretch Features) -[short description goes here] +### πŸ’¬ Motivational Quotes βœ… +Displays daily motivational quotes to encourage users and help maintain consistent habit-building momentum. -[gif goes here] +![](gifs/Motivational-Quotes.gif) -### [Name of Feature 3] +### πŸ“Š Progress Bar and Streak Tracking βœ… +Visual indicators show how close users are to completing each habit and how long they’ve kept a streak going, motivating them to continue. -[short description goes here] +![](gifs/Progress-Bar-and-Streak-Tracking.gif) -[gif goes here] +### πŸ€– AI Habit Coach Chatbot βœ… +An AI-powered assistant that guides users, answers questions, and offers personalized habit-building tips or suggestions. -### [ADDITIONAL FEATURES GO HERE - ADD ALL FEATURES HERE IN THE FORMAT ABOVE; you will check these off and add gifs as you complete them] +![](gifs/AI-Habit-Coach-Chatbot.gif) -## Installation Instructions +### ✍️ AI Mood Reflection and Journaling βœ… +Allows users to log their mood and daily reflections. AI summarizes, analyzes patterns, and provides emotional or habit-based insights. -[instructions go here] +![](gifs/AI-Mood-Reflection-and-Journaling.gif) + +### ⚑ AI-Powered Autocomplete for Habit Names βœ… +When users type a new habit, the system predicts and suggests common habit names to make creation faster and easier. + +![](gifs/AI-Powered-Autocomplete.gif) + +### 🎨 AI-Powered Habit Naming and Description Generator βœ… +Generates creative, well-written habit names and descriptions based on user goals, helping them articulate habits more effectively. + +![](gifs/AI-Powered-Habit-Naming.gif) + +### πŸ”₯ Accountability Heatmap βœ… +A visual heatmap showing activity levels over time, helping users see patterns in consistency and identify strong or weak periods in their habit-building journey. + +![](gifs/Accountability-Heatmap.gif) + +--- + +## πŸ’» Installation Instructions + +### 1. Clone the repository +```bash +git clone https://github.com/your-team/habithero.git +``` +### 2. Install dependencies +```bash +cd habithero +npm install +cd client +npm install +``` +### 3. Create and seed the PostgreSQL database +```bash +npm run db:reset +``` +### 4. Run both servers +```bash +npm run dev +``` +### 5. Open the app +- Visit: http://localhost:5173 diff --git a/gifs/AI-Habit-Coach-Chatbot.gif b/gifs/AI-Habit-Coach-Chatbot.gif new file mode 100644 index 000000000..9456b5c3e Binary files /dev/null and b/gifs/AI-Habit-Coach-Chatbot.gif differ diff --git a/gifs/AI-Mood-Reflection-and-Journaling.gif b/gifs/AI-Mood-Reflection-and-Journaling.gif new file mode 100644 index 000000000..b80bffbf2 Binary files /dev/null and b/gifs/AI-Mood-Reflection-and-Journaling.gif differ diff --git a/gifs/AI-Powered-Autocomplete.gif b/gifs/AI-Powered-Autocomplete.gif new file mode 100644 index 000000000..a37cae670 Binary files /dev/null and b/gifs/AI-Powered-Autocomplete.gif differ diff --git a/gifs/AI-Powered-Habit-Naming.gif b/gifs/AI-Powered-Habit-Naming.gif new file mode 100644 index 000000000..01e333982 Binary files /dev/null and b/gifs/AI-Powered-Habit-Naming.gif differ diff --git a/gifs/Accountability-Heatmap.gif b/gifs/Accountability-Heatmap.gif new file mode 100644 index 000000000..94a64075c Binary files /dev/null and b/gifs/Accountability-Heatmap.gif differ diff --git a/gifs/Add-Edit-Delete-Habits.gif b/gifs/Add-Edit-Delete-Habits.gif new file mode 100644 index 000000000..01163b887 Binary files /dev/null and b/gifs/Add-Edit-Delete-Habits.gif differ diff --git a/gifs/Authentication.gif b/gifs/Authentication.gif new file mode 100644 index 000000000..0a8113a03 Binary files /dev/null and b/gifs/Authentication.gif differ diff --git a/gifs/Habit-Dashboard.gif b/gifs/Habit-Dashboard.gif new file mode 100644 index 000000000..a5acbae3e Binary files /dev/null and b/gifs/Habit-Dashboard.gif differ diff --git a/gifs/Motivational-Quotes.gif b/gifs/Motivational-Quotes.gif new file mode 100644 index 000000000..ffcb136ff Binary files /dev/null and b/gifs/Motivational-Quotes.gif differ diff --git a/gifs/Progress-Bar-and-Streak-Tracking.gif b/gifs/Progress-Bar-and-Streak-Tracking.gif new file mode 100644 index 000000000..d8777284f Binary files /dev/null and b/gifs/Progress-Bar-and-Streak-Tracking.gif differ diff --git a/milestones/milestone1.md b/milestones/milestone1.md index 52b9b0038..2598332fe 100644 --- a/milestones/milestone1.md +++ b/milestones/milestone1.md @@ -6,27 +6,24 @@ This document should be completed and submitted during **Unit 5** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] Read and understand all required features - - [ ] Understand you **must** implement **all** baseline features and **two** custom features -- [ ] In `readme.md`: update app name to your app's name -- [ ] In `readme.md`: add all group members' names -- [ ] In `readme.md`: complete the **Description and Purpose** section -- [ ] In `readme.md`: complete the **Inspiration** section -- [ ] In `readme.md`: list a name and description for all features (minimum 6 for full points) you intend to include in your app (in future units, you will check off features as you complete them and add GIFs demonstrating the features) -- [ ] In `planning/user_stories.md`: add all user stories (minimum 10 for full points) -- [ ] In `planning/user_stories.md`: use 1-3 unique user roles in your user stories -- [ ] In this document, complete all thre questions in the **Reflection** section below +- [x] Read and understand all required features + - [x] Understand you **must** implement **all** baseline features and **two** custom features +- [x] In `readme.md`: update app name to your app's name +- [x] In `readme.md`: add all group members' names +- [x] In `readme.md`: complete the **Description and Purpose** section +- [x] In `readme.md`: complete the **Inspiration** section +- [x] In `readme.md`: list a name and description for all features (minimum 6 for full points) you intend to include in your app (in future units, you will check off features as you complete them and add GIFs demonstrating the features) +- [x] In `planning/user_stories.md`: add all user stories (minimum 10 for full points) +- [x] In `planning/user_stories.md`: use 1-3 unique user roles in your user stories +- [x] In this document, complete all thre questions in the **Reflection** section below ## Reflection -### 1. What went well during this unit? +1. What went well during this unit? +πŸ‘‰πŸΎ We brainstormed and agreed quickly on a clear idea β€” HabitHero β€” a productivity and motivation app that helps users build daily habits. Our group collaborated well to define the main features, roles, and responsibilities. Everyone contributed ideas for the user stories and feature list. -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +2. What were some challenges your group faced in this unit? +πŸ‘‰πŸΎ It was a bit hard to narrow down which features to include since there are so many possible ideas for a habit tracker. We also had to make sure all required baseline features fit naturally into the project. Setting up the shared GitHub repo and assigning tasks clearly took some coordination time. -### 2. What were some challenges your group faced in this unit? - -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] - -### 3. What additional support will you need in upcoming units as you continue to work on your final project? - -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +3. What additional support will you need in upcoming units as you continue to work on your final project? +πŸ‘‰πŸΎ We may need extra help with database relationships (especially many-to-many) and setting up deployment on Render. Some of us also want guidance on connecting the backend API with React efficiently. diff --git a/milestones/milestone2.md b/milestones/milestone2.md index e3178cd81..3ea2ce4a0 100644 --- a/milestones/milestone2.md +++ b/milestones/milestone2.md @@ -6,24 +6,22 @@ This document should be completed and submitted during **Unit 6** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] In `planning/wireframes.md`: add wireframes for at least three pages in your web app. - - [ ] Include a list of pages in your app -- [ ] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database. - - [ ] Your entity relationship diagram should include the tables in your database. -- [ ] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit). - - [ ] You do **not** need to submit any materials in advance of your pitch. -- [ ] In this document, complete all three questions in the **Reflection** section below +- [X] In `planning/wireframes.md`: add wireframes for at least three pages in your web app. + - [X] Include a list of pages in your app +- [X] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database. + - [X] Your entity relationship diagram should include the tables in your database. +- [X] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit). + - [X] You do **not** need to submit any materials in advance of your pitch. +- [X] In this document, complete all three questions in the **Reflection** section below ## Reflection ### 1. What went well during this unit? - -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Our team did a great job organizing our ideas and visualizing the structure of our project. Creating the entity relationship diagrams helped us clearly understand how our database should be built. The wireframing process also went smoothly since several team members were familiar with Figma, which made it easier to design and collaborate efficiently. ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +One of the main challenges we faced was figuring out how to structure our data and connect the different parts of our database properly. It took some trial and error to make sure everything worked together logically. We also had to make sure everyone was on the same page about the project’s overall direction. ### 3. What additional support will you need in upcoming units as you continue to work on your final project? - -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Moving forward, we might need more support with backend integration, especially how to connect our database to the front end of the website. Some guidance on best practices for implementing user authentication, managing data relationships, and deploying the project would also be helpful as we start to build everything out. diff --git a/milestones/milestone3.md b/milestones/milestone3.md index 571ce7651..14ab57caa 100644 --- a/milestones/milestone3.md +++ b/milestones/milestone3.md @@ -8,34 +8,34 @@ This unit, be sure to complete all tasks listed below. To complete a task, place You will need to reference the GitHub Project Management guide in the course portal for more information about how to complete each of these steps. -- [ ] In your repo, create a project board. +- [X] In your repo, create a project board. - *Please be sure to share your project board with the grading team's GitHub **codepathreview**. This is separate from your repository's sharing settings.* -- [ ] In your repo, create at least 5 issues from the features on your feature list. -- [ ] In your repo, update the status of issues in your project board. -- [ ] In your repo, create a GitHub Milestone for each final project unit, corresponding to each of the 5 milestones in your `milestones/` directory. - - [ ] Set the completion percentage of each milestone. The GitHub Milestone for this unit (Milestone 3 - Unit 7) should be 100% completed when you submit for full points. -- [ ] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of the feature's name. - - [ ] Under each feature you have completed, include a GIF showing feature functionality. -- [ ] In this documents, complete all five questions in the **Reflection** section below. +- [X] In your repo, create at least 5 issues from the features on your feature list. +- [X] In your repo, update the status of issues in your project board. +- [X] In your repo, create a GitHub Milestone for each final project unit, corresponding to each of the 5 milestones in your `milestones/` directory. + - [X] Set the completion percentage of each milestone. The GitHub Milestone for this unit (Milestone 3 - Unit 7) should be 100% completed when you submit for full points. +- [X] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of the feature's name. + - [X] Under each feature you have completed, include a GIF showing feature functionality. +- [X] In this documents, complete all five questions in the **Reflection** section below. ## Reflection ### 1. What went well during this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +We were able to set up our project board, create issues, and organize everything clearly. Updating the milestones and tracking progress helped our team stay aligned and understand what still needed to be done. ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +One challenge was making sure each issue was properly connected to the project board and assigned to the correct milestone. It also took extra time for us to match features with the right categories on our board. -### Did you finish all of your tasks in your sprint plan for this week? If you did not finish all of the planned tasks, how would you prioritize the remaining tasks on your list? +### 3. Did you finish all of your tasks in your sprint plan for this week? If you did not finish all of the planned tasks, how would you prioritize the remaining tasks on your list? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +We finished most tasks, but we still need to finalize the README updates and add the GIFs. We would prioritize completing the README first since it clearly shows our progress and is required for the milestone. -### Which features and user stories would you consider β€œat risk”? How will you change your plan if those items remain β€œat risk”? +### 4. Which features and user stories would you consider β€œat risk”? How will you change your plan if those items remain β€œat risk”? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +The README documentation and GIF creation are currently at risk because they require extra time and coordination. If they remain at risk, we’ll break them into smaller tasks and complete them as individual features are finished instead of waiting until the end. ### 5. What additional support will you need in upcoming units as you continue to work on your final project? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +We may need support with organizing and recording feature GIFs, keeping our project board updated consistently, and making sure our pacing stays aligned with future milestones. diff --git a/milestones/milestone4.md b/milestones/milestone4.md index 61dc73bb9..c5d6ee439 100644 --- a/milestones/milestone4.md +++ b/milestones/milestone4.md @@ -6,29 +6,29 @@ This document should be completed and submitted during **Unit 8** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] Update the completion percentage of each GitHub Milestone. The milestone for this unit (Milestone 4 - Unit 8) should be 100% completed when you submit for full points. -- [ ] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of the feature's name. - - [ ] Under each feature you have completed, include a GIF showing feature functionality. -- [ ] In this document, complete all five questions in the **Reflection** section below. +- [X] Update the completion percentage of each GitHub Milestone. The milestone for this unit (Milestone 4 - Unit 8) should be 100% completed when you submit for full points. +- [X] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of the feature's name. + - [X] Under each feature you have completed, include a GIF showing feature functionality. +- [X] In this document, complete all five questions in the **Reflection** section below. ## Reflection ### 1. What went well during this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +This unit went really well in terms of productivity and collaboration. Our basic features were already implemented, which allowed us to focus on adding multiple AI-powered stretch features such as the Habit Coach Chatbot, AI Mood Reflection, Autocomplete, and automatic habit naming. As a group, we worked efficiently, shared ideas openly, and successfully integrated a large set of advanced features into the app. Overall, the quality of the final features shows that we worked well as a team and pushed the project beyond the basic requirements. ### 2. What were some challenges your group faced in this unit? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Our biggest challenge was communication around time commitments and workload. Because we didn’t always update each other on availability, some team members ended up taking on more tasks than planned while others were less involved at certain points. This created some imbalance in workload. We also had to navigate the complexity of adding multiple AI features, which required extra testing, debugging, and alignment across components. ### Did you finish all of your tasks in your sprint plan for this week? If you did not finish all of the planned tasks, how would you prioritize the remaining tasks on your list? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +We finished most of the tasks in our sprint plan, especially the major AI stretch features. Any remaining small tasks like polish, UI consistency, and minor bug fixes would be prioritized by focusing first on functionality issues that affect the user experience, then on UI cleanup, and finally on optional enhancements. Our priority would be to ensure all AI features work smoothly and integrate cleanly with the dashboard and existing habit flow. ### Which features and user stories would you consider β€œat risk”? How will you change your plan if those items remain β€œat risk”? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +The only items that might be considered β€œat risk” are polish-related features such as final UI refinements or minor improvements to AI responses and text generation. These don’t block core functionality but could impact the overall feel of the app if left unfinished. If they remain at risk, we would adjust our plan by reducing the scope and focus on making sure the feature works reliably rather than trying to make it perfect. Then we'd move any nice-to-have improvements into a later sprint. ### 5. What additional support will you need in upcoming units as you continue to work on your final project? -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ your answer here] +Moving forward, we may need support in two areas: (1) organizing our workflow more effectively so that responsibilities and deadlines are clearer, and (2) technical guidance on optimizing our AI integrations, especially if we add more advanced functionality. Having more structured guidance for project management and occasional help with debugging AI/OpenAI API features would help us stay on track for the final project. \ No newline at end of file diff --git a/milestones/milestone5.md b/milestones/milestone5.md index 139284283..2ee28e7a8 100644 --- a/milestones/milestone5.md +++ b/milestones/milestone5.md @@ -6,13 +6,13 @@ This document should be completed and submitted during **Unit 9** of this course This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets. -- [ ] Deploy your project on Render - - [ ] In `readme.md`, add the link to your deployed project -- [ ] Update the status of issues in your project board as you complete them -- [ ] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of their title - - [ ] Under each feature you have completed, **include a GIF** showing feature functionality -- [ ] In this document, complete the **Reflection** section below -- [ ] 🚩🚩🚩**Complete the Final Project Feature Checklist section below**, detailing each feature you completed in the project (ONLY include features you implemented, not features you planned) +- [X] Deploy your project on Render + - [X] In `readme.md`, add the link to your deployed project +- [X] Update the status of issues in your project board as you complete them +- [X] In `readme.md`, check off the features you have completed in this unit by adding a βœ… emoji in front of their title + - [X] Under each feature you have completed, **include a GIF** showing feature functionality +- [X] In this document, complete the **Reflection** section below +- [X] 🚩🚩🚩**Complete the Final Project Feature Checklist section below**, detailing each feature you completed in the project (ONLY include features you implemented, not features you planned) - [ ] 🚩🚩🚩**Record a GIF showing a complete run-through of your app** that displays all the components included in the **Final Project Feature Checklist** below - [ ] Include this GIF in the **Final Demo GIF** section below diff --git a/planning/db_relationship_diagram.png b/planning/db_relationship_diagram.png new file mode 100644 index 000000000..c960389e1 Binary files /dev/null and b/planning/db_relationship_diagram.png differ diff --git a/planning/entity_relationship_diagram.md b/planning/entity_relationship_diagram.md index 12c25f62c..10e29bc7d 100644 --- a/planning/entity_relationship_diagram.md +++ b/planning/entity_relationship_diagram.md @@ -3,15 +3,82 @@ Reference the Creating an Entity Relationship Diagram final project guide in the course portal for more information about how to complete this deliverable. ## Create the List of Tables - -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ List each table in your diagram] +- users +- habits +- habit_logs +- achievements +- user_achievements +- badges +- user_badges ## Add the Entity Relationship Diagram -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include an image or images of the diagram below. You may also wish to use the following markdown syntax to outline each table, as per your preference.] +![Entity Relationship Diagram](db_relationship_diagram.png) + +### πŸ§β€β™‚οΈ Users Table +| Column Name | Type | Description | +| ------------- | ----------------------- | ------------------------------------------ | +| id | SERIAL PRIMARY KEY | Unique user ID | +| username | VARCHAR(50) | User’s display name | +| email | VARCHAR(100) UNIQUE | User’s email | +| password_hash | TEXT | Hashed password | +| hero_level | INTEGER DEFAULT 1 | Level of user’s hero based on achievements | +| created_at | TIMESTAMP DEFAULT NOW() | Date the user registered | + + +### πŸ“† Habits Table +| Column Name | Type | Description | +| ------------ | ---------------------------------------------- | -------------------------------------------------- | +| id | SERIAL PRIMARY KEY | Unique habit ID | +| user_id | INTEGER REFERENCES users(id) ON DELETE CASCADE | The user who owns this habit | +| name | VARCHAR(100) | Habit name (e.g., β€œDrink Water”) | +| description | TEXT | Description of the habit | +| date | DATE | Date for the tracked habit | +| completed | BOOLEAN DEFAULT FALSE | Whether the habit was completed that day | + + + +### πŸ“Š Habit Logs Table +| Column Name | Type | Description | +| ----------- | ----------------------------------------------- | ---------------------------------------- | +| id | SERIAL PRIMARY KEY | Unique log ID | +| habit_id | INTEGER REFERENCES habits(id) ON DELETE CASCADE | Linked habit | + + +### πŸ† Achievements Table +| Column Name | Type | Description | +| ----------- | ------------------ | --------------------------------------- | +| id | SERIAL PRIMARY KEY | Unique achievement ID | +| name | VARCHAR(100) | Achievement name (e.g., β€œ7-Day Streak”) | +| description | TEXT | What the achievement represents | +| requirement | TEXT | Criteria to earn this achievement | +| icon | TEXT | Icon or image for display | + + +### πŸ… User Achievements Table +| Column Name | Type | Description +| -------------- | ----------------------------------------------------- | -------------------------------- +| id | SERIAL PRIMARY KEY | Unique record ID +| user_id | INTEGER REFERENCES users(id) ON DELETE CASCADE | User who earned this achievement +| achievement_id | INTEGER REFERENCES achievements(id) ON DELETE CASCADE | Linked achievement +| earned_at | TIMESTAMP DEFAULT NOW() | When it was unlocked + + +### πŸŽ–οΈ Badges Table +| Column Name | Type | Description | +| -------------- | ------------------ | ---------------------------------------- | +| id | SERIAL PRIMARY KEY | Unique badge ID | +| name | VARCHAR(100) | Badge name (e.g., β€œConsistency Star”) | +| description | TEXT | Description of the badge | +| icon | TEXT | Icon or color associated with badge | +| level_required | INTEGER | Minimum hero level or milestone required | + + +### πŸ’« User Badges Table +| Column Name | Type | Description | +| ----------- | ----------------------------------------------- | ------------------------- | +| id | SERIAL PRIMARY KEY | Unique record ID | +| user_id | INTEGER REFERENCES users(id) ON DELETE CASCADE | User who earned the badge | +| badge_id | INTEGER REFERENCES badges(id) ON DELETE CASCADE | Badge earned by the user | +| earned_at | TIMESTAMP DEFAULT NOW() | Date the badge was earned | -| Column Name | Type | Description | -|-------------|------|-------------| -| id | integer | primary key | -| name | text | name of the shoe model | -| ... | ... | ... | diff --git a/planning/features.md b/planning/features.md new file mode 100644 index 000000000..a994552e7 --- /dev/null +++ b/planning/features.md @@ -0,0 +1,55 @@ +# ✨ Features + +## 🧩 1. User Authentication +- Users can create an account, log in, and log out securely. +- This ensures personal habit data is saved and accessible only to them. +- *(Baseline feature)* + +--- + +## βœ… 2. Habit Management (CRUD) +- Users can add, edit, delete, and mark habits as complete. +- Supports creating personalized habits with names, icons, and schedules. +- *(Baseline feature β€” implements GET, POST, PATCH, DELETE requests)* + +--- + +## πŸ“Š 3. Progress Dashboard +- Displays habit completion stats with weekly and monthly streaks. +- Users can visualize their progress with simple charts or streak counters. +- *(Baseline feature)* + +--- + +## πŸ† 4. Achievement System +- Users earn badges or rewards for consistency (e.g., 7-day streaks). +- This feature auto-generates achievements when a condition is met. +- *(Custom feature – β€œData automatically generated in response to an event”)* + +--- + +## πŸ” 5. Filter and Sort Habits +- Users can filter habits by category (e.g., health, focus, lifestyle) or sort by completion rate. +- Helps organize and find habits easily. +- *(Custom feature – β€œFilter or sort items based on criteria”)* + +--- + +## πŸ’¬ 6. Motivational Popup +- When users hit milestones or log habits for several days in a row, a modal pops up congratulating them. +- Adds fun interaction without leaving the page. +- *(Baseline + nice UX addition)* + +--- + +## ☁️ 7. Database Reset / Seed Data +- Developers can reset the database to default habits for demo or testing purposes. +- *(Baseline backend feature requirement)* + +--- + +## 🧠 8. Optional Stretch Features +- Toast notifications for success/error (e.g., β€œHabit added!” or β€œError saving habit”) +- Loading spinner during form submissions +- User profile picture upload via cloud service + diff --git a/planning/user_stories.md b/planning/user_stories.md index 1e55ecbcd..252ccef32 100644 --- a/planning/user_stories.md +++ b/planning/user_stories.md @@ -1,13 +1,29 @@ -# User Stories +# πŸ‘₯ User Roles & Stories -Reference the Writing User Stories final project guide in the course portal for more information about how to complete each of the sections below. +## User Roles +- **User:** Creates and tracks daily habits, goals, and progress. +- **Admin:** Manages the app, monitors user activity, and ensures smooth operation. +- **Guest:** Can explore or preview the app without signing up. -## Outline User Roles +--- -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include at least at least 1, but no more than 3, user roles.] +## 🧩 User Stories -## Draft User Stories +### πŸ‘€ Regular User +- I want to **create an account and log in**, so I can securely save and access my habits and progress. +- I want to **add, edit, or delete habits** with custom names, icons, and schedules to track meaningful activities. +- I want to **mark habits as completed**, so I can stay motivated and maintain streaks. +- I want to **view a dashboard** with weekly and monthly progress to visualize my consistency. +- I want to **receive motivational messages or badges** when reaching milestones to stay encouraged. +- I want to **filter habits by category** (e.g., health, productivity, mindfulness) for easier organization. -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ Include at least at least 10 user stories in this format:] +--- -1. As a [user role], I want to [what], so that [why]. +### πŸ§‘β€πŸ« Admin +- I want to **view all users and their progress summaries** to monitor overall app engagement. +- I want to **remove inactive or duplicate accounts** to maintain database quality and performance. + +--- + +### πŸ’¬ Guest +- I want to **explore a demo mode or preview features** without signing up, to understand the app before creating an account. diff --git a/planning/wireframes.md b/planning/wireframes.md index fbcd15a0c..f3695fd92 100644 --- a/planning/wireframes.md +++ b/planning/wireframes.md @@ -4,18 +4,33 @@ Reference the Creating an Entity Relationship Diagram final project guide in the ## List of Pages -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ List the pages you expect to have in your app, with a ⭐ next to pages you have wireframed] +- Landing Page ⭐ +- Sign Up Page ⭐ +- Login Page ⭐ +- Habit Dashboard Page ⭐ +- Profile Page ⭐ +- About Page +- Contact Page +- Features Page -## Wireframe 1: [page title] +## Wireframe 1: Landing Page -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include wireframe 1] +![Landing Page](wireframes/LandingPage.png) -## Wireframe 2: [page title] +## Wireframe 2: Sign Up Page -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include wireframe 2] +![SignUp Page](wireframes/SignUp.png) -## Wireframe 3: [page title] +## Wireframe 3: Login Page -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include wireframe 3] +![Login Page](wireframes/Login.png) + +## Wireframe 4: Habit Dashboard Page + +![Habit Dashboard Page](wireframes/HabitsDashboard.png) + + +## Wireframe 4: Profile Page + +![Profile Page](wireframes/ProfilePage.png) -[πŸ‘‰πŸΎπŸ‘‰πŸΎπŸ‘‰πŸΎ include more wireframes as desired] diff --git a/planning/wireframes/HabitsDashboard.png b/planning/wireframes/HabitsDashboard.png new file mode 100644 index 000000000..3eca1d52a Binary files /dev/null and b/planning/wireframes/HabitsDashboard.png differ diff --git a/planning/wireframes/LandingPage.png b/planning/wireframes/LandingPage.png new file mode 100644 index 000000000..8c6be84c3 Binary files /dev/null and b/planning/wireframes/LandingPage.png differ diff --git a/planning/wireframes/Login.png b/planning/wireframes/Login.png new file mode 100644 index 000000000..c4ba28629 Binary files /dev/null and b/planning/wireframes/Login.png differ diff --git a/planning/wireframes/ProfilePage.png b/planning/wireframes/ProfilePage.png new file mode 100644 index 000000000..582602bdc Binary files /dev/null and b/planning/wireframes/ProfilePage.png differ diff --git a/planning/wireframes/SignUp.png b/planning/wireframes/SignUp.png new file mode 100644 index 000000000..d363ff35f Binary files /dev/null and b/planning/wireframes/SignUp.png differ diff --git a/src/backend/AI/analyze-mood-trends/index.ts b/src/backend/AI/analyze-mood-trends/index.ts new file mode 100644 index 000000000..c77192dc1 --- /dev/null +++ b/src/backend/AI/analyze-mood-trends/index.ts @@ -0,0 +1,144 @@ +import "https://deno.land/x/xhr@0.1.0/mod.ts"; +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": + "authorization, x-client-info, apikey, content-type", +}; + +serve(async (req) => { + if (req.method === "OPTIONS") { + return new Response(null, { headers: corsHeaders }); + } + + try { + const supabaseClient = createClient( + Deno.env.get("SUPABASE_URL") ?? "", + Deno.env.get("SUPABASE_ANON_KEY") ?? "", + { + global: { + headers: { Authorization: req.headers.get("Authorization")! }, + }, + }, + ); + + const { data: { user } } = await supabaseClient.auth.getUser(); + if (!user) { + return new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + // Get reflections + const { data: reflections, error: reflectionsError } = await supabaseClient + .from("reflections") + .select("*") + .eq("user_id", user.id) + .order("reflection_date", { ascending: false }) + .limit(14); + + if (reflectionsError) throw reflectionsError; + + // Get habits + const { data: habits, error: habitsError } = await supabaseClient + .from("habits") + .select("name, current_streak, best_streak, category") + .eq("user_id", user.id); + + if (habitsError) throw habitsError; + + if (!reflections || reflections.length === 0) { + return new Response( + JSON.stringify({ + insights: + "Start journaling daily to unlock personalized insights about your mood patterns and habit performance!", + }), + { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } + + const reflectionSummary = reflections.map((r) => ({ + date: r.reflection_date, + feeling: r.feeling, + challenges: r.challenges, + wins: r.wins, + })); + + const habitSummary = habits?.map((h) => ({ + name: h.name, + category: h.category, + currentStreak: h.current_streak, + bestStreak: h.best_streak, + })) ?? []; + + const prompt = ` +You are a supportive life coach analyzing mood and habit data. Provide encouraging insights in 3–4 sentences. + +Recent reflections (${reflections.length} entries): +${JSON.stringify(reflectionSummary, null, 2)} + +Current habits and streaks: +${JSON.stringify(habitSummary, null, 2)} + +Analyze: +1. Mood patterns and trends +2. Correlations between mood and habit performance +3. Encouraging observations about progress +4. One actionable suggestion + +Keep it warm, non-medical, and empowering. Focus on patterns, not diagnoses. +`; + + // --- xAI integration --- + const XAI_API_KEY = Deno.env.get("XAI_API_KEY"); + if (!XAI_API_KEY) throw new Error("XAI_API_KEY is not set"); + + const aiResponse = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { + Authorization: `Bearer ${XAI_API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: "grok-2-latest", + messages: [ + { + role: "system", + content: + "You are a warm, supportive life coach providing non-medical mental wellness insights.", + }, + { role: "user", content: prompt }, + ], + }), + }); + + if (!aiResponse.ok) { + const errText = await aiResponse.text(); + console.error("xAI error:", aiResponse.status, errText); + throw new Error(`xAI error: ${aiResponse.status}`); + } + + const aiData = await aiResponse.json(); + const insights = aiData.choices[0].message.content; + + return new Response(JSON.stringify({ insights }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } catch (err) { + console.error("Error in analyze-mood-trends:", err); + return new Response( + JSON.stringify({ + error: err instanceof Error ? err.message : "Unknown error", + }), + { + status: 500, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } +}); diff --git a/src/backend/AI/generate-habit-suggestions/index.ts b/src/backend/AI/generate-habit-suggestions/index.ts new file mode 100644 index 000000000..a5d83383f --- /dev/null +++ b/src/backend/AI/generate-habit-suggestions/index.ts @@ -0,0 +1,191 @@ +import "https://deno.land/x/xhr@0.1.0/mod.ts"; +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", +}; + +serve(async (req) => { + if (req.method === "OPTIONS") { + return new Response(null, { headers: corsHeaders }); + } + + try { + const { currentName, category, autocomplete } = await req.json(); + const XAI_API_KEY = Deno.env.get("XAI_API_KEY"); + + if (!XAI_API_KEY) { + throw new Error("XAI_API_KEY is not configured"); + } + + console.log("Generating habit suggestions for:", { currentName, category, autocomplete }); + + // ──────────── AUTOCOMPLETE MODE ──────────── + if (autocomplete) { + if (!currentName || currentName.length < 2) { + return new Response(JSON.stringify({ suggestions: [] }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + const systemPrompt = `You are a helpful assistant that suggests habit names based on partial user input. +Generate 3-5 relevant, specific habit suggestions that complete or expand on what the user is typing. +Make suggestions practical, actionable, and varied. + +Return ONLY valid JSON in this exact format: +{ + "suggestions": [ + {"name": "Habit Name", "description": "Brief description"} + ] +}`; + + const userPrompt = `User is typing: "${currentName}"${ + category ? ` in category: ${category}` : "" + } +Generate 3-5 relevant habit name completions with brief descriptions.`; + + + const response = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { + Authorization: `Bearer ${XAI_API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: "grok-2-latest", + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: userPrompt }, + ], + }), + }); + + if (!response.ok) { + if (response.status === 429) { + return new Response( + JSON.stringify({ error: "Rate limit exceeded", suggestions: [] }), + { + status: 429, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } + if (response.status === 402) { + return new Response( + JSON.stringify({ error: "Credits exhausted", suggestions: [] }), + { + status: 402, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } + throw new Error(`xAI API error: ${response.status}`); + } + + const data = await response.json(); + const content = data.choices[0].message.content; + + const jsonMatch = content.match(/\{[\s\S]*\}/); + if (!jsonMatch) { + return new Response(JSON.stringify({ suggestions: [] }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + const parsed = JSON.parse(jsonMatch[0]); + return new Response(JSON.stringify(parsed), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + // ──────────── IMPROVE MODE ──────────── + const systemPrompt = `You are a habit formation expert who helps people create effective, motivating habit names and descriptions. + +Your task is to take a basic habit name and improve it to be: +- Clear and specific +- Motivating and positive +- Action-oriented +- Concise (3–6 words max) + +The description should: +- Explain the benefits +- Be inspiring and encouraging +- Be 1–2 sentences max +- Connect to personal growth or wellbeing`; + + const userPrompt = `Improve this habit: +Current name: "${currentName || "New Habit"}" +Category: ${category || "general"} + +Respond with ONLY a JSON object in this exact format (no markdown, no extra text): +{"name": "improved habit name", "description": "motivating description"}`; + + const response = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { + Authorization: `Bearer ${XAI_API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: "grok-2-latest", + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: userPrompt }, + ], + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error("xAI API error:", response.status, errorText); + + if (response.status === 429) { + return new Response( + JSON.stringify({ error: "Rate limit exceeded. Please try again soon." }), + { + status: 429, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } + + if (response.status === 402) { + return new Response( + JSON.stringify({ error: "AI credits exhausted." }), + { + status: 402, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } + + throw new Error(`xAI API error: ${response.status}`); + } + + const data = await response.json(); + const content = data.choices[0].message.content; + + const jsonMatch = content.match(/\{[\s\S]*\}/); + if (!jsonMatch) throw new Error("Invalid response format from AI"); + + const suggestions = JSON.parse(jsonMatch[0]); + + return new Response(JSON.stringify(suggestions), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + + } catch (error) { + console.error("Error in generate-habit-suggestions:", error); + + return new Response( + JSON.stringify({ + error: error instanceof Error ? error.message : "Failed to generate suggestions", + }), + { + status: 500, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }, + ); + } +}); diff --git a/src/backend/AI/generate-recovery-plan/index.ts b/src/backend/AI/generate-recovery-plan/index.ts new file mode 100644 index 000000000..c22b85494 --- /dev/null +++ b/src/backend/AI/generate-recovery-plan/index.ts @@ -0,0 +1,70 @@ +import "https://deno.land/x/xhr@0.1.0/mod.ts"; +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response(null, { headers: corsHeaders }); + } + + try { + const { habitName, habitCategory, previousStreak } = await req.json(); + const XAI_API_KEY = Deno.env.get('XAI_API_KEY'); + + if (!XAI_API_KEY) { + throw new Error('XAI_API_KEY not configured'); + } + + console.log('Generating recovery plan for habit:', habitName); + + const response = await fetch('https://api.x.ai/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${XAI_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'grok-3', + messages: [ + { + role: 'system', + content: 'You are a compassionate habit coach helping users recover from breaking a streak. Provide exactly 3 actionable, personalized steps to help them get back on track. Be empathetic, motivating, and specific. Each step should be practical and achievable.' + }, + { + role: 'user', + content: `I just broke my ${previousStreak}-day streak for "${habitName}" (${habitCategory} category). Help me get back on track with a 3-step recovery plan. Make it personal and actionable.` + } + ], + temperature: 0.7, + stream: false, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('xAI API error:', response.status, errorText); + throw new Error(`xAI API error: ${response.status}`); + } + + const data = await response.json(); + console.log('Recovery plan generated successfully'); + + return new Response(JSON.stringify({ + recoveryPlan: data.choices[0].message.content + }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('Error in generate-recovery-plan function:', error); + return new Response(JSON.stringify({ + error: error instanceof Error ? error.message : 'Internal server error' + }), { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } +}); diff --git a/src/backend/AI/habit-coach/index.ts b/src/backend/AI/habit-coach/index.ts new file mode 100644 index 000000000..7ecaf7524 --- /dev/null +++ b/src/backend/AI/habit-coach/index.ts @@ -0,0 +1,67 @@ +import "https://deno.land/x/xhr@0.1.0/mod.ts"; +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response(null, { headers: corsHeaders }); + } + + try { + const { messages } = await req.json(); + const XAI_API_KEY = Deno.env.get('XAI_API_KEY'); + + if (!XAI_API_KEY) { + throw new Error('XAI_API_KEY not configured'); + } + + console.log('Calling xAI API with messages:', messages); + + const response = await fetch('https://api.x.ai/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${XAI_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'grok-3', + messages: [ + { + role: 'system', + content: 'You are a supportive AI Habit Coach. Help users build better habits with personalized advice, step-by-step routines, and encouragement. Be empathetic, practical, and motivating. Provide specific, actionable guidance for habit formation and breaking bad habits.' + }, + ...messages + ], + temperature: 0.7, + stream: false, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('xAI API error:', response.status, errorText); + throw new Error(`xAI API error: ${response.status}`); + } + + const data = await response.json(); + console.log('xAI API response received'); + + return new Response(JSON.stringify({ + message: data.choices[0].message.content + }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('Error in habit-coach function:', error); + return new Response(JSON.stringify({ + error: error instanceof Error ? error.message : 'Internal server error' + }), { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } +}); diff --git a/src/backend/config.toml b/src/backend/config.toml new file mode 100644 index 000000000..5f1009936 --- /dev/null +++ b/src/backend/config.toml @@ -0,0 +1 @@ +project_id = "hatkvzghsxaywhkrunuh" diff --git a/src/backend/migrations/tables.sql b/src/backend/migrations/tables.sql new file mode 100644 index 000000000..794635831 --- /dev/null +++ b/src/backend/migrations/tables.sql @@ -0,0 +1,187 @@ +-- Create profiles table for additional user data +CREATE TABLE public.profiles ( + id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + username text NOT NULL, + hero_level integer DEFAULT 1 NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL +); + +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; + +-- Profiles policies +CREATE POLICY "Users can view their own profile" + ON public.profiles FOR SELECT + USING (auth.uid() = id); + +CREATE POLICY "Users can update their own profile" + ON public.profiles FOR UPDATE + USING (auth.uid() = id); + +CREATE POLICY "Users can insert their own profile" + ON public.profiles FOR INSERT + WITH CHECK (auth.uid() = id); + +-- Create habits table +CREATE TABLE public.habits ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + name text NOT NULL, + description text, + category text NOT NULL, + icon text DEFAULT '⭐', + created_at timestamptz DEFAULT now() NOT NULL, + UNIQUE(user_id, name) +); + +ALTER TABLE public.habits ENABLE ROW LEVEL SECURITY; + +-- Habits policies +CREATE POLICY "Users can view their own habits" + ON public.habits FOR SELECT + USING (auth.uid() = user_id); + +CREATE POLICY "Users can create their own habits" + ON public.habits FOR INSERT + WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update their own habits" + ON public.habits FOR UPDATE + USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete their own habits" + ON public.habits FOR DELETE + USING (auth.uid() = user_id); + +-- Create habit logs table for tracking daily completions +CREATE TABLE public.habit_logs ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + habit_id uuid NOT NULL REFERENCES public.habits(id) ON DELETE CASCADE, + user_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + completed_at date DEFAULT CURRENT_DATE NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL, + UNIQUE(habit_id, completed_at) +); + +ALTER TABLE public.habit_logs ENABLE ROW LEVEL SECURITY; + +-- Habit logs policies +CREATE POLICY "Users can view their own habit logs" + ON public.habit_logs FOR SELECT + USING (auth.uid() = user_id); + +CREATE POLICY "Users can create their own habit logs" + ON public.habit_logs FOR INSERT + WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can delete their own habit logs" + ON public.habit_logs FOR DELETE + USING (auth.uid() = user_id); + +-- Create achievements table +CREATE TABLE public.achievements ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + name text NOT NULL UNIQUE, + description text NOT NULL, + requirement text NOT NULL, + icon text NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL +); + +ALTER TABLE public.achievements ENABLE ROW LEVEL SECURITY; + +-- Everyone can view achievements +CREATE POLICY "Achievements are viewable by everyone" + ON public.achievements FOR SELECT + USING (true); + +-- Create user_achievements table +CREATE TABLE public.user_achievements ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + achievement_id uuid NOT NULL REFERENCES public.achievements(id) ON DELETE CASCADE, + earned_at timestamptz DEFAULT now() NOT NULL, + UNIQUE(user_id, achievement_id) +); + +ALTER TABLE public.user_achievements ENABLE ROW LEVEL SECURITY; + +-- User achievements policies +CREATE POLICY "Users can view their own achievements" + ON public.user_achievements FOR SELECT + USING (auth.uid() = user_id); + +CREATE POLICY "Users can earn achievements" + ON public.user_achievements FOR INSERT + WITH CHECK (auth.uid() = user_id); + +-- Create badges table +CREATE TABLE public.badges ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + name text NOT NULL UNIQUE, + description text NOT NULL, + icon text NOT NULL, + level_required integer DEFAULT 1 NOT NULL, + created_at timestamptz DEFAULT now() NOT NULL +); + +ALTER TABLE public.badges ENABLE ROW LEVEL SECURITY; + +-- Everyone can view badges +CREATE POLICY "Badges are viewable by everyone" + ON public.badges FOR SELECT + USING (true); + +-- Create user_badges table +CREATE TABLE public.user_badges ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + badge_id uuid NOT NULL REFERENCES public.badges(id) ON DELETE CASCADE, + earned_at timestamptz DEFAULT now() NOT NULL, + UNIQUE(user_id, badge_id) +); + +ALTER TABLE public.user_badges ENABLE ROW LEVEL SECURITY; + +-- User badges policies +CREATE POLICY "Users can view their own badges" + ON public.user_badges FOR SELECT + USING (auth.uid() = user_id); + +CREATE POLICY "Users can earn badges" + ON public.user_badges FOR INSERT + WITH CHECK (auth.uid() = user_id); + +-- Function to create profile on signup +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, username, hero_level) + VALUES ( + new.id, + COALESCE(new.raw_user_meta_data->>'username', split_part(new.email, '@', 1)), + 1 + ); + RETURN new; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = public; + +-- Trigger to create profile automatically +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + +-- Insert default achievements +INSERT INTO public.achievements (name, description, requirement, icon) VALUES + ('First Step', 'Complete your first habit', '1 habit completed', '🌟'), + ('Week Warrior', 'Maintain a 7-day streak', '7-day streak on any habit', 'πŸ”₯'), + ('Consistency King', 'Maintain a 30-day streak', '30-day streak on any habit', 'πŸ‘‘'), + ('Habit Master', 'Complete 100 total habits', '100 total completions', 'πŸ’Ž'), + ('Early Bird', 'Complete a habit 5 days in a row', '5-day streak on any habit', 'πŸŒ…'); + +-- Insert default badges +INSERT INTO public.badges (name, description, icon, level_required) VALUES + ('Beginner Hero', 'Started your journey', '🦸', 1), + ('Rising Star', 'Growing stronger', '⭐', 5), + ('Mighty Warrior', 'Becoming powerful', 'βš”οΈ', 10), + ('Legendary Champion', 'Achieved greatness', 'πŸ†', 20), + ('Ultimate Hero', 'Master of habits', 'πŸ’«', 50); diff --git a/src/bun.lockb b/src/bun.lockb new file mode 100644 index 000000000..f41003f46 Binary files /dev/null and b/src/bun.lockb differ diff --git a/src/components.json b/src/components.json new file mode 100644 index 000000000..62e101166 --- /dev/null +++ b/src/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/src/eslint.config.js b/src/eslint.config.js new file mode 100644 index 000000000..40f72cc45 --- /dev/null +++ b/src/eslint.config.js @@ -0,0 +1,26 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + "@typescript-eslint/no-unused-vars": "off", + }, + }, +); diff --git a/src/frontend/App.css b/src/frontend/App.css new file mode 100644 index 000000000..b9d355df2 --- /dev/null +++ b/src/frontend/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx new file mode 100644 index 000000000..231901dca --- /dev/null +++ b/src/frontend/App.tsx @@ -0,0 +1,30 @@ +import { Toaster } from "@/components/ui/toaster"; +import { Toaster as Sonner } from "@/components/ui/sonner"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Index from "./pages/Index"; +import Auth from "./pages/Auth"; +import Dashboard from "./pages/Dashboard"; +import NotFound from "./pages/NotFound"; + +const queryClient = new QueryClient(); + +const App = () => ( + + + + + + + } /> + } /> + } /> + } /> + + + + +); + +export default App; diff --git a/src/frontend/components/Radix/accordion.tsx b/src/frontend/components/Radix/accordion.tsx new file mode 100644 index 000000000..1e7878ced --- /dev/null +++ b/src/frontend/components/Radix/accordion.tsx @@ -0,0 +1,52 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/src/frontend/components/Radix/alert-dialog.tsx b/src/frontend/components/Radix/alert-dialog.tsx new file mode 100644 index 000000000..6dfbfb49f --- /dev/null +++ b/src/frontend/components/Radix/alert-dialog.tsx @@ -0,0 +1,104 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/src/frontend/components/Radix/alert.tsx b/src/frontend/components/Radix/alert.tsx new file mode 100644 index 000000000..2efc3c8ba --- /dev/null +++ b/src/frontend/components/Radix/alert.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/frontend/components/Radix/aspect-ratio.tsx b/src/frontend/components/Radix/aspect-ratio.tsx new file mode 100644 index 000000000..c9e6f4bf9 --- /dev/null +++ b/src/frontend/components/Radix/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +const AspectRatio = AspectRatioPrimitive.Root; + +export { AspectRatio }; diff --git a/src/frontend/components/Radix/avatar.tsx b/src/frontend/components/Radix/avatar.tsx new file mode 100644 index 000000000..68d21bbf6 --- /dev/null +++ b/src/frontend/components/Radix/avatar.tsx @@ -0,0 +1,38 @@ +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/frontend/components/Radix/badge.tsx b/src/frontend/components/Radix/badge.tsx new file mode 100644 index 000000000..0853c441d --- /dev/null +++ b/src/frontend/components/Radix/badge.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps extends React.HTMLAttributes, VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return
; +} + +export { Badge, badgeVariants }; diff --git a/src/frontend/components/Radix/breadcrumb.tsx b/src/frontend/components/Radix/breadcrumb.tsx new file mode 100644 index 000000000..ca91ff532 --- /dev/null +++ b/src/frontend/components/Radix/breadcrumb.tsx @@ -0,0 +1,90 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>