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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -0
  3. package/dist/create.js +141 -0
  4. package/dist/index.js +45 -0
  5. package/dist/utils.js +29 -0
  6. package/package.json +61 -0
  7. package/template/.env.example +17 -0
  8. package/template/KNOWN_ISSUES.md +69 -0
  9. package/template/LICENSE +21 -0
  10. package/template/README.md +241 -0
  11. package/template/gitignore +42 -0
  12. package/template/next-env.d.ts +6 -0
  13. package/template/next.config.js +21 -0
  14. package/template/package.json +39 -0
  15. package/template/postcss.config.js +6 -0
  16. package/template/public/logo.svg +4 -0
  17. package/template/public/robots.txt +4 -0
  18. package/template/public/sitemap.xml +4 -0
  19. package/template/src/app/dashboard/layout.tsx +298 -0
  20. package/template/src/app/dashboard/page.tsx +209 -0
  21. package/template/src/app/dashboard/projects/page.tsx +638 -0
  22. package/template/src/app/dashboard/settings/page.tsx +749 -0
  23. package/template/src/app/dashboard/tasks/page.tsx +301 -0
  24. package/template/src/app/dashboard/team/page.tsx +295 -0
  25. package/template/src/app/globals.css +177 -0
  26. package/template/src/app/icon.svg +4 -0
  27. package/template/src/app/layout.tsx +33 -0
  28. package/template/src/app/login/page.tsx +98 -0
  29. package/template/src/app/not-found.tsx +20 -0
  30. package/template/src/app/page.tsx +23 -0
  31. package/template/src/components/dashboard/DashboardStats.tsx +137 -0
  32. package/template/src/components/dashboard/RecentActivity.tsx +63 -0
  33. package/template/src/components/landing/CTA.tsx +42 -0
  34. package/template/src/components/landing/Features.tsx +116 -0
  35. package/template/src/components/landing/Hero.tsx +146 -0
  36. package/template/src/components/landing/HowItWorks.tsx +80 -0
  37. package/template/src/components/landing/Pricing.tsx +124 -0
  38. package/template/src/components/landing/Testimonials.tsx +78 -0
  39. package/template/src/components/providers.tsx +11 -0
  40. package/template/src/components/shared/Footer.tsx +71 -0
  41. package/template/src/components/shared/Navbar.tsx +87 -0
  42. package/template/src/lib/constants.ts +35 -0
  43. package/template/src/lib/database.ts +7 -0
  44. package/template/src/lib/hooks.ts +331 -0
  45. package/template/src/lib/utils.ts +68 -0
  46. package/template/src/lib/varity.ts +1 -0
  47. package/template/src/services/dashboardService.ts +589 -0
  48. package/template/src/types/index.ts +52 -0
  49. package/template/tailwind.config.js +27 -0
  50. package/template/tsconfig.json +23 -0
  51. 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
+ }