create-varity-app 2.0.0-beta.14 → 2.0.0-beta.16

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.14",
3
+ "version": "2.0.0-beta.16",
4
4
  "description": "Create production-ready apps with auth, database, and payments built in",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -3,7 +3,6 @@ const nextConfig = {
3
3
  output: 'export',
4
4
  images: { unoptimized: true },
5
5
  trailingSlash: true,
6
- basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
7
6
  productionBrowserSourceMaps: false,
8
7
  webpack: (config, { isServer, dev }) => {
9
8
  // Suppress MetaMask SDK warning for @react-native-async-storage
@@ -13,8 +13,7 @@
13
13
  "test:e2e:ui": "playwright test --ui",
14
14
  "test:e2e:headed": "playwright test --headed",
15
15
  "test:e2e:debug": "playwright test --debug",
16
- "prepare": "husky install",
17
- "deploy": "varitykit app deploy"
16
+ "prepare": "husky install"
18
17
  },
19
18
  "dependencies": {
20
19
  "@varity-labs/sdk": "workspace:^",
@@ -4,7 +4,6 @@ import { useState, useEffect, useCallback } from 'react';
4
4
  import { useRouter, usePathname } from 'next/navigation';
5
5
  import { APP_NAME, NAVIGATION_ITEMS } from '@/lib/constants';
6
6
  import { useProjects, useTasks, useTeam } from '@/lib/hooks';
7
- import { withBasePath } from '@/lib/utils';
8
7
  import { CommandPalette } from '@varity-labs/ui-kit';
9
8
  import { Menu, X } from 'lucide-react';
10
9
 
@@ -25,7 +24,7 @@ try {
25
24
  function RedirectToLogin() {
26
25
  const router = useRouter();
27
26
  useEffect(() => {
28
- router.push(withBasePath('/login'));
27
+ router.push('/login');
29
28
  }, [router]);
30
29
  return null;
31
30
  }
@@ -114,8 +113,8 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
114
113
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
115
114
  const privy = usePrivyHook ? usePrivyHook() : { user: null, logout: async () => {} };
116
115
  const { user, logout } = privy;
117
- const pathname = usePathname();
118
116
  const router = useRouter();
117
+ const pathname = usePathname();
119
118
  const isMobile = useIsMobile();
120
119
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
121
120
  const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
@@ -155,7 +154,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
155
154
 
156
155
  const handleLogout = async () => {
157
156
  await logout();
158
- window.location.href = withBasePath('/');
157
+ window.location.href = '/';
159
158
  };
160
159
 
161
160
  // Fallback layout when DashboardLayout from ui-kit isn't available
@@ -165,7 +164,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
165
164
  <CommandPalette
166
165
  open={commandPaletteOpen}
167
166
  onClose={() => setCommandPaletteOpen(false)}
168
- onNavigate={(path: string) => router.push(withBasePath(path))}
167
+ onNavigate={(path: string) => router.push(path)}
169
168
  projects={projects}
170
169
  tasks={tasks}
171
170
  team={team}
@@ -182,7 +181,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
182
181
  {navWithActive.map((item) => (
183
182
  <button
184
183
  key={item.path}
185
- onClick={() => router.push(withBasePath(item.path))}
184
+ onClick={() => router.push(item.path)}
186
185
  className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
187
186
  item.active
188
187
  ? 'bg-primary-50 text-primary-700'
@@ -212,7 +211,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
212
211
  navItems={navWithActive}
213
212
  userEmail={userEmail}
214
213
  onLogout={handleLogout}
215
- onNavigate={(path) => router.push(withBasePath(path))}
214
+ onNavigate={(path) => router.push(path)}
216
215
  />
217
216
  )}
218
217
 
@@ -229,7 +228,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
229
228
  <CommandPalette
230
229
  open={commandPaletteOpen}
231
230
  onClose={() => setCommandPaletteOpen(false)}
232
- onNavigate={(path: string) => router.push(withBasePath(path))}
231
+ onNavigate={(path: string) => router.push(path)}
233
232
  projects={projects}
234
233
  tasks={tasks}
235
234
  team={team}
@@ -243,7 +242,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
243
242
  navItems={navWithActive}
244
243
  userEmail={userEmail}
245
244
  onLogout={handleLogout}
246
- onNavigate={(path) => router.push(withBasePath(path))}
245
+ onNavigate={(path) => router.push(path)}
247
246
  />
248
247
  )}
249
248
 
@@ -258,9 +257,10 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
258
257
  name: userName,
259
258
  address: userEmail,
260
259
  }}
