Move production to beenvoice.app with migrated accounts, refreshed auth and timer UX, and expanded invoice flows.
Official URL migration preserves sessions, shortcuts prefs, and last clock-in client; auth screens match web with legal links; time clock and invoice editor/send flows are updated for the new domain and UI patterns. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+82
-165
@@ -1,21 +1,16 @@
|
||||
import { Link, router } from "expo-router";
|
||||
import * as Linking from "expo-linking";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { AuthBackground } from "@/components/AppBackground";
|
||||
import { Pressable, StyleSheet, Text, View } from "react-native";
|
||||
|
||||
import { AuthCard } from "@/components/auth/AuthCard";
|
||||
import { AuthCardHeader } from "@/components/auth/AuthCardHeader";
|
||||
import { AuthDivider } from "@/components/auth/AuthDivider";
|
||||
import { AuthNotice } from "@/components/auth/AuthNotice";
|
||||
import { AuthScreenLayout } from "@/components/auth/AuthScreenLayout";
|
||||
import { AuthServerPicker } from "@/components/AuthServerPicker";
|
||||
import { HeadingText, Logo } from "@/components/Logo";
|
||||
import { FullScreen } from "@/components/Screen";
|
||||
import { LegalAgreementNotice } from "@/components/legal/LegalAgreementNotice";
|
||||
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";
|
||||
@@ -124,177 +119,99 @@ export default function SignInScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthBackground>
|
||||
<FullScreen style={styles.safe}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
||||
style={styles.flex}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.container}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<View style={styles.content}>
|
||||
<Card style={styles.card}>
|
||||
<View style={styles.header}>
|
||||
<Logo size="lg" />
|
||||
<HeadingText style={styles.title}>Welcome back</HeadingText>
|
||||
<Text style={[styles.subtitle, { color: colors.mutedForeground }]}>
|
||||
Sign in to manage invoices on the go
|
||||
</Text>
|
||||
</View>
|
||||
<AuthScreenLayout>
|
||||
<AuthCard>
|
||||
<AuthCardHeader title="Welcome back" description="Sign in to your workspace" />
|
||||
|
||||
<AuthServerPicker onReadyChange={setServerReady} embedded />
|
||||
<AuthServerPicker onReadyChange={setServerReady} embedded />
|
||||
|
||||
{signupsDisabled ? (
|
||||
<Text style={[styles.notice, { color: colors.mutedForeground }]}>
|
||||
New account registration is currently disabled on this server.
|
||||
</Text>
|
||||
) : null}
|
||||
{signupsDisabled ? (
|
||||
<AuthNotice>New account registration is currently disabled.</AuthNotice>
|
||||
) : null}
|
||||
|
||||
{authentikEnabled ? (
|
||||
<View style={styles.ssoSection}>
|
||||
<Button
|
||||
title="Sign in with Authentik"
|
||||
variant="secondary"
|
||||
loading={loading}
|
||||
disabled={!serverReady}
|
||||
onPress={() => void handleAuthentikSignIn()}
|
||||
/>
|
||||
<View style={styles.dividerRow}>
|
||||
<View style={[styles.dividerLine, { backgroundColor: colors.border }]} />
|
||||
<Text style={[styles.dividerLabel, { color: colors.mutedForeground }]}>
|
||||
or
|
||||
</Text>
|
||||
<View style={[styles.dividerLine, { backgroundColor: colors.border }]} />
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
{authentikEnabled ? (
|
||||
<View style={styles.ssoSection}>
|
||||
<Button
|
||||
title="Sign in with Authentik"
|
||||
variant="secondary"
|
||||
loading={loading}
|
||||
disabled={!serverReady}
|
||||
onPress={() => void handleAuthentikSignIn()}
|
||||
/>
|
||||
<AuthDivider />
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<View style={styles.form}>
|
||||
<Input
|
||||
label="Email"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
keyboardType="email-address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
onBlur={() => touch("email")}
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
error={visible("email") ? emailValidationError : undefined}
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
secureTextEntry
|
||||
autoComplete="password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
onBlur={() => touch("password")}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
error={visible("password") ? passwordValidationError : undefined}
|
||||
/>
|
||||
<View style={styles.form}>
|
||||
<Input
|
||||
label="Email"
|
||||
leftIcon="mail-outline"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
keyboardType="email-address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
onBlur={() => touch("email")}
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
error={visible("email") ? emailValidationError : undefined}
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
leftIcon="lock-closed-outline"
|
||||
secureTextEntry
|
||||
autoComplete="password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
onBlur={() => touch("password")}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
error={visible("password") ? passwordValidationError : undefined}
|
||||
labelAccessory={
|
||||
<Pressable onPress={() => router.push("/(auth)/forgot-password")} hitSlop={8}>
|
||||
<Text style={[styles.forgot, { color: colors.mutedForeground }]}>
|
||||
Forgot password?
|
||||
</Text>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
|
||||
<Pressable onPress={() => router.push("/(auth)/forgot-password")}>
|
||||
<Text style={[styles.forgot, { color: colors.mutedForeground }]}>
|
||||
Forgot password?
|
||||
</Text>
|
||||
</Pressable>
|
||||
{error ? (
|
||||
<Text style={[styles.error, { color: colors.destructive }]}>{error}</Text>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<Text style={[styles.error, { color: colors.destructive }]}>{error}</Text>
|
||||
) : null}
|
||||
<Button
|
||||
title={loading ? "Signing in…" : "Sign in"}
|
||||
loading={loading}
|
||||
disabled={!canSignIn}
|
||||
showArrow={!loading}
|
||||
onPress={handleSignIn}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Sign In"
|
||||
loading={loading}
|
||||
disabled={!canSignIn}
|
||||
onPress={handleSignIn}
|
||||
/>
|
||||
</View>
|
||||
{!signupsDisabled ? (
|
||||
<Text style={[styles.footer, { color: colors.mutedForeground }]}>
|
||||
Don't have an account?{" "}
|
||||
<Link href="/(auth)/register" style={[styles.link, { color: colors.foreground }]}>
|
||||
Create account
|
||||
</Link>
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
{!signupsDisabled ? (
|
||||
<Text style={[styles.footer, { color: colors.mutedForeground }]}>
|
||||
Don't have an account?{" "}
|
||||
<Link href="/(auth)/register" style={[styles.link, { color: colors.foreground }]}>
|
||||
Create one
|
||||
</Link>
|
||||
</Text>
|
||||
) : null}
|
||||
</Card>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</FullScreen>
|
||||
</AuthBackground>
|
||||
<LegalAgreementNotice action="signing in" />
|
||||
</AuthCard>
|
||||
</AuthScreenLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: {
|
||||
flex: 1,
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: spacing.lg,
|
||||
paddingVertical: spacing.xl,
|
||||
},
|
||||
content: {
|
||||
width: "100%",
|
||||
maxWidth: 420,
|
||||
},
|
||||
card: {
|
||||
gap: spacing.lg,
|
||||
},
|
||||
header: {
|
||||
alignItems: "center",
|
||||
gap: spacing.sm,
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
marginTop: spacing.sm,
|
||||
textAlign: "center",
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
fontFamily: fonts.body,
|
||||
textAlign: "center",
|
||||
},
|
||||
notice: {
|
||||
fontSize: 13,
|
||||
fontFamily: fonts.body,
|
||||
textAlign: "center",
|
||||
lineHeight: 18,
|
||||
},
|
||||
ssoSection: {
|
||||
gap: spacing.md,
|
||||
},
|
||||
dividerRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: spacing.sm,
|
||||
},
|
||||
dividerLine: {
|
||||
flex: 1,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
},
|
||||
dividerLabel: {
|
||||
fontSize: 12,
|
||||
fontFamily: fonts.bodyMedium,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.6,
|
||||
},
|
||||
form: {
|
||||
gap: spacing.md,
|
||||
},
|
||||
forgot: {
|
||||
alignSelf: "flex-end",
|
||||
fontFamily: fonts.bodyMedium,
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user