create-varity-app 2.0.0-beta.10 → 2.0.0-beta.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-varity-app",
3
- "version": "2.0.0-beta.10",
3
+ "version": "2.0.0-beta.12",
4
4
  "description": "Create production-ready apps with auth, database, and payments built in",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
- import { usePathname } from 'next/navigation';
5
- import { appNavigate } from '@/lib/utils';
4
+ import { useRouter, usePathname } from 'next/navigation';
6
5
  import { APP_NAME, NAVIGATION_ITEMS } from '@/lib/constants';
7
6
  import { useProjects, useTasks, useTeam } from '@/lib/hooks';
8
7
  import { CommandPalette } from '@varity-labs/ui-kit';
@@ -23,9 +22,10 @@ try {
23
22
  } catch {}
24
23
 
25
24
  function RedirectToLogin() {
25
+ const router = useRouter();
26
26
  useEffect(() => {
27
- appNavigate('/login/');
28
- }, []);
27
+ router.push('/login');
28
+ }, [router]);
29
29
  return null;
30
30
  }
31
31
 
@@ -114,6 +114,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
114
114
  const privy = usePrivyHook ? usePrivyHook() : { user: null, logout: async () => {} };
115
115
  const { user, logout } = privy;
116
116
  const pathname = usePathname();
117
+ const router = useRouter();
117
118
  const isMobile = useIsMobile();
118
119
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
119
120
  const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
@@ -153,7 +154,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
153
154
 
154
155
  const handleLogout = async () => {
155
156
  await logout();
156
- appNavigate('/');
157
+ router.push('/');
157
158
  };
158
159
 
159
160
  // Fallback layout when DashboardLayout from ui-kit isn't available
@@ -163,7 +164,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
163
164
  <CommandPalette
164
165
  open={commandPaletteOpen}
165
166
  onClose={() => setCommandPaletteOpen(false)}
166
- onNavigate={(path: string) => appNavigate(path.endsWith('/') ? path : path + '/')}
167
+ onNavigate={(path: string) => router.push(path)}
167
168
  projects={projects}
168
169
  tasks={tasks}
169
170
  team={team}
@@ -180,7 +181,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
180
181
  {navWithActive.map((item) => (
181
182
  <button
182
183
  key={item.path}
183
- onClick={() => appNavigate(item.path + '/')}
184
+ onClick={() => router.push(item.path)}
184
185
  className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
185
186
  item.active
186
187
  ? 'bg-primary-50 text-primary-700'
@@ -210,7 +211,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
210
211
  navItems={navWithActive}
211
212
  userEmail={userEmail}
212
213
  onLogout={handleLogout}
213
- onNavigate={(path) => appNavigate(path.endsWith('/') ? path : path + '/')}
214
+ onNavigate={(path) => router.push(path)}
214
215
  />
215
216
  )}
216
217
 
@@ -227,7 +228,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
227
228
  <CommandPalette
228
229
  open={commandPaletteOpen}
229
230
  onClose={() => setCommandPaletteOpen(false)}
230
- onNavigate={(path: string) => appNavigate(path.endsWith('/') ? path : path + '/')}
231
+ onNavigate={(path: string) => router.push(path)}
231
232
  projects={projects}
232
233
  tasks={tasks}
233
234
  team={team}
@@ -241,7 +242,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
241
242
  navItems={navWithActive}
242
243
  userEmail={userEmail}
243
244
  onLogout={handleLogout}
244
- onNavigate={(path) => appNavigate(path.endsWith('/') ? path : path + '/')}
245
+ onNavigate={(path) => router.push(path)}
245
246
  />
246
247
  )}
247
248
 
@@ -256,10 +257,9 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
256
257
  name: userName,
257
258
  address: userEmail,
258
259
  }}
259
- onNavigate={(path: string) => appNavigate(path.endsWith('/') ? path : path + '/')}
260
260
  onLogout={handleLogout}
261
- onNavigateToProfile={() => appNavigate('/dashboard/settings/')}
262
- onNavigateToSettings={() => appNavigate('/dashboard/settings/')}
261
+ onNavigateToProfile={() => router.push('/dashboard/settings')}
262
+ onNavigateToSettings={() => router.push('/dashboard/settings')}
263
263
  onSearchClick={() => setCommandPaletteOpen(true)}
264
264
  searchPlaceholder="Search projects, tasks, team..."
265
265
  >
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { appNavigate } from '@/lib/utils';
3
+ import { useRouter } from 'next/navigation';
4
4
  import { useProjects, useTasks, useTeam, useCurrentUser } from '@/lib/hooks';
