init project portal web
This commit is contained in:
7
lib/login/data/repository/index.ts
Normal file
7
lib/login/data/repository/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { apiClient } from "@/services/api/api-client"
|
||||
|
||||
|
||||
export const loginRepository = {
|
||||
login: async (username: string, password: string) => await apiClient("/api/login", "POST", {username, password}),
|
||||
logout: async () => await apiClient("/api/logout", "POST")
|
||||
}
|
||||
23
lib/login/store/index.ts
Normal file
23
lib/login/store/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// stores/loginStore.ts
|
||||
import { makeAutoObservable } from "mobx";
|
||||
|
||||
class LoginStore {
|
||||
username = "";
|
||||
password = "";
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
setUsername(username: string) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
setPassword(password: string) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
const loginStore = new LoginStore();
|
||||
|
||||
export default loginStore;
|
||||
28
lib/login/view-model/index.ts
Normal file
28
lib/login/view-model/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { loginRepository } from "../data/repository";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import loginStore from "../store";
|
||||
|
||||
|
||||
export const useLogin = () => {
|
||||
const router = useRouter()
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => loginRepository.login(loginStore.username, loginStore.password),
|
||||
onSuccess: () => {
|
||||
router.push("/")
|
||||
toast.success('Welcome to dashboard')
|
||||
},
|
||||
onError: ((error)=> {
|
||||
toast.error(error.message)
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
login: mutation.mutate,
|
||||
isLoading: mutation.isPending,
|
||||
isError: mutation.isError,
|
||||
error: mutation.error,
|
||||
}
|
||||
}
|
||||
110
lib/login/view/index.tsx
Normal file
110
lib/login/view/index.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useLogin } from "../view-model";
|
||||
import InputPassword from "@/components/module/input-password";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import QueryWrapper from "@/components/module/query-wrapper";
|
||||
import leftClipath from "@/images/left_clip_path.png";
|
||||
import rightClipPath from "@/images/right_clip_path.png";
|
||||
import mainLogo from "@/images/Telkomcel.png";
|
||||
import formDecor from "@/images/login_accesoris.png";
|
||||
import Image from "next/image";
|
||||
import { Toaster } from "sonner";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import loginStore from "../store";
|
||||
|
||||
const Content = observer(() => {
|
||||
const { isLoading, login } = useLogin();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
login();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen bg-white lg:flex">
|
||||
<div className="hidden w-full h-full bg-primary lg:block relative">
|
||||
<div className="absolute left-0 top-0">
|
||||
<Image src={leftClipath} alt="left-clipt" />
|
||||
</div>
|
||||
<div className="absolute right-0 bottom-0">
|
||||
<Image src={rightClipPath} alt="right-clipt" />
|
||||
</div>
|
||||
<section className="h-full flex flex-col justify-center items-center">
|
||||
<div className="max-w-96">
|
||||
<Image src={mainLogo} alt="main-logo" />
|
||||
</div>
|
||||
<div className="mt-[60px] space-y-10 text-center text-white">
|
||||
<h3 className="text-3xl">Connect. Secure. Thrive</h3>
|
||||
<p className="max-w-[542px] text-left text-2xl">
|
||||
Empowering business with advanced networking solutions that drive growth and innovation.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div className="w-full h-full p-4 flex lg:block flex-col justify-center items-center lg:p-0">
|
||||
<div className="w-full mt-[-30px] hidden lg:block">
|
||||
<Image src={formDecor} alt="form-decor-1" width={188} height={188} />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center items-center lg:mt-[-30px]">
|
||||
<h1 className="text-black font-bold text-[40px]">Welcome!</h1>
|
||||
<div className="w-1/2 lg:w-1/6 border-2 border-primaryBlue-100"></div>
|
||||
</div>
|
||||
<form
|
||||
className="space-y-4 mt-[45px] mx-auto w-full lg:max-w-[399px]"
|
||||
onSubmit={handleSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="email">Username</label>
|
||||
<Input
|
||||
className="placeholder:text-black-50 border border-black-90 h-[38px]"
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
value={loginStore.username}
|
||||
onChange={(e) => loginStore.setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password">Password</label>
|
||||
<InputPassword
|
||||
className="placeholder:text-black-50 border border-black-90 h-[38px]"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
value={loginStore.password}
|
||||
onChange={(e) => loginStore.setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="destructive"
|
||||
className="w-full h-[38px] bg-primary p-2 text-lg"
|
||||
>
|
||||
{isLoading ? "Please wait..." : "Login"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-[-30px] hidden lg:flex justify-end">
|
||||
<Image src={formDecor} alt="form-decor-1" width={188} height={188} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Login = () => {
|
||||
return (
|
||||
<QueryWrapper>
|
||||
<Content />
|
||||
<Toaster position="top-right" richColors duration={1500} />
|
||||
</QueryWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
Reference in New Issue
Block a user