77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { useState } from "react";
|
|
import { Text, View } from "react-native";
|
|
import { Link, Redirect, router } from "expo-router";
|
|
import { api } from "@/lib/api";
|
|
import { useAuth, type AuthUser } from "@/lib/auth";
|
|
import { t } from "@/lib/i18n";
|
|
import { Body, Button, Input, Screen, Spacer, Title } from "@/components/ui";
|
|
import { colors, spacing } from "@/lib/theme";
|
|
import { BRAND } from "@/lib/brand";
|
|
|
|
interface LoginResponse {
|
|
user: AuthUser;
|
|
accessToken: string;
|
|
refreshToken: string;
|
|
}
|
|
|
|
export default function LoginScreen() {
|
|
const { accessToken, setSession } = useAuth();
|
|
const [email, setEmail] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
if (accessToken) return <Redirect href="/(tabs)" />;
|
|
|
|
const submit = async () => {
|
|
setBusy(true);
|
|
setError(null);
|
|
try {
|
|
const result = await api<LoginResponse>("/v1/auth/login", {
|
|
method: "POST",
|
|
body: { email, password },
|
|
});
|
|
await setSession(
|
|
{ accessToken: result.accessToken, refreshToken: result.refreshToken },
|
|
result.user,
|
|
);
|
|
router.replace("/(tabs)");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : t("common.error"));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Screen style={{ flexGrow: 1, justifyContent: "center", gap: spacing.md }}>
|
|
<View style={{ alignItems: "center", marginBottom: spacing.lg }}>
|
|
<Title>{BRAND.name}</Title>
|
|
<Body muted>Hushållets mat-OS</Body>
|
|
</View>
|
|
<Input
|
|
placeholder={t("auth.email")}
|
|
autoCapitalize="none"
|
|
keyboardType="email-address"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
/>
|
|
<Input
|
|
placeholder={t("auth.password")}
|
|
secureTextEntry
|
|
value={password}
|
|
onChangeText={setPassword}
|
|
/>
|
|
{error && <Text style={{ color: colors.danger }}>{error}</Text>}
|
|
<Button label={t("auth.login")} onPress={() => void submit()} loading={busy} />
|
|
<Spacer size={spacing.sm} />
|
|
<Link href="/(auth)/register" style={{ textAlign: "center", color: colors.primaryDark }}>
|
|
{t("auth.noAccount")}
|
|
</Link>
|
|
<Link href="/(auth)/forgot-password" style={{ textAlign: "center", color: colors.textMuted }}>
|
|
{t("auth.forgotLink")}
|
|
</Link>
|
|
</Screen>
|
|
);
|
|
}
|