@asynx/create-asynx-next-app 1.0.3 → 1.0.6
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/CONTRIBUTING.md +16 -15
- package/README.md +3 -3
- package/create-asynx-next-app-wrapper/index.js +3 -0
- package/create-asynx-next-app-wrapper/package.json +12 -0
- package/dist/cli.js +6 -6
- package/dist/constants/template.js +5 -5
- package/package.json +2 -2
- package/src/cli.ts +6 -6
- package/src/constants/template.ts +6 -6
- package/templates/advanced/.env.example +23 -0
- package/templates/advanced/README.md +148 -0
- package/templates/advanced/next.config.mjs +14 -0
- package/templates/{simple → advanced}/package.json +4 -0
- package/templates/advanced/postcss.config.mjs +6 -0
- package/templates/advanced/src/app/app/billing/page.tsx +57 -0
- package/templates/advanced/src/app/app/layout.tsx +25 -0
- package/templates/advanced/src/app/app/page.tsx +36 -0
- package/templates/advanced/src/app/app/settings/page.tsx +50 -0
- package/templates/advanced/src/app/auth/login/page.tsx +41 -0
- package/templates/advanced/src/app/auth/signup/page.tsx +45 -0
- package/templates/advanced/src/app/globals.css +151 -0
- package/templates/advanced/src/app/layout.tsx +19 -0
- package/templates/advanced/src/app/page.tsx +25 -0
- package/templates/advanced/src/lib/api/client.ts +67 -0
- package/templates/advanced/src/lib/api/config/app.ts +19 -0
- package/templates/advanced/src/lib/api/config/constants.ts +38 -0
- package/templates/advanced/src/lib/api/features/analytics/lib/analytics-service.ts +26 -0
- package/templates/advanced/src/lib/api/features/app-shell/components/app-header.tsx +18 -0
- package/templates/advanced/src/lib/api/features/app-shell/components/app-sidebar.tsx +38 -0
- package/templates/advanced/src/lib/api/features/auth/lib/auth-service.ts +36 -0
- package/templates/advanced/src/lib/api/features/billing/lib/billing-service.ts +38 -0
- package/templates/advanced/src/types/index.ts +21 -0
- package/templates/advanced/tsconfig.json +29 -0
- package/templates/standard/README.md +53 -0
- package/templates/{moderate → standard}/package.json +4 -0
- package/templates/standard/postcss.config.mjs +6 -0
- package/templates/standard/src/app/(dashboard)/dashboard/page.tsx +45 -0
- package/templates/standard/src/app/(dashboard)/layout.tsx +30 -0
- package/templates/standard/src/app/(public)/layout.tsx +16 -0
- package/templates/standard/src/app/(public)/login/page.tsx +33 -0
- package/templates/standard/src/app/globals.css +158 -0
- package/templates/standard/src/app/layout.tsx +38 -0
- package/templates/standard/src/app/page.tsx +27 -0
- package/templates/standard/src/lib/api.ts +37 -0
- package/templates/standard/src/lib/constants.ts +14 -0
- package/templates/{simple → standard}/tsconfig.json +12 -5
- package/templates/{saas → starter}/package.json +4 -0
- package/templates/{saas → starter}/src/app/globals.css +1 -0
- package/templates/{moderate → starter}/tsconfig.json +10 -4
- package/templates/moderate/src/app/globals.css +0 -8
- package/templates/saas/src/app/layout.tsx +0 -18
- package/templates/saas/src/app/page.tsx +0 -8
- package/templates/saas/tsconfig.json +0 -20
- package/templates/simple/next-env.d.ts +0 -4
- package/templates/simple/src/app/globals.css +0 -8
- package/templates/simple/src/app/layout.tsx +0 -18
- package/templates/simple/src/app/page.tsx +0 -8
- /package/templates/{moderate → advanced}/utils/async.ts +0 -0
- /package/templates/{moderate → advanced}/utils/date.ts +0 -0
- /package/templates/{moderate → advanced}/utils/index.ts +0 -0
- /package/templates/{moderate → advanced}/utils/number.ts +0 -0
- /package/templates/{moderate → advanced}/utils/string.ts +0 -0
- /package/templates/{moderate → standard}/next-env.d.ts +0 -0
- /package/templates/{saas → standard}/utils/async.ts +0 -0
- /package/templates/{saas → standard}/utils/date.ts +0 -0
- /package/templates/{saas → standard}/utils/index.ts +0 -0
- /package/templates/{saas → standard}/utils/number.ts +0 -0
- /package/templates/{saas → standard}/utils/string.ts +0 -0
- /package/templates/{saas → starter}/next-env.d.ts +0 -0
- /package/templates/{moderate → starter}/src/app/layout.tsx +0 -0
- /package/templates/{moderate → starter}/src/app/page.tsx +0 -0
- /package/templates/{simple → starter}/utils/async.ts +0 -0
- /package/templates/{simple → starter}/utils/date.ts +0 -0
- /package/templates/{simple → starter}/utils/index.ts +0 -0
- /package/templates/{simple → starter}/utils/number.ts +0 -0
- /package/templates/{simple → starter}/utils/string.ts +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Auth feature - signup page
|
|
2
|
+
|
|
3
|
+
export default function SignupPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="min-h-screen flex items-center justify-center p-6">
|
|
6
|
+
<div className="w-full max-w-md">
|
|
7
|
+
<h1 className="text-3xl font-bold mb-2 text-center">Create Account</h1>
|
|
8
|
+
<p className="text-muted-foreground text-center mb-8">Get started with your free account</p>
|
|
9
|
+
|
|
10
|
+
<div className="border rounded-lg p-8">
|
|
11
|
+
<form className="space-y-6">
|
|
12
|
+
<div>
|
|
13
|
+
<label className="block text-sm font-medium mb-2">Full Name</label>
|
|
14
|
+
<input type="text" className="w-full px-4 py-2 border rounded-md" placeholder="John Doe" />
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div>
|
|
18
|
+
<label className="block text-sm font-medium mb-2">Email Address</label>
|
|
19
|
+
<input type="email" className="w-full px-4 py-2 border rounded-md" placeholder="you@example.com" />
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div>
|
|
23
|
+
<label className="block text-sm font-medium mb-2">Password</label>
|
|
24
|
+
<input type="password" className="w-full px-4 py-2 border rounded-md" placeholder="••••••••" />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<button
|
|
28
|
+
type="submit"
|
|
29
|
+
className="w-full bg-foreground text-background py-3 rounded-md hover:opacity-90 font-medium"
|
|
30
|
+
>
|
|
31
|
+
Create Account
|
|
32
|
+
</button>
|
|
33
|
+
</form>
|
|
34
|
+
|
|
35
|
+
<p className="text-center text-sm text-muted-foreground mt-6">
|
|
36
|
+
Already have an account?{" "}
|
|
37
|
+
<a href="/auth/login" className="underline">
|
|
38
|
+
Sign in
|
|
39
|
+
</a>
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</main>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 0 0% 3.9%;
|
|
9
|
+
--card: oklch(1 0 0);
|
|
10
|
+
--card-foreground: oklch(0.145 0 0);
|
|
11
|
+
--popover: oklch(1 0 0);
|
|
12
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
13
|
+
--primary: 221.2 83.2% 53.3%;
|
|
14
|
+
--primary-foreground: 0 0% 100%;
|
|
15
|
+
--secondary: oklch(0.97 0 0);
|
|
16
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
17
|
+
--muted: 0 0% 96.1%;
|
|
18
|
+
--muted-foreground: 0 0% 45.1%;
|
|
19
|
+
--accent: oklch(0.97 0 0);
|
|
20
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
21
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
22
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
23
|
+
--border: 0 0% 89.8%;
|
|
24
|
+
--input: oklch(0.922 0 0);
|
|
25
|
+
--ring: oklch(0.708 0 0);
|
|
26
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
27
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
28
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
29
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
30
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
31
|
+
--radius: 0.5rem;
|
|
32
|
+
--sidebar: oklch(0.985 0 0);
|
|
33
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
34
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
35
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
36
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
37
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
38
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
39
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dark {
|
|
43
|
+
--background: 0 0% 3.9%;
|
|
44
|
+
--foreground: 0 0% 98%;
|
|
45
|
+
--card: oklch(0.145 0 0);
|
|
46
|
+
--card-foreground: oklch(0.985 0 0);
|
|
47
|
+
--popover: oklch(0.145 0 0);
|
|
48
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
49
|
+
--primary: 217.2 91.2% 59.8%;
|
|
50
|
+
--primary-foreground: 0 0% 100%;
|
|
51
|
+
--secondary: oklch(0.269 0 0);
|
|
52
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
53
|
+
--muted: 0 0% 14.9%;
|
|
54
|
+
--muted-foreground: 0 0% 63.9%;
|
|
55
|
+
--accent: oklch(0.269 0 0);
|
|
56
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
57
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
58
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
59
|
+
--border: 0 0% 14.9%;
|
|
60
|
+
--input: oklch(0.269 0 0);
|
|
61
|
+
--ring: oklch(0.439 0 0);
|
|
62
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
63
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
64
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
65
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
66
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
67
|
+
--sidebar: oklch(0.205 0 0);
|
|
68
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
69
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
70
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
71
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
72
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
73
|
+
--sidebar-border: oklch(0.269 0 0);
|
|
74
|
+
--sidebar-ring: oklch(0.439 0 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@theme inline {
|
|
78
|
+
/* optional: --font-sans, --font-serif, --font-mono if they are applied in the layout.tsx */
|
|
79
|
+
--color-background: var(--background);
|
|
80
|
+
--color-foreground: var(--foreground);
|
|
81
|
+
--color-card: var(--card);
|
|
82
|
+
--color-card-foreground: var(--card-foreground);
|
|
83
|
+
--color-popover: var(--popover);
|
|
84
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
85
|
+
--color-primary: var(--primary);
|
|
86
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
87
|
+
--color-secondary: var(--secondary);
|
|
88
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
89
|
+
--color-muted: var(--muted);
|
|
90
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
91
|
+
--color-accent: var(--accent);
|
|
92
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
93
|
+
--color-destructive: var(--destructive);
|
|
94
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
95
|
+
--color-border: var(--border);
|
|
96
|
+
--color-input: var(--input);
|
|
97
|
+
--color-ring: var(--ring);
|
|
98
|
+
--color-chart-1: var(--chart-1);
|
|
99
|
+
--color-chart-2: var(--chart-2);
|
|
100
|
+
--color-chart-3: var(--chart-3);
|
|
101
|
+
--color-chart-4: var(--chart-4);
|
|
102
|
+
--color-chart-5: var(--chart-5);
|
|
103
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
104
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
105
|
+
--radius-lg: var(--radius);
|
|
106
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
107
|
+
--color-sidebar: var(--sidebar);
|
|
108
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
109
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
110
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
111
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
112
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
113
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
114
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@layer base {
|
|
118
|
+
* {
|
|
119
|
+
@apply border-border outline-ring/50;
|
|
120
|
+
}
|
|
121
|
+
body {
|
|
122
|
+
@apply bg-background text-foreground;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
* {
|
|
127
|
+
box-sizing: border-box;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
body {
|
|
131
|
+
margin: 0;
|
|
132
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
133
|
+
-webkit-font-smoothing: antialiased;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.container {
|
|
137
|
+
width: 100%;
|
|
138
|
+
max-width: 1280px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.text-muted-foreground {
|
|
142
|
+
color: hsl(var(--muted-foreground));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.border {
|
|
146
|
+
border: 1px solid hsl(var(--border));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.bg-muted {
|
|
150
|
+
background: hsl(var(--muted));
|
|
151
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import "./globals.css"
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
title: "Asynx SaaS",
|
|
6
|
+
description: "Production-ready SaaS template",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function RootLayout({
|
|
10
|
+
children,
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<body>{children}</body>
|
|
17
|
+
</html>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Landing page - public facing
|
|
2
|
+
// In advanced apps, this is often separate from the app shell
|
|
3
|
+
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
|
|
6
|
+
export default function LandingPage() {
|
|
7
|
+
return (
|
|
8
|
+
<main className="min-h-screen">
|
|
9
|
+
<section className="container mx-auto px-6 py-20 text-center">
|
|
10
|
+
<h1 className="text-5xl font-bold mb-6">Welcome to Your SaaS Platform</h1>
|
|
11
|
+
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
|
|
12
|
+
Built with a scalable, production-ready architecture
|
|
13
|
+
</p>
|
|
14
|
+
<div className="flex gap-4 justify-center">
|
|
15
|
+
<Link href="/app" className="px-6 py-3 bg-foreground text-background rounded-md hover:opacity-90">
|
|
16
|
+
Get Started
|
|
17
|
+
</Link>
|
|
18
|
+
<Link href="/auth/login" className="px-6 py-3 border rounded-md hover:bg-muted">
|
|
19
|
+
Sign In
|
|
20
|
+
</Link>
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
</main>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Shared infrastructure - API client
|
|
2
|
+
// Centralized HTTP client for all features
|
|
3
|
+
|
|
4
|
+
export class ApiError extends Error {
|
|
5
|
+
constructor(message: string, public status: number, public data?: unknown) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'ApiError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ApiClient {
|
|
12
|
+
private baseUrl: string;
|
|
13
|
+
|
|
14
|
+
constructor(baseUrl = '/api') {
|
|
15
|
+
this.baseUrl = baseUrl;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private async request<T>(
|
|
19
|
+
endpoint: string,
|
|
20
|
+
options?: RequestInit,
|
|
21
|
+
): Promise<T> {
|
|
22
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
23
|
+
|
|
24
|
+
const response = await fetch(url, {
|
|
25
|
+
...options,
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
...options?.headers,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const error = await response.json().catch(() => ({}));
|
|
34
|
+
throw new ApiError(
|
|
35
|
+
error.message || 'Request failed',
|
|
36
|
+
response.status,
|
|
37
|
+
error,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return response.json();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async get<T>(endpoint: string): Promise<T> {
|
|
45
|
+
return this.request<T>(endpoint, { method: 'GET' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async post<T>(endpoint: string, data?: unknown): Promise<T> {
|
|
49
|
+
return this.request<T>(endpoint, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async put<T>(endpoint: string, data?: unknown): Promise<T> {
|
|
56
|
+
return this.request<T>(endpoint, {
|
|
57
|
+
method: 'PUT',
|
|
58
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async delete<T>(endpoint: string): Promise<T> {
|
|
63
|
+
return this.request<T>(endpoint, { method: 'DELETE' });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const apiClient = new ApiClient();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Shared infrastructure - App configuration
|
|
2
|
+
// Environment-aware configuration
|
|
3
|
+
|
|
4
|
+
export const appConfig = {
|
|
5
|
+
name: 'Your SaaS',
|
|
6
|
+
description: 'Production-ready SaaS template',
|
|
7
|
+
url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
8
|
+
api: {
|
|
9
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL || '/api',
|
|
10
|
+
},
|
|
11
|
+
features: {
|
|
12
|
+
auth: true,
|
|
13
|
+
billing: true,
|
|
14
|
+
analytics: true,
|
|
15
|
+
},
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export const isProduction = process.env.NODE_ENV === 'production';
|
|
19
|
+
export const isDevelopment = process.env.NODE_ENV === 'development';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Shared infrastructure - Constants
|
|
2
|
+
// Application-wide constants
|
|
3
|
+
|
|
4
|
+
export const ROUTES = {
|
|
5
|
+
// Public routes
|
|
6
|
+
HOME: '/',
|
|
7
|
+
LOGIN: '/auth/login',
|
|
8
|
+
SIGNUP: '/auth/signup',
|
|
9
|
+
|
|
10
|
+
// App routes
|
|
11
|
+
APP: '/app',
|
|
12
|
+
DASHBOARD: '/app',
|
|
13
|
+
BILLING: '/app/billing',
|
|
14
|
+
SETTINGS: '/app/settings',
|
|
15
|
+
ANALYTICS: '/app/analytics',
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export const API_ROUTES = {
|
|
19
|
+
AUTH_LOGIN: '/api/auth/login',
|
|
20
|
+
AUTH_SIGNUP: '/api/auth/signup',
|
|
21
|
+
AUTH_LOGOUT: '/api/auth/logout',
|
|
22
|
+
USER_PROFILE: '/api/user/profile',
|
|
23
|
+
BILLING_SUBSCRIPTION: '/api/billing/subscription',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export const SUBSCRIPTION_PLANS = {
|
|
27
|
+
FREE: { name: 'Free', price: 0, features: ['Basic features'] },
|
|
28
|
+
PRO: {
|
|
29
|
+
name: 'Pro',
|
|
30
|
+
price: 49,
|
|
31
|
+
features: ['All features', 'Priority support'],
|
|
32
|
+
},
|
|
33
|
+
ENTERPRISE: {
|
|
34
|
+
name: 'Enterprise',
|
|
35
|
+
price: 199,
|
|
36
|
+
features: ['Custom everything'],
|
|
37
|
+
},
|
|
38
|
+
} as const;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Analytics feature - Service layer
|
|
2
|
+
// Track events and metrics
|
|
3
|
+
|
|
4
|
+
export type AnalyticsEvent = {
|
|
5
|
+
name: string
|
|
6
|
+
properties?: Record<string, unknown>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class AnalyticsService {
|
|
10
|
+
track(event: AnalyticsEvent) {
|
|
11
|
+
// TODO: Send to analytics provider
|
|
12
|
+
console.log("Analytics event:", event)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
identify(userId: string, traits?: Record<string, unknown>) {
|
|
16
|
+
// TODO: Identify user
|
|
17
|
+
console.log("Identify user:", userId, traits)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
page(name: string) {
|
|
21
|
+
// TODO: Track page view
|
|
22
|
+
console.log("Page view:", name)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const analytics = new AnalyticsService()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// App shell feature - Header component
|
|
2
|
+
|
|
3
|
+
export function AppHeader() {
|
|
4
|
+
return (
|
|
5
|
+
<header className="border-b px-6 py-4">
|
|
6
|
+
<div className="flex items-center justify-between">
|
|
7
|
+
<div>
|
|
8
|
+
<input type="search" placeholder="Search..." className="px-4 py-2 border rounded-md w-80" />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div className="flex items-center gap-4">
|
|
12
|
+
<button className="p-2 hover:bg-muted rounded-md">🔔</button>
|
|
13
|
+
<button className="p-2 hover:bg-muted rounded-md">👤</button>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</header>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// App shell feature - Sidebar component
|
|
2
|
+
// This is part of the core app navigation infrastructure
|
|
3
|
+
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
|
|
6
|
+
export function AppSidebar() {
|
|
7
|
+
const navigation = [
|
|
8
|
+
{ name: "Dashboard", href: "/app", icon: "📊" },
|
|
9
|
+
{ name: "Analytics", href: "/app/analytics", icon: "📈" },
|
|
10
|
+
{ name: "Billing", href: "/app/billing", icon: "💳" },
|
|
11
|
+
{ name: "Settings", href: "/app/settings", icon: "⚙️" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<aside className="w-64 border-r flex flex-col">
|
|
16
|
+
<div className="p-6 border-b">
|
|
17
|
+
<h1 className="text-xl font-bold">Your SaaS</h1>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<nav className="flex-1 p-4">
|
|
21
|
+
<ul className="space-y-2">
|
|
22
|
+
{navigation.map((item) => (
|
|
23
|
+
<li key={item.name}>
|
|
24
|
+
<Link href={item.href} className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-muted">
|
|
25
|
+
<span>{item.icon}</span>
|
|
26
|
+
<span>{item.name}</span>
|
|
27
|
+
</Link>
|
|
28
|
+
</li>
|
|
29
|
+
))}
|
|
30
|
+
</ul>
|
|
31
|
+
</nav>
|
|
32
|
+
|
|
33
|
+
<div className="p-4 border-t">
|
|
34
|
+
<button className="w-full px-3 py-2 text-left hover:bg-muted rounded-md">Sign Out</button>
|
|
35
|
+
</div>
|
|
36
|
+
</aside>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Auth feature - Service layer
|
|
2
|
+
// Business logic for authentication
|
|
3
|
+
|
|
4
|
+
export class AuthService {
|
|
5
|
+
async login(email: string, password: string) {
|
|
6
|
+
// TODO: Implement actual authentication
|
|
7
|
+
// This is a placeholder structure
|
|
8
|
+
console.log("Login attempt:", email)
|
|
9
|
+
|
|
10
|
+
// Example: call your auth API
|
|
11
|
+
// const response = await fetch('/api/auth/login', {
|
|
12
|
+
// method: 'POST',
|
|
13
|
+
// body: JSON.stringify({ email, password })
|
|
14
|
+
// });
|
|
15
|
+
|
|
16
|
+
return { success: true }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async signup(email: string, password: string, name: string) {
|
|
20
|
+
// TODO: Implement signup
|
|
21
|
+
console.log("Signup attempt:", email, name)
|
|
22
|
+
return { success: true }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async logout() {
|
|
26
|
+
// TODO: Implement logout
|
|
27
|
+
console.log("Logout")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getCurrentUser() {
|
|
31
|
+
// TODO: Get current user session
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const authService = new AuthService()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Billing feature - Service layer
|
|
2
|
+
// Business logic for subscription and payments
|
|
3
|
+
|
|
4
|
+
export type SubscriptionPlan = "free" | "pro" | "enterprise"
|
|
5
|
+
|
|
6
|
+
export interface Subscription {
|
|
7
|
+
plan: SubscriptionPlan
|
|
8
|
+
status: "active" | "canceled" | "past_due"
|
|
9
|
+
currentPeriodEnd: Date
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class BillingService {
|
|
13
|
+
async getSubscription(): Promise<Subscription | null> {
|
|
14
|
+
// TODO: Fetch from your billing provider (Stripe, etc.)
|
|
15
|
+
return {
|
|
16
|
+
plan: "pro",
|
|
17
|
+
status: "active",
|
|
18
|
+
currentPeriodEnd: new Date("2024-01-01"),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async updateSubscription(plan: SubscriptionPlan) {
|
|
23
|
+
// TODO: Update subscription via API
|
|
24
|
+
console.log("Updating to plan:", plan)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async cancelSubscription() {
|
|
28
|
+
// TODO: Cancel subscription
|
|
29
|
+
console.log("Canceling subscription")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getInvoices() {
|
|
33
|
+
// TODO: Fetch invoice history
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const billingService = new BillingService()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type User = {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
role: 'user' | 'admin';
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ApiResponse<T> = {
|
|
10
|
+
data: T;
|
|
11
|
+
message?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PaginatedResponse<T> = {
|
|
15
|
+
data: T[];
|
|
16
|
+
pagination: {
|
|
17
|
+
page: number;
|
|
18
|
+
limit: number;
|
|
19
|
+
total: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"],
|
|
23
|
+
"@/features/*": ["./src/features/*"],
|
|
24
|
+
"@/lib/*": ["./src/lib/*"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
28
|
+
"exclude": ["node_modules"]
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Standard Template
|
|
2
|
+
|
|
3
|
+
This template is designed for **dashboards, content platforms, and early-stage startups**.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── app/
|
|
10
|
+
│ ├── (public)/ # Public routes (landing, login, signup)
|
|
11
|
+
│ │ ├── login/
|
|
12
|
+
│ │ └── layout.tsx
|
|
13
|
+
│ ├── (dashboard)/ # Authenticated routes
|
|
14
|
+
│ │ ├── dashboard/
|
|
15
|
+
│ │ └── layout.tsx
|
|
16
|
+
│ ├── layout.tsx # Root layout
|
|
17
|
+
│ ├── page.tsx # Homepage
|
|
18
|
+
│ └── globals.css
|
|
19
|
+
├── components/
|
|
20
|
+
│ ├── ui/ # Reusable UI components
|
|
21
|
+
│ │ ├── button.tsx
|
|
22
|
+
│ │ └── card.tsx
|
|
23
|
+
│ └── layout/ # Layout components
|
|
24
|
+
│ ├── header.tsx
|
|
25
|
+
│ └── footer.tsx
|
|
26
|
+
├── lib/
|
|
27
|
+
│ ├── api.ts # API client utilities
|
|
28
|
+
│ └── constants.ts # App constants
|
|
29
|
+
└── utils/ # Utility functions
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Key Features
|
|
33
|
+
|
|
34
|
+
- **Route Groups**: Separate public and authenticated layouts
|
|
35
|
+
- **Component Library**: Basic UI components to get started
|
|
36
|
+
- **API Client**: Centralized API utilities
|
|
37
|
+
- **Layout Hierarchy**: Header, footer, and sidebar examples
|
|
38
|
+
- **TypeScript**: Full type safety
|
|
39
|
+
|
|
40
|
+
## Getting Started
|
|
41
|
+
|
|
42
|
+
1. Customize the layouts in \`app/(public)\` and \`app/(dashboard)\`
|
|
43
|
+
2. Add your UI components in \`components/ui\`
|
|
44
|
+
3. Configure API endpoints in \`lib/constants.ts\`
|
|
45
|
+
4. Build your features!
|
|
46
|
+
|
|
47
|
+
## Auth-Ready Structure
|
|
48
|
+
|
|
49
|
+
This template is structured to easily integrate authentication:
|
|
50
|
+
- Public routes for unauthenticated users
|
|
51
|
+
- Dashboard routes for authenticated users
|
|
52
|
+
- Add middleware for route protection when ready
|
|
53
|
+
```
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
"react-dom": "latest"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
+
"tailwindcss": "^4.0.0",
|
|
17
|
+
"postcss": "^8.4.0",
|
|
18
|
+
"autoprefixer": "^10.4.0",
|
|
16
19
|
"typescript": "^5.0.0",
|
|
20
|
+
"@types/node": "^20.0.0",
|
|
17
21
|
"@types/react": "^18.0.0",
|
|
18
22
|
"@types/react-dom": "^18.0.0"
|
|
19
23
|
}
|