create-varity-app 2.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/create.js +141 -0
- package/dist/index.js +45 -0
- package/dist/utils.js +29 -0
- package/package.json +61 -0
- package/template/.env.example +17 -0
- package/template/KNOWN_ISSUES.md +69 -0
- package/template/LICENSE +21 -0
- package/template/README.md +241 -0
- package/template/gitignore +42 -0
- package/template/next-env.d.ts +6 -0
- package/template/next.config.js +21 -0
- package/template/package.json +39 -0
- package/template/postcss.config.js +6 -0
- package/template/public/logo.svg +4 -0
- package/template/public/robots.txt +4 -0
- package/template/public/sitemap.xml +4 -0
- package/template/src/app/dashboard/layout.tsx +298 -0
- package/template/src/app/dashboard/page.tsx +209 -0
- package/template/src/app/dashboard/projects/page.tsx +638 -0
- package/template/src/app/dashboard/settings/page.tsx +749 -0
- package/template/src/app/dashboard/tasks/page.tsx +301 -0
- package/template/src/app/dashboard/team/page.tsx +295 -0
- package/template/src/app/globals.css +177 -0
- package/template/src/app/icon.svg +4 -0
- package/template/src/app/layout.tsx +33 -0
- package/template/src/app/login/page.tsx +98 -0
- package/template/src/app/not-found.tsx +20 -0
- package/template/src/app/page.tsx +23 -0
- package/template/src/components/dashboard/DashboardStats.tsx +137 -0
- package/template/src/components/dashboard/RecentActivity.tsx +63 -0
- package/template/src/components/landing/CTA.tsx +42 -0
- package/template/src/components/landing/Features.tsx +116 -0
- package/template/src/components/landing/Hero.tsx +146 -0
- package/template/src/components/landing/HowItWorks.tsx +80 -0
- package/template/src/components/landing/Pricing.tsx +124 -0
- package/template/src/components/landing/Testimonials.tsx +78 -0
- package/template/src/components/providers.tsx +11 -0
- package/template/src/components/shared/Footer.tsx +71 -0
- package/template/src/components/shared/Navbar.tsx +87 -0
- package/template/src/lib/constants.ts +35 -0
- package/template/src/lib/database.ts +7 -0
- package/template/src/lib/hooks.ts +331 -0
- package/template/src/lib/utils.ts +68 -0
- package/template/src/lib/varity.ts +1 -0
- package/template/src/services/dashboardService.ts +589 -0
- package/template/src/types/index.ts +52 -0
- package/template/tailwind.config.js +27 -0
- package/template/tsconfig.json +23 -0
- package/template/varity.config.json +14 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Service
|
|
3
|
+
*
|
|
4
|
+
* Centralized service for dashboard-related API calls.
|
|
5
|
+
* Provides type-safe interfaces and error handling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Project, Task, TeamMember } from '@/types';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Type Definitions
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
export interface KPIMetric {
|
|
15
|
+
title: string;
|
|
16
|
+
value: string | number;
|
|
17
|
+
change?: {
|
|
18
|
+
value: number;
|
|
19
|
+
period: string;
|
|
20
|
+
};
|
|
21
|
+
trend?: 'up' | 'down' | 'neutral';
|
|
22
|
+
sparklineData?: number[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface KPIResponse {
|
|
26
|
+
kpis: KPIMetric[];
|
|
27
|
+
has_data: boolean;
|
|
28
|
+
last_updated: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Activity {
|
|
32
|
+
id: string;
|
|
33
|
+
type: 'project_created' | 'task_completed' | 'member_added' | 'comment_added';
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
timestamp: string;
|
|
37
|
+
user?: {
|
|
38
|
+
name: string;
|
|
39
|
+
avatar?: string;
|
|
40
|
+
};
|
|
41
|
+
metadata?: Record<string, any>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ActivityResponse {
|
|
45
|
+
activities: Activity[];
|
|
46
|
+
total_count: number;
|
|
47
|
+
has_more: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProjectResponse {
|
|
51
|
+
projects: Project[];
|
|
52
|
+
total_count: number;
|
|
53
|
+
active_count: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TaskResponse {
|
|
57
|
+
tasks: Task[];
|
|
58
|
+
total_count: number;
|
|
59
|
+
completed_count: number;
|
|
60
|
+
by_status: {
|
|
61
|
+
todo: number;
|
|
62
|
+
in_progress: number;
|
|
63
|
+
done: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface TeamMemberResponse {
|
|
68
|
+
members: TeamMember[];
|
|
69
|
+
total_count: number;
|
|
70
|
+
roles: {
|
|
71
|
+
owner: number;
|
|
72
|
+
admin: number;
|
|
73
|
+
member: number;
|
|
74
|
+
viewer: number;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface DashboardOverviewResponse {
|
|
79
|
+
kpis: KPIResponse;
|
|
80
|
+
recent_activity: Activity[];
|
|
81
|
+
projects_summary: {
|
|
82
|
+
active: number;
|
|
83
|
+
total: number;
|
|
84
|
+
recent: Project[];
|
|
85
|
+
};
|
|
86
|
+
tasks_summary: {
|
|
87
|
+
open: number;
|
|
88
|
+
completed: number;
|
|
89
|
+
completion_rate: number;
|
|
90
|
+
};
|
|
91
|
+
team_summary: {
|
|
92
|
+
total: number;
|
|
93
|
+
active: number;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Error Handling
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
export class DashboardServiceError extends Error {
|
|
102
|
+
constructor(
|
|
103
|
+
message: string,
|
|
104
|
+
public statusCode?: number,
|
|
105
|
+
public originalError?: Error
|
|
106
|
+
) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = 'DashboardServiceError';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function handleResponse<T>(response: Response): Promise<T> {
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
115
|
+
throw new DashboardServiceError(
|
|
116
|
+
`API request failed: ${response.statusText}`,
|
|
117
|
+
response.status,
|
|
118
|
+
new Error(errorText)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
return await response.json();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw new DashboardServiceError(
|
|
126
|
+
'Failed to parse API response',
|
|
127
|
+
response.status,
|
|
128
|
+
error instanceof Error ? error : undefined
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// API Client
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Fetch dashboard KPIs
|
|
141
|
+
*/
|
|
142
|
+
export async function getKPIs(userId: string): Promise<KPIResponse> {
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(
|
|
145
|
+
`${API_BASE_URL}/dashboard/kpis?userId=${encodeURIComponent(userId)}`,
|
|
146
|
+
{
|
|
147
|
+
method: 'GET',
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
},
|
|
151
|
+
cache: 'no-store', // Always fetch fresh data
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return await handleResponse<KPIResponse>(response);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error instanceof DashboardServiceError) {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
throw new DashboardServiceError(
|
|
161
|
+
'Failed to fetch KPIs',
|
|
162
|
+
undefined,
|
|
163
|
+
error instanceof Error ? error : undefined
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Fetch recent activity
|
|
170
|
+
*/
|
|
171
|
+
export async function getRecentActivity(
|
|
172
|
+
userId: string,
|
|
173
|
+
limit: number = 10
|
|
174
|
+
): Promise<Activity[]> {
|
|
175
|
+
try {
|
|
176
|
+
const response = await fetch(
|
|
177
|
+
`${API_BASE_URL}/dashboard/activity?userId=${encodeURIComponent(userId)}&limit=${limit}`,
|
|
178
|
+
{
|
|
179
|
+
method: 'GET',
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
},
|
|
183
|
+
cache: 'no-store',
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const data = await handleResponse<ActivityResponse>(response);
|
|
188
|
+
return data.activities;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error instanceof DashboardServiceError) {
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
throw new DashboardServiceError(
|
|
194
|
+
'Failed to fetch recent activity',
|
|
195
|
+
undefined,
|
|
196
|
+
error instanceof Error ? error : undefined
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Fetch all projects for a user
|
|
203
|
+
*/
|
|
204
|
+
export async function getProjects(userId: string): Promise<Project[]> {
|
|
205
|
+
try {
|
|
206
|
+
const response = await fetch(
|
|
207
|
+
`${API_BASE_URL}/projects?userId=${encodeURIComponent(userId)}`,
|
|
208
|
+
{
|
|
209
|
+
method: 'GET',
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
},
|
|
213
|
+
cache: 'no-store',
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const data = await handleResponse<ProjectResponse>(response);
|
|
218
|
+
return data.projects;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error instanceof DashboardServiceError) {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
throw new DashboardServiceError(
|
|
224
|
+
'Failed to fetch projects',
|
|
225
|
+
undefined,
|
|
226
|
+
error instanceof Error ? error : undefined
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Fetch tasks, optionally filtered by project
|
|
233
|
+
*/
|
|
234
|
+
export async function getTasks(
|
|
235
|
+
userId: string,
|
|
236
|
+
projectId?: string
|
|
237
|
+
): Promise<Task[]> {
|
|
238
|
+
try {
|
|
239
|
+
const url = projectId
|
|
240
|
+
? `${API_BASE_URL}/tasks?userId=${encodeURIComponent(userId)}&projectId=${encodeURIComponent(projectId)}`
|
|
241
|
+
: `${API_BASE_URL}/tasks?userId=${encodeURIComponent(userId)}`;
|
|
242
|
+
|
|
243
|
+
const response = await fetch(url, {
|
|
244
|
+
method: 'GET',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/json',
|
|
247
|
+
},
|
|
248
|
+
cache: 'no-store',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const data = await handleResponse<TaskResponse>(response);
|
|
252
|
+
return data.tasks;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (error instanceof DashboardServiceError) {
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
throw new DashboardServiceError(
|
|
258
|
+
'Failed to fetch tasks',
|
|
259
|
+
undefined,
|
|
260
|
+
error instanceof Error ? error : undefined
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Fetch team members
|
|
267
|
+
*/
|
|
268
|
+
export async function getTeamMembers(userId: string): Promise<TeamMember[]> {
|
|
269
|
+
try {
|
|
270
|
+
const response = await fetch(
|
|
271
|
+
`${API_BASE_URL}/team?userId=${encodeURIComponent(userId)}`,
|
|
272
|
+
{
|
|
273
|
+
method: 'GET',
|
|
274
|
+
headers: {
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
},
|
|
277
|
+
cache: 'no-store',
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const data = await handleResponse<TeamMemberResponse>(response);
|
|
282
|
+
return data.members;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (error instanceof DashboardServiceError) {
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
throw new DashboardServiceError(
|
|
288
|
+
'Failed to fetch team members',
|
|
289
|
+
undefined,
|
|
290
|
+
error instanceof Error ? error : undefined
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Fetch complete dashboard overview
|
|
297
|
+
*/
|
|
298
|
+
export async function getDashboardOverview(
|
|
299
|
+
userId: string
|
|
300
|
+
): Promise<DashboardOverviewResponse> {
|
|
301
|
+
try {
|
|
302
|
+
const response = await fetch(
|
|
303
|
+
`${API_BASE_URL}/dashboard/overview?userId=${encodeURIComponent(userId)}`,
|
|
304
|
+
{
|
|
305
|
+
method: 'GET',
|
|
306
|
+
headers: {
|
|
307
|
+
'Content-Type': 'application/json',
|
|
308
|
+
},
|
|
309
|
+
cache: 'no-store',
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return await handleResponse<DashboardOverviewResponse>(response);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof DashboardServiceError) {
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
throw new DashboardServiceError(
|
|
319
|
+
'Failed to fetch dashboard overview',
|
|
320
|
+
undefined,
|
|
321
|
+
error instanceof Error ? error : undefined
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Create a new project
|
|
328
|
+
*/
|
|
329
|
+
export async function createProject(
|
|
330
|
+
userId: string,
|
|
331
|
+
data: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>
|
|
332
|
+
): Promise<Project> {
|
|
333
|
+
try {
|
|
334
|
+
const response = await fetch(`${API_BASE_URL}/projects`, {
|
|
335
|
+
method: 'POST',
|
|
336
|
+
headers: {
|
|
337
|
+
'Content-Type': 'application/json',
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({ userId, ...data }),
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return await handleResponse<Project>(response);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error instanceof DashboardServiceError) {
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
throw new DashboardServiceError(
|
|
348
|
+
'Failed to create project',
|
|
349
|
+
undefined,
|
|
350
|
+
error instanceof Error ? error : undefined
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Update an existing project
|
|
357
|
+
*/
|
|
358
|
+
export async function updateProject(
|
|
359
|
+
projectId: string,
|
|
360
|
+
data: Partial<Project>
|
|
361
|
+
): Promise<Project> {
|
|
362
|
+
try {
|
|
363
|
+
const response = await fetch(`${API_BASE_URL}/projects/${projectId}`, {
|
|
364
|
+
method: 'PATCH',
|
|
365
|
+
headers: {
|
|
366
|
+
'Content-Type': 'application/json',
|
|
367
|
+
},
|
|
368
|
+
body: JSON.stringify(data),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return await handleResponse<Project>(response);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (error instanceof DashboardServiceError) {
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
throw new DashboardServiceError(
|
|
377
|
+
'Failed to update project',
|
|
378
|
+
undefined,
|
|
379
|
+
error instanceof Error ? error : undefined
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Delete a project
|
|
386
|
+
*/
|
|
387
|
+
export async function deleteProject(projectId: string): Promise<void> {
|
|
388
|
+
try {
|
|
389
|
+
const response = await fetch(`${API_BASE_URL}/projects/${projectId}`, {
|
|
390
|
+
method: 'DELETE',
|
|
391
|
+
headers: {
|
|
392
|
+
'Content-Type': 'application/json',
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
throw new DashboardServiceError(
|
|
398
|
+
`Failed to delete project: ${response.statusText}`,
|
|
399
|
+
response.status
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error instanceof DashboardServiceError) {
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
throw new DashboardServiceError(
|
|
407
|
+
'Failed to delete project',
|
|
408
|
+
undefined,
|
|
409
|
+
error instanceof Error ? error : undefined
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Create a new task
|
|
416
|
+
*/
|
|
417
|
+
export async function createTask(
|
|
418
|
+
userId: string,
|
|
419
|
+
data: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>
|
|
420
|
+
): Promise<Task> {
|
|
421
|
+
try {
|
|
422
|
+
const response = await fetch(`${API_BASE_URL}/tasks`, {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: {
|
|
425
|
+
'Content-Type': 'application/json',
|
|
426
|
+
},
|
|
427
|
+
body: JSON.stringify({ userId, ...data }),
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return await handleResponse<Task>(response);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
if (error instanceof DashboardServiceError) {
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
throw new DashboardServiceError(
|
|
436
|
+
'Failed to create task',
|
|
437
|
+
undefined,
|
|
438
|
+
error instanceof Error ? error : undefined
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Update an existing task
|
|
445
|
+
*/
|
|
446
|
+
export async function updateTask(
|
|
447
|
+
taskId: string,
|
|
448
|
+
data: Partial<Task>
|
|
449
|
+
): Promise<Task> {
|
|
450
|
+
try {
|
|
451
|
+
const response = await fetch(`${API_BASE_URL}/tasks/${taskId}`, {
|
|
452
|
+
method: 'PATCH',
|
|
453
|
+
headers: {
|
|
454
|
+
'Content-Type': 'application/json',
|
|
455
|
+
},
|
|
456
|
+
body: JSON.stringify(data),
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return await handleResponse<Task>(response);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof DashboardServiceError) {
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
throw new DashboardServiceError(
|
|
465
|
+
'Failed to update task',
|
|
466
|
+
undefined,
|
|
467
|
+
error instanceof Error ? error : undefined
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Delete a task
|
|
474
|
+
*/
|
|
475
|
+
export async function deleteTask(taskId: string): Promise<void> {
|
|
476
|
+
try {
|
|
477
|
+
const response = await fetch(`${API_BASE_URL}/tasks/${taskId}`, {
|
|
478
|
+
method: 'DELETE',
|
|
479
|
+
headers: {
|
|
480
|
+
'Content-Type': 'application/json',
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
throw new DashboardServiceError(
|
|
486
|
+
`Failed to delete task: ${response.statusText}`,
|
|
487
|
+
response.status
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error instanceof DashboardServiceError) {
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
throw new DashboardServiceError(
|
|
495
|
+
'Failed to delete task',
|
|
496
|
+
undefined,
|
|
497
|
+
error instanceof Error ? error : undefined
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Invite a team member
|
|
504
|
+
*/
|
|
505
|
+
export async function inviteTeamMember(
|
|
506
|
+
userId: string,
|
|
507
|
+
email: string,
|
|
508
|
+
role: TeamMember['role']
|
|
509
|
+
): Promise<TeamMember> {
|
|
510
|
+
try {
|
|
511
|
+
const response = await fetch(`${API_BASE_URL}/team/invite`, {
|
|
512
|
+
method: 'POST',
|
|
513
|
+
headers: {
|
|
514
|
+
'Content-Type': 'application/json',
|
|
515
|
+
},
|
|
516
|
+
body: JSON.stringify({ userId, email, role }),
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
return await handleResponse<TeamMember>(response);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error instanceof DashboardServiceError) {
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
throw new DashboardServiceError(
|
|
525
|
+
'Failed to invite team member',
|
|
526
|
+
undefined,
|
|
527
|
+
error instanceof Error ? error : undefined
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Remove a team member
|
|
534
|
+
*/
|
|
535
|
+
export async function removeTeamMember(memberId: string): Promise<void> {
|
|
536
|
+
try {
|
|
537
|
+
const response = await fetch(`${API_BASE_URL}/team/${memberId}`, {
|
|
538
|
+
method: 'DELETE',
|
|
539
|
+
headers: {
|
|
540
|
+
'Content-Type': 'application/json',
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (!response.ok) {
|
|
545
|
+
throw new DashboardServiceError(
|
|
546
|
+
`Failed to remove team member: ${response.statusText}`,
|
|
547
|
+
response.status
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
} catch (error) {
|
|
551
|
+
if (error instanceof DashboardServiceError) {
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
throw new DashboardServiceError(
|
|
555
|
+
'Failed to remove team member',
|
|
556
|
+
undefined,
|
|
557
|
+
error instanceof Error ? error : undefined
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Update team member role
|
|
564
|
+
*/
|
|
565
|
+
export async function updateTeamMemberRole(
|
|
566
|
+
memberId: string,
|
|
567
|
+
role: TeamMember['role']
|
|
568
|
+
): Promise<TeamMember> {
|
|
569
|
+
try {
|
|
570
|
+
const response = await fetch(`${API_BASE_URL}/team/${memberId}`, {
|
|
571
|
+
method: 'PATCH',
|
|
572
|
+
headers: {
|
|
573
|
+
'Content-Type': 'application/json',
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify({ role }),
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
return await handleResponse<TeamMember>(response);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (error instanceof DashboardServiceError) {
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
throw new DashboardServiceError(
|
|
584
|
+
'Failed to update team member role',
|
|
585
|
+
undefined,
|
|
586
|
+
error instanceof Error ? error : undefined
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface Project {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
status: 'active' | 'paused' | 'completed';
|
|
6
|
+
owner: string;
|
|
7
|
+
members: string[];
|
|
8
|
+
dueDate: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Task {
|
|
13
|
+
id: string;
|
|
14
|
+
projectId: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
status: 'todo' | 'in_progress' | 'done';
|
|
18
|
+
priority: 'low' | 'medium' | 'high';
|
|
19
|
+
assignee?: string;
|
|
20
|
+
dueDate?: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TeamMember {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
email: string;
|
|
28
|
+
role: 'admin' | 'member' | 'viewer';
|
|
29
|
+
avatarUrl?: string;
|
|
30
|
+
joinedAt: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UserSettings {
|
|
34
|
+
id: string;
|
|
35
|
+
user_id: string;
|
|
36
|
+
// Preferences
|
|
37
|
+
theme: 'light' | 'dark' | 'system';
|
|
38
|
+
email_notifications: boolean;
|
|
39
|
+
marketing_emails: boolean;
|
|
40
|
+
product_updates: boolean;
|
|
41
|
+
date_format: string;
|
|
42
|
+
timezone: string;
|
|
43
|
+
language: string;
|
|
44
|
+
dashboard_layout: 'comfortable' | 'compact';
|
|
45
|
+
// Security
|
|
46
|
+
two_factor_enabled: boolean;
|
|
47
|
+
// Privacy
|
|
48
|
+
analytics_enabled: boolean;
|
|
49
|
+
cookies_enabled: boolean;
|
|
50
|
+
// Timestamps
|
|
51
|
+
updated_at: string;
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
darkMode: 'class',
|
|
4
|
+
content: [
|
|
5
|
+
'./src/**/*.{js,ts,jsx,tsx,mdx}',
|
|
6
|
+
'./node_modules/@varity-labs/ui-kit/dist/**/*.{js,jsx}',
|
|
7
|
+
],
|
|
8
|
+
theme: {
|
|
9
|
+
extend: {
|
|
10
|
+
colors: {
|
|
11
|
+
primary: {
|
|
12
|
+
50: 'var(--color-primary-50)',
|
|
13
|
+
100: 'var(--color-primary-100)',
|
|
14
|
+
200: 'var(--color-primary-200)',
|
|
15
|
+
300: 'var(--color-primary-300)',
|
|
16
|
+
400: 'var(--color-primary-400)',
|
|
17
|
+
500: 'var(--color-primary-500)',
|
|
18
|
+
600: 'var(--color-primary-600)',
|
|
19
|
+
700: 'var(--color-primary-700)',
|
|
20
|
+
800: 'var(--color-primary-800)',
|
|
21
|
+
900: 'var(--color-primary-900)',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
plugins: [],
|
|
27
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./src/*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-saas-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"framework": "nextjs",
|
|
5
|
+
"hosting": "ipfs",
|
|
6
|
+
"build": {
|
|
7
|
+
"command": "npm run build",
|
|
8
|
+
"output": "out"
|
|
9
|
+
},
|
|
10
|
+
"database": {
|
|
11
|
+
"provider": "varity",
|
|
12
|
+
"collections": ["projects", "tasks", "team_members"]
|
|
13
|
+
}
|
|
14
|
+
}
|