next-ts-cli 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/dist/index.js +94 -0
- package/package.json +97 -0
- package/template/base/.cusror/mpc.json +8 -0
- package/template/base/.husky/commit-msg +1 -0
- package/template/base/.husky/pre-commit +1 -0
- package/template/base/LICENSE +21 -0
- package/template/base/README.md +0 -0
- package/template/base/app/apple-icon.png +0 -0
- package/template/base/app/favicon.ico +0 -0
- package/template/base/app/globals.css +9 -0
- package/template/base/app/layout.tsx +61 -0
- package/template/base/app/loading.tsx +7 -0
- package/template/base/app/manifest.ts +33 -0
- package/template/base/app/opengraph-image.png +0 -0
- package/template/base/app/page.tsx +7 -0
- package/template/base/app/robots.ts +28 -0
- package/template/base/app/sitemap.ts +17 -0
- package/template/base/app/twitter-image.png +0 -0
- package/template/base/biome.jsonc +272 -0
- package/template/base/commitlint.config.ts +25 -0
- package/template/base/hooks/use-hydration.tsx +16 -0
- package/template/base/jest.config.js +18 -0
- package/template/base/jest.setup.js +2 -0
- package/template/base/lib/fonts.ts +14 -0
- package/template/base/lib/indexing.ts +14 -0
- package/template/base/lib/microdata.ts +51 -0
- package/template/base/lib/utils.ts +6 -0
- package/template/base/next.config.ts +7 -0
- package/template/base/package-lock.json +9296 -0
- package/template/base/package.json +59 -0
- package/template/base/postcss.config.js +5 -0
- package/template/base/providers/MicrodataScript.tsx +18 -0
- package/template/base/public/.gitkeep +3 -0
- package/template/base/test/index.test.tsx +9 -0
- package/template/base/tsconfig.json +38 -0
- package/template/extras/better-auth/api/auth/[...all]/route.ts +7 -0
- package/template/extras/better-auth/base-auth.ts +12 -0
- package/template/extras/better-auth/with-drizzle-auth.ts +31 -0
- package/template/extras/clerk/layout.tsx +89 -0
- package/template/extras/clerk/proxy.ts +21 -0
- package/template/extras/docker/.dockerignore +60 -0
- package/template/extras/docker/Dockerfile +51 -0
- package/template/extras/docker/docker-compose.prod.yml +34 -0
- package/template/extras/drizzle/db/index.ts +9 -0
- package/template/extras/drizzle/db/schema.ts +7 -0
- package/template/extras/drizzle/drizzle.config.ts +15 -0
- package/template/extras/neon/index.ts +10 -0
- package/template/extras/shadcnui/components.json +21 -0
- package/template/extras/shadcnui/globals.css +71 -0
- package/template/extras/stripe/checkout_session/route.ts +60 -0
- package/template/extras/stripe/stripe.ts +17 -0
- package/template/extras/stripe/webhook/stripe/route.ts +89 -0
- package/template/extras/supabase/client.ts +8 -0
- package/template/extras/supabase/getAuth.ts +50 -0
- package/template/extras/supabase/proxy.ts +70 -0
- package/template/extras/supabase/server.ts +34 -0
- package/template/extras/supabase/storage.ts +90 -0
- package/template/extras/vercel-ai/route.ts +12 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "next dev",
|
|
6
|
+
"build": "next build",
|
|
7
|
+
"start": "next start",
|
|
8
|
+
"lint": "biome check",
|
|
9
|
+
"format": "biome format --write",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch",
|
|
12
|
+
"test:ci": "jest --ci --passWithNoTests",
|
|
13
|
+
"docker:build": "docker compose -f docker-compose.prod.yml build",
|
|
14
|
+
"docker:up": "docker compose -f docker-compose.prod.yml up -d",
|
|
15
|
+
"docker:down": "docker compose -f docker-compose.prod.yml down",
|
|
16
|
+
"docker:logs": "docker compose -f docker-compose.prod.yml logs -f",
|
|
17
|
+
"docker:exec": "docker compose -f docker-compose.prod.yml exec app sh",
|
|
18
|
+
"docker:exec:bash": "docker compose -f docker-compose.prod.yml exec app bash",
|
|
19
|
+
"docker:exec:sh": "docker compose -f docker-compose.prod.yml exec app sh",
|
|
20
|
+
"docker:exec:npm": "docker compose -f docker-compose.prod.yml exec app npm"
|
|
21
|
+
},
|
|
22
|
+
"lint-staged": {
|
|
23
|
+
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
|
24
|
+
"biome check --write --no-errors-on-unmatched"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@next/third-parties": "^16.1.1",
|
|
29
|
+
"clsx": "^2.1.1",
|
|
30
|
+
"framer-motion": "^12.23.6",
|
|
31
|
+
"husky": "^9.1.7",
|
|
32
|
+
"lint-staged": "^16.2.7",
|
|
33
|
+
"next": "^16.1.1",
|
|
34
|
+
"react": "^19.2.3",
|
|
35
|
+
"react-dom": "^19.2.3",
|
|
36
|
+
"schema-dts": "^1.1.5",
|
|
37
|
+
"sharp": "^0.34.5",
|
|
38
|
+
"tailwind-merge": "^3.4.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@biomejs/biome": "2.3.10",
|
|
42
|
+
"@commitlint/cli": "^20.2.0",
|
|
43
|
+
"@commitlint/config-conventional": "^20.2.0",
|
|
44
|
+
"@tailwindcss/postcss": "^4.1.11",
|
|
45
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
46
|
+
"@testing-library/react": "^16.3.0",
|
|
47
|
+
"@testing-library/user-event": "^14.6.1",
|
|
48
|
+
"@types/jest": "^30.0.0",
|
|
49
|
+
"@types/node": "^25.0.3",
|
|
50
|
+
"@types/react": "^19.2.7",
|
|
51
|
+
"@types/react-dom": "^19.2.3",
|
|
52
|
+
"autoprefixer": "^10.4.21",
|
|
53
|
+
"jest": "^30.2.0",
|
|
54
|
+
"jest-environment-jsdom": "^30.2.0",
|
|
55
|
+
"postcss": "^8.5.6",
|
|
56
|
+
"tailwindcss": "^4.1.11",
|
|
57
|
+
"typescript": "^5.9.3"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Script from "next/script";
|
|
2
|
+
import type { WithContext } from "schema-dts";
|
|
3
|
+
import type { SchemaType, SchemaTypeMap } from "@/lib/microdata";
|
|
4
|
+
|
|
5
|
+
interface MicrodataScriptProps {
|
|
6
|
+
id: string;
|
|
7
|
+
microdata: WithContext<SchemaTypeMap[SchemaType]>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MicrodataScript({ id, microdata }: MicrodataScriptProps) {
|
|
11
|
+
return (
|
|
12
|
+
<Script
|
|
13
|
+
id={id}
|
|
14
|
+
type="application/ld+json"
|
|
15
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(microdata) }}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
"skipLibCheck": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"module": "esnext",
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"incremental": true,
|
|
15
|
+
"plugins": [
|
|
16
|
+
{
|
|
17
|
+
"name": "next"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["./*"],
|
|
22
|
+
"@/components/*": ["./components/*"],
|
|
23
|
+
"@/hooks/*": ["./hooks/*"],
|
|
24
|
+
"@/lib/*": ["./lib/*"],
|
|
25
|
+
"@/public/*": ["./public/*"],
|
|
26
|
+
"@/providers/*": ["./providers/*"]
|
|
27
|
+
},
|
|
28
|
+
"target": "ES2017"
|
|
29
|
+
},
|
|
30
|
+
"include": [
|
|
31
|
+
"next-env.d.ts",
|
|
32
|
+
"**/*.ts",
|
|
33
|
+
"**/*.tsx",
|
|
34
|
+
".next/types/**/*.ts",
|
|
35
|
+
".next/dev/types/**/*.ts"
|
|
36
|
+
],
|
|
37
|
+
"exclude": ["node_modules"]
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { nextCookies } from "better-auth/next-js";
|
|
3
|
+
|
|
4
|
+
export const auth = betterAuth({
|
|
5
|
+
emailAndPassword: {
|
|
6
|
+
enabled: true,
|
|
7
|
+
},
|
|
8
|
+
// Make sure nextCookies() is the last plugin in the array
|
|
9
|
+
plugins: [nextCookies()],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type Session = typeof auth.$Infer.Session;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { betterAuth } from "better-auth";
|
|
3
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
4
|
+
import { nextCookies } from "better-auth/next-js";
|
|
5
|
+
|
|
6
|
+
/* Here imports have errors because they are related to the final directory structure */
|
|
7
|
+
import { db } from "@/lib/db";
|
|
8
|
+
|
|
9
|
+
if (!process.env.BETTER_AUTH_GITHUB_CLIENT_ID || !process.env.BETTER_AUTH_GITHUB_CLIENT_SECRET) {
|
|
10
|
+
throw new Error("BETTER_AUTH_GITHUB_CLIENT_ID and BETTER_AUTH_GITHUB_CLIENT_SECRET must be set");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const auth = betterAuth({
|
|
14
|
+
database: drizzleAdapter(db, {
|
|
15
|
+
provider: "pg",
|
|
16
|
+
}),
|
|
17
|
+
emailAndPassword: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
},
|
|
20
|
+
socialProviders: {
|
|
21
|
+
github: {
|
|
22
|
+
clientId: process.env.BETTER_AUTH_GITHUB_CLIENT_ID,
|
|
23
|
+
clientSecret: process.env.BETTER_AUTH_GITHUB_CLIENT_SECRET,
|
|
24
|
+
redirectURI: "http://localhost:3000/api/auth/callback/github",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
// Make sure nextCookies() is the last plugin in the array
|
|
28
|
+
plugins: [nextCookies()],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export type Session = typeof auth.$Infer.Session;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { GoogleAnalytics } from "@next/third-parties/google";
|
|
3
|
+
import { barrio, dmSans } from "@/lib/fonts";
|
|
4
|
+
import { allowIndexing } from "@/lib/indexing";
|
|
5
|
+
import { microdata } from "@/lib/microdata";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
import { MicrodataScript } from "@/providers/MicrodataScript";
|
|
8
|
+
import {
|
|
9
|
+
ClerkProvider,
|
|
10
|
+
SignInButton,
|
|
11
|
+
SignUpButton,
|
|
12
|
+
SignedIn,
|
|
13
|
+
SignedOut,
|
|
14
|
+
UserButton,
|
|
15
|
+
} from '@clerk/nextjs'
|
|
16
|
+
import "./globals.css";
|
|
17
|
+
|
|
18
|
+
const homePageMicrodata = microdata("WebSite", {
|
|
19
|
+
name: "next-ts",
|
|
20
|
+
url: process.env.NEXT_PUBLIC_BASE_URL,
|
|
21
|
+
description:
|
|
22
|
+
"next-ts is a Production-Ready and Scalable Next.js Template Starter. Stop wasting time setting up your _next_ big project, with next-ts it's all ready to go!",
|
|
23
|
+
author: "next-ts",
|
|
24
|
+
publisher: "next-ts",
|
|
25
|
+
inLanguage: "en_US",
|
|
26
|
+
isAccessibleForFree: true,
|
|
27
|
+
image: `${process.env.NEXT_PUBLIC_BASE_URL}/public/myimage.png`,
|
|
28
|
+
mainEntityOfPage: {
|
|
29
|
+
"@type": "WebSite",
|
|
30
|
+
"@id": process.env.NEXT_PUBLIC_BASE_URL,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const metaData: Metadata = {
|
|
35
|
+
metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"),
|
|
36
|
+
title: {
|
|
37
|
+
default: "next-ts",
|
|
38
|
+
template: "next-ts - %s",
|
|
39
|
+
},
|
|
40
|
+
description:
|
|
41
|
+
"next-ts is a Production-Ready and Scalable Next.js Template Starter. Stop wasting time setting up your _next_ big project, with next-ts it's all ready to go!",
|
|
42
|
+
keywords: ["next-ts"],
|
|
43
|
+
authors: [{ name: "next-ts" }],
|
|
44
|
+
creator: "next-ts",
|
|
45
|
+
publisher: "next-ts",
|
|
46
|
+
formatDetection: {
|
|
47
|
+
email: false,
|
|
48
|
+
address: false,
|
|
49
|
+
telephone: false,
|
|
50
|
+
},
|
|
51
|
+
alternates: {
|
|
52
|
+
canonical: process.env.NEXT_PUBLIC_BASE_URL,
|
|
53
|
+
},
|
|
54
|
+
...allowIndexing(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
58
|
+
return (
|
|
59
|
+
<ClerkProvider>
|
|
60
|
+
<html lang="en">
|
|
61
|
+
<body className={cn("bg-background", barrio.variable, dmSans.variable)}>
|
|
62
|
+
<header className="flex fixed top-0 left-0 right-0 justify-between items-center py-8 max-w-4xl mx-auto gap-4 h-16">
|
|
63
|
+
<div className="flex items-center gap-4">
|
|
64
|
+
My SaaS App
|
|
65
|
+
</div>
|
|
66
|
+
<div className="flex items-center gap-4">
|
|
67
|
+
<SignedOut>
|
|
68
|
+
<SignInButton />
|
|
69
|
+
<SignUpButton>
|
|
70
|
+
<button className="bg-[#6c47ff] text-white rounded-full font-medium text-sm px-4 py-2 cursor-pointer">
|
|
71
|
+
Sign Up
|
|
72
|
+
</button>
|
|
73
|
+
</SignUpButton>
|
|
74
|
+
</SignedOut>
|
|
75
|
+
<SignedIn>
|
|
76
|
+
<UserButton />
|
|
77
|
+
</SignedIn>
|
|
78
|
+
</div>
|
|
79
|
+
</header>
|
|
80
|
+
{children}
|
|
81
|
+
<MicrodataScript id="home-microdata" microdata={homePageMicrodata} />
|
|
82
|
+
{process.env.GOOGLE_ANALYTICS_TAG && (
|
|
83
|
+
<GoogleAnalytics gaId={process.env.GOOGLE_ANALYTICS_TAG} />
|
|
84
|
+
)}
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|
|
87
|
+
</ClerkProvider>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const protectedRoutes = createRouteMatcher([
|
|
5
|
+
// Add your protected routes here, example: /dashboard
|
|
6
|
+
])
|
|
7
|
+
|
|
8
|
+
export default clerkMiddleware(async (auth,req) =>{
|
|
9
|
+
if(protectedRoutes(req)){
|
|
10
|
+
await auth.protect()
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const config = {
|
|
15
|
+
matcher: [
|
|
16
|
+
// Skip Next.js internals and all static files, unless found in search params
|
|
17
|
+
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
|
18
|
+
// Always run for API routes
|
|
19
|
+
'/(api|trpc)(.*)',
|
|
20
|
+
],
|
|
21
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# Docker Ignore File
|
|
3
|
+
# ============================================
|
|
4
|
+
# Excludes files from the Docker build context
|
|
5
|
+
# for faster builds and smaller images
|
|
6
|
+
# ============================================
|
|
7
|
+
|
|
8
|
+
# Dependencies
|
|
9
|
+
node_modules
|
|
10
|
+
.pnp
|
|
11
|
+
.pnp.js
|
|
12
|
+
|
|
13
|
+
# Build outputs
|
|
14
|
+
.next
|
|
15
|
+
out
|
|
16
|
+
build
|
|
17
|
+
dist
|
|
18
|
+
|
|
19
|
+
# Testing
|
|
20
|
+
coverage
|
|
21
|
+
.nyc_output
|
|
22
|
+
|
|
23
|
+
# Debug logs
|
|
24
|
+
npm-debug.log*
|
|
25
|
+
yarn-debug.log*
|
|
26
|
+
yarn-error.log*
|
|
27
|
+
.pnpm-debug.log*
|
|
28
|
+
|
|
29
|
+
# Environment files (add to context only what you need)
|
|
30
|
+
.env
|
|
31
|
+
.env.*
|
|
32
|
+
!.env.example
|
|
33
|
+
|
|
34
|
+
# IDE & Editor
|
|
35
|
+
.idea
|
|
36
|
+
.vscode
|
|
37
|
+
*.swp
|
|
38
|
+
*.swo
|
|
39
|
+
.DS_Store
|
|
40
|
+
|
|
41
|
+
# Git
|
|
42
|
+
.git
|
|
43
|
+
.gitignore
|
|
44
|
+
|
|
45
|
+
# Docker
|
|
46
|
+
Dockerfile*
|
|
47
|
+
docker-compose*
|
|
48
|
+
.dockerignore
|
|
49
|
+
|
|
50
|
+
# Documentation
|
|
51
|
+
README.md
|
|
52
|
+
LICENSE
|
|
53
|
+
CHANGELOG.md
|
|
54
|
+
docs/
|
|
55
|
+
|
|
56
|
+
# Misc
|
|
57
|
+
*.md
|
|
58
|
+
.husky
|
|
59
|
+
.github
|
|
60
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 1. Base Image
|
|
2
|
+
FROM node:22-alpine AS base
|
|
3
|
+
|
|
4
|
+
# 2. Dependencies Stage
|
|
5
|
+
FROM base AS deps
|
|
6
|
+
RUN apk add --no-cache libc6-compat
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
COPY package.json package-lock.json* ./
|
|
10
|
+
RUN npm ci
|
|
11
|
+
|
|
12
|
+
# 3. Builder Stage
|
|
13
|
+
FROM base AS builder
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
16
|
+
COPY . .
|
|
17
|
+
|
|
18
|
+
ARG NEXT_PUBLIC_BASE_URL
|
|
19
|
+
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
|
20
|
+
|
|
21
|
+
# Disabilita la telemetria durante la build
|
|
22
|
+
ENV NEXT_TELEMETRY_DISABLED 1
|
|
23
|
+
|
|
24
|
+
RUN npm run build
|
|
25
|
+
|
|
26
|
+
# 4. Runner Stage (Production)
|
|
27
|
+
FROM base AS runner
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
|
|
30
|
+
ENV NODE_ENV production
|
|
31
|
+
ENV NEXT_TELEMETRY_DISABLED 1
|
|
32
|
+
|
|
33
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
34
|
+
RUN adduser --system --uid 1001 nextjs
|
|
35
|
+
|
|
36
|
+
COPY --from=builder /app/public ./public
|
|
37
|
+
|
|
38
|
+
RUN mkdir .next
|
|
39
|
+
RUN chown nextjs:nodejs .next
|
|
40
|
+
|
|
41
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
42
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
43
|
+
|
|
44
|
+
USER nextjs
|
|
45
|
+
|
|
46
|
+
EXPOSE 3000
|
|
47
|
+
|
|
48
|
+
ENV PORT 3000
|
|
49
|
+
ENV HOSTNAME "0.0.0.0"
|
|
50
|
+
|
|
51
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# Next.js SEO Starter - Docker Compose (Production)
|
|
3
|
+
# ============================================
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# Build & Start: docker compose -f docker-compose.prod.yml up --build -d
|
|
7
|
+
# Stop: docker compose -f docker-compose.prod.yml down
|
|
8
|
+
#
|
|
9
|
+
# ============================================
|
|
10
|
+
|
|
11
|
+
services:
|
|
12
|
+
app:
|
|
13
|
+
build:
|
|
14
|
+
context: .
|
|
15
|
+
target: runner
|
|
16
|
+
args:
|
|
17
|
+
NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL}
|
|
18
|
+
container_name: nextjs-seo-web
|
|
19
|
+
volumes: [] # No volume mounts in production
|
|
20
|
+
environment:
|
|
21
|
+
- NODE_ENV=production
|
|
22
|
+
- NEXT_TELEMETRY_DISABLED=1
|
|
23
|
+
env_file:
|
|
24
|
+
- path: .env
|
|
25
|
+
restart: always
|
|
26
|
+
# Resource limits (adjust based on your needs)
|
|
27
|
+
deploy:
|
|
28
|
+
resources:
|
|
29
|
+
limits:
|
|
30
|
+
cpus: "1"
|
|
31
|
+
memory: 512M
|
|
32
|
+
reservations:
|
|
33
|
+
cpus: "0.25"
|
|
34
|
+
memory: 256M
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
|
|
2
|
+
export const usersTable = pgTable("users", {
|
|
3
|
+
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
|
4
|
+
name: varchar({ length: 255 }).notNull(),
|
|
5
|
+
age: integer().notNull(),
|
|
6
|
+
email: varchar({ length: 255 }).notNull().unique(),
|
|
7
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { defineConfig } from "drizzle-kit";
|
|
3
|
+
|
|
4
|
+
if (!process.env.DATABASE_URL) {
|
|
5
|
+
throw new Error("DATABASE_URL is not set");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
out: "./drizzle",
|
|
10
|
+
schema: "./lib/db/schema.ts",
|
|
11
|
+
dialect: "postgresql",
|
|
12
|
+
dbCredentials: {
|
|
13
|
+
url: process.env.DATABASE_URL,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* Drizzle configuration for Neon */
|
|
2
|
+
import { drizzle } from "drizzle-orm/neon-http";
|
|
3
|
+
import { neon } from "@neondatabase/serverless";
|
|
4
|
+
|
|
5
|
+
if (!process.env.DATABASE_URL) {
|
|
6
|
+
throw new Error("DATABASE_URL is not set");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const sql = neon(process.env.DATABASE_URL);
|
|
10
|
+
export const db = drizzle({ client: sql });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@custom-variant dark (&:is(.dark *));
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
--radius: 0.625rem;
|
|
6
|
+
--background: oklch(1 0 0);
|
|
7
|
+
--foreground: oklch(0.145 0 0);
|
|
8
|
+
--card: oklch(1 0 0);
|
|
9
|
+
--card-foreground: oklch(0.145 0 0);
|
|
10
|
+
--popover: oklch(1 0 0);
|
|
11
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
12
|
+
--primary: oklch(0.205 0 0);
|
|
13
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
14
|
+
--secondary: oklch(0.97 0 0);
|
|
15
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
16
|
+
--muted: oklch(0.97 0 0);
|
|
17
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
18
|
+
--accent: oklch(0.97 0 0);
|
|
19
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
20
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
21
|
+
--border: oklch(0.922 0 0);
|
|
22
|
+
--input: oklch(0.922 0 0);
|
|
23
|
+
--ring: oklch(0.708 0 0);
|
|
24
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
25
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
26
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
27
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
28
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
29
|
+
--sidebar: oklch(0.985 0 0);
|
|
30
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
31
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
32
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
33
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
34
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
35
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
36
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dark {
|
|
40
|
+
--background: oklch(0.145 0 0);
|
|
41
|
+
--foreground: oklch(0.985 0 0);
|
|
42
|
+
--card: oklch(0.205 0 0);
|
|
43
|
+
--card-foreground: oklch(0.985 0 0);
|
|
44
|
+
--popover: oklch(0.205 0 0);
|
|
45
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
46
|
+
--primary: oklch(0.922 0 0);
|
|
47
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
48
|
+
--secondary: oklch(0.269 0 0);
|
|
49
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
50
|
+
--muted: oklch(0.269 0 0);
|
|
51
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
52
|
+
--accent: oklch(0.269 0 0);
|
|
53
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
54
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
55
|
+
--border: oklch(1 0 0 / 10%);
|
|
56
|
+
--input: oklch(1 0 0 / 15%);
|
|
57
|
+
--ring: oklch(0.556 0 0);
|
|
58
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
59
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
60
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
61
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
62
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
63
|
+
--sidebar: oklch(0.205 0 0);
|
|
64
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
65
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
66
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
67
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
68
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
69
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
70
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
71
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
/* Here imports are set up for the final project structure, so it's fine */
|
|
3
|
+
import { stripe } from "@/lib/stripe";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const TRIAL_PERIOD_DAYS = 3;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export async function GET(req: Request) {
|
|
10
|
+
try {
|
|
11
|
+
|
|
12
|
+
// Usually you would check for authentication first
|
|
13
|
+
const fakeUser = {
|
|
14
|
+
id: "123",
|
|
15
|
+
email: "test@test.com",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const url = new URL(req.url);
|
|
19
|
+
const priceId = url.searchParams.get("priceId");
|
|
20
|
+
|
|
21
|
+
if (!priceId) {
|
|
22
|
+
return new NextResponse("Price ID is required", { status: 400 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Set subscription data with trial period, remove if not wanted
|
|
26
|
+
const subscriptionData: {
|
|
27
|
+
trial_period_days?: number;
|
|
28
|
+
} = {
|
|
29
|
+
trial_period_days: TRIAL_PERIOD_DAYS,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Create Stripe checkout session
|
|
33
|
+
const checkoutSession = await stripe().checkout.sessions.create({
|
|
34
|
+
mode: "subscription",
|
|
35
|
+
payment_method_types: ["card", "paypal"],
|
|
36
|
+
line_items: [
|
|
37
|
+
{
|
|
38
|
+
price: priceId,
|
|
39
|
+
quantity: 1,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
subscription_data: subscriptionData,
|
|
43
|
+
// Metadata will be used to identify the user in the webhook
|
|
44
|
+
metadata: {
|
|
45
|
+
userId: fakeUser.id,
|
|
46
|
+
},
|
|
47
|
+
customer_email: fakeUser.email,
|
|
48
|
+
success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
49
|
+
cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/`,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!checkoutSession.url) {
|
|
53
|
+
return new NextResponse("Checkout session URL is missing", { status: 500 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return NextResponse.json({ url: checkoutSession.url });
|
|
57
|
+
} catch (_) {
|
|
58
|
+
return new NextResponse("Internal Error", { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
|
|
3
|
+
let stripeInstance: Stripe | null = null;
|
|
4
|
+
|
|
5
|
+
const getStripe = () => {
|
|
6
|
+
if (!stripeInstance) {
|
|
7
|
+
if (!process.env.STRIPE_SECRET_KEY) {
|
|
8
|
+
throw new Error("STRIPE_SECRET_KEY is not set");
|
|
9
|
+
}
|
|
10
|
+
stripeInstance = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
|
11
|
+
typescript: true,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return stripeInstance;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const stripe = getStripe;
|