Add beenvoice mobile companion app with full dark mode support.

Expo app with dashboard, time clock, invoices, and settings — native tabs, glass UI, theme-aware components, and iOS Live Activities.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 22:36:37 -04:00
parent 8a7a8df477
commit 14c880123c
93 changed files with 8849 additions and 7849 deletions
+191
View File
@@ -0,0 +1,191 @@
import { Link, router } from "expo-router";
import { useState } from "react";
import {
KeyboardAvoidingView,
Platform,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { FullScreen } from "@/components/Screen";
import { AuthBackground } from "@/components/AppBackground";
import { CollapsibleServerField } from "@/components/CollapsibleServerField";
import { HeadingText, Logo } from "@/components/Logo";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input";
import { fonts, spacing } from "@/constants/theme";
import { useAccounts } from "@/contexts/AccountsContext";
import { useAuthClient } from "@/contexts/AuthContext";
import { useAppTheme } from "@/contexts/ThemeContext";
import { registerAccount } from "@/lib/auth-api";
export default function RegisterScreen() {
const authClient = useAuthClient();
const { apiUrl, registerAccount: saveAccount } = useAccounts();
const { colors } = useAppTheme();
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleRegister() {
setError(null);
setLoading(true);
try {
await registerAccount({
firstName: firstName.trim(),
lastName: lastName.trim(),
email: email.trim(),
password,
});
const { error: signInError } = await authClient.signIn.email({
email: email.trim(),
password,
});
if (signInError) {
router.replace("/(auth)/sign-in");
return;
}
const session = await authClient.getSession();
const user = session.data?.user;
if (user) {
await saveAccount({
instanceUrl: apiUrl,
userId: user.id,
email: user.email,
name: user.name,
});
}
router.replace("/(app)");
} catch (err) {
setError(err instanceof Error ? err.message : "Registration failed");
} finally {
setLoading(false);
}
}
return (
<AuthBackground>
<FullScreen style={styles.safe}>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={styles.flex}
>
<ScrollView
contentContainerStyle={styles.container}
keyboardShouldPersistTaps="handled"
>
<Card style={styles.card}>
<View style={styles.header}>
<Logo size="lg" />
<HeadingText style={styles.title}>Create your account</HeadingText>
<Text style={[styles.subtitle, { color: colors.mutedForeground }]}>
Get started today
</Text>
</View>
<View style={styles.form}>
<View style={styles.row}>
<View style={styles.half}>
<Input
label="First name"
value={firstName}
onChangeText={setFirstName}
autoComplete="given-name"
placeholder="Jane"
/>
</View>
<View style={styles.half}>
<Input
label="Last name"
value={lastName}
onChangeText={setLastName}
autoComplete="family-name"
placeholder="Doe"
/>
</View>
</View>
<Input
label="Email"
autoCapitalize="none"
autoComplete="email"
keyboardType="email-address"
value={email}
onChangeText={setEmail}
placeholder="you@example.com"
/>
<Input
label="Password"
secureTextEntry
autoComplete="new-password"
value={password}
onChangeText={setPassword}
placeholder="At least 8 characters"
/>
{error ? (
<Text style={[styles.error, { color: colors.destructive }]}>{error}</Text>
) : null}
<Button title="Create Account" loading={loading} onPress={handleRegister} />
</View>
<Text style={[styles.footer, { color: colors.mutedForeground }]}>
Already have an account?{" "}
<Link href="/(auth)/sign-in" style={[styles.link, { color: colors.foreground }]}>
Sign in
</Link>
</Text>
</Card>
</ScrollView>
</KeyboardAvoidingView>
<CollapsibleServerField />
</FullScreen>
</AuthBackground>
);
}
const styles = StyleSheet.create({
safe: { flex: 1 },
flex: { flex: 1 },
container: {
flexGrow: 1,
justifyContent: "center",
padding: spacing.lg,
paddingBottom: spacing.md,
},
card: {
gap: spacing.lg,
},
header: { gap: spacing.sm },
title: { fontSize: 24, marginTop: spacing.sm },
subtitle: {
fontSize: 14,
fontFamily: fonts.body,
},
form: { gap: spacing.md },
row: { flexDirection: "row", gap: spacing.md },
half: { flex: 1 },
error: {
fontSize: 14,
fontFamily: fonts.body,
},
footer: {
textAlign: "center",
fontSize: 14,
fontFamily: fonts.body,
},
link: {
fontFamily: fonts.bodySemiBold,
},
});