nobalmako 1.0.0

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 (123) hide show
  1. package/README.md +112 -0
  2. package/components.json +22 -0
  3. package/dist/nobalmako.js +272 -0
  4. package/drizzle/0000_pink_spiral.sql +126 -0
  5. package/drizzle/meta/0000_snapshot.json +1027 -0
  6. package/drizzle/meta/_journal.json +13 -0
  7. package/drizzle.config.ts +10 -0
  8. package/eslint.config.mjs +18 -0
  9. package/next.config.ts +7 -0
  10. package/package.json +80 -0
  11. package/postcss.config.mjs +7 -0
  12. package/public/file.svg +1 -0
  13. package/public/globe.svg +1 -0
  14. package/public/next.svg +1 -0
  15. package/public/vercel.svg +1 -0
  16. package/public/window.svg +1 -0
  17. package/server/index.ts +118 -0
  18. package/src/app/api/api-keys/[id]/route.ts +147 -0
  19. package/src/app/api/api-keys/route.ts +151 -0
  20. package/src/app/api/audit-logs/route.ts +84 -0
  21. package/src/app/api/auth/forgot-password/route.ts +47 -0
  22. package/src/app/api/auth/login/route.ts +99 -0
  23. package/src/app/api/auth/logout/route.ts +15 -0
  24. package/src/app/api/auth/me/route.ts +23 -0
  25. package/src/app/api/auth/mfa/setup/route.ts +33 -0
  26. package/src/app/api/auth/mfa/verify/route.ts +45 -0
  27. package/src/app/api/auth/register/route.ts +140 -0
  28. package/src/app/api/auth/reset-password/route.ts +52 -0
  29. package/src/app/api/auth/update/route.ts +71 -0
  30. package/src/app/api/auth/verify/route.ts +39 -0
  31. package/src/app/api/environments/route.ts +227 -0
  32. package/src/app/api/team-members/route.ts +385 -0
  33. package/src/app/api/teams/route.ts +217 -0
  34. package/src/app/api/variable-history/route.ts +218 -0
  35. package/src/app/api/variables/route.ts +476 -0
  36. package/src/app/api/webhooks/route.ts +77 -0
  37. package/src/app/api-keys/APIKeysClient.tsx +316 -0
  38. package/src/app/api-keys/page.tsx +10 -0
  39. package/src/app/api-reference/page.tsx +324 -0
  40. package/src/app/audit-log/AuditLogClient.tsx +229 -0
  41. package/src/app/audit-log/page.tsx +10 -0
  42. package/src/app/auth/forgot-password/page.tsx +121 -0
  43. package/src/app/auth/login/LoginForm.tsx +145 -0
  44. package/src/app/auth/login/page.tsx +11 -0
  45. package/src/app/auth/register/RegisterForm.tsx +156 -0
  46. package/src/app/auth/register/page.tsx +16 -0
  47. package/src/app/auth/reset-password/page.tsx +160 -0
  48. package/src/app/dashboard/DashboardClient.tsx +219 -0
  49. package/src/app/dashboard/page.tsx +11 -0
  50. package/src/app/docs/page.tsx +251 -0
  51. package/src/app/favicon.ico +0 -0
  52. package/src/app/globals.css +123 -0
  53. package/src/app/layout.tsx +35 -0
  54. package/src/app/page.tsx +231 -0
  55. package/src/app/profile/ProfileClient.tsx +230 -0
  56. package/src/app/profile/page.tsx +10 -0
  57. package/src/app/project/[id]/ProjectDetailsClient.tsx +512 -0
  58. package/src/app/project/[id]/page.tsx +17 -0
  59. package/src/bin/nobalmako.ts +341 -0
  60. package/src/components/ApiKeysManager.tsx +529 -0
  61. package/src/components/AppLayout.tsx +193 -0
  62. package/src/components/BulkActions.tsx +138 -0
  63. package/src/components/CreateEnvironmentDialog.tsx +207 -0
  64. package/src/components/CreateTeamDialog.tsx +174 -0
  65. package/src/components/CreateVariableDialog.tsx +311 -0
  66. package/src/components/DeleteEnvironmentDialog.tsx +104 -0
  67. package/src/components/DeleteTeamDialog.tsx +112 -0
  68. package/src/components/DeleteVariableDialog.tsx +103 -0
  69. package/src/components/EditEnvironmentDialog.tsx +202 -0
  70. package/src/components/EditMemberDialog.tsx +143 -0
  71. package/src/components/EditTeamDialog.tsx +178 -0
  72. package/src/components/EditVariableDialog.tsx +231 -0
  73. package/src/components/ImportVariablesDialog.tsx +347 -0
  74. package/src/components/InviteMemberDialog.tsx +191 -0
  75. package/src/components/LeaveProjectDialog.tsx +111 -0
  76. package/src/components/MFASettings.tsx +136 -0
  77. package/src/components/ProjectDiff.tsx +123 -0
  78. package/src/components/Providers.tsx +24 -0
  79. package/src/components/RemoveMemberDialog.tsx +112 -0
  80. package/src/components/SearchDialog.tsx +276 -0
  81. package/src/components/SecurityOverview.tsx +92 -0
  82. package/src/components/TeamMembersManager.tsx +103 -0
  83. package/src/components/VariableHistoryDialog.tsx +265 -0
  84. package/src/components/WebhooksManager.tsx +169 -0
  85. package/src/components/ui/alert-dialog.tsx +160 -0
  86. package/src/components/ui/alert.tsx +59 -0
  87. package/src/components/ui/avatar.tsx +53 -0
  88. package/src/components/ui/badge.tsx +46 -0
  89. package/src/components/ui/button.tsx +62 -0
  90. package/src/components/ui/card.tsx +92 -0
  91. package/src/components/ui/checkbox.tsx +32 -0
  92. package/src/components/ui/dialog.tsx +143 -0
  93. package/src/components/ui/dropdown-menu.tsx +257 -0
  94. package/src/components/ui/input.tsx +21 -0
  95. package/src/components/ui/label.tsx +24 -0
  96. package/src/components/ui/select.tsx +190 -0
  97. package/src/components/ui/separator.tsx +28 -0
  98. package/src/components/ui/sonner.tsx +37 -0
  99. package/src/components/ui/switch.tsx +31 -0
  100. package/src/components/ui/table.tsx +117 -0
  101. package/src/components/ui/tabs.tsx +66 -0
  102. package/src/components/ui/textarea.tsx +18 -0
  103. package/src/hooks/use-api-keys.ts +95 -0
  104. package/src/hooks/use-audit-logs.ts +58 -0
  105. package/src/hooks/use-auth.tsx +121 -0
  106. package/src/hooks/use-environments.ts +33 -0
  107. package/src/hooks/use-project-permissions.ts +49 -0
  108. package/src/hooks/use-team-members.ts +30 -0
  109. package/src/hooks/use-teams.ts +33 -0
  110. package/src/hooks/use-variables.ts +38 -0
  111. package/src/lib/audit.ts +36 -0
  112. package/src/lib/auth.ts +108 -0
  113. package/src/lib/crypto.ts +39 -0
  114. package/src/lib/db.ts +15 -0
  115. package/src/lib/dynamic-providers.ts +19 -0
  116. package/src/lib/email.ts +110 -0
  117. package/src/lib/mail.ts +51 -0
  118. package/src/lib/permissions.ts +51 -0
  119. package/src/lib/schema.ts +240 -0
  120. package/src/lib/seed.ts +107 -0
  121. package/src/lib/utils.ts +6 -0
  122. package/src/lib/webhooks.ts +42 -0
  123. package/tsconfig.json +34 -0
