From 0c22dd579b61c9e1d964e307311ccdab0749de2c Mon Sep 17 00:00:00 2001 From: "Mike P. Sinn" Date: Tue, 2 Apr 2024 15:04:27 -0500 Subject: [PATCH] Initial commit --- .env.example | 21 + .eslintrc.json | 3 - .gitattributes | 2 + .gitignore | 14 +- .prettierignore | 5 + .vscode/settings.json | 2 + README.md | 98 +- __tests__/activity/activity-item.test.tsx | 57 + .../activity/activity-operations.test.tsx | 24 + __tests__/activity/stats/stats-card.test.tsx | 56 + __tests__/dashboard/dashboard-cards.test.tsx | 56 + __tests__/dashboard/dashboard-header.test.tsx | 44 + __tests__/dashboard/dashboard-nav.test.tsx | 25 + .../(routes)/sign-in/[[...sign-in]]/page.tsx | 13 - .../(routes)/sign-up/[[...sign-up]]/page.tsx | 5 - app/(auth)/error.tsx | 11 - app/(auth)/layout.tsx | 13 - app/(auth)/signin/page.tsx | 49 + app/(auth)/signup/page.tsx | 51 + app/(dashboard)/(routes)/dashboard/page.tsx | 41 - .../(routes)/settings/constants.ts | 7 - app/(dashboard)/(routes)/settings/page.tsx | 30 - app/(dashboard)/error.tsx | 11 - app/(dashboard)/layout.tsx | 30 - app/(frontpage)/layout.tsx | 27 + app/(frontpage)/loading.tsx | 9 + app/(frontpage)/page.tsx | 19 + app/(landing)/error.tsx | 11 - app/(landing)/layout.tsx | 15 - app/(landing)/page.tsx | 15 - .../[activityId]/logs/[logId]/route.ts | 42 + app/api/activities/[activityId]/logs/route.ts | 115 + app/api/activities/[activityId]/route.ts | 76 + app/api/activities/route.ts | 73 + app/api/auth/[...nextauth]/route.ts | 7 + app/api/stripe/route.ts | 67 - app/api/users/[userId]/route.ts | 48 + app/api/vote/route.ts | 65 +- app/api/webhook/route.ts | 67 - .../activities/[activityId]/not-found.tsx | 27 + .../activities/[activityId]/page.tsx | 90 + .../activities/[activityId]/settings/page.tsx | 50 + app/dashboard/activities/page.tsx | 36 + app/dashboard/layout.tsx | 34 + app/dashboard/loading.tsx | 12 + app/dashboard/page.tsx | 73 + app/dashboard/settings/page.tsx | 35 + app/globals.css | 100 +- app/layout.tsx | 107 +- app/robots.ts | 10 + components.json | 4 +- components/AfterLoginHandler.tsx | 36 + components/activity/activity-add-button.tsx | 99 + components/activity/activity-edit-form.tsx | 145 + components/activity/activity-item.tsx | 74 + components/activity/activity-list.tsx | 36 + components/activity/activity-operations.tsx | 174 + components/activity/logs/logs-add-form.tsx | 157 + components/activity/logs/logs-columns.tsx | 100 + .../activity/logs/logs-delete-button.tsx | 110 + components/activity/logs/quick-log-button.tsx | 61 + components/activity/stats/stats-cards.tsx | 82 + components/bot-avatar.tsx | 9 - components/chart-container.tsx | 90 + components/charts/heatmap.tsx | 171 + components/charts/linechart.tsx | 58 + components/charts/piechart.tsx | 58 + components/crisp-chat.tsx | 12 - components/crisp-provider.tsx | 7 - components/data-table.tsx | 164 + components/date-range-picker.tsx | 127 + components/donate-modal.tsx | 78 - components/donor-button.tsx | 37 - components/empty-placeholder.tsx | 55 + components/heading-text.tsx | 22 + components/heading.tsx | 35 - components/icons.tsx | 72 + components/image-frame.tsx | 13 + components/landing-content.tsx | 55 - components/landing-hero.tsx | 46 - components/landing-navbar.tsx | 35 - components/layout/footer.tsx | 53 + components/layout/navbar.tsx | 62 + components/layout/shell.tsx | 13 + components/loader.tsx | 18 - components/mobile-sidebar.tsx | 39 - components/modal-provider.tsx | 23 - components/mode-toggle.tsx | 40 + components/navbar.tsx | 21 - .../pages/dashboard/dashboard-cards.tsx | 86 + .../pages/dashboard/dashboard-header.tsx | 21 + components/pages/dashboard/dashboard-nav.tsx | 44 + components/pages/feature-cards.tsx | 58 + components/pages/hero.tsx | 59 + components/pages/opensource.tsx | 39 + components/pages/overview.tsx | 72 + components/poll-results.tsx | 27 + components/poll.tsx | 60 +- components/pwa-redirect.tsx | 15 + components/settings/appearance-form.tsx | 133 + components/sidebar.tsx | 78 - components/signature-counter.tsx | 51 - components/toaster-provider.tsx | 7 - components/tool-card.tsx | 54 - components/ui/alert-dialog.tsx | 141 + components/ui/alert.tsx | 59 + components/ui/avatar.tsx | 2 +- components/ui/badge.tsx | 37 - components/ui/button.tsx | 1 - components/ui/calendar.tsx | 65 + components/ui/card.tsx | 7 +- components/ui/command.tsx | 155 - components/ui/credenza.tsx | 140 + components/ui/dialog.tsx | 21 +- components/ui/drawer.tsx | 118 + components/ui/dropdown-menu.tsx | 8 +- components/ui/empty.tsx | 21 - components/ui/form.tsx | 4 +- components/ui/popover.tsx | 32 + components/ui/progress.tsx | 28 - components/ui/radio-group.tsx | 44 + components/ui/scroll-area.tsx | 48 + components/ui/select.tsx | 121 - components/ui/separator.tsx | 31 - components/ui/sheet.tsx | 144 - components/ui/skeleton.tsx | 15 + components/ui/table.tsx | 114 + components/ui/textarea.tsx | 24 + components/ui/toast.tsx | 127 + components/ui/toaster.tsx | 35 + components/ui/use-toast.ts | 189 + components/user-avatar.tsx | 17 - components/user/user-account-nav.tsx | 75 + components/user/user-auth-form.tsx | 55 + components/user/user-avatar.tsx | 26 + components/user/user-name-form.tsx | 112 + components/user/user-nav-display.tsx | 34 + config/links.ts | 42 + config/site.ts | 29 + constants.ts | 13 - env.mjs | 27 + hooks/donate-modal.ts | 13 - hooks/use-media-query.tsx | 19 + jest.config.js | 15 + jest.setup.js | 2 + kill_node.ps1 | 13 - lib/api-limit.ts | 65 - lib/api/activities.ts | 64 + lib/api/dashboard.ts | 67 + lib/api/logs.ts | 306 + lib/auth.ts | 60 + lib/db.ts | 17 + lib/prismadb.ts | 10 - lib/session.ts | 9 + lib/stripe.ts | 6 - lib/subscription.ts | 35 - lib/utils.ts | 43 +- lib/validations/activity.ts | 7 + lib/validations/user.ts | 5 + middleware.ts | 35 +- package-lock.json | 7156 ----------------- package.json | 100 +- pnpm-lock.yaml | 6701 +++++++++++++++ prettier.config.js | 30 + .../migrations/20240401232731_/migration.sql | 72 - prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 156 +- public/android-chrome-192x192.png | Bin 0 -> 52951 bytes public/android-chrome-512x512.png | Bin 0 -> 241439 bytes public/apple-touch-icon.png | Bin 0 -> 38678 bytes public/browserconfig.xml | 9 + public/chat.png | Bin 278256 -> 0 bytes public/empty.png | Bin 444795 -> 0 bytes public/favicon-16x16.png | Bin 0 -> 2412 bytes public/favicon-32x32.png | Bin 0 -> 4185 bytes public/favicon.ico | Bin 0 -> 15086 bytes public/home.png | Bin 306850 -> 0 bytes public/logo.png | Bin 23541 -> 0 bytes public/mail.png | Bin 316397 -> 0 bytes public/mstile-150x150.png | Bin 0 -> 27499 bytes public/next.svg | 1 - public/og.png | Bin 0 -> 75971 bytes public/og.svg | 1 + public/photo.png | Bin 342118 -> 0 bytes public/safari-pinned-tab.svg | 15 + public/site.webmanifest | 19 + public/star.png | Bin 242160 -> 0 bytes public/transcript.png | Bin 337058 -> 0 bytes public/vercel.svg | 1 - public/voice.png | Bin 372630 -> 0 bytes tailwind.config.js | 12 +- tsconfig.json | 5 +- types/auth.d.ts | 24 + types/index.d.ts | 57 + 194 files changed, 13277 insertions(+), 9230 deletions(-) create mode 100644 .env.example delete mode 100644 .eslintrc.json create mode 100644 .gitattributes create mode 100644 .prettierignore create mode 100644 __tests__/activity/activity-item.test.tsx create mode 100644 __tests__/activity/activity-operations.test.tsx create mode 100644 __tests__/activity/stats/stats-card.test.tsx create mode 100644 __tests__/dashboard/dashboard-cards.test.tsx create mode 100644 __tests__/dashboard/dashboard-header.test.tsx create mode 100644 __tests__/dashboard/dashboard-nav.test.tsx delete mode 100644 app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx delete mode 100644 app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx delete mode 100644 app/(auth)/error.tsx delete mode 100644 app/(auth)/layout.tsx create mode 100644 app/(auth)/signin/page.tsx create mode 100644 app/(auth)/signup/page.tsx delete mode 100644 app/(dashboard)/(routes)/dashboard/page.tsx delete mode 100644 app/(dashboard)/(routes)/settings/constants.ts delete mode 100644 app/(dashboard)/(routes)/settings/page.tsx delete mode 100644 app/(dashboard)/error.tsx delete mode 100644 app/(dashboard)/layout.tsx create mode 100644 app/(frontpage)/layout.tsx create mode 100644 app/(frontpage)/loading.tsx create mode 100644 app/(frontpage)/page.tsx delete mode 100644 app/(landing)/error.tsx delete mode 100644 app/(landing)/layout.tsx delete mode 100644 app/(landing)/page.tsx create mode 100644 app/api/activities/[activityId]/logs/[logId]/route.ts create mode 100644 app/api/activities/[activityId]/logs/route.ts create mode 100644 app/api/activities/[activityId]/route.ts create mode 100644 app/api/activities/route.ts create mode 100644 app/api/auth/[...nextauth]/route.ts delete mode 100644 app/api/stripe/route.ts create mode 100644 app/api/users/[userId]/route.ts delete mode 100644 app/api/webhook/route.ts create mode 100644 app/dashboard/activities/[activityId]/not-found.tsx create mode 100644 app/dashboard/activities/[activityId]/page.tsx create mode 100644 app/dashboard/activities/[activityId]/settings/page.tsx create mode 100644 app/dashboard/activities/page.tsx create mode 100644 app/dashboard/layout.tsx create mode 100644 app/dashboard/loading.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/dashboard/settings/page.tsx create mode 100644 app/robots.ts create mode 100644 components/AfterLoginHandler.tsx create mode 100644 components/activity/activity-add-button.tsx create mode 100644 components/activity/activity-edit-form.tsx create mode 100644 components/activity/activity-item.tsx create mode 100644 components/activity/activity-list.tsx create mode 100644 components/activity/activity-operations.tsx create mode 100644 components/activity/logs/logs-add-form.tsx create mode 100644 components/activity/logs/logs-columns.tsx create mode 100644 components/activity/logs/logs-delete-button.tsx create mode 100644 components/activity/logs/quick-log-button.tsx create mode 100644 components/activity/stats/stats-cards.tsx delete mode 100644 components/bot-avatar.tsx create mode 100644 components/chart-container.tsx create mode 100644 components/charts/heatmap.tsx create mode 100644 components/charts/linechart.tsx create mode 100644 components/charts/piechart.tsx delete mode 100644 components/crisp-chat.tsx delete mode 100644 components/crisp-provider.tsx create mode 100644 components/data-table.tsx create mode 100644 components/date-range-picker.tsx delete mode 100644 components/donate-modal.tsx delete mode 100644 components/donor-button.tsx create mode 100644 components/empty-placeholder.tsx create mode 100644 components/heading-text.tsx delete mode 100644 components/heading.tsx create mode 100644 components/icons.tsx create mode 100644 components/image-frame.tsx delete mode 100644 components/landing-content.tsx delete mode 100644 components/landing-hero.tsx delete mode 100644 components/landing-navbar.tsx create mode 100644 components/layout/footer.tsx create mode 100644 components/layout/navbar.tsx create mode 100644 components/layout/shell.tsx delete mode 100644 components/loader.tsx delete mode 100644 components/mobile-sidebar.tsx delete mode 100644 components/modal-provider.tsx create mode 100644 components/mode-toggle.tsx delete mode 100644 components/navbar.tsx create mode 100644 components/pages/dashboard/dashboard-cards.tsx create mode 100644 components/pages/dashboard/dashboard-header.tsx create mode 100644 components/pages/dashboard/dashboard-nav.tsx create mode 100644 components/pages/feature-cards.tsx create mode 100644 components/pages/hero.tsx create mode 100644 components/pages/opensource.tsx create mode 100644 components/pages/overview.tsx create mode 100644 components/poll-results.tsx create mode 100644 components/pwa-redirect.tsx create mode 100644 components/settings/appearance-form.tsx delete mode 100644 components/sidebar.tsx delete mode 100644 components/signature-counter.tsx delete mode 100644 components/toaster-provider.tsx delete mode 100644 components/tool-card.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx delete mode 100644 components/ui/badge.tsx create mode 100644 components/ui/calendar.tsx delete mode 100644 components/ui/command.tsx create mode 100644 components/ui/credenza.tsx create mode 100644 components/ui/drawer.tsx delete mode 100644 components/ui/empty.tsx create mode 100644 components/ui/popover.tsx delete mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/scroll-area.tsx delete mode 100644 components/ui/select.tsx delete mode 100644 components/ui/separator.tsx delete mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/use-toast.ts delete mode 100644 components/user-avatar.tsx create mode 100644 components/user/user-account-nav.tsx create mode 100644 components/user/user-auth-form.tsx create mode 100644 components/user/user-avatar.tsx create mode 100644 components/user/user-name-form.tsx create mode 100644 components/user/user-nav-display.tsx create mode 100644 config/links.ts create mode 100644 config/site.ts delete mode 100644 constants.ts create mode 100644 env.mjs delete mode 100644 hooks/donate-modal.ts create mode 100644 hooks/use-media-query.tsx create mode 100644 jest.config.js create mode 100644 jest.setup.js delete mode 100644 kill_node.ps1 delete mode 100644 lib/api-limit.ts create mode 100644 lib/api/activities.ts create mode 100644 lib/api/dashboard.ts create mode 100644 lib/api/logs.ts create mode 100644 lib/auth.ts create mode 100644 lib/db.ts delete mode 100644 lib/prismadb.ts create mode 100644 lib/session.ts delete mode 100644 lib/stripe.ts delete mode 100644 lib/subscription.ts create mode 100644 lib/validations/activity.ts create mode 100644 lib/validations/user.ts delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 prettier.config.js delete mode 100644 prisma/migrations/20240401232731_/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml create mode 100644 public/android-chrome-192x192.png create mode 100644 public/android-chrome-512x512.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/browserconfig.xml delete mode 100644 public/chat.png delete mode 100644 public/empty.png create mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.ico delete mode 100644 public/home.png delete mode 100644 public/logo.png delete mode 100644 public/mail.png create mode 100644 public/mstile-150x150.png delete mode 100644 public/next.svg create mode 100644 public/og.png create mode 100644 public/og.svg delete mode 100644 public/photo.png create mode 100644 public/safari-pinned-tab.svg create mode 100644 public/site.webmanifest delete mode 100644 public/star.png delete mode 100644 public/transcript.png delete mode 100644 public/vercel.svg delete mode 100644 public/voice.png create mode 100644 types/auth.d.ts create mode 100644 types/index.d.ts diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..9a58b83c --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# App +NEXT_PUBLIC_APP_URL=http://localhost:3000 +NODE_ENV=development + +# Authentication (NextAuth.js) +NEXTAUTH_URL=http://localhost:3000 +# https://generate-secret.vercel.app/32 +NEXTAUTH_SECRET= + +# https://console.cloud.google.com/apis/credentials +# Callback URL: http://localhost:3000/api/auth/callback/google +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# https://github.com/settings/applications +# Callback URL: http://localhost:3000/api/auth/callback/github +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= + +# Database +DATABASE_URL="mysql://user:pass@localhost:3306/mydb?schema=public" \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bffb357a..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..dfe07704 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore index a93a8424..95f7c58e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,8 +25,8 @@ yarn-debug.log* yarn-error.log* # local env files -.env*.local .env +.env*.local # vercel .vercel @@ -34,5 +34,13 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts -.idea -/data \ No newline at end of file + +# extra +/private_docs +.vercel + +# IDE +.idea/ + +# Docker Compose Volumes +/data/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7f842506 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +dist +node_modules +.next +package.json +pnpm-lock.yaml \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41b..35412482 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,4 @@ { + "WillLuke.nextjs.addTypesOnSave": true, + "WillLuke.nextjs.hasPrompted": true } \ No newline at end of file diff --git a/README.md b/README.md index 728b64ed..fcb38e2e 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,38 @@ -# Build a SaaS AI Platform with Next.js 13, React, Tailwind, Prisma, Stripe | Full Tutorial 2023 +# Global Referendum on War and Disease -![Copy of Copy of Copy of Fullstack Twitter Clone](https://github.com/AntonioErdeljac/next13-ai-saas/assets/23248726/c47e604a-b50b-4eb0-b420-fda20908f522) +## Overview +This project aims to analyze public opinion on research spending. It seeks to understand the general sentiment towards funding research projects across various fields. +## Installation +To set up this project locally, follow these steps: +1. Clone the repository to your local machine. +2. Navigate to the project directory. +3. Install the required dependencies using `npm install`. +## Features -This is a repository for Build a SaaS AI Platform with Next.js 13, React, Tailwind, Prisma, Stripe | Full Tutorial 2023. +1. Desired Allocation Poll: presents the user with a question asking how much they think should be spent on war (Option A) relative to clinical research (Option B). It includes a single slider input to allocate the percentage between the two options, along with two bars above the slider that dynamically visualize the allocation as the user adjusts the slider. The user submits their desired allocation by clicking the "Submit" button, and their response is saved in the browser's local storage. -[VIDEO TUTORIAL](https://www.youtube.com/watch?v=ffJ38dBzrlY) +2. Actual Allocation Poll: asking the user how much they think is actually spent on war (Option A) relative to clinical research (Option B). It includes the same slider input and dynamic bars for visualizing the allocation. The user submits their perceived actual allocation by clicking the "Submit" button, and their response is saved in the browser's local storage. -Features: +3. User Authentication: After completing both polls, the user is prompted to log in or register to view the results. They can log in using their email and password or through OAuth with Google. Upon successful login or registration, a new user record is created in the database, and their poll responses are retrieved from local storage and saved in the database. -- Tailwind design -- Tailwind animations and effects -- Full responsiveness -- Clerk Authentication (Email, Google, 9+ Social Logins) -- Client form validation and handling using react-hook-form -- Server error handling using react-toast -- Image Generation Tool (Open AI) -- Video Generation Tool (Replicate AI) -- Conversation Generation Tool (Open AI) -- Music Generation Tool (Replicate AI) -- Page loading state -- Stripe monthly subscription -- Free tier with API limiting -- How to write POST, DELETE, and GET routes in route handlers (app/api) -- How to fetch data in server react components by directly accessing database (WITHOUT API! like Magic!) -- How to handle relations between Server and Child components! -- How to reuse layouts -- Folder structure in Next 13 App Router +4. Results Page: After logging in, the user is directed to the Results page, which displays their desired and perceived actual allocation percentages between "War" and "Clinical Research" using bar visualizations. The page also shows the average desired and perceived actual allocation percentages from all users using bar visualizations, allowing the user to compare their responses to the average person. The page includes a link to the petition page. -### Prerequisites +5. Petition Page: presents information about the petition to shift 1% of military spending to clinical research. It includes a form for users to sign the petition by providing their name, email, address, city, state, and zip code. Upon successful submission, the user is redirected to a thank you page. -**Node version 18.x.x** +6. Thank You Page: displays a message thanking the user for signing the petition and provides information about the next steps or updates related to the petition. -### Cloning the repository +7. Profile Page: displays the user's unique referral link, which they can share with others to invite them to participate in the polls. It also shows a list of users who have signed up using the referral link, indicating whether they have completed the polls and signed the petition. -```shell -git clone https://github.com/AntonioErdeljac/next13-ai-saas.git -``` +8. Social Sharing: allows users to easily share their participation in the polls and petition on social media platforms, with pre-populated posts and relevant hashtags. -### Install packages +9. Email Notifications: sends email notifications to users after they complete the polls, sign the petition, or refer someone to the app. Users can opt-out of email notifications if desired. -```shell -npm i -``` +10. Localization: allows the app to support multiple languages based on the user's browser settings or a language selector, ensuring that the app's content, including questions and instructions, can be easily translated. -### Setup .env file +11. Gamification: introduces gamification elements to encourage user participation and engagement, such as a points or badge system for completing polls, signing the petition, or referring others. It may also include leaderboards to showcase top referrers or most active participants. +12. Personalized Experience: tailors the app's content and recommendations based on the user's previous interactions and preferences, providing personalized insights or comparisons based on their poll responses and suggesting related petitions or initiatives that align with their interests. -```js -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= -CLERK_SECRET_KEY= - -NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in -NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up -NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard -NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard - -OPENAI_API_KEY= -REPLICATE_API_TOKEN= - -DATABASE_URL= - -STRIPE_API_KEY= -STRIPE_WEBHOOK_SECRET= - -NEXT_PUBLIC_APP_URL="http://localhost:3000" -``` - -### Setup Prisma - -Add MySQL Database (I used PlanetScale) - -```shell -npx prisma db push - -``` - -### Start the app - -```shell -npm run dev -``` - -## Available commands - -Running commands with npm `npm run [command]` - -| command | description | -| :-------------- | :--------------------------------------- | -| `dev` | Starts a development instance of the app | +13. Data Visualization: enhances the Results page with interactive data visualizations, such as dynamic charts or graphs, allowing users to explore and compare the poll results based on different criteria, such as demographics or location. It may also provide downloadable reports or infographics for users to share or use in their advocacy efforts. diff --git a/__tests__/activity/activity-item.test.tsx b/__tests__/activity/activity-item.test.tsx new file mode 100644 index 00000000..4e1bc467 --- /dev/null +++ b/__tests__/activity/activity-item.test.tsx @@ -0,0 +1,57 @@ +import { render, screen } from "@testing-library/react" + +import { ActivityItem } from "@/components/activity/activity-item" + +jest.mock("next/navigation", () => ({ + useRouter() { + return { + prefetch: () => null, + } + }, +})) + +describe("ActivityItem", () => { + const activity = { + id: "1", + name: "Test Activity", + description: "This is a test activity", + colorCode: "#FF0000", + createdAt: new Date("2023-01-01T00:00:00"), + } + + it("renders activity name and description", () => { + render() + + const nameElement = screen.getByText(activity.name) + const descriptionElement = screen.getByText(activity.description) + + expect(nameElement).toBeInTheDocument() + expect(descriptionElement).toBeInTheDocument() + }) + + it("renders activity color code", () => { + render() + + const colorCodeElement = screen.getByTestId("color-code") + + expect(colorCodeElement).toHaveStyle( + `background-color: ${activity.colorCode}` + ) + }) + + it("renders activity creation date", () => { + render() + + const createdAtElement = screen.getByText("Jan 1, 2023") + + expect(createdAtElement).toBeInTheDocument() + }) + + it("links to the activity details page", () => { + render() + + const linkElement = screen.getByRole("link") + + expect(linkElement).toHaveAttribute("href", "/dashboard/activities/1") + }) +}) diff --git a/__tests__/activity/activity-operations.test.tsx b/__tests__/activity/activity-operations.test.tsx new file mode 100644 index 00000000..ebb6a010 --- /dev/null +++ b/__tests__/activity/activity-operations.test.tsx @@ -0,0 +1,24 @@ +import { render, screen } from "@testing-library/react" + +import { ActivityOperations } from "@/components/activity/activity-operations" + +jest.mock("next/navigation", () => ({ + useRouter() { + return { + prefetch: () => null, + } + }, +})) + +// TODO: more test cases + +describe("ActivityOperations component", () => { + const mockActivity = { + id: "1", + } + + it("renders without crashing", () => { + render() + expect(screen.getByText("Open")).toBeInTheDocument() + }) +}) diff --git a/__tests__/activity/stats/stats-card.test.tsx b/__tests__/activity/stats/stats-card.test.tsx new file mode 100644 index 00000000..cd5cde76 --- /dev/null +++ b/__tests__/activity/stats/stats-card.test.tsx @@ -0,0 +1,56 @@ +import { render, RenderResult } from "@testing-library/react" + +import { StatsCards } from "@/components/activity/stats/stats-cards" + +describe("StatsCards", () => { + let data: { + streak: { + currentStreak: number + longestStreak: number + } + totalLogs: number + dailyAverage: number + } + let searchParams: { + from: string + to: string + } + let getByText: RenderResult["getByText"] + + // Mock data + beforeEach(() => { + data = { + streak: { + currentStreak: 5, + longestStreak: 10, + }, + totalLogs: 100, + dailyAverage: 7, + } + + searchParams = { + from: "2022-01-01", + to: "2022-12-31", + } + ;({ getByText } = render( + + )) + }) + + test("Render streaks properly", () => { + expect(getByText("Current Streak")).toBeInTheDocument() + expect(getByText("Longest Streak")).toBeInTheDocument() + expect(getByText("5")).toBeInTheDocument() + expect(getByText("10")).toBeInTheDocument() + }) + + test("Render total logs properly", () => { + expect(getByText("Total Logs")).toBeInTheDocument() + expect(getByText("100")).toBeInTheDocument() + }) + + test("Render daily average properly", () => { + expect(getByText("Daily Average")).toBeInTheDocument() + expect(getByText("7")).toBeInTheDocument() + }) +}) diff --git a/__tests__/dashboard/dashboard-cards.test.tsx b/__tests__/dashboard/dashboard-cards.test.tsx new file mode 100644 index 00000000..a7c3d45a --- /dev/null +++ b/__tests__/dashboard/dashboard-cards.test.tsx @@ -0,0 +1,56 @@ +import { render, RenderResult } from "@testing-library/react" + +import { DashboardCards } from "@/components/pages/dashboard/dashboard-cards" + +describe("DashboardCards", () => { + let data: { + streak: { + currentStreak: number + longestStreak: number + } + totalLogs: number + mostLoggedActivity: string + } + let searchParams: { + from: string + to: string + } + let getByText: RenderResult["getByText"] + + // Mock data + beforeEach(() => { + data = { + streak: { + currentStreak: 5, + longestStreak: 10, + }, + totalLogs: 100, + mostLoggedActivity: "Running", + } + + searchParams = { + from: "2022-01-01", + to: "2022-12-31", + } + ;({ getByText } = render( + + )) + }) + + test("Render streaks properly", () => { + expect(getByText("Current Streak")).toBeInTheDocument() + expect(getByText("Longest Streak")).toBeInTheDocument() + expect(getByText("5")).toBeInTheDocument() + expect(getByText("10")).toBeInTheDocument() + }) + + test("Render total logs properly", () => { + expect(getByText("Total Logs")).toBeInTheDocument() + expect(getByText("100")).toBeInTheDocument() + }) + + test("Render most logged activity properly", () => { + expect(getByText("Most Logged Activity")).toBeInTheDocument() + expect(getByText("Running")).toBeInTheDocument() + }) +}) diff --git a/__tests__/dashboard/dashboard-header.test.tsx b/__tests__/dashboard/dashboard-header.test.tsx new file mode 100644 index 00000000..f3b3c166 --- /dev/null +++ b/__tests__/dashboard/dashboard-header.test.tsx @@ -0,0 +1,44 @@ +import { render } from "@testing-library/react" + +import { DashboardHeader } from "@/components/pages/dashboard/dashboard-header" + +describe("DashboardHeader", () => { + let heading: string + let text: string + + // Mock data + beforeEach(() => { + heading = "Dashboard" + text = "Shows your activities" + }) + + test("renders heading and text correctly", () => { + const { getByText } = render( + + ) + + expect(getByText(heading)).toBeInTheDocument() + expect(getByText(text)).toBeInTheDocument() + }) + + test("Renders heading without text", () => { + const { getByText, queryByText } = render( + + ) + + expect(getByText(heading)).toBeInTheDocument() + expect(queryByText("Test Text")).toBeNull() + }) + + test("renders children correctly", () => { + const { getByText } = render( + +
Test children
+
+ ) + + expect(getByText(heading)).toBeInTheDocument() + expect(getByText(text)).toBeInTheDocument() + expect(getByText("Test children")).toBeInTheDocument() + }) +}) diff --git a/__tests__/dashboard/dashboard-nav.test.tsx b/__tests__/dashboard/dashboard-nav.test.tsx new file mode 100644 index 00000000..6c28bf54 --- /dev/null +++ b/__tests__/dashboard/dashboard-nav.test.tsx @@ -0,0 +1,25 @@ +import { render, screen } from "@testing-library/react" +import userEvent from "@testing-library/user-event" + +import { dashboardLinks } from "@/config/links" +import { DashboardNav } from "@/components/pages/dashboard/dashboard-nav" + +describe("DashboardNav", () => { + test("renders DashboardNav component with items", () => { + render() + + expect(screen.getByText("Dashboard")).toBeInTheDocument() + expect(screen.getByText("Activities")).toBeInTheDocument() + expect(screen.getByText("Settings")).toBeInTheDocument() + + userEvent.click(screen.getByText("Dashboard")) + }) + + test("renders DashboardNav component with no items", () => { + render() + + expect(screen.queryByText("Dashboard")).toBeNull() + expect(screen.queryByText("Activities")).toBeNull() + expect(screen.queryByText("Settings")).toBeNull() + }) +}) diff --git a/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx deleted file mode 100644 index 0d639461..00000000 --- a/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import {SignIn, SignInButton} from "@clerk/nextjs"; - -export default function Page() { - return ( -
-

Are you a damn robot?

- - - -
- ); - //return ; -} \ No newline at end of file diff --git a/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx deleted file mode 100644 index 3ac029cc..00000000 --- a/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { SignUp } from "@clerk/nextjs"; - -export default function Page() { - return ; -}; diff --git a/app/(auth)/error.tsx b/app/(auth)/error.tsx deleted file mode 100644 index 032711e6..00000000 --- a/app/(auth)/error.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import { Empty } from "@/components/ui/empty"; - -const Error = () => { - return ( - - ); -} - -export default Error; diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx deleted file mode 100644 index 21f23ef1..00000000 --- a/app/(auth)/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -const AuthLayout = ({ - children -}: { - children: React.ReactNode; -}) => { - return ( -
- {children} -
- ); -} - -export default AuthLayout; \ No newline at end of file diff --git a/app/(auth)/signin/page.tsx b/app/(auth)/signin/page.tsx new file mode 100644 index 00000000..2a5029f8 --- /dev/null +++ b/app/(auth)/signin/page.tsx @@ -0,0 +1,49 @@ +import { Metadata } from "next" +import Link from "next/link" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" +import { Icons } from "@/components/icons" +import { UserAuthForm } from "@/components/user/user-auth-form" + +export const metadata: Metadata = { + title: "Sign in", + description: "Sign in to your account", +} + +export default function Signin() { + return ( +
+ + <> + + Back + + +
+
+

