This commit is contained in:
2026-03-09 06:16:36 +01:00
parent ef54906d9a
commit 40dacc0b62
52 changed files with 1922 additions and 143 deletions
+59
View File
@@ -0,0 +1,59 @@
import Link from "next/link";
import { useId } from "react";
export default function Logo() {
const mainGradientId = useId();
const foldGradientId = useId();
return (
<Link
href="/"
aria-label="TurboCI home"
className="inline-flex items-center gap-3"
>
<svg
viewBox="0 0 48 48"
aria-hidden="true"
className="h-10 w-10 shrink-0"
>
<defs>
<linearGradient
id={mainGradientId}
x1="9"
y1="9"
x2="31"
y2="34"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" stopColor="#9cf0c0" />
<stop offset="100%" stopColor="#42d392" />
</linearGradient>
<linearGradient
id={foldGradientId}
x1="22"
y1="22"
x2="36"
y2="40"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" stopColor="#2bc67e" />
<stop offset="100%" stopColor="#1f8458" />
</linearGradient>
</defs>
<path
d="M35 6H23.6c-2 0-3.8 1-4.8 2.7L7.5 28.3c-1.4 2.5.4 5.7 3.2 5.7h9.1c2 0 3.8-1 4.8-2.7l11.2-19.6C39.6 9.2 37.8 6 35 6Z"
fill={`url(#${mainGradientId})`}
/>
<path
d="M23.9 24h13.4c2.8 0 4.6 3.1 3.2 5.6l-4.1 7.1c-1 1.7-2.8 2.7-4.8 2.7H18.2c-2.8 0-4.6-3.1-3.2-5.6l4.1-7.1c1-1.7 2.8-2.7 4.8-2.7Z"
fill={`url(#${foldGradientId})`}
/>
</svg>
<span className="font-display text-[1.02rem] font-semibold tracking-tight text-foreground">
TurboCI
</span>
</Link>
);
}
@@ -1,5 +1,85 @@
import useLoginForm from "@/src/hooks/use-login-form";
import Tag from "@/twui/components/elements/Tag";
import Form from "@/twui/components/form/Form";
import Input from "@/twui/components/form/Input";
import Button from "@/twui/components/layout/Button";
import Stack from "@/twui/components/layout/Stack";
import z from "zod";
export default function LoginForm() {
return <Stack></Stack>;
const { loading, setLoginData, submitLogin, alert } = useLoginForm();
return (
<Form
className="w-full"
submitHandler={() => {
submitLogin();
}}
>
<Stack className="w-full items-stretch gap-6">
{alert?.text ? (
<Tag
color="error"
variant="outlined"
className="py-2 px-6 opacity-70"
>
{alert.text}
</Tag>
) : null}
<Input
placeholder="Email Address or Username"
title="Email/Username"
changeHandler={(v) => {
const email_schema = z.email();
const is_email = email_schema.safeParse(v);
setLoginData((prev) => ({
...prev,
email: is_email.success ? v : undefined,
username: is_email.success ? undefined : v,
}));
}}
validity={
alert?.field_name == "email-username"
? {
isValid: false,
msg: alert?.text,
}
: {
isValid: true,
}
}
showLabel
/>
<Input
placeholder="Password"
title="Password"
type="password"
changeHandler={(v) => {
setLoginData((prev) => ({
...prev,
password: v,
}));
}}
validity={
alert?.field_name == "password"
? {
isValid: false,
msg: alert?.text,
}
: {
isValid: true,
}
}
showLabel
/>
<Button title="Login" loading={loading} onClick={submitLogin}>
Login
</Button>
</Stack>{" "}
</Form>
);
}
+8 -5
View File
@@ -1,13 +1,16 @@
import Paper from "@/twui/components/elements/Paper";
import H2 from "@/twui/components/layout/H2";
import Stack from "@/twui/components/layout/Stack";
import LoginForm from "./(partials)/login-form";
import Span from "@/twui/components/layout/Span";
export default function Main() {
return (
<Paper>
<Stack>
<H2>Home</H2>
<Stack className="w-full items-center max-w-lg gap-10">
<Stack className="gap-4">
<H2>Login</H2>
<Span>Welcome Back</Span>
</Stack>
</Paper>
<LoginForm />
</Stack>
);
}
@@ -0,0 +1,18 @@
import { TurboCISignupFormObject } from "@/src/types";
import useStatus from "@/twui/components/hooks/useStatus";
import { useState } from "react";
export default function useSignupForm() {
const [newUser, setNewUser] = useState<TurboCISignupFormObject>({});
const { loading, setLoading } = useStatus();
const [isPasswordConfirmed, setIsPasswordConfirmed] = useState(false);
return {
newUser,
setNewUser,
loading,
setLoading,
isPasswordConfirmed,
setIsPasswordConfirmed,
};
}
@@ -0,0 +1,133 @@
import Input from "@/twui/components/form/Input";
import Button from "@/twui/components/layout/Button";
import Stack from "@/twui/components/layout/Stack";
import useSignupForm from "../(hooks)/use-signup-form";
import fetchApi from "@/twui/components/utils/fetch/fetchApi";
import { APIReqObject } from "@/src/types";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import { useEffect } from "react";
export default function SignupForm() {
const {
newUser,
setNewUser,
loading,
setLoading,
isPasswordConfirmed,
setIsPasswordConfirmed,
} = useSignupForm();
const is_password_valid = Boolean(
isPasswordConfirmed &&
Boolean(newUser.password?.match(/./)) &&
Boolean(newUser.confirmed_password?.match(/./)),
);
return (
<Stack className="w-full items-stretch">
<Input
placeholder="Eg. John"
title="First Name"
changeHandler={(v) => {
setNewUser((prev) => ({
...prev,
first_name: v,
}));
}}
showLabel
/>
<Input
placeholder="Eg. Doe"
title="Last Name"
changeHandler={(v) => {
setNewUser((prev) => ({
...prev,
last_name: v,
}));
}}
showLabel
/>
<Input
placeholder="Email Address or Username"
title="Email/Username"
type="email"
changeHandler={(v) => {
setNewUser((prev) => ({
...prev,
email: v,
}));
}}
showLabel
/>
<Input
placeholder="Password"
title="Password"
type="password"
changeHandler={(v) => {
setNewUser((prev) => ({
...prev,
password: v,
}));
}}
validity={{
isValid:
!Boolean(newUser.password?.match(/./)) ||
!Boolean(newUser.confirmed_password?.match(/./))
? true
: is_password_valid,
msg: `Passwords don't match`,
}}
showLabel
/>
<Input
placeholder="Confirm Password"
title="Confirm Password"
type="password"
changeHandler={(v) => {
setNewUser((prev) => ({
...prev,
confirmed_password: v,
}));
setIsPasswordConfirmed(v == newUser.password);
}}
showLabel
/>
<Button
title="Login"
onClick={() => {
if (!is_password_valid) {
return;
}
if (!window.confirm(`Create Super Admin Account?`)) {
return;
}
setLoading(true);
fetchApi<APIReqObject, APIResponseObject>(
`/api/auth/signup`,
{
method: "POST",
body: {
new_user: newUser,
},
},
)
.then((res) => {
console.log("res", res);
if (res.success) {
window.location.reload();
}
})
.finally(() => {});
}}
loading={loading}
>
Signup
</Button>
</Stack>
);
}
@@ -0,0 +1,12 @@
import H2 from "@/twui/components/layout/H2";
import Stack from "@/twui/components/layout/Stack";
import SignupForm from "./(partials)/signup-form";
export default function Main() {
return (
<Stack className="w-full items-center max-w-lg">
<H2>Create Super Admin Account</H2>
<SignupForm />
</Stack>
);
}