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.
Files changed (77) hide show
  1. package/dist/ai/index.js +365 -179
  2. package/dist/ai/index.js.map +4 -4
  3. package/dist/ai/react.js +1 -3
  4. package/dist/ai/react.js.map +2 -2
  5. package/dist/cli.js +359 -2304
  6. package/dist/components.js +4 -2
  7. package/dist/components.js.map +2 -2
  8. package/dist/config.js +4 -2
  9. package/dist/config.js.map +2 -2
  10. package/dist/data.js +4 -2
  11. package/dist/data.js.map +2 -2
  12. package/dist/index.js +7 -2
  13. package/dist/index.js.map +2 -2
  14. package/dist/templates/ai/ai/agents/assistant.ts +20 -0
  15. package/dist/templates/ai/ai/prompts/assistant.ts +14 -0
  16. package/dist/templates/ai/ai/tools/get-weather.ts +29 -0
  17. package/dist/templates/ai/app/api/chat/route.ts +37 -0
  18. package/dist/templates/ai/app/layout.tsx +18 -0
  19. package/dist/templates/ai/app/page.tsx +28 -0
  20. package/dist/templates/ai/tsconfig.json +18 -0
  21. package/dist/templates/ai/veryfront.config.ts +9 -0
  22. package/dist/templates/app/_env.example +16 -0
  23. package/dist/templates/app/app/api/auth/login/route.ts +53 -0
  24. package/dist/templates/app/app/api/auth/logout/route.ts +27 -0
  25. package/dist/templates/app/app/api/auth/me/route.ts +34 -0
  26. package/dist/templates/app/app/api/auth/register/route.ts +42 -0
  27. package/dist/templates/app/app/api/stats/route.ts +21 -0
  28. package/dist/templates/app/app/api/users/route.ts +42 -0
  29. package/dist/templates/app/app/dashboard/page.tsx +29 -0
  30. package/dist/templates/app/app/layout.tsx +45 -0
  31. package/dist/templates/app/app/login/page.tsx +222 -0
  32. package/dist/templates/app/app/page.tsx +15 -0
  33. package/dist/templates/app/components/AuthProvider.tsx +51 -0
  34. package/dist/templates/app/components/DashboardLayout.tsx +142 -0
  35. package/dist/templates/app/components/FeatureGrid.tsx +98 -0
  36. package/dist/templates/app/components/Header.tsx +58 -0
  37. package/dist/templates/app/components/HeroSection.tsx +49 -0
  38. package/dist/templates/app/components/RecentActivity.tsx +98 -0
  39. package/dist/templates/app/components/StatsGrid.tsx +126 -0
  40. package/dist/templates/app/components/Toaster.tsx +113 -0
  41. package/dist/templates/app/lib/auth-client.ts +38 -0
  42. package/dist/templates/app/lib/auth.ts +49 -0
  43. package/dist/templates/app/lib/stats.ts +34 -0
  44. package/dist/templates/app/lib/users.ts +86 -0
  45. package/dist/templates/app/middleware/auth.ts +34 -0
  46. package/dist/templates/app/public/robots.txt +4 -0
  47. package/dist/templates/app/veryfront.config.js +74 -0
  48. package/dist/templates/blog/app/about/page.mdx +14 -0
  49. package/dist/templates/blog/app/archive/page.tsx +42 -0
  50. package/dist/templates/blog/app/blog/[slug]/page.tsx +47 -0
  51. package/dist/templates/blog/app/layout.tsx +54 -0
  52. package/dist/templates/blog/app/page.tsx +13 -0
  53. package/dist/templates/blog/components/BlogPostList.tsx +53 -0
  54. package/dist/templates/blog/components/MDXContent.tsx +26 -0
  55. package/dist/templates/blog/content/posts/hello-world.mdx +29 -0
  56. package/dist/templates/blog/content/posts/markdown-showcase.mdx +105 -0
  57. package/dist/templates/blog/lib/posts.ts +76 -0
  58. package/dist/templates/blog/lib/utils.ts +14 -0
  59. package/dist/templates/blog/public/robots.txt +4 -0
  60. package/dist/templates/blog/styles/globals.css +21 -0
  61. package/dist/templates/blog/veryfront.config.js +39 -0
  62. package/dist/templates/docs/app/docs/api/page.mdx +102 -0
  63. package/dist/templates/docs/app/docs/core-concepts/page.mdx +82 -0
  64. package/dist/templates/docs/app/docs/getting-started/page.mdx +67 -0
  65. package/dist/templates/docs/app/layout.tsx +41 -0
  66. package/dist/templates/docs/app/page.mdx +51 -0
  67. package/dist/templates/docs/components/CodeBlock.tsx +44 -0
  68. package/dist/templates/docs/components/Header.tsx +49 -0
  69. package/dist/templates/docs/components/Sidebar.tsx +68 -0
  70. package/dist/templates/docs/public/robots.txt +4 -0
  71. package/dist/templates/docs/styles/globals.css +48 -0
  72. package/dist/templates/docs/veryfront.config.js +47 -0
  73. package/dist/templates/minimal/app/about/page.mdx +18 -0
  74. package/dist/templates/minimal/app/layout.tsx +20 -0
  75. package/dist/templates/minimal/app/page.tsx +26 -0
  76. package/dist/templates/minimal/veryfront.config.js +29 -0
  77. package/package.json +1 -1
