veryfront 0.0.35 → 0.0.37
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/dist/ai/index.js +365 -179
- package/dist/ai/index.js.map +4 -4
- package/dist/ai/react.js +1 -3
- package/dist/ai/react.js.map +2 -2
- package/dist/cli.js +359 -2304
- package/dist/components.js +4 -2
- package/dist/components.js.map +2 -2
- package/dist/config.js +4 -2
- package/dist/config.js.map +2 -2
- package/dist/data.js +4 -2
- package/dist/data.js.map +2 -2
- package/dist/index.js +7 -2
- package/dist/index.js.map +2 -2
- package/dist/templates/ai/ai/agents/assistant.ts +20 -0
- package/dist/templates/ai/ai/prompts/assistant.ts +14 -0
- package/dist/templates/ai/ai/tools/get-weather.ts +29 -0
- package/dist/templates/ai/app/api/chat/route.ts +37 -0
- package/dist/templates/ai/app/layout.tsx +18 -0
- package/dist/templates/ai/app/page.tsx +28 -0
- package/dist/templates/ai/tsconfig.json +18 -0
- package/dist/templates/ai/veryfront.config.ts +9 -0
- package/dist/templates/app/_env.example +16 -0
- package/dist/templates/app/app/api/auth/login/route.ts +53 -0
- package/dist/templates/app/app/api/auth/logout/route.ts +27 -0
- package/dist/templates/app/app/api/auth/me/route.ts +34 -0
- package/dist/templates/app/app/api/auth/register/route.ts +42 -0
- package/dist/templates/app/app/api/stats/route.ts +21 -0
- package/dist/templates/app/app/api/users/route.ts +42 -0
- package/dist/templates/app/app/dashboard/page.tsx +29 -0
- package/dist/templates/app/app/layout.tsx +45 -0
- package/dist/templates/app/app/login/page.tsx +222 -0
- package/dist/templates/app/app/page.tsx +15 -0
- package/dist/templates/app/components/AuthProvider.tsx +51 -0
- package/dist/templates/app/components/DashboardLayout.tsx +142 -0
- package/dist/templates/app/components/FeatureGrid.tsx +98 -0
- package/dist/templates/app/components/Header.tsx +58 -0
- package/dist/templates/app/components/HeroSection.tsx +49 -0
- package/dist/templates/app/components/RecentActivity.tsx +98 -0
- package/dist/templates/app/components/StatsGrid.tsx +126 -0
- package/dist/templates/app/components/Toaster.tsx +113 -0
- package/dist/templates/app/lib/auth-client.ts +38 -0
- package/dist/templates/app/lib/auth.ts +49 -0
- package/dist/templates/app/lib/stats.ts +34 -0
- package/dist/templates/app/lib/users.ts +86 -0
- package/dist/templates/app/middleware/auth.ts +34 -0
- package/dist/templates/app/public/robots.txt +4 -0
- package/dist/templates/app/veryfront.config.js +74 -0
- package/dist/templates/blog/app/about/page.mdx +14 -0
- package/dist/templates/blog/app/archive/page.tsx +42 -0
- package/dist/templates/blog/app/blog/[slug]/page.tsx +47 -0
- package/dist/templates/blog/app/layout.tsx +54 -0
- package/dist/templates/blog/app/page.tsx +13 -0
- package/dist/templates/blog/components/BlogPostList.tsx +53 -0
- package/dist/templates/blog/components/MDXContent.tsx +26 -0
- package/dist/templates/blog/content/posts/hello-world.mdx +29 -0
- package/dist/templates/blog/content/posts/markdown-showcase.mdx +105 -0
- package/dist/templates/blog/lib/posts.ts +76 -0
- package/dist/templates/blog/lib/utils.ts +14 -0
- package/dist/templates/blog/public/robots.txt +4 -0
- package/dist/templates/blog/styles/globals.css +21 -0
- package/dist/templates/blog/veryfront.config.js +39 -0
- package/dist/templates/docs/app/docs/api/page.mdx +102 -0
- package/dist/templates/docs/app/docs/core-concepts/page.mdx +82 -0
- package/dist/templates/docs/app/docs/getting-started/page.mdx +67 -0
- package/dist/templates/docs/app/layout.tsx +41 -0
- package/dist/templates/docs/app/page.mdx +51 -0
- package/dist/templates/docs/components/CodeBlock.tsx +44 -0
- package/dist/templates/docs/components/Header.tsx +49 -0
- package/dist/templates/docs/components/Sidebar.tsx +68 -0
- package/dist/templates/docs/public/robots.txt +4 -0
- package/dist/templates/docs/styles/globals.css +48 -0
- package/dist/templates/docs/veryfront.config.js +47 -0
- package/dist/templates/minimal/app/about/page.mdx +18 -0
- package/dist/templates/minimal/app/layout.tsx +20 -0
- package/dist/templates/minimal/app/page.tsx +26 -0
- package/dist/templates/minimal/veryfront.config.js +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { agent, promptRegistry } from "veryfront/ai";
|
|
2
|
+
|
|
3
|
+
function getSystemPrompt(): string {
|
|
4
|
+
const prompt = promptRegistry.get("assistant");
|
|
5
|
+
if (prompt) {
|
|
6
|
+
const content = prompt.getContent();
|
|
7
|
+
return typeof content === "string" ? content : "";
|
|
8
|
+
}
|
|
9
|
+
return "You are a helpful AI assistant.";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default agent({
|
|
13
|
+
id: "assistant",
|
|
14
|
+
model: "openai/gpt-4o",
|
|
15
|
+
system: getSystemPrompt,
|
|
16
|
+
tools: {
|
|
17
|
+
getWeather: true,
|
|
18
|
+
},
|
|
19
|
+
maxSteps: 10,
|
|
20
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { prompt } from "veryfront/ai";
|
|
2
|
+
|
|
3
|
+
export default prompt({
|
|
4
|
+
id: "assistant",
|
|
5
|
+
description: "System prompt for the AI assistant",
|
|
6
|
+
content: `You are a helpful AI assistant with access to weather information.
|
|
7
|
+
|
|
8
|
+
When users ask about the weather:
|
|
9
|
+
1. Use the getWeather tool to fetch current conditions
|
|
10
|
+
2. Provide a friendly summary of the weather
|
|
11
|
+
3. Suggest appropriate activities based on conditions
|
|
12
|
+
|
|
13
|
+
Be conversational and helpful. If you don't know something, say so honestly.`,
|
|
14
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { tool } from "veryfront/ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export default tool({
|
|
5
|
+
description: "Get the current weather for a location",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
location: z.string().describe("The city and state, e.g. San Francisco, CA"),
|
|
8
|
+
}),
|
|
9
|
+
execute: ({ location }: { location: string }) => {
|
|
10
|
+
const mockWeather: Record<string, { temp: number; condition: string }> = {
|
|
11
|
+
"San Francisco, CA": { temp: 65, condition: "Foggy" },
|
|
12
|
+
"New York, NY": { temp: 75, condition: "Sunny" },
|
|
13
|
+
"London, UK": { temp: 60, condition: "Rainy" },
|
|
14
|
+
"Tokyo, Japan": { temp: 80, condition: "Humid" },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const weather = mockWeather[location] || {
|
|
18
|
+
temp: 70,
|
|
19
|
+
condition: "Clear",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
location,
|
|
24
|
+
temperature: weather.temp,
|
|
25
|
+
condition: weather.condition,
|
|
26
|
+
unit: "fahrenheit",
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAgent } from "veryfront/ai";
|
|
3
|
+
|
|
4
|
+
const messageSchema = z.object({
|
|
5
|
+
role: z.enum(["user", "assistant", "system"]),
|
|
6
|
+
content: z.string().max(10000),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const chatRequestSchema = z.object({
|
|
10
|
+
messages: z.array(messageSchema).min(1).max(100),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export async function POST(request: Request) {
|
|
14
|
+
try {
|
|
15
|
+
const body = await request.json();
|
|
16
|
+
const { messages } = chatRequestSchema.parse(body);
|
|
17
|
+
|
|
18
|
+
const agent = getAgent("assistant");
|
|
19
|
+
if (!agent) {
|
|
20
|
+
return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = await agent.stream({ messages });
|
|
24
|
+
return result.toDataStreamResponse();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error instanceof z.ZodError) {
|
|
27
|
+
return Response.json(
|
|
28
|
+
{ error: "Invalid request", details: error.errors },
|
|
29
|
+
{ status: 400 }
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return Response.json(
|
|
33
|
+
{ error: "Internal server error" },
|
|
34
|
+
{ status: 500 }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default function RootLayout({
|
|
2
|
+
children,
|
|
3
|
+
}: {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}) {
|
|
6
|
+
return (
|
|
7
|
+
<html lang="en" className="h-full">
|
|
8
|
+
<head>
|
|
9
|
+
<title>Veryfront AI Starter</title>
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
11
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
12
|
+
</head>
|
|
13
|
+
<body className="h-full bg-slate-50 dark:bg-slate-900">
|
|
14
|
+
{children}
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Chat } from 'veryfront/ai/components'
|
|
4
|
+
import { useChat } from 'veryfront/ai/react'
|
|
5
|
+
|
|
6
|
+
export default function ChatPage() {
|
|
7
|
+
const chat = useChat({ api: '/api/chat' })
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex flex-col h-screen bg-slate-50 dark:bg-slate-900">
|
|
11
|
+
<header className="flex-shrink-0 border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900">
|
|
12
|
+
<div className="max-w-4xl mx-auto px-4 py-3 flex items-center gap-3">
|
|
13
|
+
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-500 to-purple-600 flex items-center justify-center shadow-lg">
|
|
14
|
+
<svg className="w-5 h-5 text-white" viewBox="0 0 24 24" fill="currentColor">
|
|
15
|
+
<path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z" />
|
|
16
|
+
</svg>
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
<h1 className="font-semibold text-slate-800 dark:text-white">Veryfront AI</h1>
|
|
20
|
+
<p className="text-xs text-slate-500 dark:text-slate-400">Powered by Agents & Tools</p>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<Chat {...chat} className="flex-1" placeholder="Ask me anything..." />
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"lib": ["DOM", "DOM.Iterable", "ES2020"]
|
|
15
|
+
},
|
|
16
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
17
|
+
"exclude": ["node_modules"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Environment variables
|
|
2
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
|
|
3
|
+
REDIS_URL=redis://localhost:6379
|
|
4
|
+
JWT_SECRET=your-secret-key-here
|
|
5
|
+
|
|
6
|
+
# OAuth providers
|
|
7
|
+
GITHUB_CLIENT_ID=
|
|
8
|
+
GITHUB_CLIENT_SECRET=
|
|
9
|
+
GOOGLE_CLIENT_ID=
|
|
10
|
+
GOOGLE_CLIENT_SECRET=
|
|
11
|
+
|
|
12
|
+
# Email service
|
|
13
|
+
SMTP_HOST=smtp.example.com
|
|
14
|
+
SMTP_PORT=587
|
|
15
|
+
SMTP_USER=
|
|
16
|
+
SMTP_PASS=
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getEnv } from "veryfront/platform";
|
|
3
|
+
import { createSession } from "../../../../lib/auth.ts";
|
|
4
|
+
import { validatePassword } from "../../../../lib/users.ts";
|
|
5
|
+
|
|
6
|
+
const loginSchema = z.object({
|
|
7
|
+
email: z.string().email(),
|
|
8
|
+
password: z.string().min(8),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function POST(request: Request) {
|
|
12
|
+
try {
|
|
13
|
+
const body = await request.json();
|
|
14
|
+
const { email, password } = loginSchema.parse(body);
|
|
15
|
+
|
|
16
|
+
// Validate credentials (replace with real DB lookup)
|
|
17
|
+
const user = await validatePassword(email, password);
|
|
18
|
+
if (!user) {
|
|
19
|
+
return Response.json(
|
|
20
|
+
{ error: "Invalid credentials" },
|
|
21
|
+
{ status: 401 }
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create session
|
|
26
|
+
const session = await createSession(user);
|
|
27
|
+
|
|
28
|
+
// In production, ensure Secure flag is set for HTTPS-only transmission
|
|
29
|
+
const isProduction = getEnv("NODE_ENV") === "production";
|
|
30
|
+
const secureFlag = isProduction ? "; Secure" : "";
|
|
31
|
+
|
|
32
|
+
return Response.json(
|
|
33
|
+
{ user, token: session.token },
|
|
34
|
+
{
|
|
35
|
+
headers: {
|
|
36
|
+
"Set-Cookie": `session=${session.token}; Path=/; HttpOnly; SameSite=Strict${secureFlag}`,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error instanceof z.ZodError) {
|
|
42
|
+
return Response.json(
|
|
43
|
+
{ error: "Invalid input", details: error.errors },
|
|
44
|
+
{ status: 400 }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Response.json(
|
|
49
|
+
{ error: "Internal server error" },
|
|
50
|
+
{ status: 500 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { deleteSession } from "../../../../lib/auth.ts";
|
|
2
|
+
|
|
3
|
+
export async function POST(request: Request) {
|
|
4
|
+
try {
|
|
5
|
+
const cookieHeader = request.headers.get("cookie");
|
|
6
|
+
const sessionToken = cookieHeader
|
|
7
|
+
?.split(";")
|
|
8
|
+
.find((c) => c.trim().startsWith("session="))
|
|
9
|
+
?.split("=")[1];
|
|
10
|
+
|
|
11
|
+
if (sessionToken) {
|
|
12
|
+
await deleteSession(sessionToken);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return Response.json(
|
|
16
|
+
{ success: true },
|
|
17
|
+
{
|
|
18
|
+
headers: {
|
|
19
|
+
"Set-Cookie":
|
|
20
|
+
"session=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0",
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
} catch {
|
|
25
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { verifySession, getSession } from "../../../../lib/auth.ts";
|
|
2
|
+
import { getUser } from "../../../../lib/users.ts";
|
|
3
|
+
|
|
4
|
+
export async function GET(request: Request) {
|
|
5
|
+
try {
|
|
6
|
+
const cookieHeader = request.headers.get("cookie");
|
|
7
|
+
if (!cookieHeader) {
|
|
8
|
+
return Response.json({ error: "Not authenticated" }, { status: 401 });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const sessionToken = cookieHeader
|
|
12
|
+
.split(";")
|
|
13
|
+
.find((c) => c.trim().startsWith("session="))
|
|
14
|
+
?.split("=")[1];
|
|
15
|
+
|
|
16
|
+
if (!sessionToken) {
|
|
17
|
+
return Response.json({ error: "Not authenticated" }, { status: 401 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const session = await verifySession(sessionToken);
|
|
21
|
+
if (!session) {
|
|
22
|
+
return Response.json({ error: "Invalid session" }, { status: 401 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const user = await getUser(session.userId);
|
|
26
|
+
if (!user) {
|
|
27
|
+
return Response.json({ error: "User not found" }, { status: 404 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Response.json({ user });
|
|
31
|
+
} catch {
|
|
32
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createUser } from "../../../../lib/users.ts";
|
|
3
|
+
import { createSession } from "../../../../lib/auth.ts";
|
|
4
|
+
import { getEnv } from "veryfront/platform";
|
|
5
|
+
|
|
6
|
+
const registerSchema = z.object({
|
|
7
|
+
email: z.string().email(),
|
|
8
|
+
name: z.string().min(2).max(100),
|
|
9
|
+
password: z.string().min(8).max(100),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export async function POST(request: Request) {
|
|
13
|
+
try {
|
|
14
|
+
const body = await request.json();
|
|
15
|
+
const { email, name, password } = registerSchema.parse(body);
|
|
16
|
+
|
|
17
|
+
const user = await createUser({ email, name, password });
|
|
18
|
+
const session = await createSession(user as any);
|
|
19
|
+
|
|
20
|
+
const isProduction = getEnv("NODE_ENV") === "production";
|
|
21
|
+
const secureFlag = isProduction ? "; Secure" : "";
|
|
22
|
+
|
|
23
|
+
return Response.json(
|
|
24
|
+
{ user, token: session.token },
|
|
25
|
+
{
|
|
26
|
+
status: 201,
|
|
27
|
+
headers: {
|
|
28
|
+
"Set-Cookie": `session=${session.token}; Path=/; HttpOnly; SameSite=Strict${secureFlag}`,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error instanceof z.ZodError) {
|
|
34
|
+
return Response.json(
|
|
35
|
+
{ error: "Invalid input", details: error.errors },
|
|
36
|
+
{ status: 400 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { requireAuth } from "../../../middleware/auth.ts";
|
|
2
|
+
import { getStats } from "../../../lib/stats.ts";
|
|
3
|
+
|
|
4
|
+
export async function GET(request: Request) {
|
|
5
|
+
const auth = await requireAuth(request);
|
|
6
|
+
if (!auth.ok) return auth.response;
|
|
7
|
+
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
const userId = url.searchParams.get("userId");
|
|
10
|
+
|
|
11
|
+
if (!userId) {
|
|
12
|
+
return Response.json(
|
|
13
|
+
{ error: "userId parameter required" },
|
|
14
|
+
{ status: 400 }
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const stats = await getStats(userId);
|
|
19
|
+
|
|
20
|
+
return Response.json({ stats });
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requireAuth } from "../../../middleware/auth.ts";
|
|
3
|
+
import { getUsers, createUser } from "../../../lib/users.ts";
|
|
4
|
+
|
|
5
|
+
const userSchema = z.object({
|
|
6
|
+
name: z.string().min(1),
|
|
7
|
+
email: z.string().email(),
|
|
8
|
+
role: z.enum(["user", "admin"]).default("user"),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export async function GET(request: Request) {
|
|
12
|
+
const auth = await requireAuth(request);
|
|
13
|
+
if (!auth.ok) return auth.response;
|
|
14
|
+
|
|
15
|
+
const users = await getUsers();
|
|
16
|
+
return Response.json({ users });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function POST(request: Request) {
|
|
20
|
+
const auth = await requireAuth(request);
|
|
21
|
+
if (!auth.ok) return auth.response;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const body = await request.json();
|
|
25
|
+
const data = userSchema.parse(body);
|
|
26
|
+
|
|
27
|
+
const user = await createUser(data);
|
|
28
|
+
return Response.json({ user }, { status: 201 });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof z.ZodError) {
|
|
31
|
+
return Response.json(
|
|
32
|
+
{ error: "Invalid input", details: error.errors },
|
|
33
|
+
{ status: 400 }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return Response.json(
|
|
38
|
+
{ error: "Internal server error" },
|
|
39
|
+
{ status: 500 }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { redirect } from "veryfront";
|
|
2
|
+
import { getSession } from "../../lib/auth.ts";
|
|
3
|
+
import { DashboardLayout } from "../../components/DashboardLayout.tsx";
|
|
4
|
+
import { StatsGrid } from "../../components/StatsGrid.tsx";
|
|
5
|
+
import { RecentActivity } from "../../components/RecentActivity.tsx";
|
|
6
|
+
|
|
7
|
+
export default async function DashboardPage() {
|
|
8
|
+
const session = await getSession();
|
|
9
|
+
|
|
10
|
+
if (!session) {
|
|
11
|
+
redirect("/login");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<DashboardLayout>
|
|
16
|
+
<div className="space-y-8">
|
|
17
|
+
<div className="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-sm border border-slate-200 dark:border-slate-700">
|
|
18
|
+
<h1 className="text-3xl font-bold text-slate-900 dark:text-white">Dashboard</h1>
|
|
19
|
+
<p className="text-slate-600 dark:text-slate-400 mt-2">
|
|
20
|
+
Welcome back, <span className="font-semibold text-indigo-600 dark:text-indigo-400">{session.user.name}</span>! Here's what's happening with your projects.
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<StatsGrid userId={session.user.id} />
|
|
25
|
+
<RecentActivity userId={session.user.id} />
|
|
26
|
+
</div>
|
|
27
|
+
</DashboardLayout>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { AuthProvider } from "../components/AuthProvider.tsx";
|
|
3
|
+
import { Toaster } from "../components/Toaster.tsx";
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: "My App",
|
|
7
|
+
description: "A full-stack app built with Veryfront",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({
|
|
11
|
+
children
|
|
12
|
+
}: {
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
|
+
<link
|
|
20
|
+
rel="stylesheet"
|
|
21
|
+
href="https://cdn.jsdelivr.net/npm/tailwindcss@3/dist/tailwind.min.css"
|
|
22
|
+
/>
|
|
23
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
24
|
+
<style>{`
|
|
25
|
+
body { font-family: 'Inter', sans-serif; }
|
|
26
|
+
.animate-blob { animation: blob 7s infinite; }
|
|
27
|
+
.animation-delay-2000 { animation-delay: 2s; }
|
|
28
|
+
.animation-delay-4000 { animation-delay: 4s; }
|
|
29
|
+
@keyframes blob {
|
|
30
|
+
0% { transform: translate(0px, 0px) scale(1); }
|
|
31
|
+
33% { transform: translate(30px, -50px) scale(1.1); }
|
|
32
|
+
66% { transform: translate(-20px, 20px) scale(0.9); }
|
|
33
|
+
100% { transform: translate(0px, 0px) scale(1); }
|
|
34
|
+
}
|
|
35
|
+
`}</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body className="bg-slate-50 text-slate-900 antialiased">
|
|
38
|
+
<AuthProvider>
|
|
39
|
+
{children}
|
|
40
|
+
<Toaster />
|
|
41
|
+
</AuthProvider>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
44
|
+
);
|
|
45
|
+
}
|