260
+ onNavigate={(path: string) => router.push(path)}
261
261
  onLogout={handleLogout}
262
- onNavigateToProfile={() => router.push(withBasePath('/dashboard/settings'))}
263
- onNavigateToSettings={() => router.push(withBasePath('/dashboard/settings'))}
262
+ onNavigateToProfile={() => router.push('/dashboard/settings')}
263
+ onNavigateToSettings={() => router.push('/dashboard/settings')}
264
264
  onSearchClick={() => setCommandPaletteOpen(true)}
265
265
  searchPlaceholder="Search projects, tasks, team..."
266
266
  >
@@ -289,7 +289,7 @@ export default function DashboardRootLayout({
289
289
  appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
290
290
  thirdwebClientId={process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID}
291
291
  loginMethods={['email', 'google']}
292
- appearance={{ theme: 'light', accentColor: '#2563EB', logo: '/logo.svg' }}
292
+ appearance={{ theme: 'light', accentColor: '#2563EB' }}
293
293
  >
294
294
  <PrivyProtectedRoute fallback={<RedirectToLogin />}>
295
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
 
@@ -5,7 +5,6 @@ import { useRouter } from 'next/navigation';
5
5
  import Link from 'next/link';
6
6
  import { CheckCircle } from 'lucide-react';
7
7
  import { APP_NAME } from '@/lib/constants';
8
- import { withBasePath } from '@/lib/utils';
9
8
 
10
9
  let PrivyStackComponent: any = null;
11
10
  let usePrivyHook: (() => { authenticated: boolean; ready: boolean; login: () => void }) | null = null;
@@ -16,6 +15,12 @@ try {
16
15
  usePrivyHook = uiKit.usePrivy;
17
16
  } catch {}
18
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
+
19
24
  function LoginContent() {
20
25
  const router = useRouter();
21
26
  // eslint-disable-next-line react-hooks/rules-of-hooks -- conditional on require() success, stable across renders
@@ -23,7 +28,7 @@ function LoginContent() {
23
28
 
24
29
  useEffect(() => {
25
30
  if (privy?.authenticated) {
26
- router.push(withBasePath('/dashboard'));
31
+ router.push('/dashboard');
27
32
  }
28
33
  }, [privy?.authenticated, router]);
29
34
 
@@ -56,11 +61,7 @@ function LoginContent() {
56
61
  disabled={!privy.ready || privy.authenticated}
57
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"
58
63
  >
59
- {!privy.ready
60
- ? 'Loading...'
61
- : privy.authenticated
62
- ? 'Already Signed In'
63
- : 'Sign In with Email or Social'}
64
+ {loginButtonLabel(privy)}
64
65
  </button>
65
66
  ) : (
66
67
  <div className="text-center space-y-4">
@@ -80,7 +81,6 @@ function LoginContent() {
80
81
  }
81
82
 
82
83
  export default function LoginPage() {
83
- // Always wrap in PrivyStack - it uses dev credentials automatically when no appId is provided
84
84
  if (PrivyStackComponent) {
85
85
  return (
86
86
  <PrivyStackComponent
@@ -94,6 +94,5 @@ export default function LoginPage() {
94
94
  );
95
95
  }
96
96
 
97
- // Fallback if ui-kit package isn't installed
98
97
  return <LoginContent />;
99
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
  ))}
@@ -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,18 +1,3 @@
1
- /**
2
- * Navigate using Next.js basePath-aware paths.
3
- * In static export with basePath, router.push does NOT prepend basePath
4
- * automatically, so we do it manually.
5
- */
6
- export function getBasePath(): string {
7
- return process.env.NEXT_PUBLIC_BASE_PATH || '';
8
- }
9
-
10
- export function withBasePath(path: string): string {
11
- const base = getBasePath();
12
- if (!base) return path;
13
- return `${base}${path}`;
14
- }
15
-
16
1
  export function formatDate(dateString: string | undefined | null): string {
17
2
  if (!dateString) return '—';
18
3
  const date = new Date(dateString);