mojmajstor/app/(auth)/login.tsx
echo 5102ed23c1 feat: Phase 1 - project setup, auth, and base UI
Set up Expo SDK 56 project with TypeScript and Expo Router (file-based).

Backend:
- Full Convex schema (users, accounts, sessions, ads, categories,
  reviews, chats, messages, posts) deployed to self-hosted instance
- Custom email/password auth with SHA-256 hashing via Convex actions
- Query/mutation scaffolds for all domain tables
- Convex environment variables configured on backend

App structure:
- Root layout with ConvexProvider, ThemeProvider, AuthContext
- (auth) group: login and register screens (Macedonian UI)
  with role selection (handyman/customer)
- (tabs) group: 5 tabs (Home, Explore, Posts, Chat, Profile)
  each with nested Stack layouts
- Placeholder detail screens for ad, post, chat, review flows
- 404 not-found screen in Macedonian

UI primitives (components/ui/):
- Button (4 variants), Input, Card, Loading, Avatar, Rating, Badge
- ThemeProvider with light/dark palette via React context

All user-facing strings in Macedonian. Code identifiers in English.
TypeScript passes with zero errors.
2026-05-29 18:18:58 +02:00

100 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"
/>
<Input
placeholder="Лозинка"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<View style={{ marginTop: theme.spacing.sm }}>
<Button loading={loading} onPress={handleLogin}>
Најави се
</Button>
</View>
<View style={{ flexDirection: "row", justifyContent: "center", marginTop: theme.spacing.md }}>
<Text style={{ color: theme.colors.textSecondary }}>Немате профил? </Text>
<Text
style={{ color: theme.colors.primary, fontWeight: "600" }}
onPress={() => router.push("/(auth)/register")}
>
Регистрирајте се
</Text>
</View>
</ScrollView>
</KeyboardAvoidingView>
);
}