@contractspec/example.saas-boilerplate 3.7.5 → 3.7.7

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 (115) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/AGENTS.md +50 -27
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +64 -144
  5. package/dist/billing/billing.event.js +1 -1
  6. package/dist/billing/index.d.ts +6 -6
  7. package/dist/billing/index.js +1 -1
  8. package/dist/browser/billing/billing.event.js +1 -1
  9. package/dist/browser/billing/index.js +1 -1
  10. package/dist/browser/index.js +931 -932
  11. package/dist/browser/project/index.js +209 -209
  12. package/dist/browser/project/project.event.js +1 -1
  13. package/dist/browser/ui/SaasDashboard.js +45 -45
  14. package/dist/browser/ui/SaasProjectList.js +7 -7
  15. package/dist/browser/ui/SaasSettingsPanel.js +12 -12
  16. package/dist/browser/ui/hooks/index.js +2 -2
  17. package/dist/browser/ui/hooks/useProjectList.js +1 -1
  18. package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
  19. package/dist/browser/ui/index.js +483 -484
  20. package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
  21. package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
  22. package/dist/browser/ui/modals/index.js +23 -23
  23. package/dist/browser/ui/renderers/index.js +112 -112
  24. package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
  25. package/dist/handlers/index.d.ts +2 -2
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.js +931 -932
  28. package/dist/node/billing/billing.event.js +1 -1
  29. package/dist/node/billing/index.js +1 -1
  30. package/dist/node/index.js +931 -932
  31. package/dist/node/project/index.js +209 -209
  32. package/dist/node/project/project.event.js +1 -1
  33. package/dist/node/ui/SaasDashboard.js +45 -45
  34. package/dist/node/ui/SaasProjectList.js +7 -7
  35. package/dist/node/ui/SaasSettingsPanel.js +12 -12
  36. package/dist/node/ui/hooks/index.js +2 -2
  37. package/dist/node/ui/hooks/useProjectList.js +1 -1
  38. package/dist/node/ui/hooks/useProjectMutations.js +1 -1
  39. package/dist/node/ui/index.js +483 -484
  40. package/dist/node/ui/modals/CreateProjectModal.js +10 -10
  41. package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
  42. package/dist/node/ui/modals/index.js +23 -23
  43. package/dist/node/ui/renderers/index.js +112 -112
  44. package/dist/node/ui/renderers/project-list.renderer.js +7 -7
  45. package/dist/presentations/index.d.ts +1 -1
  46. package/dist/project/index.d.ts +7 -7
  47. package/dist/project/index.js +209 -209
  48. package/dist/project/project.event.js +1 -1
  49. package/dist/settings/index.d.ts +1 -1
  50. package/dist/ui/SaasDashboard.js +45 -45
  51. package/dist/ui/SaasProjectList.js +7 -7
  52. package/dist/ui/SaasSettingsPanel.js +12 -12
  53. package/dist/ui/hooks/index.d.ts +2 -2
  54. package/dist/ui/hooks/index.js +2 -2
  55. package/dist/ui/hooks/useProjectList.d.ts +5 -0
  56. package/dist/ui/hooks/useProjectList.js +1 -1
  57. package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
  58. package/dist/ui/hooks/useProjectMutations.js +1 -1
  59. package/dist/ui/index.d.ts +4 -4
  60. package/dist/ui/index.js +483 -484
  61. package/dist/ui/modals/CreateProjectModal.js +10 -10
  62. package/dist/ui/modals/ProjectActionsModal.js +13 -13
  63. package/dist/ui/modals/index.js +23 -23
  64. package/dist/ui/renderers/index.d.ts +1 -1
  65. package/dist/ui/renderers/index.js +112 -112
  66. package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
  67. package/dist/ui/renderers/project-list.renderer.js +7 -7
  68. package/package.json +14 -14
  69. package/src/billing/billing.entity.ts +132 -132
  70. package/src/billing/billing.enum.ts +9 -9
  71. package/src/billing/billing.event.ts +71 -71
  72. package/src/billing/billing.handler.ts +87 -87
  73. package/src/billing/billing.operations.ts +158 -158
  74. package/src/billing/billing.presentation.ts +45 -45
  75. package/src/billing/billing.schema.ts +76 -76
  76. package/src/billing/index.ts +43 -48
  77. package/src/dashboard/dashboard.presentation.ts +45 -45
  78. package/src/dashboard/index.ts +2 -2
  79. package/src/docs/saas-boilerplate.docblock.ts +43 -43
  80. package/src/example.ts +32 -32
  81. package/src/handlers/index.ts +9 -9
  82. package/src/handlers/saas.handlers.ts +250 -249
  83. package/src/index.ts +40 -41
  84. package/src/presentations/index.ts +18 -20
  85. package/src/project/index.ts +45 -50
  86. package/src/project/project.entity.ts +68 -68
  87. package/src/project/project.enum.ts +8 -8
  88. package/src/project/project.event.ts +79 -79
  89. package/src/project/project.handler.ts +103 -103
  90. package/src/project/project.operations.ts +236 -236
  91. package/src/project/project.presentation.ts +46 -46
  92. package/src/project/project.schema.ts +90 -90
  93. package/src/saas-boilerplate.feature.ts +100 -100
  94. package/src/seeders/index.ts +20 -20
  95. package/src/settings/index.ts +2 -3
  96. package/src/settings/settings.entity.ts +65 -65
  97. package/src/settings/settings.enum.ts +4 -4
  98. package/src/shared/mock-data.ts +92 -92
  99. package/src/shared/overlay-types.ts +23 -23
  100. package/src/tests/operations.test-spec.ts +96 -96
  101. package/src/ui/SaasDashboard.tsx +270 -270
  102. package/src/ui/SaasProjectList.tsx +90 -90
  103. package/src/ui/SaasSettingsPanel.tsx +84 -84
  104. package/src/ui/hooks/index.ts +3 -3
  105. package/src/ui/hooks/useProjectList.ts +69 -68
  106. package/src/ui/hooks/useProjectMutations.ts +144 -143
  107. package/src/ui/index.ts +8 -12
  108. package/src/ui/modals/CreateProjectModal.tsx +154 -154
  109. package/src/ui/modals/ProjectActionsModal.tsx +321 -321
  110. package/src/ui/overlays/demo-overlays.ts +49 -49
  111. package/src/ui/renderers/index.ts +5 -4
  112. package/src/ui/renderers/project-list.markdown.ts +204 -204
  113. package/src/ui/renderers/project-list.renderer.tsx +14 -13
  114. package/tsconfig.json +7 -8
  115. package/tsdown.config.js +7 -3
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { Button, Input } from '@contractspec/lib.design-system';
3
4
  /**
4
5
  * ProjectActionsModal - Actions for a specific project
5
6
  *
@@ -7,340 +8,339 @@
7
8
  * via useProjectMutations hook.
8
9
  */