Sign in

+

+ Sign in to your account +

+
+ +

+ Don't have an account?{" "} + + Sign up + +

+
+
+ ) +} diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx new file mode 100644 index 00000000..e349bd3d --- /dev/null +++ b/app/(auth)/signup/page.tsx @@ -0,0 +1,51 @@ +import { Metadata } from "next" +import Link from "next/link" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" +import { Icons } from "@/components/icons" +import { UserAuthForm } from "@/components/user/user-auth-form" + +export const metadata: Metadata = { + title: "Sign up", + description: "Create an account", +} + +export default function Signup() { + return ( +
+ + <> + + Back + + +
+
+

+ Create an account +

+

+ Select a provider to create your account +

+
+ +

+ Already have an account?{" "} + + Sign in + +

+
+
+ ) +} diff --git a/app/(dashboard)/(routes)/dashboard/page.tsx b/app/(dashboard)/(routes)/dashboard/page.tsx deleted file mode 100644 index 8ba48864..00000000 --- a/app/(dashboard)/(routes)/dashboard/page.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client"; - -import { ArrowRight } from "lucide-react"; -import { useRouter } from "next/navigation"; - -import { Card } from "@/components/ui/card"; -import { cn } from "@/lib/utils"; - -import { tools } from "@/constants"; - -export default function HomePage() { - const router = useRouter(); - - return ( -
-
-

