Files
soconnor 32ffe782ea Fix Live Activity lock screen rendering and polish multi-account auth.
Flatten widget layouts and use system colors so banner and expanded regions render on vibrant lock screens; migrate auth sessions per account to prevent double sign-in; scope app lock PIN to accounts; default clock description to "Clock In"; add architecture docs and deferred form validation on auth screens.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 01:23:36 -04:00

158 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import {
Modal,
Pressable,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
import { Button } from "@/components/ui/Button";
import { fonts, spacing } from "@/constants/theme";
import { useAppTheme } from "@/contexts/ThemeContext";
import { isValidPin } from "@/lib/app-lock";
type PinPromptProps = {
visible: boolean;
title: string;
message: string;
confirmLabel?: string;
requireConfirmation?: boolean;
onCancel: () => void;
onSubmit: (pin: string) => void;
};
export function PinPrompt({
visible,
title,
message,
confirmLabel = "Continue",
requireConfirmation = false,
onCancel,
onSubmit,
}: PinPromptProps) {
const { colors } = useAppTheme();
const [pin, setPin] = useState("");
const [confirmPin, setConfirmPin] = useState("");
const [error, setError] = useState("");
useEffect(() => {
if (!visible) {
setPin("");
setConfirmPin("");
setError("");
}
}, [visible]);
function handleSubmit() {
if (!isValidPin(pin)) {
setError("PIN must be 46 digits");
return;
}
if (requireConfirmation && pin !== confirmPin) {
setError("PINs do not match");
return;
}
onSubmit(pin);
}
return (
<Modal visible={visible} transparent animationType="fade" onRequestClose={onCancel}>
<Pressable style={styles.backdrop} onPress={onCancel}>
<Pressable
style={[styles.sheet, { backgroundColor: colors.card, borderColor: colors.border }]}
onPress={(event) => event.stopPropagation()}
>
<Text style={[styles.title, { color: colors.foreground }]}>{title}</Text>
<Text style={[styles.message, { color: colors.mutedForeground }]}>{message}</Text>
<TextInput
value={pin}
onChangeText={(value) => {
setError("");
setPin(value.replace(/\D/g, "").slice(0, 6));
}}
keyboardType="number-pad"
secureTextEntry
maxLength={6}
placeholder="PIN"
placeholderTextColor={colors.mutedForeground}
style={[
styles.input,
{ color: colors.foreground, borderColor: colors.border, backgroundColor: colors.background },
]}
/>
{requireConfirmation ? (
<TextInput
value={confirmPin}
onChangeText={(value) => {
setError("");
setConfirmPin(value.replace(/\D/g, "").slice(0, 6));
}}
keyboardType="number-pad"
secureTextEntry
maxLength={6}
placeholder="Confirm PIN"
placeholderTextColor={colors.mutedForeground}
style={[
styles.input,
{ color: colors.foreground, borderColor: colors.border, backgroundColor: colors.background },
]}
/>
) : null}
{error ? <Text style={[styles.error, { color: colors.destructive }]}>{error}</Text> : null}
<View style={styles.actions}>
<Button title="Cancel" variant="secondary" onPress={onCancel} />
<Button title={confirmLabel} onPress={handleSubmit} />
</View>
</Pressable>
</Pressable>
</Modal>
);
}
const styles = StyleSheet.create({
backdrop: {
flex: 1,
justifyContent: "center",
padding: spacing.lg,
backgroundColor: "rgba(0,0,0,0.35)",
},
sheet: {
borderWidth: 1,
borderRadius: 16,
padding: spacing.lg,
gap: spacing.md,
},
title: {
fontSize: 18,
fontFamily: fonts.bodySemiBold,
},
message: {
fontSize: 14,
fontFamily: fonts.body,
lineHeight: 20,
},
input: {
borderWidth: 1,
borderRadius: 12,
minHeight: 48,
paddingHorizontal: spacing.md,
fontSize: 20,
fontFamily: fonts.bodySemiBold,
textAlign: "center",
},
error: {
fontSize: 13,
fontFamily: fonts.bodyMedium,
},
actions: {
flexDirection: "row",
gap: spacing.sm,
},
});