app/(onboarding)/code.tsx
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
Concepts détectés — comprends la théorie
UI React Native
17 occurrencesComposants UI React Native (View, Text, etc.). Différents de l'HTML web — on rend du natif.
Voir l'article général
Hooks React
6 occurrencesCe fichier utilise des hooks React. Les hooks sont la façon moderne de gérer l'état et les effets dans React. Voir l'architecture mobile pour le pattern complet.
Voir l'article général
Routing Expo (mobile)
1 occurrenceCe fichier utilise le routing Expo (file-based). Convention : `_layout.tsx` pour les layouts, `[param].tsx` pour les routes dynamiques.
Voir l'article général
1 export
default
Code source· tsx
import React, { useState } from 'react';
import {
View, Text, TextInput, StyleSheet,
KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { router } from 'expo-router';
import { Button } from '@/components/ui/Button';
import WizardProgress from '@/components/onboarding/WizardProgress';
import { useTheme } from '@/hooks/useTheme';
import { useOnboardingGerant, routeFromOnboarding } from '@/hooks/useOnboardingGerant';
import { FONTS, SPACING, RADIUS, COLORS } from '@/lib/constants';
import { Key } from '@/lib/icons';
function normaliserCode(raw: string): string {
return raw.toUpperCase().replace(/[^A-Z0-9-]/g, '').slice(0, 14);
}
export default function OnboardingCodeScreen() {
const { colors } = useTheme();
const styles = makeStyles(colors);
const { verifierCode } = useOnboardingGerant();
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
async function handleSubmit() {
setLoading(true);
setError('');
const { data, error: err } = await verifierCode(code);
setLoading(false);
if (err || !data) {
setError(err ?? 'Code invalide');
return;
}
const route = routeFromOnboarding(data.onboardingStep, code);
router.replace({
pathname: route.pathname as never,
params: {
...route.params,
tenantNom: data.tenantNom,
modulesPrevus: data.modulesPrevus.join(','),
} as never,
});
}
return (
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={{ flex: 1 }}>
<WizardProgress current={1} onBack={() => router.back()} />
<ScrollView
contentContainerStyle={styles.scroll}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
>
<View style={styles.brand}>
<Text style={styles.logo}>wari<Text style={styles.logoDot}>.pro</Text></Text>
<Text style={styles.tagline}>Active ta vitrine pro</Text>
</View>
<View style={styles.card}>
<View style={styles.iconWrap}>
<Key size={28} weight="bold" color={colors.primary} />
</View>
<Text style={styles.cardTitle}>Ton code d'invitation</Text>
<Text style={styles.cardSubtitle}>
Entre le code reçu de ton accompagnant wari.pro pour activer ton espace gérant.
</Text>
<View style={styles.field}>
<Text style={styles.fieldLabel}>Code d'accès</Text>
<TextInput
style={styles.input}
value={code}
onChangeText={(v) => setCode(normaliserCode(v))}
placeholder="WARI-XXXX-XXXX"
placeholderTextColor={colors.textMuted}
autoCapitalize="characters"
autoCorrect={false}
spellCheck={false}
returnKeyType="done"
onSubmitEditing={handleSubmit}
editable={!loading}
/>
</View>
{error ? (
<View style={styles.errorBox}>
<Text style={styles.errorText}>{error}</Text>
</View>
) : null}
<Button
label={loading ? 'Vérification…' : 'Vérifier mon code'}
variant="primary"
size="lg"
fullWidth
loading={loading}
onPress={handleSubmit}
disabled={code.length < 4}
style={{ marginTop: SPACING.md }}
/>
<TouchableOpacity onPress={() => router.back()} style={styles.ghostBtn}>
<Text style={styles.ghostText}>J'ai déjà un compte</Text>
</TouchableOpacity>
</View>
<Text style={styles.hintText}>
Pas de code ? Contacte ton accompagnant wari.pro.
</Text>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
function makeStyles(colors: ReturnType<typeof useTheme>['colors']) {
return StyleSheet.create({
container: { flex: 1, backgroundColor: colors.bg },
scroll: { flexGrow: 1, padding: SPACING.lg, justifyContent: 'center', gap: SPACING.xl },
brand: { alignItems: 'center', gap: SPACING.xs },
logo: { fontSize: FONTS.display, fontWeight: FONTS.black, color: colors.text, letterSpacing: -1 },
logoDot: { color: COLORS.primary },
tagline: { fontSize: FONTS.sm, color: colors.textSecondary, textAlign: 'center' },
card: {
backgroundColor: colors.bgCard,
borderRadius: RADIUS.xl,
padding: SPACING.xl,
gap: SPACING.base,
borderWidth: 1,
borderColor: colors.border,
},
iconWrap: {
alignSelf: 'center',
width: 56, height: 56, borderRadius: 28,
backgroundColor: COLORS.primary + '20',
alignItems: 'center', justifyContent: 'center',
},
cardTitle: { fontSize: FONTS.xl, fontWeight: FONTS.bold, color: colors.text, textAlign: 'center' },
cardSubtitle: { fontSize: FONTS.sm, color: colors.textSecondary, lineHeight: FONTS.sm * 1.6, textAlign: 'center' },
field: { gap: SPACING.xs, marginTop: SPACING.sm },
fieldLabel: { fontSize: FONTS.sm, fontWeight: FONTS.semibold, color: colors.textSecondary },
input: {
backgroundColor: colors.bgInput,
borderRadius: RADIUS.md,
borderWidth: 1.5,
borderColor: colors.border,
paddingHorizontal: SPACING.base,
paddingVertical: SPACING.md,
fontSize: FONTS.lg,
color: colors.text,
fontWeight: FONTS.bold,
textAlign: 'center',
letterSpacing: 2,
},
errorBox: {
backgroundColor: COLORS.error + '20',
borderRadius: RADIUS.md,
padding: SPACING.md,
},
errorText: { fontSize: FONTS.sm, color: COLORS.error },
ghostBtn: { alignItems: 'center', paddingVertical: SPACING.sm },
ghostText: { fontSize: FONTS.sm, color: colors.textSecondary },
hintText: { fontSize: FONTS.xs, color: colors.textMuted, textAlign: 'center' },
});
}
import React, { useState } from 'react';
import {
View, Text, TextInput, StyleSheet,
KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { router } from 'expo-router';
import { Button } from '@/components/ui/Button';
import WizardProgress from '@/components/onboarding/WizardProgress';
import { useTheme } from '@/hooks/useTheme';
import { useOnboardingGerant, routeFromOnboarding } from '@/hooks/useOnboardingGerant';
import { FONTS, SPACING, RADIUS, COLORS } from '@/lib/constants';
import { Key } from '@/lib/icons';
function normaliserCode(raw: string): string {
return raw.toUpperCase().replace(/[^A-Z0-9-]/g, '').slice(0, 14);
}
export default function OnboardingCodeScreen() {
const { colors } = useTheme();
const styles = makeStyles(colors);
const { verifierCode } = useOnboardingGerant();
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
async function handleSubmit() {
setLoading(true);
setError('');
const { data, error: err } = await verifierCode(code);
setLoading(false);
if (err || !data) {
setError(err ?? 'Code invalide');
return;
}
const route = routeFromOnboarding(data.onboardingStep, code);
router.replace({
pathname: route.pathname as never,
params: {
...route.params,
tenantNom: data.tenantNom,
modulesPrevus: data.modulesPrevus.join(','),
} as never,
});
}
return (
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={{ flex: 1 }}>
<WizardProgress current={1} onBack={() => router.back()} />
<ScrollView
contentContainerStyle={styles.scroll}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
>
<View style={styles.brand}>
<Text style={styles.logo}>wari<Text style={styles.logoDot}>.pro</Text></Text>
<Text style={styles.tagline}>Active ta vitrine pro</Text>
</View>
<View style={styles.card}>
<View style={styles.iconWrap}>
<Key size={28} weight="bold" color={colors.primary} />
</View>
<Text style={styles.cardTitle}>Ton code d'invitation</Text>
<Text style={styles.cardSubtitle}>
Entre le code reçu de ton accompagnant wari.pro pour activer ton espace gérant.
</Text>
<View style={styles.field}>
<Text style={styles.fieldLabel}>Code d'accès</Text>
<TextInput
style={styles.input}
value={code}
onChangeText={(v) => setCode(normaliserCode(v))}
placeholder="WARI-XXXX-XXXX"
placeholderTextColor={colors.textMuted}
autoCapitalize="characters"
autoCorrect={false}
spellCheck={false}
returnKeyType="done"
onSubmitEditing={handleSubmit}
editable={!loading}
/>
</View>
{error ? (
<View style={styles.errorBox}>
<Text style={styles.errorText}>{error}</Text>
</View>
) : null}
<Button
label={loading ? 'Vérification…' : 'Vérifier mon code'}
variant="primary"
size="lg"
fullWidth
loading={loading}
onPress={handleSubmit}
disabled={code.length < 4}
style={{ marginTop: SPACING.md }}
/>
<TouchableOpacity onPress={() => router.back()} style={styles.ghostBtn}>
<Text style={styles.ghostText}>J'ai déjà un compte</Text>
</TouchableOpacity>
</View>
<Text style={styles.hintText}>
Pas de code ? Contacte ton accompagnant wari.pro.
</Text>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
function makeStyles(colors: ReturnType<typeof useTheme>['colors']) {
return StyleSheet.create({
container: { flex: 1, backgroundColor: colors.bg },
scroll: { flexGrow: 1, padding: SPACING.lg, justifyContent: 'center', gap: SPACING.xl },
brand: { alignItems: 'center', gap: SPACING.xs },
logo: { fontSize: FONTS.display, fontWeight: FONTS.black, color: colors.text, letterSpacing: -1 },
logoDot: { color: COLORS.primary },
tagline: { fontSize: FONTS.sm, color: colors.textSecondary, textAlign: 'center' },
card: {
backgroundColor: colors.bgCard,
borderRadius: RADIUS.xl,
padding: SPACING.xl,
gap: SPACING.base,
borderWidth: 1,
borderColor: colors.border,
},
iconWrap: {
alignSelf: 'center',
width: 56, height: 56, borderRadius: 28,
backgroundColor: COLORS.primary + '20',
alignItems: 'center', justifyContent: 'center',
},
cardTitle: { fontSize: FONTS.xl, fontWeight: FONTS.bold, color: colors.text, textAlign: 'center' },
cardSubtitle: { fontSize: FONTS.sm, color: colors.textSecondary, lineHeight: FONTS.sm * 1.6, textAlign: 'center' },
field: { gap: SPACING.xs, marginTop: SPACING.sm },
fieldLabel: { fontSize: FONTS.sm, fontWeight: FONTS.semibold, color: colors.textSecondary },
input: {
backgroundColor: colors.bgInput,
borderRadius: RADIUS.md,
borderWidth: 1.5,
borderColor: colors.border,
paddingHorizontal: SPACING.base,
paddingVertical: SPACING.md,
fontSize: FONTS.lg,
color: colors.text,
fontWeight: FONTS.bold,
textAlign: 'center',
letterSpacing: 2,
},
errorBox: {
backgroundColor: COLORS.error + '20',
borderRadius: RADIUS.md,
padding: SPACING.md,
},
errorText: { fontSize: FONTS.sm, color: COLORS.error },
ghostBtn: { alignItems: 'center', paddingVertical: SPACING.sm },
ghostText: { fontSize: FONTS.sm, color: colors.textSecondary },
hintText: { fontSize: FONTS.xs, color: colors.textMuted, textAlign: 'center' },
});
}