- Error boundary with Macedonian fallback screen and retry button wrapping all navigation - Pull-to-refresh on all list/detail screens (home, explore, posts, chat, profile, ad detail, post detail) - Accessibility: labels on all interactive elements, roles on Pressable/Button, selectable text for user data - Deep linking: mojmajstor:// scheme configured in app.json, ad/[id] and chat/[id] routes work via Expo Router auto-routing - Pagination: cursor-based paginated queries for ads, posts, messages, chats, reviews with Load More buttons on frontend - Profile: shows customer posts with PostCard, loading/error states, create-post navigation - Auth error messages in Macedonian (Невалидни акредитиви, итн.) All UI text in Macedonian. TypeScript clean, Convex functions deployed.
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import { useState } from "react";
|
||
import {
|
||
View,
|
||
Text,
|
||
KeyboardAvoidingView,
|
||
Platform,
|
||
ScrollView,
|
||
Alert,
|
||
} from "react-native";
|
||
import { useRouter } from "expo-router";
|
||
import { useMutation, useAction } from "convex/react";
|
||
import { api } from "../../convex/_generated/api";
|
||
import { Input } from "../../components/ui/input";
|
||
import { Button } from "../../components/ui/button";
|
||
import { useTheme } from "../../components/theme";
|
||
import { useAuth } from "../_layout";
|
||
|
||
export default function LoginScreen() {
|
||
const theme = useTheme();
|
||
const router = useRouter();
|
||
const { login } = useAuth();
|
||
const loginMutation = useMutation(api.auth.login);
|
||
const hashPassword = useAction(api.auth.hashPassword);
|
||
|
||
const [email, setEmail] = useState("");
|
||
const [password, setPassword] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
async function handleLogin() {
|
||
if (!email || !password) {
|
||
Alert.alert("Грешка", "Пополнете ги сите полиња");
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
try {
|
||
const passwordHash = await hashPassword({ password });
|
||
const result = await loginMutation({ email, passwordHash });
|
||
await login(result.token, result.user._id);
|
||
router.replace("/(tabs)/(home)");
|
||
} catch (e: any) {
|
||
Alert.alert("Најава неуспешна", "Погрешна е-пошта или лозинка");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<KeyboardAvoidingView
|
||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||
style={{ flex: 1, backgroundColor: theme.colors.background }}
|
||
>
|
||
<ScrollView
|
||
contentInsetAdjustmentBehavior="automatic"
|
||
contentContainerStyle={{ padding: theme.spacing.lg, gap: theme.spacing.md }}
|
||
keyboardShouldPersistTaps="handled"
|
||
>
|
||
<View style={{ marginTop: 60, marginBottom: theme.spacing.xl }}>
|
||
<Text style={{ fontSize: 32, fontWeight: "700", color: theme.colors.text }}>
|
||
МојМајстор
|
||
</Text>
|
||
<Text style={{ fontSize: 16, color: theme.colors.textSecondary, marginTop: theme.spacing.sm }}>
|
||
Најдете мајстор во вашата близина
|
||
</Text>
|
||
</View>
|
||
|
||
<Input
|
||
placeholder="Е-пошта"
|
||
value={email}
|
||
onChangeText={setEmail}
|
||
autoCapitalize="none"
|
||
keyboardType="email-address"
|
||
accessibilityLabel="Е-пошта"
|
||
/>
|
||
|
||
<Input
|
||
placeholder="Лозинка"
|
||
value={password}
|
||
onChangeText={setPassword}
|
||
secureTextEntry
|
||
accessibilityLabel="Лозинка"
|
||
/>
|
||
|
||
<View style={{ marginTop: theme.spacing.sm }}>
|
||
<Button loading={loading} onPress={handleLogin} accessibilityLabel="Најави се">
|
||
Најави се
|
||
</Button>
|
||
</View>
|
||
|
||
<View style={{ flexDirection: "row", justifyContent: "center", marginTop: theme.spacing.md }}>
|
||
<Text style={{ color: theme.colors.textSecondary }}>Немате профил? </Text>
|
||
<Text
|
||
accessible
|
||
accessibilityLabel="Регистрирајте се"
|
||
accessibilityRole="link"
|
||
style={{ color: theme.colors.primary, fontWeight: "600" }}
|
||
onPress={() => router.push("/(auth)/register")}
|
||
>
|
||
Регистрирајте се
|
||
</Text>
|
||
</View>
|
||
</ScrollView>
|
||
</KeyboardAvoidingView>
|
||
);
|
||
} |