- Results -

-

- Global Referendum on War and Disease -

-
-
- {tools.map((tool) => ( - router.push(tool.href)} key={tool.href} className="p-4 border-black/5 flex items-center justify-between hover:shadow-md transition cursor-pointer"> -
-
- -
-
- {tool.label} -
-
- -
- ))} -
-
- ); -} diff --git a/app/(dashboard)/(routes)/settings/constants.ts b/app/(dashboard)/(routes)/settings/constants.ts deleted file mode 100644 index b47aa1f5..00000000 --- a/app/(dashboard)/(routes)/settings/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as z from "zod"; - -export const formSchema = z.object({ - prompt: z.string().min(1, { - message: "Prompt is required." - }), -}); diff --git a/app/(dashboard)/(routes)/settings/page.tsx b/app/(dashboard)/(routes)/settings/page.tsx deleted file mode 100644 index e947242a..00000000 --- a/app/(dashboard)/(routes)/settings/page.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Settings } from "lucide-react"; - -import { Heading } from "@/components/heading"; -import { DonorButton } from "@/components/donor-button"; -import { checkSubscription } from "@/lib/subscription"; - -const SettingsPage = async () => { - const isDonor = await checkSubscription(); - - return ( -
- -
-
- {isDonor ? "You are currently on a Pro plan." : "You are currently on a free plan."} -
- -
-
- ); -} - -export default SettingsPage; - diff --git a/app/(dashboard)/error.tsx b/app/(dashboard)/error.tsx deleted file mode 100644 index 032711e6..00000000 --- a/app/(dashboard)/error.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import { Empty } from "@/components/ui/empty"; - -const Error = () => { - return ( - - ); -} - -export default Error; diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx deleted file mode 100644 index 2536f3d7..00000000 --- a/app/(dashboard)/layout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import Navbar from "@/components/navbar"; -import { Sidebar } from "@/components/sidebar"; -import { checkSubscription } from "@/lib/subscription"; -import { getApiLimitCount } from "@/lib/api-limit"; -import ChartContainer from "@/components/ui/chart-container"; - -const DashboardLayout = async ({ - children, -}: { - children: React.ReactNode -}) => { - const apiLimitCount = await getApiLimitCount(); - const isDonor = await checkSubscription(); - //const warPercentageDesired = localStorage.getItem("warPercentageDesired"); - - - return ( -
-
- -
-
- - {children} -
-
- ); -} - -export default DashboardLayout; diff --git a/app/(frontpage)/layout.tsx b/app/(frontpage)/layout.tsx new file mode 100644 index 00000000..d6bd3efa --- /dev/null +++ b/app/(frontpage)/layout.tsx @@ -0,0 +1,27 @@ +import { getCurrentUser } from "@/lib/session" +import Footer from "@/components/layout/footer" +import Navbar from "@/components/layout/navbar" + +interface FrontPageLayoutProps { + children: React.ReactNode +} + +export default async function FrontPageLayout({ + children, +}: FrontPageLayoutProps) { + const user = await getCurrentUser() + + return ( + <> + + {children} +