9
10
  import { useEffect, useState } from 'react';
10
- import { Button, Input } from '@contractspec/lib.design-system';
11
11
 
12
12
  // Local type definitions for modal props
13
13
  export interface Project {
14
- id: string;
15
- name: string;
16
- description?: string;
17
- status: 'DRAFT' | 'ACTIVE' | 'ARCHIVED';
18
- tier: 'FREE' | 'PRO' | 'ENTERPRISE';
14
+ id: string;
15
+ name: string;
16
+ description?: string;
17
+ status: 'DRAFT' | 'ACTIVE' | 'ARCHIVED';
18
+ tier: 'FREE' | 'PRO' | 'ENTERPRISE';
19
19
  }
20
20
 
21
21
  export interface UpdateProjectInput {
22
- id: string;
23
- name?: string;
24
- description?: string;
22
+ id: string;
23
+ name?: string;
24
+ description?: string;
25
25
  }
26
26
 
27
27
  type ActionMode = 'menu' | 'edit' | 'archive' | 'delete';
28
28
 
29
29
  interface ProjectActionsModalProps {
30
- isOpen: boolean;
31
- project: Project | null;
32
- onClose: () => void;
33
- onUpdate: (input: UpdateProjectInput) => Promise<void>;
34
- onArchive: (projectId: string) => Promise<void>;
35
- onActivate: (projectId: string) => Promise<void>;
36
- onDelete: (projectId: string) => Promise<void>;
37
- isLoading?: boolean;
30
+ isOpen: boolean;
31
+ project: Project | null;
32
+ onClose: () => void;
33
+ onUpdate: (input: UpdateProjectInput) => Promise<void>;
34
+ onArchive: (projectId: string) => Promise<void>;
35
+ onActivate: (projectId: string) => Promise<void>;
36
+ onDelete: (projectId: string) => Promise<void>;
37
+ isLoading?: boolean;
38
38
  }
39
39
 
