@asynx/create-asynx-next-app 1.0.4 → 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/create-asynx-next-app-wrapper/index.js +3 -0
- package/create-asynx-next-app-wrapper/package.json +12 -0
- package/package.json +1 -1
- package/templates/advanced/.env.example +23 -0
- package/templates/advanced/README.md +148 -0
- package/templates/advanced/next.config.mjs +14 -0
- package/templates/advanced/package.json +1 -1
- 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 -8
- package/templates/advanced/src/app/layout.tsx +19 -18
- package/templates/advanced/src/app/page.tsx +25 -8
- 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 +14 -11
- package/templates/standard/README.md +53 -0
- package/templates/standard/package.json +1 -1
- 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 +151 -1
- package/templates/standard/src/app/layout.tsx +21 -1
- package/templates/standard/src/app/page.tsx +22 -3
- package/templates/standard/src/lib/api.ts +37 -0
- package/templates/standard/src/lib/constants.ts +14 -0
- package/templates/standard/tsconfig.json +12 -11
- package/templates/starter/package.json +1 -1
- package/templates/starter/src/app/globals.css +1 -0
- package/templates/advanced/next-env.d.ts +0 -4
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
}
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
3
|
+
"target": "ES2020",
|
|
4
4
|
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
-
"allowJs":
|
|
5
|
+
"allowJs": true,
|
|
6
6
|
"skipLibCheck": true,
|
|
7
7
|
"strict": true,
|
|
8
8
|
"noEmit": true,
|
|
@@ -11,16 +11,19 @@
|
|
|
11
11
|
"moduleResolution": "bundler",
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
13
|
"isolatedModules": true,
|
|
14
|
-
"jsx": "
|
|
14
|
+
"jsx": "preserve",
|
|
15
15
|
"incremental": true,
|
|
16
|
-
"plugins": [
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"],
|
|
23
|
+
"@/features/*": ["./src/features/*"],
|
|
24
|
+
"@/lib/*": ["./src/lib/*"]
|
|
25
|
+
}
|
|
17
26
|
},
|
|
18
|
-
"include": [
|
|
19
|
-
"next-env.d.ts",
|
|
20
|
-
"**/*.ts",
|
|
21
|
-
"**/*.tsx",
|
|
22
|
-
".next/types/**/*.ts",
|
|
23
|
-
".next/dev/types/**/*.ts"
|
|
24
|
-
],
|
|
27
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
25
28
|
"exclude": ["node_modules"]
|
|
26
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
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
|
2
|
+
|
|
3
|
+
export default function DashboardPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="container mx-auto p-6">
|
|
6
|
+
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
|
|
7
|
+
|
|
8
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
9
|
+
<Card>
|
|
10
|
+
<CardHeader>
|
|
11
|
+
<CardTitle>Total Users</CardTitle>
|
|
12
|
+
</CardHeader>
|
|
13
|
+
<CardContent>
|
|
14
|
+
<p className="text-3xl font-bold">1,234</p>
|
|
15
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
16
|
+
+12% from last month
|
|
17
|
+
</p>
|
|
18
|
+
</CardContent>
|
|
19
|
+
</Card>
|
|
20
|
+
|
|
21
|
+
<Card>
|
|
22
|
+
<CardHeader>
|
|
23
|
+
<CardTitle>Revenue</CardTitle>
|
|
24
|
+
</CardHeader>
|
|
25
|
+
<CardContent>
|
|
26
|
+
<p className="text-3xl font-bold">$45,231</p>
|
|
27
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
28
|
+
+8% from last month
|
|
29
|
+
</p>
|
|
30
|
+
</CardContent>
|
|
31
|
+
</Card>
|
|
32
|
+
|
|
33
|
+
<Card>
|
|
34
|
+
<CardHeader>
|
|
35
|
+
<CardTitle>Active Sessions</CardTitle>
|
|
36
|
+
</CardHeader>
|
|
37
|
+
<CardContent>
|
|
38
|
+
<p className="text-3xl font-bold">573</p>
|
|
39
|
+
<p className="text-sm text-muted-foreground mt-2">Live now</p>
|
|
40
|
+
</CardContent>
|
|
41
|
+
</Card>
|
|
42
|
+
</div>
|
|
43
|
+
</main>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function DashboardLayout({
|
|
4
|
+
children,
|
|
5
|
+
}: {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-screen flex">
|
|
10
|
+
{/* Sidebar placeholder */}
|
|
11
|
+
<aside className="w-64 border-r p-6">
|
|
12
|
+
<h2 className="text-lg font-bold mb-6">Menu</h2>
|
|
13
|
+
<nav className="space-y-2">
|
|
14
|
+
<a href="/dashboard" className="block py-2 hover:underline">
|
|
15
|
+
Dashboard
|
|
16
|
+
</a>
|
|
17
|
+
<a href="/dashboard/users" className="block py-2 hover:underline">
|
|
18
|
+
Users
|
|
19
|
+
</a>
|
|
20
|
+
<a href="/dashboard/settings" className="block py-2 hover:underline">
|
|
21
|
+
Settings
|
|
22
|
+
</a>
|
|
23
|
+
</nav>
|
|
24
|
+
</aside>
|
|
25
|
+
|
|
26
|
+
{/* Main content */}
|
|
27
|
+
<div className="flex-1">{children}</div>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
// Public layout - for unauthenticated pages like landing, login, signup
|
|
3
|
+
// Nested layouts allow different shells for different route groups
|
|
4
|
+
|
|
5
|
+
export default function PublicLayout({
|
|
6
|
+
children,
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="min-h-screen">
|
|
12
|
+
{/* Public pages don't need the full header/footer */}
|
|
13
|
+
{children}
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default function LoginPage() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="min-h-screen flex items-center justify-center p-6">
|
|
4
|
+
<div className="w-full max-w-md border rounded-lg p-6">
|
|
5
|
+
<h1 className="text-2xl font-bold mb-6">Sign In</h1>
|
|
6
|
+
<form className="space-y-4">
|
|
7
|
+
<div>
|
|
8
|
+
<label className="block text-sm font-medium mb-2">Email</label>
|
|
9
|
+
<input
|
|
10
|
+
type="email"
|
|
11
|
+
className="w-full px-3 py-2 border rounded-md"
|
|
12
|
+
placeholder="you@example.com"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<div>
|
|
16
|
+
<label className="block text-sm font-medium mb-2">Password</label>
|
|
17
|
+
<input
|
|
18
|
+
type="password"
|
|
19
|
+
className="w-full px-3 py-2 border rounded-md"
|
|
20
|
+
placeholder="••••••••"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
<button
|
|
24
|
+
type="submit"
|
|
25
|
+
className="w-full bg-foreground text-background py-2 rounded-md hover:opacity-90"
|
|
26
|
+
>
|
|
27
|
+
Sign In
|
|
28
|
+
</button>
|
|
29
|
+
</form>
|
|
30
|
+
</div>
|
|
31
|
+
</main>
|
|
32
|
+
);
|
|
33
|
+
}
|