├── .env.example
├── .env.loc
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── app
├── api
│ └── lemonsqueezy
│ │ ├── payment
│ │ └── route.ts
│ │ └── webhook
│ │ └── route.ts
├── favicon.ico
├── globals.css
├── handler
│ └── [...stack]
│ │ └── page.tsx
├── layout.tsx
├── loading.tsx
├── not-found.tsx
└── page.tsx
├── components.json
├── components
├── About.tsx
├── AnimatedButton.tsx
├── Bento.tsx
├── FAQs.tsx
├── Features.tsx
├── Hero.tsx
├── How.tsx
├── Marquee.tsx
├── MobileNav.tsx
├── Navbar.tsx
├── PopupList.tsx
├── Press.tsx
├── Pricing.tsx
├── Shiny.tsx
├── SideNav.tsx
├── cta.tsx
├── email-template.tsx
├── footer.tsx
├── logo.tsx
├── magicui
│ ├── animated-list.tsx
│ ├── animated-shiny-text.tsx
│ ├── animated-subscribe-button.tsx
│ ├── bento-grid.tsx
│ └── marquee.tsx
├── mode-toggle.tsx
├── theme-provider.tsx
├── ui
│ ├── accordion.tsx
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dropdown-menu.tsx
│ └── sheet.tsx
└── useAccountNav.tsx
├── db
├── index.ts
└── schema.ts
├── drizzle.config.ts
├── lib
├── axios.ts
└── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── logo.svg
├── next.svg
└── vercel.svg
├── stack.tsx
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | #DATABASE
2 | TURSO_CONNECTION_URL=
3 | TURSO_AUTH_TOKEN=
4 |
5 | #PAYMENT
6 | LEMON_SQUEEZY_STORE_ID =
7 | LEMON_SQUEEZY_API_KEY =
8 | LEMON_SQUEEZY_WEBHOOK_SIGNATURE =
9 |
10 | #EMAIL
11 | RESEND_API_KEY =
--------------------------------------------------------------------------------
/.env.loc:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_STACK_PROJECT_ID=
2 | NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=
3 | STACK_SECRET_SERVER_KEY=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 wolfgunblood
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Next.js SaaS kit
4 |
🔥 Open Source MVP Boilerplate
5 |
6 | [](https://github.com/wolfgunblood/nextjs-saaskit)
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 | ## Motivation
19 |
20 | Implementing authentication in Next.js, especially Email+Password authentication, Oauth, payments, and database integration can be challenging.
21 |
22 | No more hassle with user management. No more hassle for payment integrations. No more building a landing page from scratch.
23 |
24 | A done-for-you starter kit
25 |
26 | ## Key Features
27 |
28 | - **Authentication:** 💼 Support for Credential and OAuth(google,github) authentication.
29 | - **Authorization:** 🔒 Easily manage public and protected routes within the `app directory`.
30 | - **Email Verification:** 📧 Verify user identities through email.
31 | - **Password Reset:** 🔑 Streamline password resets by sending email password reset links.
32 | - **Lemonsqueezy Payment:** 💳 Setup user subscriptions seamlessly with lemonsqueezy.
33 | - **Turso Database:** 🛢️ Utilize a turso database set up.
34 | - **Drizzle ORM:** 🛢️ Utilize a Drizzle ORM.
35 |
36 | ## Tech Stack
37 |
38 | - [Next.js](https://nextjs.org)
39 | - [Stack Auth](https://stack-auth.com/)
40 | - [Tailwind CSS](https://tailwindcss.com)
41 | - [Shadcn UI](https://ui.shadcn.com/)
42 | - [React Hook Form](https://www.react-hook-form.com/)
43 |
44 | ## Get Started
45 |
46 | 1. `git clone https://github.com/wolfgunblood/nextjs-saaskit`
47 | 2. `cd projectName`
48 | 3. Copy `.env.loc` to `.env.local`
49 | 4. Copy `.env.example` to `.env`
50 | 5. Update env variables
51 | 6. Run `npm install` to install dependencies.
52 | 7. Execute `npm dev` to start the development server and enjoy!
53 |
54 | ## Roadmap
55 |
56 | - [ ] Stripe Integration
57 | - [ ] Resend Integration
58 | - [ ] Prisma Integration
59 |
60 | ## Contributing
61 |
62 | To contribute, fork the repository and create a feature branch. Test your changes, and if possible, open an issue for discussion before submitting a pull request. Follow project guidelines, and welcome feedback to ensure a smooth integration of your contributions. Your pull requests are warmly welcome.
63 |
64 | ### New
65 |
66 | [NextWrapper](https://nextwrapper.com/) - An AI-powered No-Code tool to build your SaaS.
67 |
68 | Build SaaS, AI tool, and web app builder in days not months
69 |
70 | An AI-powered No-Code tool to build your SaaS, AI tool, or any web app in days—not months. Stop wasting time setting up everything & leverage AI to build your startup today!
71 |
--------------------------------------------------------------------------------
/app/api/lemonsqueezy/payment/route.ts:
--------------------------------------------------------------------------------
1 | import { lemonSqueezyApiInstance } from "@/lib/axios";
2 |
3 | export const dynamic = "force-dynamic";
4 |
5 | export async function POST(req :Request) {
6 | try {
7 | const reqData = await req.json();
8 |
9 | if (!reqData.product_id)
10 | return Response.json(
11 | { message: "product_id is required" },
12 | { status: 400 }
13 | );
14 |
15 | const response = await lemonSqueezyApiInstance.post("/checkouts", {
16 | data: {
17 | type: "checkouts",
18 | attributes: {
19 | checkout_data: {
20 | custom: {
21 | user_id: "123",
22 | },
23 | },
24 | },
25 | relationships: {
26 | store: {
27 | data: {
28 | type: "stores",
29 | id: process.env.LEMON_SQUEEZY_STORE_ID!.toString(),
30 | },
31 | },
32 | variant: {
33 | data: {
34 | type: "variants",
35 | id: reqData.product_id.toString(),
36 | },
37 | },
38 | },
39 | },
40 | });
41 |
42 | const checkoutUrl = response.data.data.attributes.url;
43 |
44 | console.log(response.data);
45 |
46 | return Response.json({ checkoutUrl });
47 | } catch (error) {
48 | console.error(error);
49 | Response.json({ message: "An error occured" }, { status: 500 });
50 | }
51 | }
--------------------------------------------------------------------------------
/app/api/lemonsqueezy/webhook/route.ts:
--------------------------------------------------------------------------------
1 |
2 | export async function POST(req : Request) {
3 | try {
4 | const crypto = typeof window === 'undefined' ? require('crypto') : null;
5 | if (!crypto) {
6 | throw new Error("crypto is required");
7 | }
8 | // Catch the event type
9 | const clonedReq = req.clone();
10 | const eventType = req.headers.get("X-Event-Name");
11 | const body = await req.json();
12 |
13 | // Check signature
14 | const secret = process.env.LEMON_SQUEEZY_WEBHOOK_SIGNATURE;
15 | if (!secret) {
16 | throw new Error("Webhook signature secret is not defined.");
17 | }
18 | const hmac = crypto.createHmac("sha256", secret);
19 | const digest = Buffer.from(
20 | hmac.update(await clonedReq.text()).digest("hex"),
21 | "utf8"
22 | );
23 | const signature = Buffer.from(req.headers.get("X-Signature") || "", "utf8");
24 |
25 | if (!crypto.timingSafeEqual(digest, signature)) {
26 | throw new Error("Invalid signature.");
27 | }
28 |
29 | console.log(body);
30 |
31 | if (eventType === "order_created") {
32 | const userId = body.meta.custom_data.user_id;
33 | const isSuccessful = body.data.attributes.status === "paid";
34 | }
35 |
36 | return Response.json({ message: "Webhook received" });
37 | } catch (err) {
38 | console.error(err);
39 | return Response.json({ message: "Something went wrong" }, { status: 500 });
40 | }
41 | }
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
1 | h 6 ( � 00 h&