Pull Request branch dev-clone to main #1
|
|
@ -69,5 +69,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #2555FF
|
--primary-color: #2555FF;
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import "./styles/tailwind.css";
|
import "./styles/tailwind.css";
|
||||||
import "./styles/fonts/manrope.css";
|
import "./styles/fonts/inter.css";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ interface RouteContext {
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<RouteContext>()({
|
export const Route = createRootRouteWithContext<RouteContext>()({
|
||||||
component: () => (
|
component: () => (
|
||||||
<div className="font-manrope">
|
<div className="font-inter">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<TanStackRouterDevtools />
|
<TanStackRouterDevtools />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
322
apps/frontend/src/routes/register/index.lazy.tsx
Normal file
322
apps/frontend/src/routes/register/index.lazy.tsx
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Input } from '@/shadcn/components/ui/input.tsx';
|
||||||
|
import { Button } from '@/shadcn/components/ui/button.tsx';
|
||||||
|
import { Alert } from '@/shadcn/components/ui/alert.tsx';
|
||||||
|
import { Checkbox } from "@/shadcn/components/ui/checkbox";
|
||||||
|
import { Form, FormField, FormControl, FormLabel, FormMessage, FormItem } from '@/shadcn/components/ui/form.tsx';
|
||||||
|
import { TbArrowNarrowRight } from 'react-icons/tb';
|
||||||
|
import client from "../../honoClient";
|
||||||
|
|
||||||
|
// Define the schema for validation
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
username: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
email: z.string().email("Alamat email tidak valid").min(1, "Kolom ini wajib diisi"),
|
||||||
|
password: z.string().min(6, "Kata sandi harus minimal 6 karakter"),
|
||||||
|
companyName: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
position: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
workExperience: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
address: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
phoneNumber: z.string().min(1, "Kolom ini wajib diisi"),
|
||||||
|
terms: z.boolean().refine((val) => val, "Anda harus menyetujui persyaratan dan layanan"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define the form type
|
||||||
|
type FormSchema = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/register/")({
|
||||||
|
component: RegisterPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function RegisterPage() {
|
||||||
|
const [errorFields, setErrorFields] = useState<Partial<Record<keyof FormSchema, string>> | null>(null);
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const form = useForm<FormSchema>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
companyName: "",
|
||||||
|
position: "",
|
||||||
|
workExperience: "",
|
||||||
|
address: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
terms: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async (values: FormSchema) => {
|
||||||
|
try {
|
||||||
|
const res = await client.register.$post({
|
||||||
|
json: values,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
// Redirect to login page on success
|
||||||
|
navigate({ to: "/login", replace: true });
|
||||||
|
} else {
|
||||||
|
// Handle non-200 responses from backend
|
||||||
|
const errorData = await res.json();
|
||||||
|
throw new Error(errorData.message || "An unknown error occurred");
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
const message = error.message;
|
||||||
|
|
||||||
|
if (message.includes("Email atau username sudah terdaftar")) {
|
||||||
|
setErrorFields({
|
||||||
|
email: "Email sudah terdaftar",
|
||||||
|
username: "Username sudah terdaftar",
|
||||||
|
});
|
||||||
|
} else if (message.includes("Nomor telepon sudah terdaftar")) {
|
||||||
|
setErrorFields({
|
||||||
|
phoneNumber: "Nomor telepon sudah terdaftar",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setErrorMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col lg:flex-row min-h-screen overflow-hidden">
|
||||||
|
{/* Image */}
|
||||||
|
<div className="relative h-[40vw] lg:h-screen -z-20 lg:mt-24">
|
||||||
|
<div className="-translate-y-[calc(35vw+1rem)] lg:translate-y-0 w-full lg:translate-x-[calc(10vh-45vw)] ">
|
||||||
|
<span className="absolute scale-50 lg:scale-50 -rotate-12 w-[100vw] h-[100vw] lg:w-[140vh] lg:h-[140vh] border border-gray-300 flex rounded-3xl"></span>
|
||||||
|
<span className="absolute scale-[85%] lg:scale-[70%] -rotate-12 w-[100vw] h-[100vw] lg:w-[140vh] lg:h-[140vh] border border-gray-300 flex rounded-3xl"></span>
|
||||||
|
<span className="absolute scale-[120%] lg:scale-90 -rotate-12 w-[100vw] h-[100vw] lg:w-[140vh] lg:h-[140vh] border border-gray-300 flex rounded-3xl"></span>
|
||||||
|
<span className="absolute scale-150 lg:scale-110 -rotate-12 w-[100vw] h-[100vw] lg:w-[140vh] lg:h-[140vh] border border-gray-300 hidden lg:flex rounded-3xl"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="absolute top-7 left-6">
|
||||||
|
<img src="../src/assets/logos/amati-logo.png" alt="Amati Logo" className="h-4 w-full object-contain" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main content */}
|
||||||
|
<div className="flex-1 flex flex-col md:flex-row items-center justify-center pt-10 lg:pt-20 lg:pl-56 md:justify-center lg:justify-end space-x-12 px-6 md:px-0">
|
||||||
|
|
||||||
|
{/* Form column */}
|
||||||
|
<div className="w-full md:w-1/2 mx-auto md:mx-20">
|
||||||
|
|
||||||
|
{/* Title and Already have an account */}
|
||||||
|
<div className="flex flex-col gap-1 mb-7">
|
||||||
|
<h1 className="text-3xl lg:text-4xl font-extrabold text-left">Daftar Akun</h1>
|
||||||
|
<p className="text-sm md:text-sm text-gray-400">
|
||||||
|
Sudah punya akun?{' '}
|
||||||
|
<a href="/login" className="text-blue-500 font-semibold hover:text-blue-800">
|
||||||
|
Sign In now
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
||||||
|
{errorMessage && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<p>{errorMessage}</p>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{Object.keys(errorFields || {}).length > 0 && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
{Object.values(errorFields || {}).map((msg, idx) => (
|
||||||
|
<p key={idx}>{msg}</p>
|
||||||
|
))}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Form fields */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormField name="name" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Nama Lengkap</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Masukkan nama lengkap sesuai dengan Kartu Identitas"
|
||||||
|
{...field}
|
||||||
|
className={`${form.formState.errors.name ? "border-red-500" : ""} truncate text-sm md:text-base`}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField name="username" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Username"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.username ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="email" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
placeholder="eg; user@mail.com"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.email ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="password" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Kata Sandi</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="******"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.password ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="companyName" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Nama Perusahaan</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Nama Perusahaan"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.companyName ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="position" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Jabatan</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Jabatan"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.position ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="workExperience" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Pengalaman Kerja</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Pengalaman Kerja"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.workExperience ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="address" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Alamat</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Alamat"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.address ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField name="phoneNumber" render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="font-bold text-sm">Nomor Telepon</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Nomor Telepon"
|
||||||
|
{...field}
|
||||||
|
className={form.formState.errors.phoneNumber ? "border-red-500" : ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)} />
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="terms"
|
||||||
|
render={() => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex items-center space-x-0.5 pb-10">
|
||||||
|
<Checkbox
|
||||||
|
checked={!!form.watch("terms")}
|
||||||
|
onCheckedChange={(checked) => form.setValue("terms", !!checked)}
|
||||||
|
className={`border ${form.formState.errors.terms ? "border-red-500" : "border-[#00000099]"}`}
|
||||||
|
onChange={form.register("terms").onChange}
|
||||||
|
onBlur={form.register("terms").onBlur}
|
||||||
|
name="terms"
|
||||||
|
ref={form.register("terms").ref}
|
||||||
|
id="terms"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-sm font-normal leading-none cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-[#00000099] p-2 rounded"
|
||||||
|
>
|
||||||
|
Saya setuju dengan syarat dan layanan
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
{form.formState.errors.terms && (
|
||||||
|
<FormMessage>{form.formState.errors.terms.message}</FormMessage>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 pb-6">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full flex items-center justify-between text-base font-medium md:w-auto bg-[--primary-color] text-white"
|
||||||
|
>
|
||||||
|
<span className="flex-grow text-left">Daftar Akun</span>
|
||||||
|
<TbArrowNarrowRight className="ml-12 h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
6
apps/frontend/src/styles/fonts/inter.css
Normal file
6
apps/frontend/src/styles/fonts/inter.css
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
|
||||||
|
|
||||||
|
.font-inter {
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user