@@ -0,0 +1,123 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-background: hsl(var(--background));
5
+ --color-foreground: hsl(var(--foreground));
6
+ --color-card: hsl(var(--card));
7
+ --color-card-foreground: hsl(var(--card-foreground));
8
+ --color-popover: hsl(var(--popover));
9
+ --color-popover-foreground: hsl(var(--popover-foreground));
10
+ --color-primary: hsl(var(--primary));
11
+ --color-primary-foreground: hsl(var(--primary-foreground));
12
+ --color-secondary: hsl(var(--secondary));
13
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
14
+ --color-muted: hsl(var(--muted));
15
+ --color-muted-foreground: hsl(var(--muted-foreground));
16
+ --color-accent: hsl(var(--accent));
17
+ --color-accent-foreground: hsl(var(--accent-foreground));
18
+ --color-destructive: hsl(var(--destructive));
19
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
20
+ --color-border: hsl(var(--border));
21
+ --color-input: hsl(var(--input));
22
+ --color-ring: hsl(var(--ring));
23
+ --radius-xl: var(--radius);
24
+ --radius-lg: calc(var(--radius) - 2px);
25
+ --radius-md: calc(var(--radius) - 4px);
26
+ --radius-sm: calc(var(--radius) - 6px);
27
+ }
28
+
29
+ :root {
30
+ color-scheme: light;
31
+ --background: 0 0% 100%;
32
+ --foreground: 222.2 84% 4.9%;
33
+ --card: 0 0% 100%;
34
+ --card-foreground: 222.2 84% 4.9%;
35
+ --popover: 0 0% 100%;
36
+ --popover-foreground: 222.2 84% 4.9%;
37
+ --primary: 221.2 83.2% 53.3%;
38
+ --primary-foreground: 210 40% 98%;
39
+ --secondary: 210 40% 96.1%;
40
+ --secondary-foreground: 222.2 47.4% 11.2%;
41
+ --muted: 210 40% 96.1%;
42
+ --muted-foreground: 215.4 16.3% 46.9%;
43
+ --accent: 210 40% 96.1%;
44
+ --accent-foreground: 222.2 47.4% 11.2%;
45
+ --destructive: 0 84.2% 60.2%;
46
+ --destructive-foreground: 210 40% 98%;
47
+ --border: 214.3 31.8% 91.4%;
48
+ --input: 214.3 31.8% 91.4%;
49
+ --ring: 221.2 83.2% 53.3%;
50
+ --radius: 0.5rem;
51
+ }
52
+
53
+ /* Base styles */
54
+ *, ::after, ::before {
55
+ border-color: hsl(var(--border));
56
+ }
57
+
58
+ body {
59
+ color: hsl(var(--foreground));
60
+ background-color: hsl(var(--background)) !important;
61
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
62
+ line-height: 1.6;
63
+ -webkit-font-smoothing: antialiased;
64
+ -moz-osx-font-smoothing: grayscale;
65
+ }
66
+
67
+ /* Typography */
68
+ h1, h2, h3, h4, h5, h6 {
69
+ font-weight: 600;
70
+ line-height: 1.25;
71
+ letter-spacing: -0.025em;
72
+ }
73
+
74
+ h1 {
75
+ font-size: 2.25rem;
76
+ font-weight: 700;
77
+ }
78
+
79
+ h2 {
80
+ font-size: 1.875rem;
81
+ }
82
+
83
+ h3 {
84
+ font-size: 1.5rem;
85
+ }
86
+
87
+ h4 {
88
+ font-size: 1.25rem;
89
+ }
90
+
91
+ p {
92
+ color: hsl(var(--muted-foreground));
93
+ }
94
+
95
+ /* Code blocks */
96
+ code {
97
+ font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Cascadia Code', monospace;
98
+ font-size: 0.875em;
99
+ background: hsl(var(--muted));
100
+ padding: 0.125rem 0.25rem;
101
+ border-radius: 0.25rem;
102
+ border: 1px solid hsl(var(--border));
103
+ }
104
+
105
+ pre {
106
+ background: hsl(var(--muted));
107
+ border: 1px solid hsl(var(--border));
108
+ border-radius: var(--radius);
109
+ padding: 1rem;
110
+ overflow-x: auto;
111
+ }
112
+
113
+ pre code {
114
+ background: none;
115
+ padding: 0;
116
+ border: none;
117
+ }
118
+
119
+ /* Focus styles */
120
+ .focus-visible:focus-visible {
121
+ outline: 2px solid hsl(var(--ring));
122
+ outline-offset: 2px;
123
+ }
@@ -0,0 +1,35 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import "./globals.css";
4
+ import { Toaster } from "@/components/ui/sonner";
5
+ import { AuthProvider } from "@/hooks/use-auth";
6
+ import { Providers } from "@/components/Providers";
7
+
8
+ const inter = Inter({
9
+ subsets: ["latin"],
10
+ variable: "--font-inter",
11
+ });
12
+
13
+ export const metadata: Metadata = {
14
+ title: "Nobalmako - Environment Variable Management",
15
+ description: "Secure, collaborative environment variable management platform",
16
+ };
17
+
18
+ export default function RootLayout({
19
+ children,
20
+ }: Readonly<{
21
+ children: React.ReactNode;
22
+ }>) {
23
+ return (
24
+ <html lang="en" className={inter.variable} suppressHydrationWarning>
25
+ <body className="font-sans antialiased">
26
+ <Providers>
27
+ <AuthProvider>
28
+ {children}
29
+ <Toaster />
30
+ </AuthProvider>
31
+ </Providers>
32
+ </body>
33
+ </html>
34
+ );
35
+ }
@@ -0,0 +1,231 @@
1
+ import Link from "next/link";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
4
+ import { Shield, Users, Database, Search, Download, Upload, ArrowRight, CheckCircle } from "lucide-react";
5
+
6
+ export default function Home() {
7
+ return (
8
+ <div className="min-h-screen bg-background">
9
+ {/* Header */}
10
+ <header className="sticky top-0 z-50 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
11
+ <div className="container mx-auto px-4 py-4 flex items-center justify-between">
12
+ <div className="flex items-center space-x-3">
13
+ <Shield className="h-8 w-8 text-primary" />
14
+ <div>
15
+ <span className="text-2xl font-bold">Nobalmako</span>
16
+ </div>
17
+ </div>
18
+ <nav className="hidden md:flex items-center space-x-8">
19
+ <a href="#features" className="text-muted-foreground hover:text-foreground transition-colors">
20
+ Features
21
+ </a>
22
+ <Link href="/docs" className="text-muted-foreground hover:text-foreground transition-colors">
23
+ Docs
24
+ </Link>
25
+ </nav>
26
+ <div className="flex items-center space-x-3">
27
+ <Button variant="ghost" size="sm" asChild>
28
+ <Link href="/auth/login">Sign in</Link>
29
+ </Button>
30
+ <Button size="sm" asChild>
31
+ <Link href="/auth/register">Get Started</Link>
32
+ </Button>
33
+ </div>
34
+ </div>
35
+ </header>
36
+
37
+ {/* Hero Section */}
38
+ <section className="py-24 lg:py-32">
39
+ <div className="container mx-auto px-4">
40
+ <div className="max-w-4xl mx-auto text-center">
41
+ <div className="inline-flex items-center px-4 py-2 rounded-full bg-muted mb-8">
42
+ <CheckCircle className="h-4 w-4 text-primary mr-2" />
43
+ <span className="text-sm font-medium">
44
+ Trusted by developers worldwide
45
+ </span>
46
+ </div>
47
+
48
+ <h1 className="text-4xl lg:text-6xl font-bold mb-6">
49
+ Secure Environment
50
+ <br />
51
+ Variable Management
52
+ </h1>
53
+
54
+ <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
55
+ Manage your environment variables securely across all your projects and environments.
56
+ Collaborate with your team while keeping sensitive data protected.
57
+ </p>
58
+
59
+ <div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
60
+ <Button size="lg" asChild>
61
+ <Link href="/auth/register">
62
+ Get Started for Free
63
+ <ArrowRight className="ml-2 h-5 w-5" />
64
+ </Link>
65
+ </Button>
66
+ </div>
67
+
68
+ {/* Trust indicators */}
69
+ <div className="flex flex-wrap justify-center items-center gap-8 text-sm text-muted-foreground">
70
+ <div className="flex items-center">
71
+ <CheckCircle className="h-4 w-4 text-primary mr-2" />
72
+ SOC 2 Compliant
73
+ </div>
74
+ <div className="flex items-center">
75
+ <CheckCircle className="h-4 w-4 text-primary mr-2" />
76
+ End-to-end encrypted
77
+ </div>
78
+ <div className="flex items-center">
79
+ <CheckCircle className="h-4 w-4 text-primary mr-2" />
80
+ 99.9% uptime
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </section>
86
+
87
+ {/* Features */}
88
+ <section id="features" className="py-24 bg-gray-50">
89
+ <div className="container mx-auto px-4">
90
+ <div className="text-center mb-16">
91
+ <h2 className="text-3xl font-bold mb-4 text-gray-900">
92
+ Why Choose Nobalmako?
93
+ </h2>
94
+ <p className="text-xl text-muted-foreground max-w-2xl mx-auto">
95
+ Built for modern development teams who need security and professional developer experience.
96
+ </p>
97
+ </div>
98
+
99
+ <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
100
+ <Card>
101
+ <CardHeader>
102
+ <Shield className="h-10 w-10 text-primary mb-4" />
103
+ <CardTitle>Enterprise Security</CardTitle>
104
+ <CardDescription>
105
+ Bank-level encryption and access controls to keep your secrets safe. SOC 2 compliant with audit trails.
106
+ </CardDescription>
107
+ </CardHeader>
108
+ </Card>
109
+
110
+ <Card>
111
+ <CardHeader>
112
+ <Users className="h-10 w-10 text-primary mb-4" />
113
+ <CardTitle>Team Collaboration</CardTitle>
114
+ <CardDescription>
115
+ Share environment variables with your team with granular permissions. Role-based access control.
116
+ </CardDescription>
117
+ </CardHeader>
118
+ </Card>
119
+
120
+ <Card>
121
+ <CardHeader>
122
+ <Database className="h-10 w-10 text-primary mb-4" />
123
+ <CardTitle>Multi-Environment</CardTitle>
124
+ <CardDescription>
125
+ Manage variables across development, staging, and production environments with environment-specific configs.
126
+ </CardDescription>
127
+ </CardHeader>
128
+ </Card>
129
+
130
+ <Card>
131
+ <CardHeader>
132
+ <Search className="h-10 w-10 text-primary mb-4" />
133
+ <CardTitle>Advanced Search</CardTitle>
134
+ <CardDescription>
135
+ Quickly find variables with powerful search and filtering capabilities. Search by key, value, or tags.
136
+ </CardDescription>
137
+ </CardHeader>
138
+ </Card>
139
+
140
+ <Card>
141
+ <CardHeader>
142
+ <Download className="h-10 w-10 text-primary mb-4" />
143
+ <CardTitle>Export & Import</CardTitle>
144
+ <CardDescription>
145
+ Export variables in multiple formats (JSON, .env, YAML) and import from existing configuration files.
146
+ </CardDescription>
147
+ </CardHeader>
148
+ </Card>
149
+
150
+ <Card>
151
+ <CardHeader>
152
+ <Upload className="h-10 w-10 text-primary mb-4" />
153
+ <CardTitle>Bulk Operations</CardTitle>
154
+ <CardDescription>
155
+ Update multiple variables at once with bulk edit and import features. Streamline your workflow.
156
+ </CardDescription>
157
+ </CardHeader>
158
+ </Card>
159
+ </div>
160
+ </div>
161
+ </section>
162
+
163
+ {/* CTA Section */}
164
+ <section className="py-24 bg-primary text-primary-foreground">
165
+ <div className="container mx-auto px-4 text-center">
166
+ <div className="max-w-3xl mx-auto">
167
+ <h2 className="text-3xl font-bold mb-6">
168
+ Ready to secure your environment variables?
169
+ </h2>
170
+ <p className="text-xl opacity-90 mb-8">
171
+ Join developers who trust Nobalmako with their sensitive data.
172
+ Open-source and free to use.
173
+ </p>
174
+ <div className="flex flex-col sm:flex-row gap-4 justify-center">
175
+ <Button size="lg" variant="secondary" asChild>
176
+ <Link href="/auth/register">
177
+ Get Started for Free
178
+ <ArrowRight className="ml-2 h-5 w-5" />
179
+ </Link>
180
+ </Button>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </section>
185
+
186
+ {/* Footer */}
187
+ <footer className="bg-muted py-12">
188
+ <div className="container mx-auto px-4">
189
+ <div className="grid md:grid-cols-4 gap-8 mb-8">
190
+ <div>
191
+ <div className="flex items-center space-x-2 mb-4">
192
+ <Shield className="h-6 w-6 text-primary" />
193
+ <span className="text-xl font-bold">Nobalmako</span>
194
+ </div>
195
+ <p className="text-muted-foreground text-sm">
196
+ Secure environment variable management for modern development teams.
197
+ </p>
198
+ </div>
199
+
200
+ <div>
201
+ <h4 className="font-semibold mb-4">Product</h4>
202
+ <ul className="space-y-2 text-muted-foreground text-sm">
203
+ <li><a href="#features" className="hover:text-foreground transition-colors">Features</a></li>
204
+ <li><Link href="/docs#encryption" className="hover:text-foreground transition-colors">Security</Link></li>
205
+ </ul>
206
+ </div>
207
+
208
+ <div>
209
+ <h4 className="font-semibold mb-4">Support</h4>
210
+ <ul className="space-y-2 text-muted-foreground text-sm">
211
+ <li><Link href="/docs" className="hover:text-foreground transition-colors">Documentation</Link></li>
212
+ <li><Link href="/api-reference" className="hover:text-foreground transition-colors">API Reference</Link></li>
213
+ </ul>
214
+ </div>
215
+ </div>
216
+
217
+ <div className="border-t pt-8 flex flex-col md:flex-row justify-between items-center">
218
+ <p className="text-muted-foreground text-sm">
219
+ &copy; 2026 Nobalmako. All rights reserved.
220
+ </p>
221
+ <div className="flex space-x-6 mt-4 md:mt-0">
222
+ <a href="#" className="text-muted-foreground hover:text-foreground transition-colors text-sm">Privacy</a>
223
+ <a href="#" className="text-muted-foreground hover:text-foreground transition-colors text-sm">Terms</a>
224
+ <a href="#" className="text-muted-foreground hover:text-foreground transition-colors text-sm">Security</a>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </footer>
229
+ </div>
230
+ );
231
+ }
@@ -0,0 +1,230 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { useAuth } from '@/hooks/use-auth';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Label } from '@/components/ui/label';
8
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
9
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
10
+ import { toast } from 'sonner';
11
+ import { User, Lock, Mail, Camera } from 'lucide-react';
12
+
13
+ export default function ProfileClient() {
14
+ const { user } = useAuth();
15
+ const [isUpdating, setIsUpdating] = useState(false);
16
+ const [profileData, setProfileData] = useState({
17
+ name: user?.name || '',
18
+ email: user?.email || '',
19
+ });
20
+ const [passwordData, setPasswordData] = useState({
21
+ currentPassword: '',
22
+ newPassword: '',
23
+ confirmPassword: '',
24
+ });
25
+
26
+ const handleUpdateProfile = async (e: React.FormEvent) => {
27
+ e.preventDefault();
28
+ setIsUpdating(true);
29
+
30
+ try {
31
+ const response = await fetch('/api/auth/update', {
32
+ method: 'PUT',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ name: profileData.name }),
35
+ });
36
+
37
+ if (response.ok) {
38
+ toast.success('Profile updated successfully');
39
+ // We might want to refresh the user data in the context
40
+ window.location.reload();
41
+ } else {
42
+ const data = await response.json();
43
+ toast.error(data.error || 'Failed to update profile');
44
+ }
45
+ } catch (error) {
46
+ toast.error('An error occurred while updating profile');
47
+ } finally {
48
+ setIsUpdating(false);
49
+ }
50
+ };
51
+
52
+ const handleChangePassword = async (e: React.FormEvent) => {
53
+ e.preventDefault();
54
+
55
+ if (passwordData.newPassword !== passwordData.confirmPassword) {
56
+ toast.error('New passwords do not match');
57
+ return;
58
+ }
59
+
60
+ setIsUpdating(true);
61
+
62
+ try {
63
+ const response = await fetch('/api/auth/update', {
64
+ method: 'PATCH',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({
67
+ currentPassword: passwordData.currentPassword,
68
+ newPassword: passwordData.newPassword,
69
+ }),
70
+ });
71
+
72
+ if (response.ok) {
73
+ toast.success('Password changed successfully');
74
+ setPasswordData({ currentPassword: '', newPassword: '', confirmPassword: '' });
75
+ } else {
76
+ const data = await response.json();
77
+ toast.error(data.error || 'Failed to change password');
78
+ }
79
+ } catch (error) {
80
+ toast.error('An error occurred while changing password');
81
+ } finally {
82
+ setIsUpdating(false);
83
+ }
84
+ };
85
+
86
+ const getInitials = (userName: string) => {
87
+ return userName
88
+ .split(' ')
89
+ .map((n) => n[0])
90
+ .join('')
91
+ .toUpperCase();
92
+ };
93
+
94
+ return (
95
+ <div className="max-w-4xl mx-auto space-y-8">
96
+ <div>
97
+ <h1 className="text-3xl font-bold tracking-tight">Account Settings</h1>
98
+ <p className="text-muted-foreground">Manage your personal information and security preferences.</p>
99
+ </div>
100
+
101
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
102
+ <div className="md:col-span-1">
103
+ <Card>
104
+ <CardHeader className="text-center">
105
+ <div className="flex justify-center mb-4">
106
+ <div className="relative group">
107
+ <Avatar className="h-24 w-24">
108
+ <AvatarImage src="" />
109
+ <AvatarFallback className="text-2xl font-bold">
110
+ {user?.name ? getInitials(user.name) : 'U'}
111
+ </AvatarFallback>
112
+ </Avatar>
113
+ <Button
114
+ size="icon"
115
+ variant="secondary"
116
+ className="absolute bottom-0 right-0 rounded-full w-8 h-8 opacity-0 group-hover:opacity-100 transition-opacity"
117
+ >
118
+ <Camera className="h-4 w-4" />
119
+ </Button>
120
+ </div>
121
+ </div>
122
+ <CardTitle>{user?.name}</CardTitle>
123
+ <CardDescription>{user?.email}</CardDescription>
124
+ </CardHeader>
125
+ <CardContent>
126
+ <div className="bg-muted p-4 rounded-lg text-sm text-balance text-center">
127
+ Member since {new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
128
+ </div>
129
+ </CardContent>
130
+ </Card>
131
+ </div>
132
+
133
+ <div className="md:col-span-2 space-y-8">
134
+ {/* Profile Information */}
135
+ <Card>
136
+ <form onSubmit={handleUpdateProfile}>
137
+ <CardHeader>
138
+ <CardTitle className="flex items-center gap-2">
139
+ <User className="h-5 w-5" />
140
+ Profile Information
141
+ </CardTitle>
142
+ <CardDescription>Update your name and contact details.</CardDescription>
143
+ </CardHeader>
144
+ <CardContent className="space-y-4">
145
+ <div className="space-y-2">
146
+ <Label htmlFor="name">Full Name</Label>
147
+ <Input
148
+ id="name"
149
+ value={profileData.name}
150
+ onChange={(e) => setProfileData({ ...profileData, name: e.target.value })}
151
+ required
152
+ />
153
+ </div>
154
+ <div className="space-y-2">
155
+ <Label htmlFor="email">Email Address</Label>
156
+ <Input
157
+ id="email"
158
+ type="email"
159
+ value={profileData.email}
160
+ disabled
161
+ className="bg-muted"
162
+ />
163
+ <p className="text-xs text-muted-foreground">Email address cannot be changed at this time.</p>
164
+ </div>
165
+ </CardContent>
166
+ <CardFooter className="flex justify-end bg-muted/50 py-3">
167
+ <Button type="submit" disabled={isUpdating || profileData.name === user?.name}>
168
+ {isUpdating ? 'Saving...' : 'Save Changes'}
169
+ </Button>
170
+ </CardFooter>
171
+ </form>
172
+ </Card>
173
+
174
+ {/* Change Password */}
175
+ <Card>
176
+ <form onSubmit={handleChangePassword}>
177
+ <CardHeader>
178
+ <CardTitle className="flex items-center gap-2">
179
+ <Lock className="h-5 w-5" />
180
+ Security
181
+ </CardTitle>
182
+ <CardDescription>Secure your account by updating your password.</CardDescription>
183
+ </CardHeader>
184
+ <CardContent className="space-y-4">
185
+ <div className="space-y-2">
186
+ <Label htmlFor="current-password">Current Password</Label>
187
+ <Input
188
+ id="current-password"
189
+ type="password"
190
+ value={passwordData.currentPassword}
191
+ onChange={(e) => setPasswordData({ ...passwordData, currentPassword: e.target.value })}
192
+ required
193
+ />
194
+ </div>
195
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
196
+ <div className="space-y-2">
197
+ <Label htmlFor="new-password">New Password</Label>
198
+ <Input
199
+ id="new-password"
200
+ type="password"
201
+ value={passwordData.newPassword}
202
+ onChange={(e) => setPasswordData({ ...passwordData, newPassword: e.target.value })}
203
+ required
204
+ minLength={8}
205
+ />
206
+ </div>
207
+ <div className="space-y-2">
208
+ <Label htmlFor="confirm-password">Confirm New Password</Label>
209
+ <Input
210
+ id="confirm-password"
211
+ type="password"
212
+ value={passwordData.confirmPassword}
213
+ onChange={(e) => setPasswordData({ ...passwordData, confirmPassword: e.target.value })}
214
+ required
215
+ />
216
+ </div>
217
+ </div>
218
+ </CardContent>
219
+ <CardFooter className="flex justify-end bg-muted/50 py-3">
220
+ <Button type="submit" disabled={isUpdating || !passwordData.currentPassword || !passwordData.newPassword}>
221
+ {isUpdating ? 'Updating...' : 'Change Password'}
222
+ </Button>
223
+ </CardFooter>
224
+ </form>
225
+ </Card>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ );
230
+ }
@@ -0,0 +1,10 @@
1
+ import ProfileClient from './ProfileClient';
2
+ import AppLayout from '@/components/AppLayout';
3
+
4
+ export default function ProfilePage() {
5
+ return (
6
+ <AppLayout>
7
+ <ProfileClient />
8
+ </AppLayout>
9
+ );
10
+ }