forked from wishonia/wishonia
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
194 changed files
with
13,277 additions
and
9,230 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Auto detect text files and perform LF normalization | ||
* text=auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
dist | ||
node_modules | ||
.next | ||
package.json | ||
pnpm-lock.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
{ | ||
"WillLuke.nextjs.addTypesOnSave": true, | ||
"WillLuke.nextjs.hasPrompted": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(<ActivityItem activity={activity} />) | ||
|
||
const nameElement = screen.getByText(activity.name) | ||
const descriptionElement = screen.getByText(activity.description) | ||
|
||
expect(nameElement).toBeInTheDocument() | ||
expect(descriptionElement).toBeInTheDocument() | ||
}) | ||
|
||
it("renders activity color code", () => { | ||
render(<ActivityItem activity={activity} />) | ||
|
||
const colorCodeElement = screen.getByTestId("color-code") | ||
|
||
expect(colorCodeElement).toHaveStyle( | ||
`background-color: ${activity.colorCode}` | ||
) | ||
}) | ||
|
||
it("renders activity creation date", () => { | ||
render(<ActivityItem activity={activity} />) | ||
|
||
const createdAtElement = screen.getByText("Jan 1, 2023") | ||
|
||
expect(createdAtElement).toBeInTheDocument() | ||
}) | ||
|
||
it("links to the activity details page", () => { | ||
render(<ActivityItem activity={activity} />) | ||
|
||
const linkElement = screen.getByRole("link") | ||
|
||
expect(linkElement).toHaveAttribute("href", "/dashboard/activities/1") | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(<ActivityOperations activity={mockActivity} />) | ||
expect(screen.getByText("Open")).toBeInTheDocument() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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( | ||
<StatsCards data={data} searchParams={searchParams} /> | ||
)) | ||
}) | ||
|
||
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() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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( | ||
<DashboardCards data={data} searchParams={searchParams} /> | ||
)) | ||
}) | ||
|
||
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() | ||
}) | ||
}) |
Oops, something went wrong.