@@ -0,0 +1,98 @@
1
+ import { getRecentActivity } from "../lib/stats.ts";
2
+
3
+ // Activity Icons
4
+ function ClockIcon({ className }: { className?: string }) {
5
+ return (
6
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
7
+ <circle cx="12" cy="12" r="10"/>
8
+ <polyline points="12 6 12 12 16 14"/>
9
+ </svg>
10
+ );
11
+ }
12
+
13
+ function ActivityIcon({ className }: { className?: string }) {
14
+ return (
15
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
16
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
17
+ </svg>
18
+ );
19
+ }
20
+
21
+ // Activity type badge colors
22
+ function getActivityColor(type: string) {
23
+ const colors: Record<string, string> = {
24
+ login: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 border-blue-200 dark:border-blue-800",
25
+ purchase: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400 border-emerald-200 dark:border-emerald-800",
26
+ signup: "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400 border-violet-200 dark:border-violet-800",
27
+ update: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 border-amber-200 dark:border-amber-800",
28
+ default: "bg-slate-100 text-slate-700 dark:bg-slate-700 dark:text-slate-400 border-slate-200 dark:border-slate-600",
29
+ };
30
+ return colors[type.toLowerCase()] || colors.default;
31
+ }
32
+
33
+ export async function RecentActivity({ userId }: { userId: string }) {
34
+ const activities = await getRecentActivity(userId);
35
+
36
+ return (
37
+ <div className="bg-white/60 dark:bg-slate-800/60 backdrop-blur-lg rounded-2xl shadow-sm border border-slate-200/50 dark:border-slate-700/50 overflow-hidden">
38
+ {/* Header */}
39
+ <div className="p-6 border-b border-slate-200/50 dark:border-slate-700/50 flex items-center justify-between bg-white/50 dark:bg-slate-800/50">
40
+ <div className="flex items-center gap-3">
41
+ <div className="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center">
42
+ <ActivityIcon className="w-5 h-5 text-indigo-500" />
43
+ </div>
44
+ <div>
45
+ <h2 className="text-lg font-bold text-slate-900 dark:text-white">Recent Activity</h2>
46
+ <p className="text-sm text-slate-500 dark:text-slate-400">Latest actions from your users</p>
47
+ </div>
48
+ </div>
49
+ <a
50
+ href="/dashboard/activity"
51
+ className="px-4 py-2 rounded-lg text-sm font-medium text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 transition-colors"
52
+ >
53
+ View all
54
+ </a>
55
+ </div>
56
+
57
+ {/* Activity List */}
58
+ <div className="divide-y divide-slate-200/50 dark:divide-slate-700/50">
59
+ {activities.map((activity) => (
60
+ <div key={activity.id} className="p-6 hover:bg-white/50 dark:hover:bg-slate-700/30 transition-colors group">
61
+ <div className="flex items-start gap-4">
62
+ {/* Time indicator */}
63
+ <div className="flex-shrink-0 w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-700/50 flex items-center justify-center group-hover:bg-indigo-50 dark:group-hover:bg-indigo-900/20 transition-colors">
64
+ <ClockIcon className="w-5 h-5 text-slate-400 group-hover:text-indigo-500 transition-colors" />
65
+ </div>
66
+
67
+ {/* Content */}
68
+ <div className="flex-1 min-w-0">
69
+ <p className="text-sm font-medium text-slate-900 dark:text-white">
70
+ {activity.description}
71
+ </p>
72
+ <p className="text-xs text-slate-500 dark:text-slate-400 mt-1 flex items-center gap-1">
73
+ <ClockIcon className="w-3 h-3" />
74
+ {new Date(activity.timestamp).toLocaleString()}
75
+ </p>
76
+ </div>
77
+
78
+ {/* Type badge */}
79
+ <span className={`flex-shrink-0 px-3 py-1 rounded-full text-xs font-bold border ${getActivityColor(activity.type)}`}>
80
+ {activity.type}
81
+ </span>
82
+ </div>
83
+ </div>
84
+ ))}
85
+ </div>
86
+
87
+ {/* Empty state */}
88
+ {activities.length === 0 && (
89
+ <div className="p-12 text-center">
90
+ <div className="w-16 h-16 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center mx-auto mb-4">
91
+ <ActivityIcon className="w-8 h-8 text-slate-300 dark:text-slate-600" />
92
+ </div>
93
+ <p className="text-slate-500 dark:text-slate-400 font-medium">No recent activity</p>
94
+ </div>
95
+ )}
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,126 @@
1
+ import { getStats } from "../lib/stats.ts";
2
+
3
+ // Stat Icons
4
+ function UsersIcon({ className }: { className?: string }) {
5
+ return (
6
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
7
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
8
+ <circle cx="9" cy="7" r="4"/>
9
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
10
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
11
+ </svg>
12
+ );
13
+ }
14
+
15
+ function ActivityIcon({ className }: { className?: string }) {
16
+ return (
17
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
18
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
19
+ </svg>
20
+ );
21
+ }
22
+
23
+ function DollarIcon({ className }: { className?: string }) {
24
+ return (
25
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
26
+ <line x1="12" y1="1" x2="12" y2="23"/>
27
+ <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
28
+ </svg>
29
+ );
30
+ }
31
+
32
+ function TrendingUpIcon({ className }: { className?: string }) {
33
+ return (
34
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
35
+ <polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/>
36
+ <polyline points="17 6 23 6 23 12"/>
37
+ </svg>
38
+ );
39
+ }
40
+
41
+ function TrendingDownIcon({ className }: { className?: string }) {
42
+ return (
43
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
44
+ <polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/>
45
+ <polyline points="17 18 23 18 23 12"/>
46
+ </svg>
47
+ );
48
+ }
49
+
50
+ export async function StatsGrid({ userId }: { userId: string }) {
51
+ const stats = await getStats(userId);
52
+
53
+ const items = [
54
+ {
55
+ label: "Total Users",
56
+ value: stats.totalUsers.toLocaleString(),
57
+ change: "+12%",
58
+ trend: "up",
59
+ Icon: UsersIcon,
60
+ color: "from-blue-500 to-cyan-500",
61
+ bgColor: "bg-blue-500/10",
62
+ shadowColor: "shadow-blue-500/20",
63
+ },
64
+ {
65
+ label: "Active Today",
66
+ value: stats.activeToday.toLocaleString(),
67
+ change: "+5%",
68
+ trend: "up",
69
+ Icon: ActivityIcon,
70
+ color: "from-emerald-500 to-teal-500",
71
+ bgColor: "bg-emerald-500/10",
72
+ shadowColor: "shadow-emerald-500/20",
73
+ },
74
+ {
75
+ label: "Revenue",
76
+ value: "$" + stats.revenue.toLocaleString(),
77
+ change: "+8%",
78
+ trend: "up",
79
+ Icon: DollarIcon,
80
+ color: "from-violet-500 to-purple-500",
81
+ bgColor: "bg-violet-500/10",
82
+ shadowColor: "shadow-violet-500/20",
83
+ },
84
+ {
85
+ label: "Growth Rate",
86
+ value: stats.growth + "%",
87
+ change: "+2.3%",
88
+ trend: "up",
89
+ Icon: TrendingUpIcon,
90
+ color: "from-orange-500 to-amber-500",
91
+ bgColor: "bg-orange-500/10",
92
+ shadowColor: "shadow-orange-500/20",
93
+ },
94
+ ];
95
+
96
+ return (
97
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
98
+ {items.map((item) => (
99
+ <div
100
+ key={item.label}
101
+ className="group bg-white/60 dark:bg-slate-800/60 backdrop-blur-lg p-6 rounded-2xl shadow-sm border border-slate-200/50 dark:border-slate-700/50 hover:shadow-xl hover:-translate-y-1 transition-all duration-300"
102
+ >
103
+ <div className="flex items-center justify-between mb-4">
104
+ <div className={`w-12 h-12 rounded-xl ${item.bgColor} flex items-center justify-center group-hover:scale-110 transition-transform duration-300 ${item.shadowColor} shadow-lg`}>
105
+ <item.Icon className={`w-6 h-6 bg-gradient-to-r ${item.color}`} style={{ stroke: 'currentColor' }} />
106
+ </div>
107
+ <div className={`flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold ${
108
+ item.trend === "up"
109
+ ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
110
+ : "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
111
+ }`}>
112
+ {item.trend === "up" ? (
113
+ <TrendingUpIcon className="w-3 h-3" />
114
+ ) : (
115
+ <TrendingDownIcon className="w-3 h-3" />
116
+ )}
117
+ {item.change}
118
+ </div>
119
+ </div>
120
+ <p className="text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">{item.label}</p>
121
+ <p className="text-3xl font-bold text-slate-900 dark:text-white tracking-tight">{item.value}</p>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,113 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect } from "react";
4
+
5
+ interface Toast {
6
+ id: string;
7
+ message: string;
8
+ type: "success" | "error" | "info";
9
+ }
10
+
11
+ let toastListener: ((toast: Toast) => void) | null = null;
12
+
13
+ export function showToast(message: string, type: Toast["type"] = "info") {
14
+ const toast: Toast = {
15
+ id: Math.random().toString(36),
16
+ message,
17
+ type,
18
+ };
19
+ toastListener?.(toast);
20
+ }
21
+
22
+ function CheckCircleIcon({ className }: { className?: string }) {
23
+ return (
24
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
25
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
26
+ <polyline points="22 4 12 14.01 9 11.01"/>
27
+ </svg>
28
+ );
29
+ }
30
+
31
+ function AlertCircleIcon({ className }: { className?: string }) {
32
+ return (
33
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
34
+ <circle cx="12" cy="12" r="10"/>
35
+ <line x1="12" y1="8" x2="12" y2="12"/>
36
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
37
+ </svg>
38
+ );
39
+ }
40
+
41
+ function InfoIcon({ className }: { className?: string }) {
42
+ return (
43
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
44
+ <circle cx="12" cy="12" r="10"/>
45
+ <line x1="12" y1="16" x2="12" y2="12"/>
46
+ <line x1="12" y1="8" x2="12.01" y2="8"/>
47
+ </svg>
48
+ );
49
+ }
50
+
51
+ function XIcon({ className }: { className?: string }) {
52
+ return (
53
+ <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
54
+ <line x1="18" y1="6" x2="6" y2="18"/>
55
+ <line x1="6" y1="6" x2="18" y2="18"/>
56
+ </svg>
57
+ );
58
+ }
59
+
60
+ export function Toaster() {
61
+ const [toasts, setToasts] = useState<Toast[]>([]);
62
+
63
+ useEffect(() => {
64
+ toastListener = (toast) => {
65
+ setToasts(prev => [...prev, toast]);
66
+ setTimeout(() => {
67
+ setToasts(prev => prev.filter(t => t.id !== toast.id));
68
+ }, 4000);
69
+ };
70
+
71
+ return () => {
72
+ toastListener = null;
73
+ };
74
+ }, []);
75
+
76
+ const removeToast = (id: string) => {
77
+ setToasts(prev => prev.filter(t => t.id !== id));
78
+ };
79
+
80
+ return (
81
+ <div className="fixed bottom-4 right-4 z-50 space-y-3 pointer-events-none">
82
+ {toasts.map((toast) => (
83
+ <div
84
+ key={toast.id}
85
+ className={`pointer-events-auto flex items-start gap-3 px-4 py-3 rounded-xl shadow-lg backdrop-blur-md border transition-all animate-in slide-in-from-right-full fade-in duration-300 max-w-sm w-full ${
86
+ toast.type === "success"
87
+ ? "bg-white/90 dark:bg-slate-800/90 border-emerald-200 dark:border-emerald-800 text-emerald-800 dark:text-emerald-200"
88
+ : toast.type === "error"
89
+ ? "bg-white/90 dark:bg-slate-800/90 border-red-200 dark:border-red-800 text-red-800 dark:text-red-200"
90
+ : "bg-white/90 dark:bg-slate-800/90 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-200"
91
+ }`}
92
+ >
93
+ <div className="flex-shrink-0 mt-0.5">
94
+ {toast.type === "success" && <CheckCircleIcon className="w-5 h-5" />}
95
+ {toast.type === "error" && <AlertCircleIcon className="w-5 h-5" />}
96
+ {toast.type === "info" && <InfoIcon className="w-5 h-5" />}
97
+ </div>
98
+
99
+ <div className="flex-1 text-sm font-medium">
100
+ {toast.message}
101
+ </div>
102
+
103
+ <button
104
+ onClick={() => removeToast(toast.id)}
105
+ className="flex-shrink-0 text-current opacity-60 hover:opacity-100 transition-opacity"
106
+ >
107
+ <XIcon className="w-4 h-4" />
108
+ </button>
109
+ </div>
110
+ ))}
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,38 @@
1
+ export async function login(email: string, password: string) {
2
+ const response = await fetch("/api/auth/login", {
3
+ method: "POST",
4
+ headers: { "Content-Type": "application/json" },
5
+ body: JSON.stringify({ email, password }),
6
+ });
7
+
8
+ if (!response.ok) {
9
+ const error = await response.json();
10
+ throw new Error(error.error || "Login failed");
11
+ }
12
+
13
+ return response.json();
14
+ }
15
+
16
+ export async function logout() {
17
+ await fetch("/api/auth/logout", { method: "POST" });
18
+ window.location.href = "/";
19
+ }
20
+
21
+ export async function register(data: {
22
+ email: string;
23
+ password: string;
24
+ name: string;
25
+ }) {
26
+ const response = await fetch("/api/auth/register", {
27
+ method: "POST",
28
+ headers: { "Content-Type": "application/json" },
29
+ body: JSON.stringify(data),
30
+ });
31
+
32
+ if (!response.ok) {
33
+ const error = await response.json();
34
+ throw new Error(error.error || "Registration failed");
35
+ }
36
+
37
+ return response.json();
38
+ }
@@ -0,0 +1,49 @@
1
+ interface User {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ role: "user" | "admin";
6
+ }
7
+
8
+ interface Session {
9
+ token: string;
10
+ userId: string;
11
+ expiresAt: Date;
12
+ }
13
+
14
+ // In-memory storage (replace with database)
15
+ const sessions = new Map<string, Session>();
16
+ const ONE_DAY_MS = 86_400_000;
17
+
18
+ export async function createSession(user: User): Promise<Session> {
19
+ const token = crypto.randomUUID();
20
+ const session: Session = {
21
+ token,
22
+ userId: user.id,
23
+ expiresAt: new Date(Date.now() + ONE_DAY_MS), // 24 hours
24
+ };
25
+
26
+ sessions.set(token, session);
27
+ return session;
28
+ }
29
+
30
+ export async function verifySession(token: string): Promise<Session | null> {
31
+ const session = sessions.get(token);
32
+ if (!session) return null;
33
+
34
+ if (session.expiresAt < new Date()) {
35
+ sessions.delete(token);
36
+ return null;
37
+ }
38
+
39
+ return session;
40
+ }
41
+
42
+ export async function getSession(): Promise<{ user: User } | null> {
43
+ // This is a placeholder - in real app, get from cookies
44
+ return null;
45
+ }
46
+
47
+ export async function deleteSession(token: string): Promise<void> {
48
+ sessions.delete(token);
49
+ }
@@ -0,0 +1,34 @@
1
+ interface Stats {
2
+ totalUsers: number;
3
+ activeToday: number;
4
+ revenue: number;
5
+ growth: number;
6
+ }
7
+
8
+ export async function getStats(userId: string): Promise<Stats> {
9
+ // Mock data - replace with real database queries
10
+ return {
11
+ totalUsers: 1234,
12
+ activeToday: 89,
13
+ revenue: 54321,
14
+ growth: 12.5,
15
+ };
16
+ }
17
+
18
+ export async function getRecentActivity(userId: string) {
19
+ // Mock data
20
+ return [
21
+ {
22
+ id: "1",
23
+ type: "user_signup",
24
+ description: "New user registered",
25
+ timestamp: new Date(Date.now() - 1000 * 60 * 5),
26
+ },
27
+ {
28
+ id: "2",
29
+ type: "payment",
30
+ description: "Payment received",
31
+ timestamp: new Date(Date.now() - 1000 * 60 * 30),
32
+ },
33
+ ];
34
+ }
@@ -0,0 +1,86 @@
1
+ import { nanoid } from "nanoid";
2
+
3
+ let hash: (password: string) => Promise<string>;
4
+ let compare: (password: string, hash: string) => Promise<boolean>;
5
+
6
+ // @ts-ignore - Deno global
7
+ if (typeof Deno !== 'undefined') {
8
+ const bcrypt = await import("https://deno.land/x/bcrypt@v0.4.1/mod.ts");
9
+ hash = bcrypt.hash;
10
+ compare = bcrypt.compare;
11
+ } else {
12
+ // @ts-ignore
13
+ const bcrypt = await import('@node-rs/bcrypt');
14
+ hash = bcrypt.hash;
15
+ compare = bcrypt.compare;
16
+ }
17
+
18
+ interface User {
19
+ id: string;
20
+ email: string;
21
+ name: string;
22
+ role: "user" | "admin";
23
+ passwordHash: string;
24
+ createdAt: Date;
25
+ }
26
+
27
+ // In-memory storage (replace with database)
28
+ const users = new Map<string, User>();
29
+
30
+ // Demo user
31
+ const demoUser: User = {
32
+ id: "demo-user",
33
+ email: "demo@example.com",
34
+ name: "Demo User",
35
+ role: "user",
36
+ passwordHash: await hash("password"),
37
+ createdAt: new Date(),
38
+ };
39
+ users.set(demoUser.id, demoUser);
40
+
41
+ export async function createUser(data: {
42
+ email: string;
43
+ name: string;
44
+ password: string;
45
+ role?: "user" | "admin";
46
+ }): Promise<Omit<User, "passwordHash">> {
47
+ const user: User = {
48
+ id: nanoid(),
49
+ email: data.email,
50
+ name: data.name,
51
+ role: data.role || "user",
52
+ passwordHash: await hash(data.password),
53
+ createdAt: new Date(),
54
+ };
55
+
56
+ users.set(user.id, user);
57
+
58
+ const { passwordHash, ...publicUser } = user;
59
+ return publicUser;
60
+ }
61
+
62
+ export async function validatePassword(
63
+ email: string,
64
+ password: string
65
+ ): Promise<Omit<User, "passwordHash"> | null> {
66
+ const user = Array.from(users.values()).find(u => u.email === email);
67
+ if (!user) return null;
68
+
69
+ const valid = await compare(password, user.passwordHash);
70
+ if (!valid) return null;
71
+
72
+ const { passwordHash, ...publicUser } = user;
73
+ return publicUser;
74
+ }
75
+
76
+ export async function getUsers(): Promise<Array<Omit<User, "passwordHash">>> {
77
+ return Array.from(users.values()).map(({ passwordHash, ...user }) => user);
78
+ }
79
+
80
+ export async function getUser(id: string): Promise<Omit<User, "passwordHash"> | null> {
81
+ const user = users.get(id);
82
+ if (!user) return null;
83
+
84
+ const { passwordHash, ...publicUser } = user;
85
+ return publicUser;
86
+ }
@@ -0,0 +1,34 @@
1
+ import { verifySession } from "../lib/auth.ts";
2
+
3
+ export async function requireAuth(request: Request) {
4
+ const cookie = request.headers.get("cookie");
5
+ const token = cookie?.split("; ")
6
+ .find(row => row.startsWith("session="))
7
+ ?.split("=")[1];
8
+
9
+ if (!token) {
10
+ return {
11
+ ok: false,
12
+ response: Response.json(
13
+ { error: "Authentication required" },
14
+ { status: 401 }
15
+ ),
16
+ };
17
+ }
18
+
19
+ const session = await verifySession(token);
20
+ if (!session) {
21
+ return {
22
+ ok: false,
23
+ response: Response.json(
24
+ { error: "Invalid session" },
25
+ { status: 401 }
26
+ ),
27
+ };
28
+ }
29
+
30
+ return {
31
+ ok: true,
32
+ session,
33
+ };
34
+ }
@@ -0,0 +1,4 @@
1
+ User-agent: *
2
+ Allow: /
3
+
4
+ Sitemap: /sitemap.xml
@@ -0,0 +1,74 @@
1
+ import { getEnv } from "veryfront/platform";
2
+
3
+ export default {
4
+ title: "My App",
5
+ description: "A full-stack app built with Veryfront",
6
+
7
+ // App configuration
8
+ app: {
9
+ name: "My App",
10
+ api: {
11
+ prefix: "/api",
12
+ cors: true,
13
+ },
14
+ },
15
+
16
+ // Security
17
+ security: {
18
+ csp: true,
19
+ cors: {
20
+ origin: ["http://localhost:3002"],
21
+ credentials: true,
22
+ },
23
+ },
24
+
25
+ // Theme
26
+ theme: {
27
+ colors: {
28
+ primary: "#6366F1",
29
+ secondary: "#EC4899",
30
+ success: "#10B981",
31
+ danger: "#EF4444",
32
+ },
33
+ },
34
+
35
+ // Development
36
+ dev: {
37
+ port: 3002,
38
+ open: true,
39
+ },
40
+
41
+ // Middleware
42
+ middleware: [
43
+ "auth",
44
+ "logging",
45
+ "rate-limit",
46
+ ],
47
+
48
+ // Import map
49
+ resolve: {
50
+ importMap: {
51
+ imports: {
52
+ "react": "https://esm.sh/react@19.1.1",
53
+ "react/jsx-runtime": "https://esm.sh/react@19.1.1/jsx-runtime",
54
+ "react-dom": "https://esm.sh/react-dom@19.1.1",
55
+ "react-dom/client": "https://esm.sh/react-dom@19.1.1/client",
56
+ "zod": "https://esm.sh/zod@3.22.0",
57
+ "nanoid": "https://esm.sh/nanoid@5.0.0",
58
+ },
59
+ },
60
+ },
61
+
62
+ // Cache configuration
63
+ cache: {
64
+ dir: ".veryfront/cache",
65
+ render: {
66
+ // Choose between "memory", "filesystem", "kv", or "redis"
67
+ type: getEnv("REDIS_URL") ? "redis" : "memory",
68
+ ttl: 5 * 60 * 1000,
69
+ maxEntries: 500,
70
+ redisUrl: getEnv("REDIS_URL") ?? undefined,
71
+ redisKeyPrefix: "vf:render:",
72
+ },
73
+ },
74
+ };
@@ -0,0 +1,14 @@
1
+ # About
2
+
3
+ Welcome to my blog! I write about technology, programming, and life.
4
+
5
+ ## Contact
6
+
7
+ You can reach me at:
8
+ - Email: hello@example.com
9
+ - Twitter: @yourhandle
10
+ - GitHub: @yourusername
11
+
12
+ ## About This Site
13
+
14
+ This blog is built with [Veryfront](https://github.com/veryfront/veryfront), a Deno-first React framework with excellent MDX support.