feat: implement user authentication with login and register

- Add login screen with email/username support
- Add register screen with email validation
- Implement AuthStore with expo-secure-store (native) / localStorage (web)
- Add X-User-Id header authentication (simple auth without JWT)
- Rename displayName to userName across codebase
- Add findByUserName() to UserRepository
- Check for existing email AND username on registration
- Add AuthButton component with shadow effect
- Add logout button to Header
- Add hash-password.js utility script for manual password resets
- Update CORS to allow X-User-Id header
This commit is contained in:
2026-01-10 20:07:35 +01:00
parent 71f84d1cc7
commit 8efe6c304e
20 changed files with 468 additions and 108 deletions

View File

@@ -0,0 +1,41 @@
import { Pressable, Text, ActivityIndicator } from "react-native";
import currentTheme from "../Themes";
interface AuthButtonProps {
title: string;
onPress: () => void;
isLoading?: boolean;
}
const AuthButton = ({ title, onPress, isLoading = false }: AuthButtonProps) => {
return (
<Pressable
onPress={onPress}
disabled={isLoading}
className="w-full rounded-lg p-4 mb-4 border-4"
style={{
backgroundColor: isLoading
? currentTheme.disabledButton
: currentTheme.chatBot,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
}}
>
{isLoading ? (
<ActivityIndicator color={currentTheme.buttonText} />
) : (
<Text
className="text-center font-semibold text-lg"
style={{ color: currentTheme.buttonText }}
>
{title}
</Text>
)}
</Pressable>
);
};
export default AuthButton;

View File

@@ -1,12 +1,20 @@
import { View } from "react-native";
import { View, Pressable } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import currentTheme from "../Themes";
import { ReactNode } from "react";
import { AuthService } from "../services";
type HeaderProps = {
children?: ReactNode;
className?: string;
};
const handleLogout = async () => {
await AuthService.logout();
router.replace("/login");
};
const Header = (props: HeaderProps) => {
return (
<View>
@@ -17,6 +25,13 @@ const Header = (props: HeaderProps) => {
}}
>
{props.children}
<Pressable
onPress={handleLogout}
className="absolute right-1 top-0 p-2"
hitSlop={8}
>
<Ionicons name="log-out-outline" size={24} color={currentTheme.primeFg} />
</Pressable>
</View>
<View
className="h-2 bg-black"