create-varity-app 2.0.0-beta.1 → 2.0.0-beta.11

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/create-varity-app)](https://www.npmjs.com/package/create-varity-app)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/varity-labs/varity-sdk/blob/main/LICENSE)
5
5
 
6
- Create production-ready apps with auth, database, and payments built in.
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** 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, testimonials
30
- - **Settings** profile, preferences, security, billing sections
31
- - **6 pages** dashboard, projects, tasks, team, settings, login
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
- │ ├── (auth)/login/ # Login page
40
- │ ├── (dashboard)/ # Protected dashboard pages
41
- │ │ ├── dashboard/ # Overview with KPIs
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 (6 sections)
45
+ │ │ └── settings/ # App settings (4 tabs)
46
+ │ ├── not-found.tsx # 404 page
46
47
  │ └── page.tsx # Landing page
47
- ├── tailwind.config.ts
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](../../cli/varity-mcp/) (`@varity-labs/mcp`) just ask "deploy this project".
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](../../core/varity-sdk/)** Core SDK (database, credentials)
68
- - **[@varity-labs/ui-kit](../../ui/varity-ui-kit/)** React UI components
69
- - **[@varity-labs/mcp](../../cli/varity-mcp/)** MCP server for AI editors (Cursor, Claude Code, VS Code)
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/varity)
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)** Build, deploy, and monetize apps 70% cheaper than AWS.
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) · [GitHub](https://github.com/varity-labs/varity-sdk) · [Discord](https://discord.gg/varity)
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 [Varity Labs](https://varity.so)
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-alpha.1";
11
10
  const WORKSPACE_DEPS = {
12
- "@varity-labs/sdk": VARITY_PACKAGE_VERSION,
13
- "@varity-labs/types": VARITY_PACKAGE_VERSION,
14
- "@varity-labs/ui-kit": VARITY_PACKAGE_VERSION,
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] || VARITY_PACKAGE_VERSION;
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/varity")}`);
138
+ console.log(` Help: ${chalk.underline("https://discord.gg/7vWsdwa2Bg")}`);
140
139
  console.log();
141
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-varity-app",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.11",
4
4
  "description": "Create production-ready apps with auth, database, and payments built in",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,6 @@
1
+
2
+ > my-saas-app@0.1.0 build /home/macoding/varity-workspace/varity-sdk-private/templates/saas-starter
3
+ > next build
4
+
5
+ ▲ Next.js 15.5.11
6
+
@@ -1,6 +1,6 @@
1
1
  # Known Issues — SaaS Starter Template
2
2
 
3
- > **Last Updated:** February 14, 2026
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, testimonials, dashboard mockup |
14
- | Login | Working | Privy email/Google auth, zero-config with dev credentials, auto-redirect |
15
- | Dashboard | Working | KPI cards, getting started checklist, recent activity feed |
16
- | Projects CRUD | Working | Master-detail, nested tasks, CSV export, validation, optimistic updates |
17
- | Tasks CRUD | Working | Status cycling (click to advance), filtering, CSV export, cross-references projects |
18
- | Team CRUD | Working | Invite members, role management, email validation, role badges |
19
- | Settings | Working | 4 tabs (General, Security, Billing, Account), backend persistence via DB Proxy, skeleton loading |
20
- | Command Palette | Working | Cmd+K / Ctrl+K, searches pages and actions |
21
- | Toast Notifications | Working | Success/error/info with progress bar, exit animation, max 3 stack |
22
- | Protected Routes | Working | Automatic redirect for unauthenticated users |
23
- | Color Themes | Working | 4 built-in presets (Blue, Purple, Green, Orange) via CSS variables |
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) |
24
20
  | Static Export | Working | `output: 'export'` for IPFS 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 |
21
+ | Mobile Nav | Working | Responsive sidebar with hamburger menu |
28
22
 
29
23
  ## Known Issues
30
24
 
31
- ### 1. Navigation Flash (UI-Kit Limitation)
32
- Brief "Initializing Dashboard" screen when navigating between dashboard pages. Caused by `PrivyReadyGate` in UI-Kit re-checking auth state on each route change. Resolves in <1 second. This is a UI-Kit issue, not template-side.
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. DashboardLayout Mobile Support
35
- The `DashboardLayout` from `@varity-labs/ui-kit` does not include mobile navigation. The template provides its own responsive sidebar in `src/app/dashboard/layout.tsx` as a workaround. UI-Kit mobile improvements are planned post-MVP.
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. Billing Section is Mock
38
- The Settings > Billing tab shows a mock UI (plan name, usage bars, payment method). Developers should wire their own billing provider (Stripe, etc.).
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. Sessions Are Mock
41
- The Settings > Security tab shows active sessions with a "Revoke" button. Session data is client-side mock real session management is handled by Privy (the auth provider).
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. Password & Profile Managed by Auth Provider
44
- "Change password" and profile photo are managed by Privy (the auth provider). The Settings page shows informational dialogs explaining this.
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. No Server-Side Rendering
47
- All pages are statically exported (`output: 'export'`). No server-side rendering, API routes, or middleware. Data fetching happens client-side via the SDK.
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. Shared development credentials are built in.
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
- Please report issues at: https://github.com/varity-labs/varity-sdk/issues
58
+ - GitHub: https://github.com/varity-labs/varity-sdk/issues
59
+ - Discord: https://discord.gg/7vWsdwa2Bg
@@ -1,8 +1,8 @@
1
1
  # TaskFlow — SaaS Starter Template
2
2
 
3
- [![Built with Varity](https://img.shields.io/badge/built%20with-Varity-7C3AED)](https://varity.so)
3
+ [![Built with Varity](https://img.shields.io/badge/built%20with-Varity-7C3AED)](https://www.varity.so)
4
4
 
5
- A full-featured project management app built with [Varity](https://varity.so). Everything works immediately — no configuration, no API keys, no setup.
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
 
@@ -60,7 +60,7 @@ Switch your entire app's color scheme by editing `src/app/globals.css`:
60
60
  This template works immediately with **zero setup**:
61
61
 
62
62
  ### Instant Auth
63
- - ✅ Email login (Privy)
63
+ - ✅ Email login (built-in)
64
64
  - ✅ Google/Apple social login
65
65
  - ✅ Dev credentials built-in
66
66
  - ❌ No env vars needed
@@ -75,9 +75,9 @@ This template works immediately with **zero setup**:
75
75
  ```bash
76
76
  npm run deploy
77
77
  ```
78
- - ✅ Deploys to IPFS
78
+ - ✅ Deploys to production
79
79
  - ✅ Auto-fetches credentials
80
- - ❌ No thirdweb account needed
80
+ - ❌ No extra accounts needed
81
81
 
82
82
  ---
83
83
 
@@ -103,7 +103,7 @@ This template uses `workspace:^` protocol for Varity packages:
103
103
  - ✅ `output: 'export'` in next.config.js
104
104
  - ✅ All pages pre-rendered to static HTML
105
105
  - ✅ No server-side dependencies
106
- - ✅ IPFS/CDN deployable
106
+ - ✅ CDN deployable
107
107
 
108
108
  ### Type Safety
109
109
  - ✅ TypeScript strict mode enabled
@@ -215,8 +215,7 @@ The database collection is created automatically on first use — no migrations
215
215
 
216
216
  | Variable | Required | Notes |
217
217
  |----------|----------|-------|
218
- | `NEXT_PUBLIC_PRIVY_APP_ID` | No | Auth provider (auto-configured) |
219
- | `NEXT_PUBLIC_THIRDWEB_CLIENT_ID` | No | Infrastructure (auto-configured) |
218
+ | `NEXT_PUBLIC_VARITY_AUTH_ID` | No | Auth provider (auto-configured) |
220
219
  | `NEXT_PUBLIC_VARITY_APP_TOKEN` | No | Database token (auto-configured) |
221
220
  | `NEXT_PUBLIC_VARITY_APP_ID` | No | App ID (auto-configured) |
222
221
 
@@ -113,8 +113,8 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
113
113
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
114
114
  const privy = usePrivyHook ? usePrivyHook() : { user: null, logout: async () => {} };
115
115
  const { user, logout } = privy;
116
- const pathname = usePathname();
117
116
  const router = useRouter();
117
+ const pathname = usePathname();
118
118
  const isMobile = useIsMobile();
119
119
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
120
120
  const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
@@ -154,7 +154,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
154
154
 
155
155
  const handleLogout = async () => {
156
156
  await logout();
157
- router.push('/');
157
+ window.location.href = '/';
158
158
  };
159
159
 
160
160
  // Fallback layout when DashboardLayout from ui-kit isn't available
@@ -257,6 +257,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
257
257
  name: userName,
258
258
  address: userEmail,
259
259
  }}
260
+ onNavigate={(path: string) => router.push(path)}
260
261
  onLogout={handleLogout}
261
262
  onNavigateToProfile={() => router.push('/dashboard/settings')}
262
263
  onNavigateToSettings={() => router.push('/dashboard/settings')}
@@ -288,7 +289,7 @@ export default function DashboardRootLayout({
288
289
  appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
289
290
  thirdwebClientId={process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID}
290
291
  loginMethods={['email', 'google']}
291
- appearance={{ theme: 'light', accentColor: '#2563EB', logo: '/logo.svg' }}
292
+ appearance={{ theme: 'light', accentColor: '#2563EB' }}
292
293
  >
293
294
  <PrivyProtectedRoute fallback={<RedirectToLogin />}>
294
295
  <DashboardShell>{children}</DashboardShell>
@@ -9,7 +9,6 @@ import { FolderKanban, ListTodo, Users, ArrowRight } from 'lucide-react';
9
9
 
10
10
  function QuickActions() {
11
11
  const router = useRouter();
12
-
13
12
  const actions = [
14
13
  {
15
14
  label: 'New Project',
@@ -138,7 +137,6 @@ function GettingStarted({
138
137
  }
139
138
 
140
139
  export default function DashboardPage() {
141
- const router = useRouter();
142
140
  const { name } = useCurrentUser();
143
141
  const { data: projects, loading: projectsLoading, error: projectsError, refresh: refreshProjects } = useProjects();
144
142
  const { data: tasks, loading: tasksLoading, error: tasksError, refresh: refreshTasks } = useTasks();
@@ -163,14 +161,14 @@ export default function DashboardPage() {
163
161
  </p>
164
162
  </div>
165
163
 
166
- {error && (
167
- <div className="flex items-center justify-between rounded-lg border border-red-200 bg-red-50 px-4 py-3">
168
- <p className="text-sm text-red-700">Failed to load data. Please check your connection and try again.</p>
164
+ {error && !loading && (
165
+ <div className="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
166
+ <p className="text-sm text-amber-700">Syncing your data...</p>
169
167
  <button
170
168
  onClick={() => { refreshProjects(); refreshTasks(); refreshTeam(); }}
171
- className="text-sm font-medium text-red-700 hover:text-red-800 underline"
169
+ className="text-sm font-medium text-amber-700 hover:text-amber-800 underline"
172
170
  >
173
- Retry
171
+ Refresh
174
172
  </button>
175
173
  </div>
176
174
  )}
@@ -606,10 +606,10 @@ export default function ProjectsPage() {
606
606
  </div>
607
607
  </Dialog>
608
608
 
609
- {error && (
610
- <div className="flex items-center justify-between rounded-lg border border-red-200 bg-red-50 px-4 py-3">
611
- <p className="text-sm text-red-700">Failed to load projects. Please check your connection and try again.</p>
612
- <button onClick={refresh} className="text-sm font-medium text-red-700 hover:text-red-800 underline">Retry</button>
609
+ {error && !loading && (
610
+ <div className="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
611
+ <p className="text-sm text-amber-700">Syncing your projects...</p>
612
+ <button onClick={refresh} className="text-sm font-medium text-amber-700 hover:text-amber-800 underline">Refresh</button>
613
613
  </div>
614
614
  )}
615
615
 
@@ -258,10 +258,10 @@ export default function TasksPage() {
258
258
  )}
259
259
  </div>
260
260
 
261
- {error && (
262
- <div className="flex items-center justify-between rounded-lg border border-red-200 bg-red-50 px-4 py-3">
263
- <p className="text-sm text-red-700">Failed to load tasks. Please check your connection and try again.</p>
264
- <button onClick={refresh} className="text-sm font-medium text-red-700 hover:text-red-800 underline">Retry</button>
261
+ {error && !loading && (
262
+ <div className="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
263
+ <p className="text-sm text-amber-700">Syncing your tasks...</p>
264
+ <button onClick={refresh} className="text-sm font-medium text-amber-700 hover:text-amber-800 underline">Refresh</button>
265
265
  </div>
266
266
  )}
267
267
 
@@ -246,10 +246,10 @@ export default function TeamPage() {
246
246
  loading={removeSubmitting}
247
247
  />
248
248
 
249
- {error && (
250
- <div className="flex items-center justify-between rounded-lg border border-red-200 bg-red-50 px-4 py-3">
251
- <p className="text-sm text-red-700">Failed to load team data. Please check your connection and try again.</p>
252
- <button onClick={refresh} className="text-sm font-medium text-red-700 hover:text-red-800 underline">Retry</button>
249
+ {error && !loading && (
250
+ <div className="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
251
+ <p className="text-sm text-amber-700">Syncing your team data...</p>
252
+ <button onClick={refresh} className="text-sm font-medium text-amber-700 hover:text-amber-800 underline">Refresh</button>
253
253
  </div>
254
254
  )}
255
255
 
@@ -1,20 +1,23 @@
1
1
  import type { Metadata } from 'next';
2
2
  import { Providers } from '@/components/providers';
3
+ import { APP_NAME } from '@/lib/constants';
3
4
  import './globals.css';
4
5
 
6
+ const description = 'Built with Varity — auth, database, and deployment included.';
7
+
5
8
  export const metadata: Metadata = {
6
- title: 'TaskFlow - Project Management',
7
- description: 'Manage projects, track tasks, and collaborate with your team.',
8
- metadataBase: new URL('https://example.com'),
9
+ title: APP_NAME,
10
+ description,
11
+ metadataBase: new URL('https://varity.app'),
9
12
  openGraph: {
10
- title: 'TaskFlow - Project Management',
11
- description: 'Manage projects, track tasks, and collaborate with your team.',
13
+ title: APP_NAME,
14
+ description,
12
15
  type: 'website',
13
16
  },
14
17
  twitter: {
15
18
  card: 'summary',
16
- title: 'TaskFlow - Project Management',
17
- description: 'Manage projects, track tasks, and collaborate with your team.',
19
+ title: APP_NAME,
20
+ description,
18
21
  },
19
22
  };
20
23
 
@@ -15,6 +15,12 @@ try {
15
15
  usePrivyHook = uiKit.usePrivy;
16
16
  } catch {}
17
17
 
18
+ function loginButtonLabel(privy: { ready: boolean; authenticated: boolean }): string {
19
+ if (!privy.ready) return 'Loading...';
20
+ if (privy.authenticated) return 'Already Signed In';
21
+ return 'Sign In with Email or Social';
22
+ }
23
+
18
24
  function LoginContent() {
19
25
  const router = useRouter();
20
26
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
@@ -55,11 +61,7 @@ function LoginContent() {
55
61
  disabled={!privy.ready || privy.authenticated}
56
62
  className="w-full px-6 py-3 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors shadow-sm"
57
63
  >
58
- {!privy.ready
59
- ? 'Loading...'
60
- : privy.authenticated
61
- ? 'Already Signed In'
62
- : 'Sign In with Email or Social'}
64
+ {loginButtonLabel(privy)}
63
65
  </button>
64
66
  ) : (
65
67
  <div className="text-center space-y-4">
@@ -79,20 +81,18 @@ function LoginContent() {
79
81
  }
80
82
 
81
83
  export default function LoginPage() {
82
- // Always wrap in PrivyStack - it uses dev credentials automatically when no appId is provided
83
84
  if (PrivyStackComponent) {
84
85
  return (
85
86
  <PrivyStackComponent
86
87
  appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
87
88
  thirdwebClientId={process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID}
88
89
  loginMethods={['email', 'google']}
89
- appearance={{ theme: 'light', accentColor: '#2563EB', logo: '/logo.svg' }}
90
+ appearance={{ theme: 'light', accentColor: '#2563EB' }}
90
91
  >
91
92
  <LoginContent />
92
93
  </PrivyStackComponent>
93
94
  );
94
95
  }
95
96
 
96
- // Fallback if ui-kit package isn't installed
97
97
  return <LoginContent />;
98
98
  }
@@ -1,5 +1,3 @@
1
- 'use client';
2
-
3
1
  import Link from 'next/link';
4
2
 
5
3
  export default function NotFound() {
@@ -2,7 +2,6 @@ import { Navbar } from '@/components/shared/Navbar';
2
2
  import { Hero } from '@/components/landing/Hero';
3
3
  import { Features } from '@/components/landing/Features';
4
4
  import { HowItWorks } from '@/components/landing/HowItWorks';
5
- import { Testimonials } from '@/components/landing/Testimonials';
6
5
  import { Pricing } from '@/components/landing/Pricing';
7
6
  import { CTA } from '@/components/landing/CTA';
8
7
  import { Footer } from '@/components/shared/Footer';
@@ -14,7 +13,7 @@ export default function HomePage() {
14
13
  <Hero />
15
14
  <Features />
16
15
  <HowItWorks />
17
- <Testimonials />
16
+
18
17
  <Pricing />
19
18
  <CTA />
20
19
  <Footer />
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import Link from 'next/link';
4
- import { DataTable } from '@varity-labs/ui-kit';
5
- import { TaskStatusBadge, PriorityBadge } from '@varity-labs/ui-kit';
4
+ import { DataTable, TaskStatusBadge, PriorityBadge } from '@varity-labs/ui-kit';
6
5
  import { formatRelativeDate } from '@/lib/utils';
7
6
  import { ArrowRight } from 'lucide-react';
8
7
  import type { Task } from '@/types';
@@ -1,3 +1,4 @@
1
+ import Link from 'next/link';
1
2
  import { Check } from 'lucide-react';
2
3
 
3
4
  const plans = [
@@ -52,6 +53,12 @@ const plans = [
52
53
  },
53
54
  ];
54
55
 
56
+ function buttonStyle(plan: (typeof plans)[number]): string {
57
+ if (plan.popular) return 'bg-primary-600 text-white hover:bg-primary-700';
58
+ if (plan.price === 0) return 'bg-gray-100 text-gray-900 hover:bg-gray-200';
59
+ return 'bg-gray-900 text-white hover:bg-gray-800';
60
+ }
61
+
55
62
  export function Pricing() {
56
63
  return (
57
64
  <section id="pricing" className="py-24 bg-white">
@@ -102,18 +109,12 @@ export function Pricing() {
102
109
  </ul>
103
110
 
104
111
  <div className="mt-8">
105
- <a
112
+ <Link
106
113
  href="/login"
107
- className={`block w-full rounded-lg py-3 text-center font-medium transition-colors ${
108
- plan.popular
109
- ? 'bg-primary-600 text-white hover:bg-primary-700'
110
- : plan.price === 0
111
- ? 'bg-gray-100 text-gray-900 hover:bg-gray-200'
112
- : 'bg-gray-900 text-white hover:bg-gray-800'
113
- }`}
114
+ className={`block w-full rounded-lg py-3 text-center font-medium transition-colors ${buttonStyle(plan)}`}
114
115
  >
115
116
  {plan.cta}
116
- </a>
117
+ </Link>
117
118
  </div>
118
119
  </div>
119
120
  ))}
@@ -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
  &copy; {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"
@@ -46,7 +46,6 @@ export function Navbar() {
46
46
  >
47
47
  Get Started
48
48
  </Link>
49
- {/* Mobile menu toggle */}
50
49
  <button
51
50
  onClick={() => setMobileOpen(!mobileOpen)}
52
51
  className="rounded-lg p-2 text-gray-600 hover:bg-gray-100 sm:hidden"
@@ -58,7 +57,6 @@ export function Navbar() {
58
57
  </div>
59
58
  </div>
60
59
 
61
- {/* Mobile dropdown */}
62
60
  {mobileOpen && (
63
61
  <div className="border-t border-gray-100 bg-white px-4 pb-4 pt-2 sm:hidden">
64
62
  <div className="space-y-1">
@@ -1,6 +1,6 @@
1
1
  import type { NavigationItem } from '@varity-labs/ui-kit';
2
2
 
3
- export const APP_NAME = 'TaskFlow';
3
+ export const APP_NAME = 'My App';
4
4
 
5
5
  export const NAVIGATION_ITEMS: NavigationItem[] = [
6
6
  { label: 'Dashboard', icon: 'dashboard', path: '/dashboard' },
@@ -10,6 +10,18 @@ try {
10
10
  usePrivyHook = uiKit.usePrivy;
11
11
  } catch {}
12
12
 
13
+ async function fetchWithRetry<T>(fn: () => Promise<T>, retries = 3, delay = 1500): Promise<T> {
14
+ for (let i = 0; i < retries; i++) {
15
+ try {
16
+ return await fn();
17
+ } catch (err) {
18
+ if (i === retries - 1) throw err;
19
+ await new Promise((r) => setTimeout(r, delay));
20
+ }
21
+ }
22
+ throw new Error('Unexpected');
23
+ }
24
+
13
25
  export function useCurrentUser() {
14
26
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
15
27
  const privy = usePrivyHook ? usePrivyHook() : { user: null, authenticated: false, logout: async () => {} };
@@ -54,7 +66,7 @@ export function useProjects(): UseCollectionReturn<Project> {
54
66
  try {
55
67
  setLoading(true);
56
68
  setError(null);
57
- const result = await projects().get();
69
+ const result = await fetchWithRetry(() => projects().get());
58
70
  setData(result as Project[]);
59
71
  } catch (err) {
60
72
  setError(err instanceof Error ? err.message : 'Failed to load projects');
@@ -126,7 +138,7 @@ export function useTasks(projectId?: string): UseCollectionReturn<Task> {
126
138
  try {
127
139
  setLoading(true);
128
140
  setError(null);
129
- const result = await tasks().get();
141
+ const result = await fetchWithRetry(() => tasks().get());
130
142
  setAllTasks(result as Task[]);
131
143
  } catch (err) {
132
144
  setError(err instanceof Error ? err.message : 'Failed to load tasks');
@@ -202,7 +214,7 @@ export function useTeam(): UseCollectionReturn<TeamMember> {
202
214
  try {
203
215
  setLoading(true);
204
216
  setError(null);
205
- const result = await teamMembers().get();
217
+ const result = await fetchWithRetry(() => teamMembers().get());
206
218
  setData(result as TeamMember[]);
207
219
  } catch (err) {
208
220
  setError(err instanceof Error ? err.message : 'Failed to load team');
@@ -289,7 +301,7 @@ export function useUserSettings() {
289
301
  try {
290
302
  setLoading(true);
291
303
  setError(null);
292
- const all = await userSettings().get();
304
+ const all = await fetchWithRetry(() => userSettings().get());
293
305
  const mine = (all as UserSettings[]).find((s) => s.user_id === userId);
294
306
  if (mine) {
295
307
  setSettings(mine);
@@ -1,589 +0,0 @@
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
- }