app/(onboarding)/code.tsx

component·mobile·6.1 KB · 169 lignes· Voir l'itinéraire
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.

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&apos;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&apos;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&apos;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' },
  });
}