Merge pull request #17 from IMPHNEN/feat/handle-state-modal

Feat/handle state modal
This commit is contained in:
Maulana Sodiqin
2025-04-03 21:01:05 +08:00
committed by GitHub
11 changed files with 80 additions and 36 deletions
@@ -1,19 +1,20 @@
import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Button } from '@imphnen-frontend-service/ui/atoms';
import { Modal } from '@imphnen-frontend-service/ui/molecules'; import { Modal } from '@imphnen-frontend-service/ui/molecules';
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
import { Link } from 'react-router-dom';
import { useLogin } from '../../_hooks/use-login'; import { useLogin } from '../../_hooks/use-login';
interface IModalFormLogin { interface IModalFormLogin {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onForgotPassword: () => void; onForgotPassword: () => void;
setIsOpenRegisterModal: (value: boolean) => void;
} }
const ModalFormLogin = ({ const ModalFormLogin = ({
isOpen, isOpen,
onClose, onClose,
onForgotPassword, onForgotPassword,
setIsOpenRegisterModal,
}: IModalFormLogin) => { }: IModalFormLogin) => {
const { form, onSubmit } = useLogin(); const { form, onSubmit } = useLogin();
@@ -59,12 +60,13 @@ const ModalFormLogin = ({
</Button> </Button>
<div className="flex justify-center gap-2 pt-2.5 font-medium"> <div className="flex justify-center gap-2 pt-2.5 font-medium">
<p className="text-neutral-500">Belum punya akun?</p> <p className="text-neutral-500">Belum punya akun?</p>
<Link <Button
className="text-primary-500 hover:text-primary-600" variant="text"
to="/register" className="text-primary-500 hover:text-primary-600 m-0 p-0"
onClick={() => setIsOpenRegisterModal(true)}
> >
Daftar Daftar
</Link> </Button>
</div> </div>
</form> </form>
</Modal.Content> </Modal.Content>
+9 -7
View File
@@ -1,14 +1,16 @@
import { FC, ReactElement } from 'react';
import { Outlet } from 'react-router-dom';
import { Navbar } from '@imphnen-frontend-service/ui/organisms'; import { Navbar } from '@imphnen-frontend-service/ui/organisms';
import { Outlet } from 'react-router-dom';
import { FC, ReactElement } from 'react';
import { ModalLoginProvider } from '@imphnen-frontend-service/utils';
export const AppLayout: FC = (): ReactElement => { export const AppLayout: FC = (): ReactElement => {
return ( return (
<main className="bg-primary-50 min-h-screen"> <ModalLoginProvider>
<Navbar /> <main className="bg-primary-50 min-h-screen">
<Outlet /> <Navbar />
</main> <Outlet />
</main>
</ModalLoginProvider>
); );
}; };
export default AppLayout; export default AppLayout;
+3 -13
View File
@@ -5,10 +5,11 @@ import ModalFormForgotPassword from './_components/form/modal-form-forgot-passwo
import ModalFormLogin from './_components/form/modal-form-login'; import ModalFormLogin from './_components/form/modal-form-login';
import ModalFormRegister from './_components/form/modal-form-register'; import ModalFormRegister from './_components/form/modal-form-register';
import { GachaItem } from './_components/item/gacha-item'; import { GachaItem } from './_components/item/gacha-item';
import { useModalLogin } from '@imphnen-frontend-service/utils';
export const Components: FC = (): ReactElement => { export const Components: FC = (): ReactElement => {
const { showModalLogin, setShowModalLogin } = useModalLogin();
const [showModalForgotPassword, setShowModalForgotPassword] = useState(false); const [showModalForgotPassword, setShowModalForgotPassword] = useState(false);
const [showModalLogin, setShowModalLogin] = useState(false);
const [showModalRegister, setShowModalRegister] = useState(false); const [showModalRegister, setShowModalRegister] = useState(false);
const scrollToRoulette = () => { const scrollToRoulette = () => {
@@ -176,18 +177,6 @@ export const Components: FC = (): ReactElement => {
Spin Now Spin Now
</Button> </Button>
</div> </div>
<div className="flex gap-4 col-span-4 md:col-span-8 lg:col-span-6 justify-center items-center">
<Button onClick={() => setShowModalLogin(true)}>
Open Modal Login
</Button>
<Button onClick={() => setShowModalForgotPassword(true)}>
Open Modal Forgot Password
</Button>
<Button onClick={() => setShowModalRegister(true)}>
Open Modal Register
</Button>
</div>
</section> </section>
<div className="sticky bottom-0 h-[86px] md:h-[200px] bg-gradient-to-b from-primary-500/0 to-primary-500/50 to-80%"></div> <div className="sticky bottom-0 h-[86px] md:h-[200px] bg-gradient-to-b from-primary-500/0 to-primary-500/50 to-80%"></div>
@@ -195,6 +184,7 @@ export const Components: FC = (): ReactElement => {
isOpen={showModalLogin} isOpen={showModalLogin}
onClose={() => setShowModalLogin(false)} onClose={() => setShowModalLogin(false)}
onForgotPassword={handleForgotPasswordClick} onForgotPassword={handleForgotPasswordClick}
setIsOpenRegisterModal={setShowModalRegister}
key="login" key="login"
/> />
+12 -4
View File
@@ -2,8 +2,16 @@ import { z } from 'zod';
export const authLoginSchema = z.object({ export const authLoginSchema = z.object({
email: z email: z
.string() .string({
.min(1, 'Email cannot be empty') required_error: 'Email tidak boleh kosong',
.email('Email must be valid'), invalid_type_error: 'Email harus berupa string',
password: z.string().min(1, 'Password cannot be empty'), })
.min(1, 'Email tidak boleh kosong')
.email('Email harus valid'),
password: z
.string({
required_error: 'Password tidak boleh kosong',
invalid_type_error: 'Password harus berupa string',
})
.min(1, 'Password tidak boleh kosong'),
}); });
@@ -75,7 +75,7 @@ export const InputField: FC<TInputFieldProps> = ({
{...rest} {...rest}
/> />
{error ? ( {error ? (
<p className="text-danger-500 text-xs mt-1">{error}</p> <p className="text-danger-500 text-xl mt-1">{error}</p>
) : ( ) : (
helperText && ( helperText && (
<p className={cn('text-cs mt-1', sizeClasses[size].helperText)}> <p className={cn('text-cs mt-1', sizeClasses[size].helperText)}>
+7 -6
View File
@@ -2,11 +2,12 @@ import { MenuOutlined } from '@ant-design/icons';
import { FC, ReactElement, useState } from 'react'; import { FC, ReactElement, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button } from '../../atoms/button'; import { Button } from '../../atoms/button';
import { useSession } from '@imphnen-frontend-service/utils'; import { useModalLogin, useSession } from '@imphnen-frontend-service/utils';
export const Navbar: FC = (): ReactElement => { export const Navbar: FC = (): ReactElement => {
const { session, signOut, isAuthenticated } = useSession(); const { session, signOut, isAuthenticated } = useSession();
const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const { setShowModalLogin } = useModalLogin();
return ( return (
<div className="bg-primary-50 w-full px-[32px] pt-[32px] md:px-[60px] md:pt-[60px] lg:px-[80px] sticky top-0 z-50"> <div className="bg-primary-50 w-full px-[32px] pt-[32px] md:px-[60px] md:pt-[60px] lg:px-[80px] sticky top-0 z-50">
@@ -44,7 +45,7 @@ export const Navbar: FC = (): ReactElement => {
</li> </li>
{!isAuthenticated ? ( {!isAuthenticated ? (
<li> <li>
<Button>Login</Button> <Button onClick={() => setShowModalLogin(true)}>Login</Button>
</li> </li>
) : ( ) : (
<li className="flex gap-x-4"> <li className="flex gap-x-4">
@@ -88,12 +89,12 @@ export const Navbar: FC = (): ReactElement => {
</li> </li>
{!isAuthenticated ? ( {!isAuthenticated ? (
<li> <li>
<Link <Button
to="#" onClick={() => setShowModalLogin(true)}
className="block text-gray-600 transition-colors px-4 py-2 text-center font-semibold" className="block w-full text-gray-100 transition-colors px-4 py-2 text-center font-semibold"
> >
Login Login
</Link> </Button>
</li> </li>
) : ( ) : (
<li>{session.user?.fullname}</li> <li>{session.user?.fullname}</li>
+1
View File
@@ -1,2 +1,3 @@
export * from './use-query-state'; export * from './use-query-state';
export * from './use-session'; export * from './use-session';
export * from './use-modal-login';
+28
View File
@@ -0,0 +1,28 @@
import { createContext, ReactNode, useContext, useState } from 'react';
interface ModalLoginContextType {
showModalLogin: boolean;
setShowModalLogin: (value: boolean) => void;
}
const ModalLoginContext = createContext<ModalLoginContextType | undefined>(
undefined
);
export const ModalLoginProvider = ({ children }: { children: ReactNode }) => {
const [showModalLogin, setShowModalLogin] = useState(false);
return (
<ModalLoginContext.Provider value={{ showModalLogin, setShowModalLogin }}>
{children}
</ModalLoginContext.Provider>
);
};
export const useModalLogin = () => {
const context = useContext(ModalLoginContext);
if (!context) {
throw new Error('useModalLogin must be used within an ModalLoginProvider');
}
return context;
};
+11
View File
@@ -21,6 +21,7 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.55.0", "react-hook-form": "^7.55.0",
"react-router-dom": "^7.3.0", "react-router-dom": "^7.3.0",
"sonner": "^2.0.3",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
@@ -24244,6 +24245,16 @@
"atomic-sleep": "^1.0.0" "atomic-sleep": "^1.0.0"
} }
}, },
"node_modules/sonner": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/sort-keys": { "node_modules/sort-keys": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+1
View File
@@ -30,6 +30,7 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.55.0", "react-hook-form": "^7.55.0",
"react-router-dom": "^7.3.0", "react-router-dom": "^7.3.0",
"sonner": "^2.0.3",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },