feat: implement authentication flow and session management
- Update RootLayout to handle authentication state and redirect users based on their auth status. - Create an Index component to redirect unauthenticated users to the login page. - Modify ApiAuthRepository to fetch user session data using access and refresh tokens. - Introduce a new container file to manage use case instances for authentication and filament operations. - Enhance authStore to validate tokens and fetch user data from the API. - Add babel-plugin-module-resolver for improved module imports. - Update package.json scripts for running the app on Android and iOS. - Add expo-camera dependency for camera functionalities. - Update tsconfig.json to include infrastructure path mapping.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera';
|
||||
import { colors, typography, spacing } from '@shared/theme';
|
||||
|
||||
/**
|
||||
* Tela de scanner de QR Code
|
||||
* Lê deeplinks meowspool://filament/<id> e navega para o filamento.
|
||||
*/
|
||||
export default function ScannerScreen(): React.ReactElement {
|
||||
const router = useRouter();
|
||||
const [permission, requestPermission] = useCameraPermissions();
|
||||
const handledRef = useRef(false);
|
||||
|
||||
function handleBarCodeScanned({ data }: { data: string }): void {
|
||||
if (handledRef.current) return;
|
||||
handledRef.current = true;
|
||||
|
||||
const match = data.match(/meowspool:\/\/filament\/([^/]+)/);
|
||||
if (match) {
|
||||
router.replace(`/(app)/inventory/${match[1]}` as never);
|
||||
} else {
|
||||
// QR não reconhecido — libera para próximo scan
|
||||
handledRef.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!permission) {
|
||||
return (
|
||||
<SafeAreaView style={styles.safe}>
|
||||
<View style={styles.center}>
|
||||
<Text style={styles.message}>Carregando câmera…</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
if (!permission.granted) {
|
||||
return (
|
||||
<SafeAreaView style={styles.safe}>
|
||||
<View style={styles.center}>
|
||||
<Ionicons name="camera-outline" size={48} color={colors.textSecondary} />
|
||||
<Text style={styles.message}>Permissão de câmera necessária</Text>
|
||||
<TouchableOpacity style={styles.permBtn} onPress={requestPermission}>
|
||||
<Text style={styles.permBtnText}>Conceder permissão</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.safe}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={() => router.back()}>
|
||||
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>Escanear QR Code</Text>
|
||||
<View style={{ width: 24 }} />
|
||||
</View>
|
||||
|
||||
{/* Camera */}
|
||||
<View style={styles.cameraWrapper}>
|
||||
<CameraView
|
||||
style={StyleSheet.absoluteFillObject}
|
||||
facing="back"
|
||||
mode="picture"
|
||||
barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
|
||||
onBarcodeScanned={handleBarCodeScanned}
|
||||
/>
|
||||
|
||||
{/* Viewfinder overlay */}
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.viewfinder}>
|
||||
<View style={[styles.corner, styles.cornerTL]} />
|
||||
<View style={[styles.corner, styles.cornerTR]} />
|
||||
<View style={[styles.corner, styles.cornerBL]} />
|
||||
<View style={[styles.corner, styles.cornerBR]} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.hint}>
|
||||
<Text style={styles.hintText}>
|
||||
Aponte para o QR Code do filamento
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const CORNER = 24;
|
||||
const CORNER_THICKNESS = 3;
|
||||
const VIEWFINDER_SIZE = 240;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1, backgroundColor: colors.bgBase },
|
||||
center: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: spacing[4], paddingHorizontal: spacing[6] },
|
||||
message: {
|
||||
fontFamily: typography.fontFamily.ui,
|
||||
fontSize: typography.fontSize.base,
|
||||
color: colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
permBtn: {
|
||||
backgroundColor: colors.accent,
|
||||
paddingHorizontal: spacing[5],
|
||||
paddingVertical: spacing[3],
|
||||
borderRadius: 8,
|
||||
},
|
||||
permBtnText: {
|
||||
fontFamily: typography.fontFamily.ui,
|
||||
fontSize: typography.fontSize.base,
|
||||
fontWeight: '600',
|
||||
color: colors.bgBase,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing[5],
|
||||
paddingVertical: spacing[4],
|
||||
},
|
||||
headerTitle: {
|
||||
fontFamily: typography.fontFamily.ui,
|
||||
fontSize: typography.fontSize.md,
|
||||
fontWeight: '600',
|
||||
color: colors.textPrimary,
|
||||
},
|
||||
cameraWrapper: {
|
||||
flex: 1,
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(0,0,0,0.55)',
|
||||
},
|
||||
viewfinder: {
|
||||
width: VIEWFINDER_SIZE,
|
||||
height: VIEWFINDER_SIZE,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
corner: {
|
||||
position: 'absolute',
|
||||
width: CORNER,
|
||||
height: CORNER,
|
||||
borderColor: colors.accent,
|
||||
},
|
||||
cornerTL: { top: 0, left: 0, borderTopWidth: CORNER_THICKNESS, borderLeftWidth: CORNER_THICKNESS },
|
||||
cornerTR: { top: 0, right: 0, borderTopWidth: CORNER_THICKNESS, borderRightWidth: CORNER_THICKNESS },
|
||||
cornerBL: { bottom: 0, left: 0, borderBottomWidth: CORNER_THICKNESS, borderLeftWidth: CORNER_THICKNESS },
|
||||
cornerBR: { bottom: 0, right: 0, borderBottomWidth: CORNER_THICKNESS, borderRightWidth: CORNER_THICKNESS },
|
||||
hint: {
|
||||
paddingVertical: spacing[5],
|
||||
paddingHorizontal: spacing[5],
|
||||
alignItems: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
hintText: {
|
||||
fontFamily: typography.fontFamily.ui,
|
||||
fontSize: typography.fontSize.sm,
|
||||
color: colors.textSecondary,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user