CI / check (push) Canceled after 0s
/privacy and /terms describe what the app actually collects and shows (public profiles and listening, 50 recent plays, encrypted service tokens, stripped profile pictures, Android reader scope, song lookups that carry no identity, 14-day backups, 13+), linked from a web footer, the sign-up forms and mobile Settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
import { Image } from 'expo-image';
|
|
import { Link } from 'expo-router';
|
|
import { useState } from 'react';
|
|
import { KeyboardAvoidingView, Linking, Platform, StyleSheet, Text, View } from 'react-native';
|
|
import { api } from '@/lib/api';
|
|
import { WEB_URL } from '@/lib/config';
|
|
import { useSession } from '@/lib/session';
|
|
import { colors, space } from '@/lib/theme';
|
|
import { Screen } from './Screen';
|
|
import { Body, Button, Field, Title } from './ui';
|
|
|
|
export function AuthForm({ mode }: { mode: 'login' | 'register' }) {
|
|
const { signIn } = useSession();
|
|
const [username, setUsername] = useState('');
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
const submit = async () => {
|
|
setBusy(true);
|
|
setError(null);
|
|
try {
|
|
const res = mode === 'login' ? await api.login({ email, password }) : await api.register({ username, email, password });
|
|
await signIn(res); // the protected stack swaps to the tabs automatically
|
|
} catch (err) {
|
|
setError((err as Error).message);
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
<Screen edges={mode === 'login' ? ['top'] : []} contentContainerStyle={styles.content}>
|
|
{mode === 'login' && (
|
|
<View style={{ gap: space.sm }}>
|
|
<Image source={require('@/assets/images/icon.png')} style={styles.logo} />
|
|
<Title style={{ fontSize: 36 }}>musicshare</Title>
|
|
<Body muted>See what your friends are playing, and open it in your own app.</Body>
|
|
</View>
|
|
)}
|
|
{mode === 'register' && (
|
|
<Field
|
|
label="Username"
|
|
value={username}
|
|
onChangeText={setUsername}
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
autoComplete="username-new"
|
|
textContentType="username"
|
|
/>
|
|
)}
|
|
<Field
|
|
label="Email"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
autoCapitalize="none"
|
|
autoComplete="email"
|
|
keyboardType="email-address"
|
|
textContentType="emailAddress"
|
|
/>
|
|
<Field
|
|
label="Password"
|
|
value={password}
|
|
onChangeText={setPassword}
|
|
secureTextEntry
|
|
autoComplete={mode === 'login' ? 'current-password' : 'new-password'}
|
|
textContentType={mode === 'login' ? 'password' : 'newPassword'}
|
|
onSubmitEditing={submit}
|
|
returnKeyType="go"
|
|
/>
|
|
{error && <Body style={{ color: colors.danger }}>{error}</Body>}
|
|
<Button variant="primary" title={mode === 'login' ? 'Log in' : 'Create account'} loading={busy} onPress={submit} />
|
|
{mode === 'register' && (
|
|
<Text style={styles.legal}>
|
|
By creating an account you agree to the{' '}
|
|
<Text style={styles.legalLink} onPress={() => Linking.openURL(`${WEB_URL}/terms`)}>
|
|
terms
|
|
</Text>{' '}
|
|
and{' '}
|
|
<Text style={styles.legalLink} onPress={() => Linking.openURL(`${WEB_URL}/privacy`)}>
|
|
privacy policy
|
|
</Text>
|
|
. You must be 13 or older. Your profile and what you play are public.
|
|
</Text>
|
|
)}
|
|
{mode === 'login' && (
|
|
<Link href="/forgot-password" style={styles.link}>
|
|
Forgot your password?
|
|
</Link>
|
|
)}
|
|
{mode === 'login' ? (
|
|
<Link href="/register" style={styles.link}>
|
|
New here? Create an account
|
|
</Link>
|
|
) : (
|
|
<Link href="/login" style={styles.link} dismissTo>
|
|
Already have an account? Log in
|
|
</Link>
|
|
)}
|
|
</Screen>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
content: { flexGrow: 1, justifyContent: 'center' },
|
|
link: { color: colors.muted, textAlign: 'center', paddingVertical: space.sm },
|
|
legal: { color: colors.muted, fontSize: 12, textAlign: 'center', lineHeight: 17 },
|
|
legalLink: { color: colors.text, textDecorationLine: 'underline' },
|
|
logo: { width: 76, height: 76, borderRadius: 20, marginBottom: space.sm },
|
|
});
|