5
5
  import { DashboardStats } from '@/components/dashboard/DashboardStats';
6
6
  import { RecentActivity } from '@/components/dashboard/RecentActivity';
@@ -8,7 +8,8 @@ import { APP_NAME } from '@/lib/constants';
8
8
  import { FolderKanban, ListTodo, Users, ArrowRight } from 'lucide-react';
9
9
 
10
10
  function QuickActions() {
11
-
11
+ const router = useRouter();
12
+
12
13
  const actions = [
13
14
  {
14
15
  label: 'New Project',
@@ -48,7 +49,7 @@ function QuickActions() {
48
49
  {actions.map((action) => (
49
50
  <button
50
51
  key={action.path}
51
- onClick={() => appNavigate(action.path + '/')}
52
+ onClick={() => router.push(action.path)}
52
53
  className="flex items-center gap-3 rounded-xl border border-gray-200 bg-white p-4 text-left shadow-sm hover:shadow-md hover:border-gray-300 transition-all"
53
54
  >
54
55
  <div className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${action.color}`}>
@@ -73,7 +74,8 @@ function GettingStarted({
73
74
  hasTasks: boolean;
74
75
  hasTeam: boolean;
75
76
  }) {
76
- const completedCount = [hasProjects, hasTasks, hasTeam].filter(Boolean).length;
77
+ const router = useRouter();
78
+ const completedCount = [hasProjects, hasTasks, hasTeam].filter(Boolean).length;
77
79
 
78
80
  if (completedCount === 3) return null;
79
81
 
@@ -111,7 +113,7 @@ function GettingStarted({
111
113
  {steps.map((step) => (
112
114
  <button
113
115
  key={step.label}
114
- onClick={() => !step.done && appNavigate(step.path + '/')}
116
+ onClick={() => !step.done && router.push(step.path)}
115
117
  disabled={step.done}
116
118
  className={`flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors ${
117
119
  step.done
@@ -136,7 +138,8 @@ function GettingStarted({
136
138
  }
137
139
 
138
140
  export default function DashboardPage() {
139
- const { name } = useCurrentUser();
141
+ const router = useRouter();
142
+ const { name } = useCurrentUser();
140
143
  const { data: projects, loading: projectsLoading, error: projectsError, refresh: refreshProjects } = useProjects();
141
144
  const { data: tasks, loading: tasksLoading, error: tasksError, refresh: refreshTasks } = useTasks();
142
145
  const { data: team, loading: teamLoading, error: teamError, refresh: refreshTeam } = useTeam();
@@ -160,14 +163,14 @@ export default function DashboardPage() {
160
163
  </p>
161
164
  </div>
162
165
 
163
- {error && !loading && (
164
- <div className="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3">
165
- <p className="text-sm text-amber-700">Syncing your data...</p>
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>
166
169
  <button
167
170
  onClick={() => { refreshProjects(); refreshTasks(); refreshTeam(); }}
168
- className="text-sm font-medium text-amber-700 hover:text-amber-800 underline"
171
+ className="text-sm font-medium text-red-700 hover:text-red-800 underline"
169
172
  >
170
- Refresh
173
+ Retry
171
174
  </button>
172
175
  </div>
173
176
  )}
@@ -606,10 +606,10 @@ export default function ProjectsPage() {
606
606
  </div>
607
607
  </Dialog>
608
608
 
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>
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>
613
613
  </div>
614
614
  )}
615
615
 
@@ -258,10 +258,10 @@ export default function TasksPage() {
258
258
  )}
259
259
  </div>
260
260
 
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>
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>
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 && !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>
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>
253
253
  </div>
254
254
  )}
255
255
 
@@ -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://varity.app'),
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.',
@@ -25,9 +25,6 @@ export default function RootLayout({
25
25
  }) {
26
26
  return (
27
27
  <html lang="en">
28
- <head>
29
- <base href="./" />
30
- </head>
31
28
  <body>
32
29
  <Providers>{children}</Providers>
33
30
  </body>
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect } from 'react';
4
- import { appNavigate } from '@/lib/utils';
4
+ import { useRouter } from 'next/navigation';
5
+ import Link from 'next/link';
5
6
  import { CheckCircle } from 'lucide-react';
6
7
  import { APP_NAME } from '@/lib/constants';
7
8
 
@@ -15,14 +16,15 @@ try {
15
16
  } catch {}
16
17
 
17
18
  function LoginContent() {
19
+ const router = useRouter();
18
20
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
19
21
  const privy = usePrivyHook ? usePrivyHook() : null;
20
22
 
21
23
  useEffect(() => {
22
24
  if (privy?.authenticated) {
23
- appNavigate('/dashboard/');
25
+ router.push('/dashboard');
24
26
  }
25
- }, [privy?.authenticated]);
27
+ }, [privy?.authenticated, router]);
26
28
 
27
29
  const handleLogin = () => {
28
30
  if (privy?.login) {
@@ -34,10 +36,10 @@ function LoginContent() {
34
36
  <div className="flex min-h-screen items-center justify-center bg-gray-50 px-4">
35
37
  <div className="w-full max-w-md space-y-8">
36
38
  <div className="text-center">
37
- <a href="/" onClick={(e) => { e.preventDefault(); appNavigate('/'); }} className="inline-flex items-center gap-2">
39
+ <Link href="/" className="inline-flex items-center gap-2">
38
40
  <CheckCircle className="h-8 w-8 text-primary-600" />
39
41
  <span className="text-2xl font-bold text-gray-900">{APP_NAME}</span>
40
- </a>
42
+ </Link>
41
43
  <h2 className="mt-6 text-2xl font-bold text-gray-900">
42
44
  Sign in to your account
43
45
  </h2>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { appNavigate } from '@/lib/utils';
3
+ import Link from 'next/link';
4
4
 
5
5
  export default function NotFound() {
6
6
  return (
@@ -8,13 +8,12 @@ export default function NotFound() {
8
8
  <div className="text-center">
9
9
  <h1 className="text-6xl font-bold text-gray-900 mb-4">404</h1>
10
10
  <p className="text-xl text-gray-600 mb-8">Page not found</p>
11
- <a
11
+ <Link
12
12
  href="/"
13
- onClick={(e) => { e.preventDefault(); appNavigate('/'); }}
14
13
  className="inline-flex items-center gap-2 rounded-lg bg-primary-600 px-6 py-3 text-base font-medium text-white hover:bg-primary-700 transition-all"
15
14
  >
16
15
  Go Home
17
- </a>
16
+ </Link>
18
17
  </div>
19
18
  </div>
20
19
  );
@@ -2,7 +2,7 @@ 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
-
5
+ import { Testimonials } from '@/components/landing/Testimonials';
6
6
  import { Pricing } from '@/components/landing/Pricing';
7
7
  import { CTA } from '@/components/landing/CTA';
8
8
  import { Footer } from '@/components/shared/Footer';
@@ -14,7 +14,7 @@ export default function HomePage() {
14
14
  <Hero />
15
15
  <Features />
16
16
  <HowItWorks />
17
-
17
+ <Testimonials />
18
18
  <Pricing />
19
19
  <CTA />
20
20
  <Footer />
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { appNavigate } from '@/lib/utils';
3
+ import Link from 'next/link';
4
4
  import { DataTable } from '@varity-labs/ui-kit';
5
5
  import { TaskStatusBadge, PriorityBadge } from '@varity-labs/ui-kit';
6
6
  import { formatRelativeDate } from '@/lib/utils';
@@ -43,14 +43,13 @@ export function RecentActivity({ tasks, loading = false }: RecentActivityProps)
43
43
  Recent Activity
44
44
  </h3>
45
45
  {tasks.length > 5 && (
46
- <a
47
- href="/dashboard/tasks/"
48
- onClick={(e) => { e.preventDefault(); appNavigate('/dashboard/tasks/'); }}
46
+ <Link
47
+ href="/dashboard/tasks"
49
48
  className="flex items-center gap-1 text-sm font-medium text-primary-600 hover:text-primary-700 transition-colors"
50
49
  >
51
50
  View all tasks
52
51
  <ArrowRight className="h-4 w-4" />
53
- </a>
52
+ </Link>
54
53
  )}
55
54
  </div>
56
55
  <DataTable
@@ -1,6 +1,4 @@
1
- 'use client';
2
-
3
- import { appNavigate } from '@/lib/utils';
1
+ import Link from 'next/link';
4
2
  import { ArrowRight, CheckCircle2 } from 'lucide-react';
5
3
  import { APP_NAME } from '@/lib/constants';
6
4
 
@@ -30,14 +28,13 @@ export function CTA() {
30
28
  ))}
31
29
  </div>
32
30
  <div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
33
- <a
34
- href="/login/"
35
- onClick={(e) => { e.preventDefault(); appNavigate('/login/'); }}
31
+ <Link
32
+ href="/login"
36
33
  className="group inline-flex items-center gap-2 rounded-lg bg-white px-6 py-3 text-base font-medium text-gray-900 shadow-lg hover:bg-gray-100 transition-all"
37
34
  >
38
35
  Get Started Free
39
36
  <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
40
- </a>
37
+ </Link>
41
38
  </div>
42
39
  </div>
43
40
  </section>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { appNavigate } from '@/lib/utils';
3
+ import Link from 'next/link';
4
4
  import { ArrowRight, BarChart3, Shield, Zap, CheckCircle2 } from 'lucide-react';
5
5
  import { APP_NAME } from '@/lib/constants';
6
6
 
@@ -97,14 +97,13 @@ export function Hero() {
97
97
  Stop juggling spreadsheets and start delivering on time.
98
98
  </p>
99
99
  <div className="animate-fade-in-up-delay-2 mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
100
- <a
101
- href="/login/"
102
- onClick={(e) => { e.preventDefault(); appNavigate('/login/'); }}
100
+ <Link
101
+ href="/login"
103
102
  className="group inline-flex items-center gap-2 rounded-lg bg-primary-600 px-6 py-3 text-base font-medium text-white shadow-lg shadow-primary-200 hover:bg-primary-700 hover:shadow-xl hover:shadow-primary-200/50 transition-all"
104
103
  >
105
104
  Start Free
106
105
  <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
107
- </a>
106
+ </Link>
108
107
  <a
109
108
  href="#how-it-works"
110
109
  className="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-6 py-3 text-base font-medium text-gray-700 hover:bg-gray-50 hover:border-gray-400 transition-all"
@@ -1,6 +1,3 @@
1
- 'use client';
2
-
3
- import { appNavigate } from '@/lib/utils';
4
1
  import { Check } from 'lucide-react';
5
2
 
6
3
  const plans = [
@@ -106,8 +103,7 @@ export function Pricing() {
106
103
 
107
104
  <div className="mt-8">
108
105
  <a
109
- href="/login/"
110
- onClick={(e) => { e.preventDefault(); appNavigate('/login/'); }}
106
+ href="/login"
111
107
  className={`block w-full rounded-lg py-3 text-center font-medium transition-colors ${
112
108
  plan.popular
113
109
  ? 'bg-primary-600 text-white hover:bg-primary-700'
@@ -1,6 +1,4 @@
1
- 'use client';
2
-
3
- import { appNavigate } from '@/lib/utils';
1
+ import Link from 'next/link';
4
2
  import { CheckCircle } from 'lucide-react';
5
3
  import { APP_NAME } from '@/lib/constants';
6
4
 
@@ -25,9 +23,9 @@ export function Footer() {
25
23
  <a href="#pricing" className="hover:text-gray-700 transition-colors">
26
24
  Pricing
27
25
  </a>
28
- <a href="/login/" onClick={(e) => { e.preventDefault(); appNavigate('/login/'); }} className="hover:text-gray-700 transition-colors">
26
+ <Link href="/login" className="hover:text-gray-700 transition-colors">
29
27
  Sign In
30
- </a>
28
+ </Link>
31
29
  </div>
32
30
  </div>
33
31
  <div className="mt-8 border-t border-gray-100 pt-6 text-center text-sm text-gray-400">
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import Link from 'next/link';
4
5
  import { CheckCircle, Menu, X } from 'lucide-react';
5
6
  import { APP_NAME } from '@/lib/constants';
6
- import { appNavigate } from '@/lib/utils';
7
7
 
8
8
  const navLinks = [
9
9
  { label: 'Features', href: '#features' },
@@ -18,10 +18,10 @@ export function Navbar() {
18
18
  <nav className="sticky top-0 z-40 border-b border-gray-200 bg-white/80 backdrop-blur-md">
19
19
  <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
20
20
  <div className="flex h-16 items-center justify-between">
21
- <a href="/" onClick={(e) => { e.preventDefault(); appNavigate('/'); }} className="flex items-center gap-2">
21
+ <Link href="/" className="flex items-center gap-2">
22
22
  <CheckCircle className="h-7 w-7 text-primary-600" />
23
23
  <span className="text-xl font-bold text-gray-900">{APP_NAME}</span>
24
- </a>
24
+ </Link>
25
25
  <div className="hidden items-center gap-8 sm:flex">
26
26
  {navLinks.map((link) => (
27
27
  <a
@@ -34,20 +34,18 @@ export function Navbar() {
34
34
  ))}
35
35
  </div>
36
36
  <div className="flex items-center gap-3">
37
- <a
38
- href="/login/"
39
- onClick={(e) => { e.preventDefault(); appNavigate('/login/'); }}
37
+ <Link
38
+ href="/login"
40
39
  className="hidden text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors sm:block"
41
40
  >
42
41
  Sign In
43
- </a>
44
- <a
45
- href="/login/"
46
- onClick={(e) => { e.preventDefault(); appNavigate('/login/'); }}
42
+ </Link>
43
+ <Link
44
+ href="/login"
47
45
  className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700 transition-colors"
48
46
  >
49
47
  Get Started
50
- </a>
48
+ </Link>
51
49
  {/* Mobile menu toggle */}
52
50
  <button
53
51
  onClick={() => setMobileOpen(!mobileOpen)}
@@ -74,13 +72,13 @@ export function Navbar() {
74
72
  {link.label}
75
73
  </a>
76
74
  ))}
77
- <a
78
- href="/login/"
79
- onClick={(e) => { e.preventDefault(); setMobileOpen(false); appNavigate('/login/'); }}
75
+ <Link
76
+ href="/login"
77
+ onClick={() => setMobileOpen(false)}
80
78
  className="block rounded-lg px-3 py-2.5 text-sm font-medium text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-colors"
81
79
  >
82
80
  Sign In
83
- </a>
81
+ </Link>
84
82
  </div>
85
83
  </div>
86
84
  )}
@@ -10,18 +10,6 @@ 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
-
25
13
  export function useCurrentUser() {
26
14
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
27
15
  const privy = usePrivyHook ? usePrivyHook() : { user: null, authenticated: false, logout: async () => {} };
@@ -66,7 +54,7 @@ export function useProjects(): UseCollectionReturn<Project> {
66
54
  try {
67
55
  setLoading(true);
68
56
  setError(null);
69
- const result = await fetchWithRetry(() => projects().get());
57
+ const result = await projects().get();
70
58
  setData(result as Project[]);
71
59
  } catch (err) {
72
60
  setError(err instanceof Error ? err.message : 'Failed to load projects');
@@ -138,7 +126,7 @@ export function useTasks(projectId?: string): UseCollectionReturn<Task> {
138
126
  try {
139
127
  setLoading(true);
140
128
  setError(null);
141
- const result = await fetchWithRetry(() => tasks().get());
129
+ const result = await tasks().get();
142
130
  setAllTasks(result as Task[]);
143
131
  } catch (err) {
144
132
  setError(err instanceof Error ? err.message : 'Failed to load tasks');
@@ -214,7 +202,7 @@ export function useTeam(): UseCollectionReturn<TeamMember> {
214
202
  try {
215
203
  setLoading(true);
216
204
  setError(null);
217
- const result = await fetchWithRetry(() => teamMembers().get());
205
+ const result = await teamMembers().get();
218
206
  setData(result as TeamMember[]);
219
207
  } catch (err) {
220
208
  setError(err instanceof Error ? err.message : 'Failed to load team');
@@ -301,7 +289,7 @@ export function useUserSettings() {
301
289
  try {
302
290
  setLoading(true);
303
291
  setError(null);
304
- const all = await fetchWithRetry(() => userSettings().get());
292
+ const all = await userSettings().get();
305
293
  const mine = (all as UserSettings[]).find((s) => s.user_id === userId);
306
294
  if (mine) {
307
295
  setSettings(mine);
@@ -44,27 +44,6 @@ export function cn(...classes: (string | false | undefined | null)[]): string {
44
44
  return classes.filter(Boolean).join(' ');
45
45
  }
46
46
 
47
- /**
48
- * Detect the app's base path at runtime from the current URL.
49
- * Works on any host: localhost, varity.app/saas-template, ipfs.io/ipfs/QmX, etc.
50
- */
51
- export function getAppBase(): string {
52
- if (typeof window === 'undefined') return '';
53
- const path = window.location.pathname;
54
- const routes = ['/login', '/dashboard/projects', '/dashboard/settings', '/dashboard/tasks', '/dashboard/team', '/dashboard'];
55
- for (const route of routes) {
56
- const idx = path.indexOf(route);
57
- if (idx > 0) return path.substring(0, idx);
58
- }
59
- // On landing page — pathname is the base (strip trailing slash)
60
- return path.replace(/\/+$/, '');
61
- }
62
-
63
- /** Navigate to an absolute app path (e.g. '/login/', '/dashboard/'). */
64
- export function appNavigate(absolutePath: string): void {
65
- window.location.href = getAppBase() + absolutePath;
66
- }
67
-
68
47
  export function downloadCSV(data: Record<string, unknown>[], filename: string) {
69
48
  if (data.length === 0) return;
70
49
  const headers = Object.keys(data[0]);