Skip to content

Commit 8819790

Browse files
committed
mini
1 parent 8fae9ea commit 8819790

File tree

138 files changed

+86348
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+86348
-0
lines changed

.gitignore

+40
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,43 @@ dist
128128
.yarn/build-state.yml
129129
.yarn/install-state.gz
130130
.pnp.*
131+
132+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
133+
134+
# dependencies
135+
/node_modules
136+
/.pnp
137+
.pnp.js
138+
139+
# testing
140+
/coverage
141+
142+
# next.js
143+
/.next/
144+
/out/
145+
146+
# production
147+
/build
148+
149+
# misc
150+
.DS_Store
151+
*.pem
152+
153+
# debug
154+
npm-debug.log*
155+
yarn-debug.log*
156+
yarn-error.log*
157+
158+
# local env files
159+
.env*.local
160+
.env
161+
162+
# vercel
163+
.vercel
164+
165+
# typescript
166+
*.tsbuildinfo
167+
next-env.d.ts
168+
169+
# Bun
170+
bun.lockb

README.md

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Getting Started
2+
3+
## Prerequisites
4+
- Node.js and yarn/bun installed
5+
- Accounts and API keys for:
6+
- Supabase
7+
- Stripe (if using payments)
8+
- Clerk (if using authentication)
9+
10+
## Setup
11+
12+
1. Clone the repository:
13+
```
14+
git clone <repository-url>
15+
cd <project-directory>
16+
```
17+
18+
2. Install dependencies:
19+
```
20+
yarn
21+
```
22+
23+
3. Set up environment variables:
24+
Create a `.env` file in the root directory with the following variables:
25+
```
26+
SUPABASE_URL=<your-supabase-project-url>
27+
SUPABASE_SERVICE_KEY=<your-supabase-service-key>
28+
29+
# If using Stripe
30+
STRIPE_SECRET_KEY=<your-stripe-secret-key>
31+
NEXT_PUBLIC_STRIPE_PRICE_ID=<your-stripe-price-id>
32+
33+
# If using Clerk
34+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=<your-clerk-publishable-key>
35+
CLERK_SECRET_KEY=<your-clerk-secret-key>
36+
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
37+
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
38+
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
39+
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
40+
```
41+
42+
4. Configure features:
43+
In `config.ts`, set the desired features:
44+
```typescript
45+
const config = {
46+
auth: {
47+
enabled: true, // Set to false if not using Clerk
48+
},
49+
payments: {
50+
enabled: true, // Set to false if not using Stripe
51+
}
52+
};
53+
```
54+
55+
5. Set up the database:
56+
Run Prisma migrations:
57+
```
58+
npx prisma migrate dev
59+
```
60+
61+
6. Start the development server:
62+
```
63+
yarn dev
64+
```
65+
66+
7. Open your browser and navigate to `http://localhost:3000` to see your application running.
67+
68+
## Additional Configuration
69+
70+
- Webhooks: Set up webhooks for Clerk (if using auth) at `/api/auth/webhook` and for Stripe (if using payments) at `/api/payments/webhook`.
71+
- Customize the landing page, dashboard, and other components as needed.
72+
- Modify the Prisma schema in `prisma/schema.prisma` if you need to change the database structure.
73+
74+
## Important Security Notes
75+
76+
- Enable Row Level Security (RLS) in your Supabase project to ensure data protection at the database level.
77+
- Always make Supabase calls on the server-side (in API routes or server components) to keep your service key secure.
78+
79+
## Learn More
80+
81+
Refer to the documentation of the individual technologies used in this project for more detailed information:
82+
- [Next.js Documentation](https://nextjs.org/docs)
83+
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
84+
- [Supabase Documentation](https://supabase.io/docs)
85+
- [Prisma Documentation](https://www.prisma.io/docs)
86+
- [Clerk Documentation](https://clerk.dev/docs) (if using auth)
87+
- [Stripe Documentation](https://stripe.com/docs) (if using payments)
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"use client"
2+
import PageWrapper from "@/components/wrapper/page-wrapper";
3+
import config from "@/config";
4+
import { SignIn } from "@clerk/nextjs";
5+
import { useRouter } from "next/navigation";
6+
7+
export default function SignInPage() {
8+
const router = useRouter()
9+
10+
if (!config?.auth?.enabled) {
11+
router.back()
12+
}
13+
14+
return (
15+
<PageWrapper >
16+
<div className="flex min-w-screen justify-center my-[5rem]">
17+
<SignIn />
18+
</div>
19+
</PageWrapper>
20+
);
21+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"use client"
2+
import PageWrapper from "@/components/wrapper/page-wrapper";
3+
import config from "@/config";
4+
import { SignUp } from "@clerk/nextjs";
5+
import { useRouter } from "next/navigation";
6+
7+
export default function SignUpPage() {
8+
const router = useRouter()
9+
10+
if (!config?.auth?.enabled) {
11+
router.back()
12+
}
13+
14+
return (
15+
<PageWrapper >
16+
<div className="flex min-w-screen justify-center my-[5rem]">
17+
<SignUp />
18+
</div>
19+
</PageWrapper>
20+
);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use client"
2+
import PageWrapper from "@/components/wrapper/page-wrapper";
3+
import config from "@/config";
4+
import { UserProfile } from "@clerk/nextjs";
5+
import { useRouter } from "next/navigation";
6+
7+
const UserProfilePage = () => {
8+
const router = useRouter()
9+
10+
if (!config?.auth?.enabled) {
11+
router.back()
12+
}
13+
return (
14+
<PageWrapper >
15+
<div className="h-full flex items-center justify-center p-9">
16+
{config?.auth?.enabled && <UserProfile path="/user-profile" routing="path" />}
17+
</div>
18+
</PageWrapper>
19+
)
20+
}
21+
22+
23+
export default UserProfilePage;

app/api/auth/webhook/route.ts

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { userCreate } from "@/utils/data/user/userCreate";
2+
import { userUpdate } from "@/utils/data/user/userUpdate";
3+
import { WebhookEvent } from "@clerk/nextjs/server";
4+
import { headers } from "next/headers";
5+
import { NextResponse } from "next/server";
6+
import { Webhook } from "svix";
7+
8+
export async function POST(req: Request) {
9+
// You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook
10+
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
11+
12+
if (!WEBHOOK_SECRET) {
13+
throw new Error(
14+
"Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local"
15+
);
16+
}
17+
18+
// Get the headers - await the headers() call
19+
const headerPayload = await headers();
20+
const svix_id = headerPayload.get("svix-id");
21+
const svix_timestamp = headerPayload.get("svix-timestamp");
22+
const svix_signature = headerPayload.get("svix-signature");
23+
24+
// If there are no headers, error out
25+
if (!svix_id || !svix_timestamp || !svix_signature) {
26+
return new Response("Error occured -- no svix headers", {
27+
status: 400,
28+
});
29+
}
30+
31+
// Get the body
32+
const payload = await req.json();
33+
const body = JSON.stringify(payload);
34+
35+
// Create a new SVIX instance with your secret.
36+
const wh = new Webhook(WEBHOOK_SECRET);
37+
38+
let evt: WebhookEvent;
39+
40+
// Verify the payload with the headers
41+
try {
42+
evt = wh.verify(body, {
43+
"svix-id": svix_id,
44+
"svix-timestamp": svix_timestamp,
45+
"svix-signature": svix_signature,
46+
}) as WebhookEvent;
47+
} catch (err) {
48+
console.error("Error verifying webhook:", err);
49+
return new Response("Error occured", {
50+
status: 400,
51+
});
52+
}
53+
54+
// Get the ID and type
55+
const { id } = evt.data;
56+
const eventType = evt.type;
57+
58+
59+
switch (eventType) {
60+
case "user.created":
61+
try {
62+
await userCreate({
63+
email: payload?.data?.email_addresses?.[0]?.email_address,
64+
first_name: payload?.data?.first_name,
65+
last_name: payload?.data?.last_name,
66+
profile_image_url: payload?.data?.profile_image_url,
67+
user_id: payload?.data?.id,
68+
});
69+
70+
return NextResponse.json({
71+
status: 200,
72+
message: "User info inserted",
73+
});
74+
} catch (error: any) {
75+
return NextResponse.json({
76+
status: 400,
77+
message: error.message,
78+
});
79+
}
80+
break;
81+
82+
case "user.updated":
83+
try {
84+
await userUpdate({
85+
email: payload?.data?.email_addresses?.[0]?.email_address,
86+
first_name: payload?.data?.first_name,
87+
last_name: payload?.data?.last_name,
88+
profile_image_url: payload?.data?.profile_image_url,
89+
user_id: payload?.data?.id,
90+
});
91+
92+
return NextResponse.json({
93+
status: 200,
94+
message: "User info updated",
95+
});
96+
} catch (error: any) {
97+
return NextResponse.json({
98+
status: 400,
99+
message: error.message,
100+
});
101+
}
102+
break;
103+
104+
default:
105+
return new Response("Error occured -- unhandeled event type", {
106+
status: 400,
107+
});
108+
}
109+
110+
return new Response("", { status: 201 });
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import Stripe from "stripe";
3+
4+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
5+
6+
export async function POST(req: NextRequest) {
7+
const { userId, email, priceId, subscription } = await req.json();
8+
9+
if (subscription) {
10+
try {
11+
const session = await stripe.checkout.sessions.create({
12+
payment_method_types: ["card"],
13+
line_items: [{ price: priceId, quantity: 1 }],
14+
metadata: { userId, email, subscription },
15+
mode: "subscription",
16+
success_url: `${process.env.FRONTEND_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
17+
cancel_url: `${process.env.FRONTEND_URL}/cancel`,
18+
allow_promotion_codes: true,
19+
});
20+
21+
22+
return NextResponse.json({ sessionId: session.id });
23+
} catch (error) {
24+
console.error("Error creating checkout session:", error);
25+
return NextResponse.json({ error: "Failed to create checkout session" });
26+
}
27+
} else {
28+
try {
29+
const session = await stripe.checkout.sessions.create({
30+
payment_method_types: ["card"],
31+
line_items: [{ price: priceId, quantity: 1 }],
32+
metadata: { userId, email, subscription },
33+
mode: "payment",
34+
success_url: `${process.env.FRONTEND_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
35+
cancel_url: `${process.env.FRONTEND_URL}/cancel`,
36+
});
37+
38+
return NextResponse.json({ sessionId: session.id });
39+
} catch (error) {
40+
console.error("Error creating checkout session:", error);
41+
return NextResponse.json({ error: "Failed to create checkout session" });
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)