create-varity-app 2.0.0-beta.15 → 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.15",
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:^",
@@ -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);
@@ -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,7 +81,6 @@ 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
@@ -93,6 +94,5 @@ export default function LoginPage() {
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
  ))}
@@ -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);