create-varity-app 2.0.0-beta.2 → 2.0.0-beta.22
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/README.md +21 -20
- package/dist/create.js +5 -6
- package/dist/index.js +0 -0
- package/package.json +7 -7
- package/template/.env.example +1 -1
- package/template/.turbo/turbo-build.log +2 -36
- package/template/KNOWN_ISSUES.md +27 -37
- package/template/README.md +61 -43
- package/template/next.config.js +3 -9
- package/template/package.json +1 -0
- package/template/src/app/dashboard/layout.tsx +9 -3
- package/template/src/app/dashboard/settings/page.tsx +1 -1
- package/template/src/app/layout.tsx +1 -1
- package/template/src/components/shared/Footer.tsx +1 -1
- package/template/src/services/dashboardService.ts +589 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/create-varity-app)
|
|
4
4
|
[](https://github.com/varity-labs/varity-sdk/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
Create production-ready apps with auth
|
|
6
|
+
Create production-ready apps with auth and database built in. Payments coming soon.
|
|
7
7
|
|
|
8
8
|
## Quick Start
|
|
9
9
|
|
|
@@ -23,12 +23,12 @@ yarn create varity-app my-app
|
|
|
23
23
|
## What You Get
|
|
24
24
|
|
|
25
25
|
- **Next.js 15** app with TypeScript and Tailwind CSS
|
|
26
|
-
- **Authentication**
|
|
27
|
-
- **Database**
|
|
28
|
-
- **Dashboard**
|
|
29
|
-
- **Landing page**
|
|
30
|
-
- **Settings**
|
|
31
|
-
- **
|
|
26
|
+
- **Authentication** -- email, social login, and more (zero config)
|
|
27
|
+
- **Database** -- ready-to-use collections with typed queries
|
|
28
|
+
- **Dashboard** -- professional layout with sidebar navigation
|
|
29
|
+
- **Landing page** -- hero, features, pricing, how it works, testimonials, CTA
|
|
30
|
+
- **Settings** -- General, Security, Billing, and Account tabs
|
|
31
|
+
- **8 pages** -- landing, login, dashboard, projects, tasks, team, settings, 404
|
|
32
32
|
|
|
33
33
|
## Project Structure
|
|
34
34
|
|
|
@@ -36,15 +36,16 @@ yarn create varity-app my-app
|
|
|
36
36
|
my-app/
|
|
37
37
|
├── src/
|
|
38
38
|
│ └── app/
|
|
39
|
-
│ ├──
|
|
40
|
-
│ ├──
|
|
41
|
-
│ │ ├──
|
|
39
|
+
│ ├── login/ # Login page
|
|
40
|
+
│ ├── dashboard/ # Protected dashboard pages
|
|
41
|
+
│ │ ├── page.tsx # Overview with KPIs
|
|
42
42
|
│ │ ├── projects/ # Project management
|
|
43
43
|
│ │ ├── tasks/ # Task tracking
|
|
44
44
|
│ │ ├── team/ # Team members
|
|
45
|
-
│ │ └── settings/ # App settings (
|
|
45
|
+
│ │ └── settings/ # App settings (4 tabs)
|
|
46
|
+
│ ├── not-found.tsx # 404 page
|
|
46
47
|
│ └── page.tsx # Landing page
|
|
47
|
-
├── tailwind.config.
|
|
48
|
+
├── tailwind.config.js
|
|
48
49
|
├── next.config.js
|
|
49
50
|
└── package.json
|
|
50
51
|
```
|
|
@@ -60,26 +61,26 @@ varitykit app deploy
|
|
|
60
61
|
|
|
61
62
|
Your app will be live in under 60 seconds.
|
|
62
63
|
|
|
63
|
-
Or deploy from your AI editor with the [Varity MCP server](
|
|
64
|
+
Or deploy from your AI editor with the [Varity MCP server](https://www.npmjs.com/package/@varity-labs/mcp) -- just ask "deploy this project".
|
|
64
65
|
|
|
65
66
|
## Related Packages
|
|
66
67
|
|
|
67
|
-
- **[@varity-labs/sdk](
|
|
68
|
-
- **[@varity-labs/ui-kit](
|
|
69
|
-
- **[@varity-labs/mcp](
|
|
68
|
+
- **[@varity-labs/sdk](https://www.npmjs.com/package/@varity-labs/sdk)** -- Core SDK (database, credentials)
|
|
69
|
+
- **[@varity-labs/ui-kit](https://www.npmjs.com/package/@varity-labs/ui-kit)** -- React UI components
|
|
70
|
+
- **[@varity-labs/mcp](https://www.npmjs.com/package/@varity-labs/mcp)** -- MCP server for AI editors (Cursor, Claude Code, VS Code)
|
|
70
71
|
|
|
71
72
|
## Learn More
|
|
72
73
|
|
|
73
74
|
- [Documentation](https://docs.varity.so)
|
|
74
75
|
- [GitHub](https://github.com/varity-labs/varity-sdk)
|
|
75
|
-
- [Discord](https://discord.gg/
|
|
76
|
+
- [Discord](https://discord.gg/7vWsdwa2Bg)
|
|
76
77
|
|
|
77
78
|
---
|
|
78
79
|
|
|
79
|
-
**Part of the [Varity SDK](https://github.com/varity-labs/varity-sdk)**
|
|
80
|
+
**Part of the [Varity SDK](https://github.com/varity-labs/varity-sdk)** -- Build, deploy, and monetize apps 70% cheaper than AWS.
|
|
80
81
|
|
|
81
|
-
[Documentation](https://docs.varity.so)
|
|
82
|
+
[Documentation](https://docs.varity.so) | [GitHub](https://github.com/varity-labs/varity-sdk) | [Discord](https://discord.gg/7vWsdwa2Bg)
|
|
82
83
|
|
|
83
84
|
## License
|
|
84
85
|
|
|
85
|
-
MIT
|
|
86
|
+
MIT -- [Varity Labs](https://www.varity.so)
|
package/dist/create.js
CHANGED
|
@@ -7,11 +7,10 @@ import ora from "ora";
|
|
|
7
7
|
import { getInstallCommand } from "./utils.js";
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
|
10
|
-
const VARITY_PACKAGE_VERSION = "^2.0.0-beta.2";
|
|
11
10
|
const WORKSPACE_DEPS = {
|
|
12
|
-
"@varity-labs/sdk":
|
|
13
|
-
"@varity-labs/types":
|
|
14
|
-
"@varity-labs/ui-kit":
|
|
11
|
+
"@varity-labs/sdk": "2.0.0-beta.6",
|
|
12
|
+
"@varity-labs/types": "2.0.0-beta.4",
|
|
13
|
+
"@varity-labs/ui-kit": "2.0.0-beta.7",
|
|
15
14
|
};
|
|
16
15
|
const EXCLUDED_FILES = new Set([
|
|
17
16
|
".env.local",
|
|
@@ -33,7 +32,7 @@ function rewritePackageJson(content, projectName) {
|
|
|
33
32
|
for (const [name, version] of Object.entries(deps)) {
|
|
34
33
|
if (typeof version === "string" &&
|
|
35
34
|
version.startsWith("workspace:")) {
|
|
36
|
-
deps[name] = WORKSPACE_DEPS[name] ||
|
|
35
|
+
deps[name] = WORKSPACE_DEPS[name] || "2.0.0-beta.4";
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -136,6 +135,6 @@ export async function createApp(projectName, packageManager) {
|
|
|
136
135
|
console.log(chalk.cyan(" varitykit app deploy"));
|
|
137
136
|
console.log();
|
|
138
137
|
console.log(` Docs: ${chalk.underline("https://docs.varity.so")}`);
|
|
139
|
-
console.log(` Help: ${chalk.underline("https://discord.gg/
|
|
138
|
+
console.log(` Help: ${chalk.underline("https://discord.gg/7vWsdwa2Bg")}`);
|
|
140
139
|
console.log();
|
|
141
140
|
}
|
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-varity-app",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.22",
|
|
4
4
|
"description": "Create production-ready apps with auth, database, and payments built in",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"LICENSE"
|
|
39
39
|
],
|
|
40
40
|
"type": "module",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"prebuild": "node scripts/copy-template.mjs",
|
|
43
|
+
"build": "tsc",
|
|
44
|
+
"clean": "rm -rf dist template"
|
|
45
|
+
},
|
|
41
46
|
"dependencies": {
|
|
42
47
|
"chalk": "^5.3.0",
|
|
43
48
|
"commander": "^12.1.0",
|
|
@@ -52,10 +57,5 @@
|
|
|
52
57
|
"@types/prompts": "^2.4.9",
|
|
53
58
|
"@types/validate-npm-package-name": "^4.0.2",
|
|
54
59
|
"typescript": "^5.5.0"
|
|
55
|
-
},
|
|
56
|
-
"scripts": {
|
|
57
|
-
"prebuild": "node scripts/copy-template.mjs",
|
|
58
|
-
"build": "tsc",
|
|
59
|
-
"clean": "rm -rf dist template"
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
}
|
package/template/.env.example
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# For production: `varitykit app deploy` injects all credentials automatically.
|
|
7
7
|
# You never need to manually set these values.
|
|
8
8
|
|
|
9
|
-
# Auth
|
|
9
|
+
# Auth (optional — dev credentials used automatically when blank)
|
|
10
10
|
NEXT_PUBLIC_PRIVY_APP_ID=
|
|
11
11
|
|
|
12
12
|
# Database (optional — dev database used automatically when blank)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> my-saas-app@0.1.0 build /home/macoding/varity-workspace/varity-sdk-
|
|
2
|
+
> my-saas-app@0.1.0 build /home/macoding/varity-workspace/varity-sdk-private/templates/saas-starter
|
|
3
3
|
> next build
|
|
4
4
|
|
|
5
5
|
⚠ Warning: Next.js inferred your workspace root, but it may not be correct.
|
|
@@ -7,43 +7,9 @@
|
|
|
7
7
|
To silence this warning, set `outputFileTracingRoot` in your Next.js config, or consider removing one of the lockfiles if it's not needed.
|
|
8
8
|
See https://nextjs.org/docs/app/api-reference/config/next-config-js/output#caveats for more information.
|
|
9
9
|
Detected additional lockfiles:
|
|
10
|
-
* /home/macoding/varity-workspace/varity-sdk-
|
|
10
|
+
* /home/macoding/varity-workspace/varity-sdk-private/pnpm-lock.yaml
|
|
11
11
|
|
|
12
12
|
▲ Next.js 15.5.11
|
|
13
13
|
|
|
14
14
|
Creating an optimized production build ...
|
|
15
15
|
⚠ Mismatching @next/swc version, detected: 15.5.7 while Next.js is on 15.5.11. Please ensure these match
|
|
16
|
-
✓ Compiled successfully in 27.8s
|
|
17
|
-
Linting and checking validity of types ...
|
|
18
|
-
Collecting page data ...
|
|
19
|
-
Generating static pages (0/11) ...
|
|
20
|
-
[Varity Database] Using shared development database. Data is stored in an isolated dev schema.
|
|
21
|
-
Deploy with `varitykit app deploy` to get your own private database.
|
|
22
|
-
Generating static pages (2/11)
|
|
23
|
-
Generating static pages (5/11)
|
|
24
|
-
Generating static pages (8/11)
|
|
25
|
-
✓ Generating static pages (11/11)
|
|
26
|
-
Finalizing page optimization ...
|
|
27
|
-
Collecting build traces ...
|
|
28
|
-
Exporting (0/2) ...
|
|
29
|
-
✓ Exporting (2/2)
|
|
30
|
-
|
|
31
|
-
Route (app) Size First Load JS
|
|
32
|
-
┌ ○ / 5.69 kB 125 kB
|
|
33
|
-
├ ○ /_not-found 132 B 116 kB
|
|
34
|
-
├ ○ /dashboard 3.75 kB 1.33 MB
|
|
35
|
-
├ ○ /dashboard/projects 4.95 kB 1.33 MB
|
|
36
|
-
├ ○ /dashboard/settings 5.88 kB 1.33 MB
|
|
37
|
-
├ ○ /dashboard/tasks 3.17 kB 1.33 MB
|
|
38
|
-
├ ○ /dashboard/team 3.06 kB 1.33 MB
|
|
39
|
-
├ ○ /icon.svg 0 B 0 B
|
|
40
|
-
└ ○ /login 1.31 kB 1.33 MB
|
|
41
|
-
+ First Load JS shared by all 116 kB
|
|
42
|
-
├ chunks/79135-4ac590d187e38d6f.js 45.3 kB
|
|
43
|
-
├ chunks/f0439f7a-76e885cef8778491.js 54.3 kB
|
|
44
|
-
├ chunks/webpack-ea5a8e6ec96b5a02.js 16.1 kB
|
|
45
|
-
└ other shared chunks (total) 241 B
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
○ (Static) prerendered as static content
|
|
49
|
-
|
package/template/KNOWN_ISSUES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Known Issues
|
|
1
|
+
# Known Issues — SaaS Starter Template
|
|
2
2
|
|
|
3
|
-
> **Last Updated:** March
|
|
3
|
+
> **Last Updated:** March 8, 2026
|
|
4
4
|
> **Template:** `saas-starter` (TaskFlow)
|
|
5
5
|
> **Status:** All features functional, builds with 0 errors (8 routes + _not-found)
|
|
6
6
|
|
|
@@ -10,60 +10,50 @@
|
|
|
10
10
|
|
|
11
11
|
| Feature | Status | Notes |
|
|
12
12
|
|---------|--------|-------|
|
|
13
|
-
| Landing Page | Working | 6 sections, scroll animations, social proof
|
|
14
|
-
| Login | Working | Email/Google
|
|
15
|
-
| Dashboard | Working | KPI cards,
|
|
16
|
-
| Projects CRUD | Working |
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
| Protected Routes | Working | Automatic redirect for unauthenticated users |
|
|
23
|
-
| Color Themes | Working | 4 built-in presets (Blue, Purple, Green, Orange) via CSS variables |
|
|
24
|
-
| Static Export | Working | `output: 'export'` for static hosting deployment |
|
|
25
|
-
| Mobile Nav | Working | Hamburger menu with responsive sidebar |
|
|
26
|
-
| CSV Export | Working | One-click export for tasks and projects |
|
|
27
|
-
| SEO | Working | OpenGraph, Twitter cards, robots.txt, sitemap template |
|
|
13
|
+
| Landing Page | Working | 6 sections, scroll animations, social proof |
|
|
14
|
+
| Auth (Login) | Working | Email/Google via Privy, zero-config dev credentials |
|
|
15
|
+
| Dashboard | Working | KPI cards, checklist, activity feed |
|
|
16
|
+
| Projects/Tasks/Team CRUD | Working | Full create, read, update, delete with validation |
|
|
17
|
+
| Settings | Working | 4 tabs with backend persistence via DB Proxy |
|
|
18
|
+
| Command Palette | Working | Cmd+K / Ctrl+K |
|
|
19
|
+
| Color Themes | Working | 4 presets (Blue, Purple, Green, Orange) |
|
|
20
|
+
| Static Export | Working | `output: 'export'` for static hosting (global CDN) |
|
|
21
|
+
| Mobile Nav | Working | Responsive sidebar with hamburger menu |
|
|
28
22
|
|
|
29
23
|
## Known Issues
|
|
30
24
|
|
|
31
|
-
### 1.
|
|
32
|
-
|
|
25
|
+
### 1. Auth Uses Privy Directly (Abstraction Coming Post-Beta)
|
|
26
|
+
The template uses `usePrivy()` and `NEXT_PUBLIC_PRIVY_APP_ID`. The planned `useAuth()` hook and `NEXT_PUBLIC_VARITY_AUTH_ID` env var are post-beta tasks. No action required -- current auth works correctly.
|
|
33
27
|
|
|
34
|
-
### 2.
|
|
35
|
-
|
|
28
|
+
### 2. Payments Section is Placeholder
|
|
29
|
+
Settings > Billing shows mock UI. Credit card payment integration (on/off ramp) is coming soon. Wire your own billing provider (Stripe, etc.) if needed now.
|
|
36
30
|
|
|
37
|
-
### 3.
|
|
38
|
-
|
|
31
|
+
### 3. No Server-Side Rendering
|
|
32
|
+
All pages are statically exported. No SSR, API routes, or middleware. Do not use dynamic routes (`[id]` patterns) -- use client-side state for detail views instead.
|
|
39
33
|
|
|
40
|
-
### 4.
|
|
41
|
-
|
|
34
|
+
### 4. Navigation Flash
|
|
35
|
+
Brief "Initializing Dashboard" screen when navigating between pages. Caused by `PrivyReadyGate` re-checking auth state. Resolves in under 1 second.
|
|
42
36
|
|
|
43
|
-
### 5.
|
|
44
|
-
|
|
37
|
+
### 5. Team Email Invites Are Local Only
|
|
38
|
+
No SMTP integration. Team members are added to the database but no invitation email is sent. Integrate your own email service if needed.
|
|
45
39
|
|
|
46
|
-
### 6.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
### 7. Team Email Invites
|
|
50
|
-
No SMTP integration -- team members are added to the database only. No invitation email is sent. Developers should integrate their own email service.
|
|
40
|
+
### 6. Sessions and Password Are Auth-Provider Managed
|
|
41
|
+
Settings > Security shows mock session data. Password changes and profile photos are managed by the auth provider (Privy), not the template.
|
|
51
42
|
|
|
52
43
|
## Environment
|
|
53
44
|
|
|
54
45
|
### Development (Zero Config)
|
|
55
46
|
```bash
|
|
56
|
-
npm install
|
|
57
|
-
npm run dev
|
|
47
|
+
npm install && npm run dev
|
|
58
48
|
```
|
|
59
|
-
No `.env` file, API keys, or accounts needed.
|
|
49
|
+
No `.env` file, API keys, or accounts needed.
|
|
60
50
|
|
|
61
51
|
### Production
|
|
62
52
|
```bash
|
|
63
53
|
varitykit app deploy
|
|
64
54
|
```
|
|
65
|
-
The CLI provisions a private database, injects production credentials, and deploys automatically.
|
|
66
55
|
|
|
67
56
|
## Reporting Issues
|
|
68
57
|
|
|
69
|
-
|
|
58
|
+
- GitHub: https://github.com/varity-labs/varity-sdk/issues
|
|
59
|
+
- Discord: https://discord.gg/7vWsdwa2Bg
|
package/template/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# TaskFlow
|
|
1
|
+
# TaskFlow — SaaS Starter Template
|
|
2
2
|
|
|
3
|
-
[](https://varity.so)
|
|
3
|
+
[](https://www.varity.so)
|
|
4
4
|
|
|
5
|
-
A full-featured project management app built with [Varity](https://varity.so). Everything works immediately
|
|
5
|
+
A full-featured project management app built with [Varity](https://www.varity.so). Everything works immediately — no configuration, no API keys, no setup.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -23,12 +23,12 @@ No `.env` file needed. No accounts to create. No credentials to configure.
|
|
|
23
23
|
|
|
24
24
|
Transform this into your own branded SaaS app:
|
|
25
25
|
|
|
26
|
-
1. **App name**
|
|
27
|
-
2. **Logo**
|
|
28
|
-
3. **Colors**
|
|
29
|
-
4. **Meta title**
|
|
30
|
-
5. **Navigation**
|
|
31
|
-
6. **Landing page**
|
|
26
|
+
1. **App name** — Edit `APP_NAME` in `src/lib/constants.ts`
|
|
27
|
+
2. **Logo** — Replace `public/logo.svg` with your logo
|
|
28
|
+
3. **Colors** — Open `src/app/globals.css` and uncomment a color preset (Purple, Green, or Orange) or set your own
|
|
29
|
+
4. **Meta title** — Update the `title` and `description` in `src/app/layout.tsx`
|
|
30
|
+
5. **Navigation** — Edit `NAVIGATION_ITEMS` in `src/lib/constants.ts` to rename or add sidebar links
|
|
31
|
+
6. **Landing page** — Edit the sections in `src/components/landing/` (Hero, Features, Pricing, etc.)
|
|
32
32
|
|
|
33
33
|
## Built-in Color Themes
|
|
34
34
|
|
|
@@ -44,53 +44,71 @@ Switch your entire app's color scheme by editing `src/app/globals.css`:
|
|
|
44
44
|
|
|
45
45
|
## What's Included
|
|
46
46
|
|
|
47
|
-
- **Zero-Config Auth**
|
|
48
|
-
- **Zero-Config Database**
|
|
49
|
-
- **Dashboard**
|
|
50
|
-
- **Full CRUD**
|
|
51
|
-
- **Command Palette**
|
|
52
|
-
- **Protected Routes**
|
|
53
|
-
- **Landing Page**
|
|
54
|
-
- **Mobile Responsive**
|
|
55
|
-
- **TypeScript**
|
|
56
|
-
- **Tailwind CSS**
|
|
47
|
+
- **Zero-Config Auth** — Email and social login works out of the box
|
|
48
|
+
- **Zero-Config Database** — Data persistence with isolated dev environment
|
|
49
|
+
- **Dashboard** — KPI cards, data tables, status badges, getting started guide
|
|
50
|
+
- **Full CRUD** — Create, read, update, delete for projects, tasks, and team members
|
|
51
|
+
- **Command Palette** — Cmd+K search across all data
|
|
52
|
+
- **Protected Routes** — Automatic redirect for unauthenticated users
|
|
53
|
+
- **Landing Page** — Professional marketing page with hero, features, pricing, testimonials
|
|
54
|
+
- **Mobile Responsive** — Hamburger menu, responsive layouts, touch-friendly
|
|
55
|
+
- **TypeScript** — Full type safety throughout
|
|
56
|
+
- **Tailwind CSS** — Utility-first styling with CSS variable theming
|
|
57
57
|
|
|
58
|
-
## Zero Configuration Required
|
|
58
|
+
## ✅ Zero Configuration Required
|
|
59
59
|
|
|
60
60
|
This template works immediately with **zero setup**:
|
|
61
61
|
|
|
62
62
|
### Instant Auth
|
|
63
|
-
- Email login (
|
|
64
|
-
-
|
|
65
|
-
- Dev credentials built-in
|
|
66
|
-
- No env vars needed
|
|
63
|
+
- ✅ Email login (built-in)
|
|
64
|
+
- ✅ Google/Apple social login
|
|
65
|
+
- ✅ Dev credentials built-in
|
|
66
|
+
- ❌ No env vars needed
|
|
67
67
|
|
|
68
68
|
### Instant Database
|
|
69
|
-
- Create, read, update, delete data
|
|
70
|
-
- Dev token built-in
|
|
71
|
-
- Production-ready
|
|
72
|
-
- No credentials needed
|
|
69
|
+
- ✅ Create, read, update, delete data
|
|
70
|
+
- ✅ Dev token built-in
|
|
71
|
+
- ✅ Production-ready proxy
|
|
72
|
+
- ❌ No credentials needed
|
|
73
73
|
|
|
74
74
|
### Instant Deploy
|
|
75
75
|
```bash
|
|
76
|
-
|
|
76
|
+
npm run deploy
|
|
77
77
|
```
|
|
78
|
-
- Deploys to production
|
|
79
|
-
- Auto-
|
|
80
|
-
- No accounts needed
|
|
78
|
+
- ✅ Deploys to production
|
|
79
|
+
- ✅ Auto-fetches credentials
|
|
80
|
+
- ❌ No extra accounts needed
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
84
|
+
## 🏗️ Architecture
|
|
85
|
+
|
|
86
|
+
### Workspace Dependencies
|
|
87
|
+
This template uses `workspace:^` protocol for Varity packages:
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"dependencies": {
|
|
91
|
+
"@varity-labs/sdk": "workspace:^",
|
|
92
|
+
"@varity-labs/ui-kit": "workspace:^",
|
|
93
|
+
"@varity-labs/types": "workspace:^"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Why?** Ensures you always use the latest local package versions during development.
|
|
99
|
+
|
|
100
|
+
**Publishing:** When published to npm, `workspace:^` converts to `^2.0.0-alpha.1` automatically.
|
|
101
|
+
|
|
84
102
|
### Static Export Ready
|
|
85
|
-
- `output: 'export'` in next.config.js
|
|
86
|
-
- All pages pre-rendered to static HTML
|
|
87
|
-
- No server-side dependencies
|
|
88
|
-
- CDN deployable
|
|
103
|
+
- ✅ `output: 'export'` in next.config.js
|
|
104
|
+
- ✅ All pages pre-rendered to static HTML
|
|
105
|
+
- ✅ No server-side dependencies
|
|
106
|
+
- ✅ CDN deployable
|
|
89
107
|
|
|
90
108
|
### Type Safety
|
|
91
|
-
- TypeScript strict mode enabled
|
|
92
|
-
- All errors surface during build
|
|
93
|
-
- No `ignoreBuildErrors` flag
|
|
109
|
+
- ✅ TypeScript strict mode enabled
|
|
110
|
+
- ✅ All errors surface during build
|
|
111
|
+
- ✅ No `ignoreBuildErrors` flag
|
|
94
112
|
|
|
95
113
|
## Project Structure
|
|
96
114
|
|
|
@@ -187,17 +205,17 @@ export function useInvoices(): UseCollectionReturn<Invoice> {
|
|
|
187
205
|
const { data, loading, create, update, remove } = useInvoices();
|
|
188
206
|
```
|
|
189
207
|
|
|
190
|
-
The database collection is created automatically on first use
|
|
208
|
+
The database collection is created automatically on first use — no migrations needed.
|
|
191
209
|
|
|
192
210
|
## Environment Variables
|
|
193
211
|
|
|
194
212
|
**For development:** Leave everything blank. Shared development credentials are used automatically.
|
|
195
213
|
|
|
196
|
-
**For production:** Run `varitykit app deploy`
|
|
214
|
+
**For production:** Run `varitykit app deploy` — it injects all credentials into your build automatically. You never need to manually set API keys.
|
|
197
215
|
|
|
198
216
|
| Variable | Required | Notes |
|
|
199
217
|
|----------|----------|-------|
|
|
200
|
-
| `
|
|
218
|
+
| `NEXT_PUBLIC_VARITY_AUTH_ID` | No | Auth provider (auto-configured) |
|
|
201
219
|
| `NEXT_PUBLIC_VARITY_APP_TOKEN` | No | Database token (auto-configured) |
|
|
202
220
|
| `NEXT_PUBLIC_VARITY_APP_ID` | No | App ID (auto-configured) |
|
|
203
221
|
|
|
@@ -211,7 +229,7 @@ varitykit app deploy
|
|
|
211
229
|
varitykit app deploy --submit-to-store
|
|
212
230
|
```
|
|
213
231
|
|
|
214
|
-
The CLI builds your app, provisions a private database, injects production credentials, and deploys
|
|
232
|
+
The CLI builds your app, provisions a private database, injects production credentials, and deploys — all in one command.
|
|
215
233
|
|
|
216
234
|
**Deploy from your AI editor:** Set up the [Varity MCP server](https://docs.varity.so/mcp) (`npx @varity-labs/mcp`) and ask your AI to "deploy this project".
|
|
217
235
|
|
package/template/next.config.js
CHANGED
|
@@ -5,15 +5,9 @@ const nextConfig = {
|
|
|
5
5
|
trailingSlash: true,
|
|
6
6
|
productionBrowserSourceMaps: false,
|
|
7
7
|
webpack: (config, { isServer, dev }) => {
|
|
8
|
-
// Suppress
|
|
9
|
-
config.resolve.
|
|
10
|
-
|
|
11
|
-
'@react-native-async-storage/async-storage': false,
|
|
12
|
-
};
|
|
13
|
-
// Force production devtool to avoid 35MB eval-source-map chunks
|
|
14
|
-
if (!dev && !isServer) {
|
|
15
|
-
config.devtool = false;
|
|
16
|
-
}
|
|
8
|
+
// Suppress unused optional peer dependencies from UI Kit internals
|
|
9
|
+
['@react-native-async-storage/async-storage', 'viem', 'viem/chains', 'thirdweb', 'thirdweb/chains', 'thirdweb/react', 'thirdweb/deploys', 'thirdweb/storage', 'thirdweb/wallets', 'thirdweb/wallets/in-app', 'thirdweb/extensions/erc20', 'wagmi', '@solana/kit', '@solana/sysvars', '@solana-program/token-2022', 'x402', '@coinbase/wallet-sdk', '@walletconnect/ethereum-provider'].forEach(pkg => { config.resolve.alias[pkg] = false; });
|
|
10
|
+
if (!dev && !isServer) config.devtool = false;
|
|
17
11
|
return config;
|
|
18
12
|
},
|
|
19
13
|
};
|
package/template/package.json
CHANGED
|
@@ -7,7 +7,11 @@ import { useProjects, useTasks, useTeam } from '@/lib/hooks';
|
|
|
7
7
|
import { CommandPalette } from '@varity-labs/ui-kit';
|
|
8
8
|
import { Menu, X } from 'lucide-react';
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// Defensively import Privy/UI-Kit components at runtime (not statically) so the
|
|
11
|
+
// dashboard renders gracefully even if @varity-labs/ui-kit hasn't been installed
|
|
12
|
+
// yet (e.g. during local scaffolding before `npm install` completes).
|
|
13
|
+
// This is intentional — it is NOT a sign that something is broken.
|
|
14
|
+
// See KNOWN_ISSUES.md for details on this pattern.
|
|
11
15
|
let DashboardLayout: any = null;
|
|
12
16
|
let PrivyProtectedRoute: any = null;
|
|
13
17
|
let PrivyStackComponent: any = null;
|
|
@@ -19,7 +23,9 @@ try {
|
|
|
19
23
|
PrivyProtectedRoute = uiKit.PrivyProtectedRoute;
|
|
20
24
|
PrivyStackComponent = uiKit.PrivyStack;
|
|
21
25
|
usePrivyHook = uiKit.usePrivy;
|
|
22
|
-
} catch {
|
|
26
|
+
} catch {
|
|
27
|
+
// ui-kit not installed or not yet available — DashboardShell fallback renders below
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
function RedirectToLogin() {
|
|
25
31
|
const router = useRouter();
|
|
@@ -255,7 +261,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
|
|
|
255
261
|
showSidebar={!isMobile}
|
|
256
262
|
user={{
|
|
257
263
|
name: userName,
|
|
258
|
-
|
|
264
|
+
email: userEmail,
|
|
259
265
|
}}
|
|
260
266
|
onLogout={handleLogout}
|
|
261
267
|
onNavigateToProfile={() => router.push('/dashboard/settings')}
|
|
@@ -556,7 +556,7 @@ export default function SettingsPage() {
|
|
|
556
556
|
)}
|
|
557
557
|
</div>
|
|
558
558
|
|
|
559
|
-
{/*
|
|
559
|
+
{/* Account Settings */}
|
|
560
560
|
{privyAppId && PrivyUserProfile && (
|
|
561
561
|
<div className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
562
562
|
<h2 className="mb-4 text-lg font-semibold text-gray-900 flex items-center gap-2">
|
|
@@ -5,7 +5,7 @@ import './globals.css';
|
|
|
5
5
|
export const metadata: Metadata = {
|
|
6
6
|
title: 'TaskFlow - Project Management',
|
|
7
7
|
description: 'Manage projects, track tasks, and collaborate with your team.',
|
|
8
|
-
metadataBase: new URL('https://
|
|
8
|
+
metadataBase: new URL('https://example.com'),
|
|
9
9
|
openGraph: {
|
|
10
10
|
title: 'TaskFlow - Project Management',
|
|
11
11
|
description: 'Manage projects, track tasks, and collaborate with your team.',
|
|
@@ -31,7 +31,7 @@ export function Footer() {
|
|
|
31
31
|
<div className="mt-8 border-t border-gray-100 pt-6 text-center text-sm text-gray-400">
|
|
32
32
|
© {year} {APP_NAME}. Built with{' '}
|
|
33
33
|
<a
|
|
34
|
-
href="https://varity.so"
|
|
34
|
+
href="https://www.varity.so"
|
|
35
35
|
target="_blank"
|
|
36
36
|
rel="noopener noreferrer"
|
|
37
37
|
className="inline-flex items-center gap-1 text-primary-500 hover:text-primary-600 transition-colors"
|
|
@@ -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
|
+
}
|