Dimentorin auth login responsive and implementation (#23)
* feat: responsive * fix: success register * feat: auth login responsive && auth login implementation
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
authLoginSchema,
|
||||||
|
TLoginRequest,
|
||||||
|
usePostLogin,
|
||||||
|
} from '@imphnen-frontend-service/service';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export const useLogin = () => {
|
||||||
|
const postLogin = usePostLogin();
|
||||||
|
const form = useForm<TLoginRequest>({
|
||||||
|
resolver: zodResolver(authLoginSchema),
|
||||||
|
mode: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = form.handleSubmit((data) => {
|
||||||
|
postLogin.mutate(data, {
|
||||||
|
onSuccess: () => toast.success("Login sukses"),
|
||||||
|
onError: (error) => toast.error(error.message),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,46 +1,50 @@
|
|||||||
import { FC, ReactElement } from 'react';
|
import { FC, ReactElement } from 'react';
|
||||||
import { LoginBanner } from '@imphnen-frontend-service/ui/organisms';
|
import { ControlledInputField, LoginBanner } from '@imphnen-frontend-service/ui/organisms';
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useLogin } from '../../_hooks/use-login';
|
||||||
import { InputField } from '@imphnen-frontend-service/ui/molecules';
|
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
const [searchParams] = useSearchParams();
|
const { form, onSubmit } = useLogin();
|
||||||
const error = searchParams.get('error'); // Ambil nilai ?error=
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
||||||
<div className="bg-white min-w-[1120px] min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6">
|
<div className="bg-white xl:min-w-[1120px] xl:min-h-[712px] p-10 rounded-2xl shadow-md flex gap-6">
|
||||||
<LoginBanner />
|
<LoginBanner />
|
||||||
<div className="border-2 border-primary-500/50 w-[596px] rounded-lg py-[70px] px-[96px] flex justify-center">
|
<div className="xl:border-2 xl:border-primary-500/50 md:w-[596px] rounded-lg md:py-[70px] md:px-[96px] flex justify-center">
|
||||||
<div className="w-[404px]">
|
<div className="md:w-[404px]">
|
||||||
<h2 className="text-4xl font-semibold text-primary-500 text-center mb-2">
|
<h2 className="text-4xl font-semibold text-primary-500 text-center mb-2">
|
||||||
Hallo Minna-san
|
Hallo Minna-san
|
||||||
</h2>
|
</h2>
|
||||||
<h5 className="text-xl font-medium text-primary-500 text-center">
|
<h5 className="text-xl font-medium text-primary-500 text-center mb-5">
|
||||||
Welcome to Dimentorin by IMPHNEN
|
Welcome to Dimentorin by IMPHNEN
|
||||||
</h5>
|
</h5>
|
||||||
<InputField
|
<form onSubmit={onSubmit} className="flex flex-col gap-2">
|
||||||
label="Email"
|
<ControlledInputField
|
||||||
error={error ? error : undefined}
|
control={form.control}
|
||||||
size="lg"
|
label="Email"
|
||||||
className="w-full"
|
size="lg"
|
||||||
placeholder="Masukkan email-mu, Senpai~! ✨ (Pastikan tidak typo, ya~ 😆)"
|
className="w-full mb-2"
|
||||||
/>
|
placeholder="Masukkan email-mu, Senpai~! ✨ (Pastikan tidak typo, ya~ 😆)"
|
||||||
<InputField
|
name={'email'}
|
||||||
label="password"
|
/>
|
||||||
error={error ? error : undefined}
|
<ControlledInputField
|
||||||
className="w-full"
|
control={form.control}
|
||||||
size="lg"
|
label="Password"
|
||||||
type="password"
|
className="w-full"
|
||||||
placeholder="Masukkan password rahasiamu!"
|
size="lg"
|
||||||
/>
|
type="password"
|
||||||
<div className="flex justify-end my-5">
|
placeholder="Masukkan password rahasiamu!"
|
||||||
<a href="/auth/forgot" className="text-primary-500 font-medium">
|
name={'password'}
|
||||||
Lupa Password ?
|
/>
|
||||||
</a>
|
<div className="flex justify-end my-5">
|
||||||
</div>
|
<a href="/auth/forgot" className="text-primary-500 font-medium">
|
||||||
<Button className="w-full">Enter Isekai</Button>
|
Lupa Password ?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Button className="w-full" type='submit'>Enter Isekai</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div className="flex my-3 gap-3 justify-center">
|
<div className="flex my-3 gap-3 justify-center">
|
||||||
<h5>Belum Punya akun ?</h5>
|
<h5>Belum Punya akun ?</h5>
|
||||||
<a href="/auth/register" className="text-primary-500 font-medium">
|
<a href="/auth/register" className="text-primary-500 font-medium">
|
||||||
@@ -63,6 +67,13 @@ export const Components: FC = (): ReactElement => {
|
|||||||
width={24}
|
width={24}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='xl:hidden w-full gap-3'
|
||||||
|
variant='secondary'
|
||||||
|
>
|
||||||
|
<ArrowLeftOutlined />
|
||||||
|
Kembali Ke Homepage
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const Components: FC = (): ReactElement => {
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h4 className="font-medium">OTP Code</h4>
|
<h4 className="font-medium">OTP Code</h4>
|
||||||
<Input
|
<Input
|
||||||
widthform="custom"
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
size="lg"
|
size="lg"
|
||||||
placeholder="Kode OTP"
|
placeholder="Kode OTP"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FC, ReactElement } from 'react';
|
import { FC, ReactElement } from 'react';
|
||||||
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
||||||
import { Button, Input } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { InputField } from '@imphnen-frontend-service/ui/molecules';
|
|
||||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
convertPagesToRoute,
|
convertPagesToRoute,
|
||||||
QueryProvider,
|
QueryProvider,
|
||||||
} from '@imphnen-frontend-service/utils';
|
} from '@imphnen-frontend-service/utils';
|
||||||
|
import { Toaster } from 'sonner';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
||||||
@@ -34,6 +35,7 @@ if (!rootElement) throw new Error('Failed to find the root element');
|
|||||||
createRoot(rootElement).render(
|
createRoot(rootElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
|
<Toaster position="top-right" />
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Button } from '../button';
|
|||||||
|
|
||||||
type TInputType = 'text' | 'email' | 'number' | 'password' | 'file';
|
type TInputType = 'text' | 'email' | 'number' | 'password' | 'file';
|
||||||
type TInputSize = 'sm' | 'md' | 'lg';
|
type TInputSize = 'sm' | 'md' | 'lg';
|
||||||
|
type Width = 'standard' | 'custom'
|
||||||
|
|
||||||
type TInputProps = Omit<
|
type TInputProps = Omit<
|
||||||
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
||||||
@@ -18,6 +19,7 @@ type TInputProps = Omit<
|
|||||||
> & {
|
> & {
|
||||||
type?: TInputType;
|
type?: TInputType;
|
||||||
size?: TInputSize;
|
size?: TInputSize;
|
||||||
|
widthform?: Width;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ export const Input: FC<TInputProps> = ({
|
|||||||
type = 'text',
|
type = 'text',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
placeholder = 'Placeholder',
|
placeholder = 'Placeholder',
|
||||||
|
widthform='standard',
|
||||||
disabled,
|
disabled,
|
||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
@@ -46,7 +49,7 @@ export const Input: FC<TInputProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mergedClassName = cn(
|
const mergedClassName = cn(
|
||||||
'px-[12px] py-[8px] text-neutral-800 bg-white placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree min-w-70',
|
`px-[12px] py-[8px] text-neutral-800 bg-white placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree ${widthform === "standard" ? "min-w-70" : ""}`,
|
||||||
sizeClasses[size].textSize,
|
sizeClasses[size].textSize,
|
||||||
disabled && disabledClass,
|
disabled && disabledClass,
|
||||||
className
|
className
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FC, ReactElement } from "react";
|
|||||||
|
|
||||||
function AuthBanner({text, href}: {text: string, href: string}){
|
function AuthBanner({text, href}: {text: string, href: string}){
|
||||||
return (
|
return (
|
||||||
<div className='relative rounded-md'>
|
<div className='hidden xl:block relative rounded-md'>
|
||||||
<img src="/image/95319c4f9953dfe6180200e529dfcea5.webp" alt="Banner" className='w-[420px] h-[632px] object-[80%] object-cover rounded-lg' />
|
<img src="/image/95319c4f9953dfe6180200e529dfcea5.webp" alt="Banner" className='w-[420px] h-[632px] object-[80%] object-cover rounded-lg' />
|
||||||
<div className='absolute top-0 bg-gradient-to-b from-primary-500 to-transparent w-full rounded-t-lg h-[163px] py-5 px-5'>
|
<div className='absolute top-0 bg-gradient-to-b from-primary-500 to-transparent w-full rounded-t-lg h-[163px] py-5 px-5'>
|
||||||
<Button variant='secondary' className='gap-2' onClick={() => document.location.href = `${href}`}>
|
<Button variant='secondary' className='gap-2' onClick={() => document.location.href = `${href}`}>
|
||||||
|
|||||||
Reference in New Issue
Block a user