140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
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 (
|
|
<AppBackground>
|
|
<ScrollView contentContainerStyle={styles.body}>
|
|
<Text style={[styles.kicker, { color: colors.mutedForeground }]}>
|
|
Step {step + 1} of {steps.length}
|
|
</Text>
|
|
<Text style={[styles.title, { color: colors.foreground }]}>{current.title}</Text>
|
|
<Text style={[styles.bodyText, { color: colors.mutedForeground }]}>{current.body}</Text>
|
|
|
|
{step === 1 ? (
|
|
<Input
|
|
label="Business name"
|
|
value={businessName}
|
|
onChangeText={setBusinessName}
|
|
placeholder="Your studio or company"
|
|
/>
|
|
) : null}
|
|
|
|
{step === 2 ? (
|
|
<Input
|
|
label="Client name"
|
|
value={clientName}
|
|
onChangeText={setClientName}
|
|
placeholder="Acme Corp"
|
|
/>
|
|
) : null}
|
|
|
|
<View style={styles.actions}>
|
|
{step > 0 ? (
|
|
<Button title="Back" variant="secondary" onPress={() => setStep((s) => s - 1)} />
|
|
) : null}
|
|
{step < steps.length - 1 ? (
|
|
<Button title="Continue" onPress={() => setStep((s) => s + 1)} />
|
|
) : (
|
|
<Button
|
|
title={complete.isPending ? "Finishing…" : "Go to dashboard"}
|
|
loading={complete.isPending}
|
|
onPress={() => void finish()}
|
|
/>
|
|
)}
|
|
</View>
|
|
|
|
{statusQuery.data && !statusQuery.data.completed ? (
|
|
<Button title="Skip for now" variant="secondary" onPress={() => void complete.mutateAsync()} />
|
|
) : null}
|
|
</ScrollView>
|
|
</AppBackground>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
body: {
|
|
padding: spacing.lg,
|
|
gap: spacing.md,
|
|
minHeight: "100%",
|
|
justifyContent: "center",
|
|
},
|
|
kicker: {
|
|
fontFamily: fonts.bodyMedium,
|
|
fontSize: 13,
|
|
},
|
|
title: {
|
|
fontFamily: fonts.heading,
|
|
fontSize: 32,
|
|
lineHeight: 36,
|
|
},
|
|
bodyText: {
|
|
fontFamily: fonts.body,
|
|
fontSize: 16,
|
|
lineHeight: 24,
|
|
},
|
|
actions: {
|
|
gap: spacing.sm,
|
|
marginTop: spacing.lg,
|
|
},
|
|
});
|