diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx index 2aed1e9..ea94674 100644 --- a/app/(app)/_layout.tsx +++ b/app/(app)/_layout.tsx @@ -3,6 +3,7 @@ import { NativeTabs } from "expo-router/unstable-native-tabs"; import { AppLockOverlay } from "@/components/AppLockOverlay"; import { InvoiceReminderSync } from "@/components/InvoiceReminderSync"; +import { OnboardingGate } from "@/components/OnboardingGate"; import { ShortcutHandler } from "@/components/ShortcutHandler"; import { TimeClockLiveActivitySync } from "@/components/time-clock/TimeClockLiveActivitySync"; import { useAppTheme } from "@/contexts/ThemeContext"; @@ -73,7 +74,16 @@ export default function AppLayout() { /> Settings + + + + More + + diff --git a/app/(app)/onboarding.tsx b/app/(app)/onboarding.tsx new file mode 100644 index 0000000..7659082 --- /dev/null +++ b/app/(app)/onboarding.tsx @@ -0,0 +1,139 @@ +import { router } from "expo-router"; +import { useState } from "react"; +import { ScrollView, StyleSheet, Text, View } from "react-native"; + +import { AppBackground } from "@/components/AppBackground"; +import { Button } from "@/components/ui/Button"; +import { Input } from "@/components/ui/Input"; +import { fonts, spacing } from "@/constants/theme"; +import { useAppTheme } from "@/contexts/ThemeContext"; +import { api } from "@/lib/trpc"; + +export default function OnboardingScreen() { + const { colors } = useAppTheme(); + const utils = api.useUtils(); + const statusQuery = api.settings.getOnboardingStatus.useQuery(); + + const [businessName, setBusinessName] = useState(""); + const [clientName, setClientName] = useState(""); + const [step, setStep] = useState(0); + + const createBusiness = api.businesses.create.useMutation(); + const createClient = api.clients.create.useMutation(); + const complete = api.settings.completeOnboarding.useMutation({ + onSuccess: async () => { + await utils.settings.getOnboardingStatus.invalidate(); + await utils.settings.getProfile.invalidate(); + router.replace("/(app)"); + }, + }); + + async function finish() { + if (businessName.trim()) { + await createBusiness.mutateAsync({ + name: businessName.trim(), + isDefault: true, + }); + } + if (clientName.trim()) { + await createClient.mutateAsync({ + name: clientName.trim(), + currency: "USD", + }); + } + await complete.mutateAsync(); + } + + const steps = [ + { + title: "Welcome to beenvoice", + body: "Set up your workspace in a minute — business profile and first client.", + }, + { + title: "Your business", + body: "This appears on invoices you send to clients.", + }, + { + title: "First client", + body: "Add someone you bill. You can skip and add clients later.", + }, + ]; + + const current = steps[step]!; + + return ( + + + + Step {step + 1} of {steps.length} + + {current.title} + {current.body} + + {step === 1 ? ( + + ) : null} + + {step === 2 ? ( + + ) : null} + + + {step > 0 ? ( +