summaryrefslogtreecommitdiff
path: root/src-migrate/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src-migrate/modules')
-rw-r--r--src-migrate/modules/account-activation/components/FormEmail.tsx97
-rw-r--r--src-migrate/modules/account-activation/components/FormOTP.tsx125
-rw-r--r--src-migrate/modules/account-activation/components/FormToken.tsx75
-rw-r--r--src-migrate/modules/account-activation/index.tsx16
-rw-r--r--src-migrate/modules/header/components/HeaderDesktop.tsx82
-rw-r--r--src-migrate/modules/header/components/HeaderMobile.tsx7
-rw-r--r--src-migrate/modules/header/components/SearchBar.tsx24
-rw-r--r--src-migrate/modules/header/index.tsx13
-rw-r--r--src-migrate/modules/home/components/Home.tsx15
-rw-r--r--src-migrate/modules/home/components/VerticalBanner.tsx7
-rw-r--r--src-migrate/modules/home/index.tsx3
-rw-r--r--src-migrate/modules/page-content/index.tsx22
-rw-r--r--src-migrate/modules/register/components/Form.tsx179
-rw-r--r--src-migrate/modules/register/components/FormCaptcha.tsx14
-rw-r--r--src-migrate/modules/register/components/TermCondition.tsx34
-rw-r--r--src-migrate/modules/register/index.tsx54
16 files changed, 767 insertions, 0 deletions
diff --git a/src-migrate/modules/account-activation/components/FormEmail.tsx b/src-migrate/modules/account-activation/components/FormEmail.tsx
new file mode 100644
index 00000000..ec300ba4
--- /dev/null
+++ b/src-migrate/modules/account-activation/components/FormEmail.tsx
@@ -0,0 +1,97 @@
+import { Alert, AlertIcon } from "@chakra-ui/react"
+import Link from "next/link"
+import { useRouter } from "next/router"
+import { ChangeEvent, useEffect, useState } from "react"
+import { useMutation } from "react-query"
+import Modal from "~/common/components/elements/Modal"
+import { useRegisterStore } from "~/common/stores/useRegisterStore"
+import { ActivationReqProps } from "~/common/types/auth"
+import { activationReq } from "~/services/auth"
+
+const FormEmail = () => {
+ const router = useRouter()
+ const { query } = router
+ const [active, setActive] = useState<boolean>(false)
+ const [isBtnDisabled, setIsBtnDisabled] = useState<boolean>(true)
+ const [email, setEmail] = useState<string>('')
+
+ const { form } = useRegisterStore()
+
+ useEffect(() => {
+ if (form) setEmail(form?.email)
+ }, [form])
+
+ useEffect(() => {
+ setIsBtnDisabled(email === '')
+ }, [email])
+
+ useEffect(() => {
+ setActive(query?.activation === 'email')
+ }, [query.activation])
+
+ const mutation = useMutation({
+ mutationFn: (data: ActivationReqProps) => activationReq(data)
+ })
+
+ const handleSubmit = (e: ChangeEvent<HTMLFormElement>) => {
+ e.preventDefault()
+ mutation.data = undefined
+ mutation.mutate({ email })
+ }
+
+ useEffect(() => {
+ if (mutation.data?.activation_request === true) {
+ const urlParams = new URLSearchParams({
+ activation: 'otp',
+ email,
+ redirect: (router.query?.redirect || '/') as string
+ })
+ router.push(`${router.route}?${urlParams}`)
+ }
+ }, [mutation.data?.activation_request, router, email])
+
+ return (
+ <Modal active={active} mode="desktop" close={() => router.push(router.route)} title="Aktivasi Akun">
+ <form onSubmit={handleSubmit} className="py-3 flex flex-col items-center gap-y-4">
+ {
+ mutation.data !== undefined &&
+ !mutation.data?.activation_request &&
+ (
+ <Alert status="warning">
+ <AlertIcon />
+ {mutation.data?.reason === 'ACTIVE' && 'Akun sudah aktif.'}
+ {mutation.data?.reason === 'NOT_FOUND' && 'Akun tidak ditemukan.'}
+
+ {mutation.data?.reason === 'ACTIVE' && (
+ <Link href="/login" className="ml-1 text-yellow-700 underline">Klik untuk masuk akun anda</Link>
+ )}
+
+ {mutation.data?.reason === 'NOT_FOUND' && (
+ <Link href="/register" className="ml-1 text-yellow-700 underline">Klik untuk daftar akun baru</Link>
+ )}
+ </Alert>
+ )
+ }
+
+ <input
+ type="text"
+ className="form-input w-full"
+ placeholder="Masukan alamat email anda"
+ autoComplete="email"
+ value={email}
+ onChange={(e) => setEmail(e.target.value)}
+ autoFocus
+ />
+ <button
+ type="submit"
+ className="btn-yellow w-full"
+ disabled={isBtnDisabled || mutation.isLoading}
+ >
+ {mutation.isLoading ? 'Loading...' : 'Kirim Aktivasi'}
+ </button>
+ </form>
+ </Modal>
+ )
+}
+
+export default FormEmail \ No newline at end of file
diff --git a/src-migrate/modules/account-activation/components/FormOTP.tsx b/src-migrate/modules/account-activation/components/FormOTP.tsx
new file mode 100644
index 00000000..6815a088
--- /dev/null
+++ b/src-migrate/modules/account-activation/components/FormOTP.tsx
@@ -0,0 +1,125 @@
+import { Button, HStack, PinInput, PinInputField, Spinner } from "@chakra-ui/react"
+import { useRouter } from "next/router"
+import { useEffect, useState } from "react"
+import { useMutation } from "react-query"
+import { useCountdown } from "usehooks-ts"
+import Modal from '~/common/components/elements/Modal'
+import { setAuth } from "~/common/libs/auth"
+import { ActivationOtpProps, ActivationReqProps } from "~/common/types/auth"
+import { activationReq, activationUserOTP } from "~/services/auth"
+
+const FormOTP = () => {
+ const router = useRouter()
+ const { query } = router
+ const [active, setActive] = useState<boolean>(false)
+ const [email, setEmail] = useState<string>('')
+ const [otp, setOtp] = useState<string>('')
+ const [count, { startCountdown, resetCountdown }] = useCountdown({
+ countStart: 60 * 1,
+ intervalMs: 1000,
+ })
+
+ useEffect(() => {
+ setEmail((query?.email || '') as string)
+ }, [query?.email])
+
+ useEffect(() => {
+ setActive(query?.activation === 'otp')
+ }, [query.activation])
+
+ useEffect(() => {
+ !!active && startCountdown()
+ }, [active, startCountdown])
+
+ const mutationActivationReq = useMutation({
+ mutationFn: (data: ActivationReqProps) => activationReq(data),
+ onSuccess: () => {
+ resetCountdown()
+ startCountdown()
+ }
+ })
+
+ const mutationActivation = useMutation({
+ mutationFn: (data: ActivationOtpProps) => activationUserOTP(data)
+ })
+
+ useEffect(() => {
+ if (otp.length === 4 && !mutationActivation.data?.activation) {
+ mutationActivation.mutate({ email, otp })
+ }
+ //eslint-disable-next-line
+ }, [otp])
+
+ useEffect(() => {
+ if (mutationActivation.data?.user) {
+ setAuth(mutationActivation.data.user)
+ router.push((query?.redirect || '/') as string)
+ }
+ }, [mutationActivation.data, router, query.redirect])
+
+ return (
+ <Modal active={active} className="w-10/12 md:w-fit px-10" close={() => setActive(false)} mode="desktop">
+ <div className="pb-8 flex flex-col items-center">
+ <div className="text-title-sm font-medium mb-4">Masukan Kode OTP</div>
+ <HStack>
+ <PinInput size='lg' otp onChange={(val) => setOtp(val)}>
+ <PinInputField autoFocus />
+ <PinInputField />
+ <PinInputField />
+ <PinInputField />
+ </PinInput>
+ </HStack>
+ <div className="mt-4 text-center">
+ {mutationActivation.isLoading && <Spinner size='sm' />}
+
+ {!mutationActivation.isLoading &&
+ mutationActivation.data?.reason === 'INVALID_OTP' &&
+ (
+ <span className="text-red-700">Mohon maaf kode OTP yand anda masukan salah</span>
+ )
+ }
+ </div>
+
+ {!mutationActivation.data?.activation && (
+ <div className="mt-4 text-center text-gray-700">
+ Kode verifikasi telah dikirimkan ke alamat email <span className="font-medium">{email}</span>. Silakan periksa kotak masuk atau folder spam.
+ </div>
+ )}
+
+ <div className="mt-4 flex flex-col items-center">
+ {!mutationActivation.data?.activation && (
+ <>
+ {count > 0 && timeFormat(count)}
+
+ {!mutationActivation.data?.activation && count == 0 && (
+ <>
+ <div className="mb-3">Belum menerima kode apapun?</div>
+
+ <Button
+ onClick={() => mutationActivationReq.mutate({ email })}
+ disabled={mutationActivationReq.isLoading}
+ >
+ {mutationActivationReq.isLoading ? <Spinner size='sm' /> : 'Kirim Ulang'}
+ </Button>
+ </>
+ )}
+ </>
+ )}
+
+ </div>
+ </div>
+ </Modal>
+ )
+}
+
+const timeFormat = (time: number): string => {
+ const minute = Math.floor(time / 60);
+ const second = time % 60;
+
+ const minuteFormat = minute < 10 ? `0${minute}` : `${minute}`
+ const secondFormat = second < 10 ? `0${second}` : `${second}`
+
+ return `${minuteFormat}:${secondFormat}`
+}
+
+export default FormOTP \ No newline at end of file
diff --git a/src-migrate/modules/account-activation/components/FormToken.tsx b/src-migrate/modules/account-activation/components/FormToken.tsx
new file mode 100644
index 00000000..b68b244f
--- /dev/null
+++ b/src-migrate/modules/account-activation/components/FormToken.tsx
@@ -0,0 +1,75 @@
+import { Alert, AlertDescription, AlertIcon, AlertTitle, Spinner } from "@chakra-ui/react"
+import { useRouter } from "next/router"
+import { useEffect, useState } from "react"
+import Link from "next/link"
+import { useMutation } from "react-query"
+
+import Modal from "~/common/components/elements/Modal"
+import { ActivationTokenProps } from "~/common/types/auth"
+import { activationUserToken } from "~/services/auth"
+import { setAuth } from "~/common/libs/auth"
+
+const FormToken = () => {
+ const router = useRouter()
+ const { query } = router
+ const [active, setActive] = useState<boolean>(false)
+
+ const mutation = useMutation({
+ mutationFn: (data: ActivationTokenProps) => activationUserToken(data),
+ })
+
+ useEffect(() => {
+ if (query?.activation === 'token' && typeof query?.token === 'string') {
+ mutation.mutate({ token: query.token })
+ setActive(true)
+ }
+ //eslint-disable-next-line
+ }, [query.activation, query.token])
+
+ useEffect(() => {
+ if (mutation.data?.user) {
+ setAuth(mutation.data.user)
+ router.push((query?.redirect || '/') as string)
+ }
+ }, [mutation.data, router, query.redirect])
+
+ return (
+ <Modal active={active} mode="desktop">
+ <div className="pb-6 flex flex-col items-center gap-y-6">
+ {mutation.isLoading && (
+ <>
+ <Spinner size='lg' borderWidth='3px' />
+ <div className="text-h-sm">Mohon tunggu sedang memverifikasi akun</div>
+ </>
+ )}
+
+ {!mutation.isLoading && !mutation.data?.activation && (
+ <Alert
+ status={mutation.data?.activation ? 'success' : 'error'}
+ flexDirection="column"
+ alignItems="center"
+ justifyContent="center"
+ textAlign="center"
+ height="200px"
+ bg="transparent"
+ >
+ <AlertIcon boxSize="40px" mr={0} />
+ <AlertTitle className="mt-4 mb-1 text-h-sm">
+ Aktivasi akun gagal
+ </AlertTitle>
+ <AlertDescription maxWidth="sm">
+ {mutation.data?.reason === 'INVALID_TOKEN' && (
+ <>
+ Token sudah kadaluwarsa, silahkan coba kembali.
+ <Link href='/register?activation=email' className="block mt-8 text-red-700 underline">Aktivasi Akun</Link>
+ </>
+ )}
+ </AlertDescription>
+ </Alert>
+ )}
+ </div>
+ </Modal>
+ )
+}
+
+export default FormToken \ No newline at end of file
diff --git a/src-migrate/modules/account-activation/index.tsx b/src-migrate/modules/account-activation/index.tsx
new file mode 100644
index 00000000..97c96953
--- /dev/null
+++ b/src-migrate/modules/account-activation/index.tsx
@@ -0,0 +1,16 @@
+import { useRouter } from "next/router"
+import FormToken from "./components/FormToken"
+import FormEmail from "./components/FormEmail"
+import FormOTP from "./components/FormOTP"
+
+const AccountActivation = () => {
+ return (
+ <>
+ <FormEmail />
+ <FormToken />
+ <FormOTP />
+ </>
+ )
+}
+
+export default AccountActivation \ No newline at end of file
diff --git a/src-migrate/modules/header/components/HeaderDesktop.tsx b/src-migrate/modules/header/components/HeaderDesktop.tsx
new file mode 100644
index 00000000..3860bded
--- /dev/null
+++ b/src-migrate/modules/header/components/HeaderDesktop.tsx
@@ -0,0 +1,82 @@
+import Logo from "~/images/logo.png";
+import { DocumentCheckIcon, HeartIcon } from "@heroicons/react/24/outline";
+
+import Image from 'next/image'
+import Link from 'next/link'
+
+// Components
+import SearchBar from "./SearchBar";
+
+// Constants
+import { SECONDARY_MENU_ITEMS } from "~/common/constants/menu";
+
+const LOGO_WIDTH = 210;
+const LOGO_HEIGHT = LOGO_WIDTH / 3;
+
+const HeaderDesktop = () => {
+ return (
+ <header>
+ <nav className="pt-6 sticky top-0 z-50 bg-white border-b-2 border-danger-500">
+ <div className="container flex items-center gap-x-6">
+ <Link href='/'>
+ <Image src={Logo} alt="Logo Indoteknik.com" width={LOGO_WIDTH} height={LOGO_HEIGHT} />
+ </Link>
+
+ <SearchBar />
+
+ <div className="flex gap-x-4 items-center">
+ <Link
+ target='_blank'
+ rel='noreferrer'
+ href='/my/transactions'
+ className='flex items-center gap-x-2 !text-gray-900'
+ >
+ <DocumentCheckIcon className='w-7' />
+ Daftar<br />Quotation
+ </Link>
+
+ <Link
+ target='_blank'
+ rel='noreferrer'
+ href='/my/wishlist'
+ className='flex items-center gap-x-2 !text-gray-900'
+ >
+ <HeartIcon className='w-7' />
+ Wishlist
+ </Link>
+
+ <a
+ href={''}
+ target='_blank'
+ rel='noreferrer'
+ className='flex items-center gap-x-1 !text-gray_r-12/80'
+ >
+ <Image src='/images/socials/Whatsapp-2.png' alt='Whatsapp' width={48} height={48} />
+ <div>
+ <div className='font-semibold'>Whatsapp</div>
+ 0812 8080 622 (Chat)
+ </div>
+ </a>
+ </div>
+ </div>
+
+ <div className="container mt-6 flex">
+ <button type="button" className="w-3/12 p-4 font-semibold border border-gray_r-6 rounded-t-xl flex items-center relative">
+ Kategori
+ </button>
+
+ <nav className="w-6/12 flex px-1 divide-x divide-gray-200">
+ {SECONDARY_MENU_ITEMS.map((item, index) => (
+ <Link key={index} href={item.href} target="_blank" rel="noreferrer" className="font-medium text-center p-4 flex-1 !text-gray-800 hover:bg-gray-100 transition-all">
+ {item.label}
+ </Link>
+ ))}
+ </nav>
+
+ </div>
+ </nav>
+ </header>
+ )
+}
+
+export default HeaderDesktop \ No newline at end of file
diff --git a/src-migrate/modules/header/components/HeaderMobile.tsx b/src-migrate/modules/header/components/HeaderMobile.tsx
new file mode 100644
index 00000000..626f30d7
--- /dev/null
+++ b/src-migrate/modules/header/components/HeaderMobile.tsx
@@ -0,0 +1,7 @@
+const HeaderMobile = () => {
+ return (
+ <div>HeaderMobile</div>
+ )
+}
+
+export default HeaderMobile \ No newline at end of file
diff --git a/src-migrate/modules/header/components/SearchBar.tsx b/src-migrate/modules/header/components/SearchBar.tsx
new file mode 100644
index 00000000..ec17abe8
--- /dev/null
+++ b/src-migrate/modules/header/components/SearchBar.tsx
@@ -0,0 +1,24 @@
+
+import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
+
+const SearchBar = () => {
+ return (
+ <form className="flex-1 flex items-center">
+ <input
+ type="text"
+ className="form-input p-3 rounded-r-none border-r-0 border-gray-300 focus:border-gray-300"
+ placeholder="Ketik nama / part number / merk"
+ />
+
+ <button
+ type="submit"
+ className="rounded-r border border-l-0 border-gray-300 px-2 py-2.5"
+ >
+ <MagnifyingGlassIcon className='w-6' />
+ </button>
+
+ </form>
+ )
+}
+
+export default SearchBar \ No newline at end of file
diff --git a/src-migrate/modules/header/index.tsx b/src-migrate/modules/header/index.tsx
new file mode 100644
index 00000000..5c0e2933
--- /dev/null
+++ b/src-migrate/modules/header/index.tsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { useWindowSize } from "usehooks-ts"
+
+import HeaderDesktop from './components/HeaderDesktop'
+import HeaderMobile from './components/HeaderMobile'
+
+const Header = () => {
+ const { width } = useWindowSize()
+
+ return width > 768 ? <HeaderDesktop /> : <HeaderMobile />
+}
+
+export default Header \ No newline at end of file
diff --git a/src-migrate/modules/home/components/Home.tsx b/src-migrate/modules/home/components/Home.tsx
new file mode 100644
index 00000000..5d3bf104
--- /dev/null
+++ b/src-migrate/modules/home/components/Home.tsx
@@ -0,0 +1,15 @@
+import VerticalBanner from "./VerticalBanner"
+
+const Home = () => {
+ return (
+ <>
+ <div className="container grid">
+ <div className="col-span-2">
+ <VerticalBanner />
+ </div>
+ </div>
+ </>
+ )
+}
+
+export default Home \ No newline at end of file
diff --git a/src-migrate/modules/home/components/VerticalBanner.tsx b/src-migrate/modules/home/components/VerticalBanner.tsx
new file mode 100644
index 00000000..57328037
--- /dev/null
+++ b/src-migrate/modules/home/components/VerticalBanner.tsx
@@ -0,0 +1,7 @@
+const VerticalBanner = () => {
+ return (
+ <div>VerticalBanner</div>
+ )
+}
+
+export default VerticalBanner \ No newline at end of file
diff --git a/src-migrate/modules/home/index.tsx b/src-migrate/modules/home/index.tsx
new file mode 100644
index 00000000..6993d9cf
--- /dev/null
+++ b/src-migrate/modules/home/index.tsx
@@ -0,0 +1,3 @@
+import Home from "./components/Home";
+
+export default Home \ No newline at end of file
diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx
new file mode 100644
index 00000000..cbd58633
--- /dev/null
+++ b/src-migrate/modules/page-content/index.tsx
@@ -0,0 +1,22 @@
+import { useQuery } from "react-query"
+import PageContentSkeleton from "~/common/components/skeleton/PageContentSkeleton"
+import { PageContentProps } from "~/common/types/pageContent"
+import { getPageContent } from "~/services/pageContent"
+
+type Props = {
+ path: string
+}
+
+const PageContent = ({ path }: Props) => {
+ const { data, isLoading } = useQuery<PageContentProps>(`page-content:${path}`, async () => await getPageContent({ path }))
+
+ if (isLoading) {
+ return <PageContentSkeleton />
+ }
+
+ return (
+ <div dangerouslySetInnerHTML={{ __html: data?.content || '' }}></div>
+ )
+}
+
+export default PageContent \ No newline at end of file
diff --git a/src-migrate/modules/register/components/Form.tsx b/src-migrate/modules/register/components/Form.tsx
new file mode 100644
index 00000000..dc9107b2
--- /dev/null
+++ b/src-migrate/modules/register/components/Form.tsx
@@ -0,0 +1,179 @@
+import { ChangeEvent, useMemo } from "react";
+import { useMutation } from "react-query";
+import { useRegisterStore } from "~/common/stores/useRegisterStore";
+import { RegisterProps } from "~/common/types/auth";
+import { registerUser } from "~/services/auth";
+import TermCondition from "./TermCondition";
+import FormCaptcha from "./FormCaptcha";
+import { useRouter } from "next/router";
+import { UseToastOptions, useToast } from "@chakra-ui/react";
+import Link from "next/link";
+
+const Form = () => {
+ const {
+ form,
+ isCheckedTNC,
+ isValidCaptcha,
+ errors,
+ updateForm,
+ validate,
+ } = useRegisterStore()
+
+ const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors])
+
+ const router = useRouter()
+ const toast = useToast()
+
+ const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
+ const { name, value } = event.target;
+ updateForm(name, value)
+ validate()
+ }
+
+ const mutation = useMutation({
+ mutationFn: (data: RegisterProps) => registerUser(data)
+ })
+
+ const handleSubmit = async (e: ChangeEvent<HTMLFormElement>) => {
+ e.preventDefault()
+
+ const response = await mutation.mutateAsync(form)
+
+ if (response?.register === true) {
+ const urlParams = new URLSearchParams({
+ activation: 'otp',
+ email: form.email,
+ redirect: (router.query?.next || '/') as string
+ })
+ router.push(`${router.route}?${urlParams}`)
+ }
+
+ const toastProps: UseToastOptions = {
+ duration: 5000,
+ isClosable: true
+ }
+
+ switch (response?.reason) {
+ case 'EMAIL_USED':
+ toast({
+ ...toastProps,
+ title: 'Email sudah digunakan',
+ status: 'warning'
+ })
+ break;
+ case 'NOT_ACTIVE':
+ const activationUrl = `${router.route}?activation=email`
+ toast({
+ ...toastProps,
+ title: 'Akun belum aktif',
+ description: <>Akun sudah terdaftar namun belum aktif. <Link href={activationUrl} className="underline">Klik untuk aktivasi akun</Link></>,
+ status: 'warning'
+ })
+ break
+ }
+ }
+
+ return (
+ <form className="mt-6 grid grid-cols-1 gap-y-4" onSubmit={handleSubmit}>
+ <div>
+ <label htmlFor="company">
+ Nama Perusahaan <span className='text-gray_r-11'>(opsional)</span>
+ </label>
+
+ <input
+ type="text"
+ name="company"
+ id="company"
+ className="form-input mt-3"
+ placeholder="cth: INDOTEKNIK DOTCOM GEMILANG"
+ autoCapitalize="true"
+ value={form.company}
+ onChange={handleInputChange}
+ />
+ </div>
+
+ <div>
+ <label htmlFor='name'>Nama Lengkap</label>
+
+ <input
+ type='text'
+ id='name'
+ name='name'
+ className='form-input mt-3'
+ placeholder='Masukan nama lengkap anda'
+ value={form.name}
+ onChange={handleInputChange}
+ aria-invalid={!!errors.name}
+ />
+
+ {!!errors.name && <span className="form-msg-danger">{errors.name}</span>}
+ </div>
+
+ <div>
+ <label htmlFor='phone'>No Handphone</label>
+
+ <input
+ type='tel'
+ id='phone'
+ name='phone'
+ className='form-input mt-3'
+ placeholder='08xxxxxxxx'
+ value={form.phone}
+ onChange={handleInputChange}
+ aria-invalid={!!errors.phone}
+ />
+
+ {!!errors.phone && <span className="form-msg-danger">{errors.phone}</span>}
+ </div>
+
+ <div>
+ <label htmlFor='email'>Alamat Email</label>
+
+ <input
+ type='text'
+ id='email'
+ name='email'
+ className='form-input mt-3'
+ placeholder='Masukan alamat email anda'
+ value={form.email}
+ onChange={handleInputChange}
+ autoComplete="username"
+ aria-invalid={!!errors.email}
+ />
+
+ {!!errors.email && <span className="form-msg-danger">{errors.email}</span>}
+ </div>
+
+ <div>
+ <label htmlFor='password'>Kata Sandi</label>
+ <input
+ type='password'
+ name='password'
+ id='password'
+ className='form-input mt-3'
+ placeholder='••••••••••••'
+ value={form.password}
+ onChange={handleInputChange}
+ autoComplete="current-password"
+ aria-invalid={!!errors.password}
+ />
+
+ {!!errors.password && <span className="form-msg-danger">{errors.password}</span>}
+ </div>
+
+ <FormCaptcha />
+
+ <TermCondition />
+
+ <button
+ type="submit"
+ className="btn-yellow w-full mt-2"
+ disabled={!isFormValid || !isCheckedTNC || mutation.isLoading || !isValidCaptcha}
+ >
+ {mutation.isLoading ? 'Loading...' : 'Daftar'}
+ </button>
+ </form>
+ )
+}
+
+export default Form \ No newline at end of file
diff --git a/src-migrate/modules/register/components/FormCaptcha.tsx b/src-migrate/modules/register/components/FormCaptcha.tsx
new file mode 100644
index 00000000..967be017
--- /dev/null
+++ b/src-migrate/modules/register/components/FormCaptcha.tsx
@@ -0,0 +1,14 @@
+import ReCaptcha from '~/common/components/elements/ReCaptcha'
+import { useRegisterStore } from '~/common/stores/useRegisterStore'
+
+const FormCaptcha = () => {
+ const { updateValidCaptcha } = useRegisterStore()
+
+ const handleCaptchaChange = (value: string | null) => {
+ updateValidCaptcha(value !== null)
+ }
+
+ return <ReCaptcha onChange={handleCaptchaChange} />
+}
+
+export default FormCaptcha \ No newline at end of file
diff --git a/src-migrate/modules/register/components/TermCondition.tsx b/src-migrate/modules/register/components/TermCondition.tsx
new file mode 100644
index 00000000..6b95ba19
--- /dev/null
+++ b/src-migrate/modules/register/components/TermCondition.tsx
@@ -0,0 +1,34 @@
+import { Checkbox } from '@chakra-ui/react'
+import React from 'react'
+import Modal from '~/common/components/elements/Modal'
+import { useRegisterStore } from '~/common/stores/useRegisterStore'
+import PageContent from '~/modules/page-content'
+
+const TermCondition = () => {
+ const { isOpenTNC, closeTNC, isCheckedTNC, toggleCheckTNC, openTNC } = useRegisterStore()
+
+ return (
+ <>
+ <div className="mt-4 flex items-center gap-x-2">
+ <Checkbox id='tnc' name='tnc' isChecked={isCheckedTNC} onChange={toggleCheckTNC} />
+ <div>
+ <label htmlFor="tnc" className="cursor-pointer">Dengan ini saya menyetujui</label>
+ {' '}
+ <span
+ className="font-medium text-danger-500 cursor-pointer"
+ onClick={openTNC}
+ >
+ syarat dan ketentuan
+ </span>
+ <label htmlFor="tnc" className="ml-2 cursor-pointer">yang berlaku</label>
+ </div>
+ </div>
+
+ <Modal active={isOpenTNC} close={closeTNC} >
+ <PageContent path='/register#tnd' />
+ </Modal>
+ </>
+ )
+}
+
+export default TermCondition \ No newline at end of file
diff --git a/src-migrate/modules/register/index.tsx b/src-migrate/modules/register/index.tsx
new file mode 100644
index 00000000..00931284
--- /dev/null
+++ b/src-migrate/modules/register/index.tsx
@@ -0,0 +1,54 @@
+import PageContent from "~/modules/page-content"
+import Form from "./components/Form"
+import Link from "next/link"
+import Image from "next/image"
+import IndoteknikLogo from "~/images/logo.png"
+import AccountActivation from "../account-activation"
+
+const LOGO_WIDTH = 150;
+const LOGO_HEIGHT = LOGO_WIDTH / 3;
+
+const Register = () => {
+ return (
+ <div className="container">
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 pt-10 px-2 md:pt-16">
+ <section>
+ <Link href='/' className="block md:hidden">
+ <Image src={IndoteknikLogo} alt='Logo Indoteknik' width={LOGO_WIDTH} height={LOGO_HEIGHT} className="mx-auto mb-4 w-auto h-auto" priority />
+ </Link>
+
+ <h1 className="text-2xl font-semibold text-center md:text-left">
+ Daftar Akun Indoteknik
+ </h1>
+ <h2 className="text-gray_r-11 mt-1 mb-10 text-center md:text-left">
+ Buat akun sekarang lebih mudah dan terverifikasi
+ </h2>
+
+ <Form />
+
+ <div className='text-gray_r-11 mt-4 text-center md:text-left'>
+ Sudah punya akun Indoteknik?{' '}
+ <Link href='/login' className='inline font-medium text-danger-500'>
+ Masuk
+ </Link>
+ </div>
+
+ <div className='text-gray_r-11 mt-4 text-center md:text-left'>
+ Akun anda belum aktif?{' '}
+ <Link href='/register?activation=email' className='inline font-medium text-danger-500'>
+ Aktivasi
+ </Link>
+ </div>
+ </section>
+
+ <section className="my-10 md:my-0">
+ <PageContent path="/register" />
+ </section>
+ </div>
+
+ <AccountActivation />
+ </div>
+ )
+}
+
+export default Register \ No newline at end of file