40
40
  export function ProjectActionsModal({
41
- isOpen,
42
- project,
43
- onClose,
44
- onUpdate,
45
- onArchive,
46
- onActivate,
47
- onDelete,
48
- isLoading = false,
41
+ isOpen,
42
+ project,
43
+ onClose,
44
+ onUpdate,
45
+ onArchive,
46
+ onActivate,
47
+ onDelete,
48
+ isLoading = false,
49
49
  }: ProjectActionsModalProps) {
50
- const [mode, setMode] = useState<ActionMode>('menu');
51
- const [name, setName] = useState('');
52
- const [description, setDescription] = useState('');
53
- const [error, setError] = useState<string | null>(null);
54
-
55
- const resetForm = () => {
56
- setMode('menu');
57
- setError(null);
58
- if (project) {
59
- setName(project.name);
60
- setDescription(project.description ?? '');
61
- }
62
- };
63
-
64
- const handleClose = () => {
65
- resetForm();
66
- onClose();
67
- };
68
-
69
- // Initialize form when project changes
70
- useEffect(() => {
71
- if (project) {
72
- setName(project.name);
73
- setDescription(project.description ?? '');
74
- }
75
- }, [project]);
76
-
77
- const handleEdit = async () => {
78
- if (!project) return;
79
- setError(null);
80
-
81
- if (!name.trim()) {
82
- setError('Project name is required');
83
- return;
84
- }
85
-
86
- try {
87
- await onUpdate({
88
- id: project.id,
89
- name: name.trim(),
90
- description: description.trim() || undefined,
91
- });
92
- handleClose();
93
- } catch (err) {
94
- setError(err instanceof Error ? err.message : 'Failed to update project');
95
- }
96
- };
97
-
98
- const handleArchive = async () => {
99
- if (!project) return;
100
- setError(null);
101
-
102
- try {
103
- await onArchive(project.id);
104
- handleClose();
105
- } catch (err) {
106
- setError(
107
- err instanceof Error ? err.message : 'Failed to archive project'
108
- );
109
- }
110
- };
111
-
112
- const handleActivate = async () => {
113
- if (!project) return;
114
- setError(null);
115
-
116
- try {
117
- await onActivate(project.id);
118
- handleClose();
119
- } catch (err) {
120
- setError(
121
- err instanceof Error ? err.message : 'Failed to activate project'
122
- );
123
- }
124
- };
125
-
126
- const handleDelete = async () => {
127
- if (!project) return;
128
- setError(null);
129
-
130
- try {
131
- await onDelete(project.id);
132
- handleClose();
133
- } catch (err) {
134
- setError(err instanceof Error ? err.message : 'Failed to delete project');
135
- }
136
- };
137
-
138
- if (!isOpen || !project) return null;
139
-
140
- return (
141
- <div className="fixed inset-0 z-50 flex items-center justify-center">
142
- {/* Backdrop */}
143
- <div
144
- className="bg-background/80 absolute inset-0 backdrop-blur-sm"
145
- onClick={handleClose}
146
- role="button"
147
- tabIndex={0}
148
- onKeyDown={(e) => {
149
- if (e.key === 'Enter' || e.key === ' ') handleClose();
150
- }}
151
- aria-label="Close modal"
152
- />
153
-
154
- {/* Modal */}
155
- <div className="bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl">
156
- {/* Project Header */}
157
- <div className="border-border mb-4 border-b pb-4">
158
- <h2 className="text-xl font-semibold">{project.name}</h2>
159
- <p className="text-muted-foreground text-sm">
160
- {project.tier} · {project.status}
161
- </p>
162
- </div>
163
-
164
- {/* Main Menu */}
165
- {mode === 'menu' && (
166
- <div className="space-y-3">
167
- <Button
168
- className="w-full justify-start"
169
- variant="ghost"
170
- onPress={() => setMode('edit')}
171
- >
172
- <span className="mr-2">✏️</span> Edit Project
173
- </Button>
174
-
175
- {project.status === 'ACTIVE' || project.status === 'DRAFT' ? (
176
- <Button
177
- className="w-full justify-start"
178
- variant="ghost"
179
- onPress={() => setMode('archive')}
180
- >
181
- <span className="mr-2">📦</span> Archive Project
182
- </Button>
183
- ) : project.status === 'ARCHIVED' ? (
184
- <Button
185
- className="w-full justify-start"
186
- variant="ghost"
187
- onPress={handleActivate}
188
- disabled={isLoading}
189
- >
190
- <span className="mr-2">🔄</span> Restore Project
191
- </Button>
192
- ) : null}
193
-
194
- <Button
195
- className="w-full justify-start text-red-500 hover:text-red-600"
196
- variant="ghost"
197
- onPress={() => setMode('delete')}
198
- >
199
- <span className="mr-2">🗑️</span> Delete Project
200
- </Button>
201
-
202
- <div className="border-border border-t pt-3">
203
- <Button
204
- className="w-full"
205
- variant="outline"
206
- onPress={handleClose}
207
- >
208
- Close
209
- </Button>
210
- </div>
211
- </div>
212
- )}
213
-
214
- {/* Edit Form */}
215
- {mode === 'edit' && (
216
- <div className="space-y-4">
217
- <div>
218
- <label
219
- htmlFor="edit-name"
220
- className="text-muted-foreground mb-1 block text-sm font-medium"
221
- >
222
- Project Name *
223
- </label>
224
- <Input
225
- id="edit-name"
226
- value={name}
227
- onChange={(e) => setName(e.target.value)}
228
- disabled={isLoading}
229
- />
230
- </div>
231
-
232
- <div>
233
- <label
234
- htmlFor="edit-description"
235
- className="text-muted-foreground mb-1 block text-sm font-medium"
236
- >
237
- Description
238
- </label>
239
- <textarea
240
- id="edit-description"
241
- value={description}
242
- onChange={(e) => setDescription(e.target.value)}
243
- rows={3}
244
- disabled={isLoading}
245
- className="border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
246
- />
247
- </div>
248
-
249
- {error && (
250
- <div className="bg-destructive/10 text-destructive rounded-md p-3 text-sm">
251
- {error}
252
- </div>
253
- )}
254
-
255
- <div className="flex justify-end gap-3 pt-2">
256
- <Button
257
- variant="ghost"
258
- onPress={() => setMode('menu')}
259
- disabled={isLoading}
260
- >
261
- Back
262
- </Button>
263
- <Button onPress={handleEdit} disabled={isLoading}>
264
- {isLoading ? 'Saving...' : 'Save Changes'}
265
- </Button>
266
- </div>
267
- </div>
268
- )}
269
-
270
- {/* Archive Confirmation */}
271
- {mode === 'archive' && (
272
- <div className="space-y-4">
273
- <p className="text-muted-foreground">
274
- Are you sure you want to archive{' '}
275
- <span className="text-foreground font-medium">
276
- {project.name}
277
- </span>
278
- ?
279
- </p>
280
- <p className="text-muted-foreground text-sm">
281
- Archived projects can be restored later.
282
- </p>
283
-
284
- {error && (
285
- <div className="bg-destructive/10 text-destructive rounded-md p-3 text-sm">
286
- {error}
287
- </div>
288
- )}
289
-
290
- <div className="flex justify-end gap-3 pt-2">
291
- <Button
292
- variant="ghost"
293
- onPress={() => setMode('menu')}
294
- disabled={isLoading}
295
- >
296
- Cancel
297
- </Button>
298
- <Button onPress={handleArchive} disabled={isLoading}>
299
- {isLoading ? 'Archiving...' : '📦 Archive'}
300
- </Button>
301
- </div>
302
- </div>
303
- )}
304
-
305
- {/* Delete Confirmation */}
306
- {mode === 'delete' && (
307
- <div className="space-y-4">
308
- <p className="text-muted-foreground">
309
- Are you sure you want to delete{' '}
310
- <span className="text-foreground font-medium">
311
- {project.name}
312
- </span>
313
- ?
314
- </p>
315
- <p className="text-destructive text-sm">
316
- This action cannot be undone.
317
- </p>
318
-
319
- {error && (
320
- <div className="bg-destructive/10 text-destructive rounded-md p-3 text-sm">
321
- {error}
322
- </div>
323
- )}
324
-
325
- <div className="flex justify-end gap-3 pt-2">
326
- <Button
327
- variant="ghost"
328
- onPress={() => setMode('menu')}
329
- disabled={isLoading}
330
- >
331
- Cancel
332
- </Button>
333
- <Button
334
- variant="destructive"
335
- onPress={handleDelete}
336
- disabled={isLoading}
337
- >
338
- {isLoading ? 'Deleting...' : '🗑️ Delete'}
339
- </Button>
340
- </div>
341
- </div>
342
- )}
343
- </div>
344
- </div>
345
- );
50
+ const [mode, setMode] = useState<ActionMode>('menu');
51
+ const [name, setName] = useState('');
52
+ const [description, setDescription] = useState('');
53
+ const [error, setError] = useState<string | null>(null);
54
+
55
+ const resetForm = () => {
56
+ setMode('menu');
57
+ setError(null);
58
+ if (project) {
59
+ setName(project.name);
60
+ setDescription(project.description ?? '');
61
+ }
62
+ };
63
+
64
+ const handleClose = () => {
65
+ resetForm();
66
+ onClose();
67
+ };
68
+
69
+ // Initialize form when project changes
70
+ useEffect(() => {
71
+ if (project) {
72
+ setName(project.name);
73
+ setDescription(project.description ?? '');
74
+ }
75
+ }, [project]);
76
+
77
+ const handleEdit = async () => {
78
+ if (!project) return;
79
+ setError(null);
80
+
81
+ if (!name.trim()) {
82
+ setError('Project name is required');
83
+ return;
84
+ }
85
+
86
+ try {
87
+ await onUpdate({
88
+ id: project.id,
89
+ name: name.trim(),
90
+ description: description.trim() || undefined,
91
+ });
92
+ handleClose();
93
+ } catch (err) {
94
+ setError(err instanceof Error ? err.message : 'Failed to update project');
95
+ }
96
+ };
97
+
98
+ const handleArchive = async () => {
99
+ if (!project) return;
100
+ setError(null);
101
+
102
+ try {
103
+ await onArchive(project.id);
104
+ handleClose();
105
+ } catch (err) {
106
+ setError(
107
+ err instanceof Error ? err.message : 'Failed to archive project'
108
+ );
109
+ }
110
+ };
111
+
112
+ const handleActivate = async () => {
113
+ if (!project) return;
114
+ setError(null);
115
+
116
+ try {
117
+ await onActivate(project.id);
118
+ handleClose();
119
+ } catch (err) {
120
+ setError(
121
+ err instanceof Error ? err.message : 'Failed to activate project'
122
+ );
123
+ }
124
+ };
125
+
126
+ const handleDelete = async () => {
127
+ if (!project) return;
128
+ setError(null);
129
+
130
+ try {
131
+ await onDelete(project.id);
132
+ handleClose();
133
+ } catch (err) {
134
+ setError(err instanceof Error ? err.message : 'Failed to delete project');
135
+ }
136
+ };
137
+
138
+ if (!isOpen || !project) return null;
139
+
140
+ return (
141
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
142
+ {/* Backdrop */}
143
+ <div
144
+ className="absolute inset-0 bg-background/80 backdrop-blur-sm"
145
+ onClick={handleClose}
146
+ role="button"
147
+ tabIndex={0}
148
+ onKeyDown={(e) => {
149
+ if (e.key === 'Enter' || e.key === ' ') handleClose();
150
+ }}
151
+ aria-label="Close modal"
152
+ />
153
+
154
+ {/* Modal */}
155
+ <div className="relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl">
156
+ {/* Project Header */}
157
+ <div className="mb-4 border-border border-b pb-4">
158
+ <h2 className="font-semibold text-xl">{project.name}</h2>
159
+ <p className="text-muted-foreground text-sm">
160
+ {project.tier} · {project.status}
161
+ </p>
162
+ </div>
163
+
164
+ {/* Main Menu */}
165
+ {mode === 'menu' && (
166
+ <div className="space-y-3">
167
+ <Button
168
+ className="w-full justify-start"
169
+ variant="ghost"
170
+ onPress={() => setMode('edit')}
171
+ >
172
+ <span className="mr-2">✏️</span> Edit Project
173
+ </Button>
174
+
175
+ {project.status === 'ACTIVE' || project.status === 'DRAFT' ? (
176
+ <Button
177
+ className="w-full justify-start"
178
+ variant="ghost"
179
+ onPress={() => setMode('archive')}
180
+ >
181
+ <span className="mr-2">📦</span> Archive Project
182
+ </Button>
183
+ ) : project.status === 'ARCHIVED' ? (
184
+ <Button
185
+ className="w-full justify-start"
186
+ variant="ghost"
187
+ onPress={handleActivate}
188
+ disabled={isLoading}
189
+ >
190
+ <span className="mr-2">🔄</span> Restore Project
191
+ </Button>
192
+ ) : null}
193
+
194
+ <Button
195
+ className="w-full justify-start text-red-500 hover:text-red-600"
196
+ variant="ghost"
197
+ onPress={() => setMode('delete')}
198
+ >
199
+ <span className="mr-2">🗑️</span> Delete Project
200
+ </Button>
201
+
202
+ <div className="border-border border-t pt-3">
203
+ <Button
204
+ className="w-full"
205
+ variant="outline"
206
+ onPress={handleClose}
207
+ >
208
+ Close
209
+ </Button>
210
+ </div>
211
+ </div>
212
+ )}
213
+
214
+ {/* Edit Form */}
215
+ {mode === 'edit' && (
216
+ <div className="space-y-4">
217
+ <div>
218
+ <label
219
+ htmlFor="edit-name"
220
+ className="mb-1 block font-medium text-muted-foreground text-sm"
221
+ >
222
+ Project Name *
223
+ </label>
224
+ <Input
225
+ id="edit-name"
226
+ value={name}
227
+ onChange={(e) => setName(e.target.value)}
228
+ disabled={isLoading}
229
+ />
230
+ </div>
231
+
232
+ <div>
233
+ <label
234
+ htmlFor="edit-description"
235
+ className="mb-1 block font-medium text-muted-foreground text-sm"
236
+ >
237
+ Description
238
+ </label>
239
+ <textarea
240
+ id="edit-description"
241
+ value={description}
242
+ onChange={(e) => setDescription(e.target.value)}
243
+ rows={3}
244
+ disabled={isLoading}
245
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
246
+ />
247
+ </div>
248
+
249
+ {error && (
250
+ <div className="rounded-md bg-destructive/10 p-3 text-destructive text-sm">
251
+ {error}
252
+ </div>
253
+ )}
254
+
255
+ <div className="flex justify-end gap-3 pt-2">
256
+ <Button
257
+ variant="ghost"
258
+ onPress={() => setMode('menu')}
259
+ disabled={isLoading}
260
+ >
261
+ Back
262
+ </Button>
263
+ <Button onPress={handleEdit} disabled={isLoading}>
264
+ {isLoading ? 'Saving...' : 'Save Changes'}
265
+ </Button>
266
+ </div>
267
+ </div>
268
+ )}
269
+
270
+ {/* Archive Confirmation */}
271
+ {mode === 'archive' && (
272
+ <div className="space-y-4">
273
+ <p className="text-muted-foreground">
274
+ Are you sure you want to archive{' '}
275
+ <span className="font-medium text-foreground">
276
+ {project.name}
277
+ </span>
278
+ ?
279
+ </p>
280
+ <p className="text-muted-foreground text-sm">
281
+ Archived projects can be restored later.
282
+ </p>
283
+
284
+ {error && (
285
+ <div className="rounded-md bg-destructive/10 p-3 text-destructive text-sm">
286
+ {error}
287
+ </div>
288
+ )}
289
+
290
+ <div className="flex justify-end gap-3 pt-2">
291
+ <Button
292
+ variant="ghost"
293
+ onPress={() => setMode('menu')}
294
+ disabled={isLoading}
295
+ >
296
+ Cancel
297
+ </Button>
298
+ <Button onPress={handleArchive} disabled={isLoading}>
299
+ {isLoading ? 'Archiving...' : '📦 Archive'}
300
+ </Button>
301
+ </div>
302
+ </div>
303
+ )}
304
+
305
+ {/* Delete Confirmation */}
306
+ {mode === 'delete' && (
307
+ <div className="space-y-4">
308
+ <p className="text-muted-foreground">
309
+ Are you sure you want to delete{' '}
310
+ <span className="font-medium text-foreground">
311
+ {project.name}
312
+ </span>
313
+ ?
314
+ </p>
315
+ <p className="text-destructive text-sm">
316
+ This action cannot be undone.
317
+ </p>
318
+
319
+ {error && (
320
+ <div className="rounded-md bg-destructive/10 p-3 text-destructive text-sm">
321
+ {error}
322
+ </div>
323
+ )}
324
+
325
+ <div className="flex justify-end gap-3 pt-2">
326
+ <Button
327
+ variant="ghost"
328
+ onPress={() => setMode('menu')}
329
+ disabled={isLoading}
330
+ >
331
+ Cancel
332
+ </Button>
333
+ <Button
334
+ variant="destructive"
335
+ onPress={handleDelete}
336
+ disabled={isLoading}
337
+ >
338
+ {isLoading ? 'Deleting...' : '🗑️ Delete'}
339
+ </Button>
340
+ </div>
341
+ </div>
342
+ )}
343
+ </div>
344
+ </div>
345
+ );
346
346
  }