Inspired by a dashboard design the user shared. - App: new palette and fonts, borderless rounded panels with soft shadows, a pink-to-blue gradient card for your own song (text and controls switch to white on it), flat cards for friends, a 'Hi Chris, welcome back' header, an outline bell, and a pink pill on the active tab. - Website: the same look, plus a wide-screen layout with a sidebar, the page in a rounded panel, and a right column with your profile, recent activity and recent plays. Phones keep the top bar and bottom tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y2iUsKEWDuRGjK5FPmcUP
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, fonts, 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, fontFamily: fonts.body },
|
|
legalLink: { color: colors.text, textDecorationLine: 'underline' },
|
|
logo: { width: 76, height: 76, borderRadius: 20, marginBottom: space.sm },
|
|
});
|