mirror of
https://github.com/khairul169/home-lab.git
synced 2026-10-31 15:20:50 +07:00
feat: add authentication
This commit is contained in:
+25
-10
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Slot } from "expo-router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Slot, router, usePathname } from "expo-router";
|
||||
import { QueryClientProvider } from "react-query";
|
||||
import queryClient from "@/lib/queryClient";
|
||||
import { View } from "react-native";
|
||||
@@ -7,22 +7,37 @@ import { cn, tw } from "@/lib/utils";
|
||||
import { useDeviceContext } from "twrnc";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useStore } from "zustand";
|
||||
import authStore from "@/stores/authStore";
|
||||
|
||||
const RootLayout = () => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const pathname = usePathname();
|
||||
const isLoggedIn = useStore(authStore, (i) => i.isLoggedIn);
|
||||
const [isLoaded, setLoaded] = useState(false);
|
||||
|
||||
useDeviceContext(tw);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLoggedIn && !pathname.startsWith("/auth")) {
|
||||
router.navigate("/auth/login");
|
||||
return;
|
||||
}
|
||||
}, [pathname, isLoggedIn, isLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoaded(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StatusBar style="auto" />
|
||||
<View style={cn("flex-1 bg-[#f2f7fb]")}>
|
||||
<View
|
||||
style={cn("flex-1 mx-auto w-full max-w-xl", {
|
||||
paddingTop: insets.top,
|
||||
})}
|
||||
>
|
||||
<Slot />
|
||||
</View>
|
||||
<View style={cn("flex-1 bg-[#f2f7fb]", { paddingTop: insets.top })}>
|
||||
<Slot />
|
||||
</View>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import Box from "@ui/Box";
|
||||
import Container from "@ui/Container";
|
||||
import Text from "@ui/Text";
|
||||
import { ScrollView } from "react-native";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Input from "@ui/Input";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import Button from "@ui/Button";
|
||||
import { useMutation } from "react-query";
|
||||
import api from "@/lib/api";
|
||||
import Alert from "@ui/Alert";
|
||||
import { setAuthToken } from "@/stores/authStore";
|
||||
import { router } from "expo-router";
|
||||
|
||||
const schema = z.object({
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
type FormSchema = z.infer<typeof schema>;
|
||||
|
||||
const defaultValues: FormSchema = {
|
||||
username: "",
|
||||
password: "",
|
||||
};
|
||||
|
||||
const LoginPage = () => {
|
||||
const form = useForm({ resolver: zodResolver(schema), defaultValues });
|
||||
const login = useMutation({
|
||||
mutationFn: (json: FormSchema) =>
|
||||
api.auth.login.$post({ json }).then((res) => res.json()),
|
||||
onSuccess: async (data) => {
|
||||
setAuthToken(data.token);
|
||||
router.navigate("/");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={cn("flex-1 flex items-center justify-center")}
|
||||
>
|
||||
<Container className="p-4">
|
||||
<Box className="p-8 bg-white rounded-lg">
|
||||
<Text className="text-2xl">Login</Text>
|
||||
|
||||
<Input
|
||||
label="Username"
|
||||
form={form}
|
||||
path="username"
|
||||
className="mt-6"
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
form={form}
|
||||
path="password"
|
||||
className="mt-4"
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
<Alert className="mt-4" error={login.error} />
|
||||
|
||||
<Button
|
||||
className="mt-8"
|
||||
onPress={form.handleSubmit((val) => login.mutate(val))}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -6,7 +6,6 @@ import Text from "@ui/Text";
|
||||
import { HStack, VStack } from "@ui/Stack";
|
||||
import Box from "@ui/Box";
|
||||
import ProcessList from "./ProcessList";
|
||||
import Divider from "@ui/Divider";
|
||||
import Button from "@ui/Button";
|
||||
import { Ionicons } from "@ui/Icons";
|
||||
|
||||
@@ -5,28 +5,31 @@ import Text from "@ui/Text";
|
||||
import Performance from "./_sections/Performance";
|
||||
import Summary from "./_sections/Summary";
|
||||
import Storage from "./_sections/Storage";
|
||||
import { ScrollView } from "react-native";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Container from "@ui/Container";
|
||||
import { useAuth } from "@/stores/authStore";
|
||||
|
||||
const App = () => {
|
||||
const HomePage = () => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
const { data: system } = useQuery({
|
||||
queryKey: ["system"],
|
||||
queryFn: () => api.system.$get().then((r) => r.json()),
|
||||
refetchInterval: 1000,
|
||||
enabled: isLoggedIn,
|
||||
});
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={cn("px-4 py-8 md:py-16")}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Container scrollable className="px-4 py-8 md:py-16">
|
||||
<Text className="text-2xl font-medium">Home Lab</Text>
|
||||
|
||||
<Summary data={system} />
|
||||
<Performance data={system} />
|
||||
<Storage data={system} />
|
||||
</ScrollView>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default HomePage;
|
||||
Reference in New Issue
Block a user