First Commit
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import H1 from "@/src/components/twui/layout/H1";
|
||||
import Section from "@/src/components/twui/layout/Section";
|
||||
import { useContext, type ReactNode } from "react";
|
||||
import Row from "../twui/layout/Row";
|
||||
import Stack from "../twui/layout/Stack";
|
||||
import Divider from "../twui/layout/Divider";
|
||||
import Breadcrumbs from "../twui/elements/Breadcrumbs";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import Span from "../twui/layout/Span";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description?: string | ReactNode;
|
||||
buttons?: ReactNode;
|
||||
};
|
||||
|
||||
export default function AdminHero({ title, description, buttons }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Stack className="w-full items-stretch">
|
||||
<Row className="w-full justify-between items-start">
|
||||
<Stack>
|
||||
<H1>{title}</H1>
|
||||
{description ? (
|
||||
typeof description == "string" ? (
|
||||
<Span>{description}</Span>
|
||||
) : (
|
||||
description
|
||||
)
|
||||
) : null}
|
||||
<Breadcrumbs
|
||||
dividerProps={{
|
||||
className: "-skew-10",
|
||||
}}
|
||||
currentLinkProps={{
|
||||
className:
|
||||
"text-slate-400! pointer-events-none",
|
||||
}}
|
||||
pageUrl={pageProps?.url?.pathname}
|
||||
backButton
|
||||
skipHome
|
||||
/>
|
||||
</Stack>
|
||||
<Row>{buttons}</Row>
|
||||
</Row>
|
||||
<Divider className="my-4" />
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
import Img from "@/src/components/twui/layout/Img";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = {
|
||||
src: string;
|
||||
opacity?: number;
|
||||
};
|
||||
|
||||
export default function BlurredImageBG({ src, opacity = 0.1 }: Props) {
|
||||
/**
|
||||
* Render component
|
||||
*/
|
||||
return (
|
||||
<Img
|
||||
alt="bg image"
|
||||
src={src}
|
||||
className={twMerge(
|
||||
`absolute top-0 left-0 w-[150vw] h-full blur-xs object-cover`,
|
||||
"z-0",
|
||||
)}
|
||||
style={{ opacity }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
import Img from "@/src/components/twui/layout/Img";
|
||||
import type { ComponentProps } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = {
|
||||
src: string;
|
||||
img_props?: Omit<ComponentProps<typeof Img>, "alt">;
|
||||
};
|
||||
|
||||
export default function ImageBG({ src, img_props }: Props) {
|
||||
return (
|
||||
<Img
|
||||
{...img_props}
|
||||
alt="bg image"
|
||||
src={src}
|
||||
className={twMerge(
|
||||
`absolute top-0 left-0 w-full h-full object-cover`,
|
||||
"z-0",
|
||||
img_props?.className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import Section from "@/src/components/twui/layout/Section";
|
||||
import Container from "@/src/components/twui/layout/Container";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import H1 from "@/src/components/twui/layout/H1";
|
||||
import Span from "@/src/components/twui/layout/Span";
|
||||
import BlurredImageBG from "@/src/components/general/blured-image-bg";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
sub_title?: string;
|
||||
description?: string;
|
||||
bg_image?: string;
|
||||
};
|
||||
|
||||
export default function InnerPageHeroSection({
|
||||
title,
|
||||
bg_image,
|
||||
description,
|
||||
sub_title,
|
||||
}: Props) {
|
||||
return (
|
||||
<Section className={twMerge("")}>
|
||||
<Container className="relative z-10">
|
||||
<Stack className="w-full items-center max-w-3xl mx-auto gap-7">
|
||||
{sub_title ? (
|
||||
<Span className="htag">{sub_title}</Span>
|
||||
) : null}
|
||||
<H1 className="text-center font-bold">{title}</H1>
|
||||
{description ? (
|
||||
<Span className="text-xl sm:text-2xl leading-snug text-center">
|
||||
{description}
|
||||
</Span>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Tag from "../twui/elements/Tag";
|
||||
import Row from "../twui/layout/Row";
|
||||
import { TriangleAlert, CircleCheck } from "lucide-react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { TWUIStatusType } from "../twui/hooks/useStatus";
|
||||
import Paper from "../twui/elements/Paper";
|
||||
import Center from "../twui/layout/Center";
|
||||
import Stack from "../twui/layout/Stack";
|
||||
import Border from "../twui/elements/Border";
|
||||
import Logo from "./logo";
|
||||
|
||||
type Props = PropsWithChildren & {
|
||||
title?: string;
|
||||
className?: string;
|
||||
status?: TWUIStatusType;
|
||||
};
|
||||
|
||||
export default function LoginShell({ children, className, status }: Props) {
|
||||
return (
|
||||
<Stack className="w-screen h-screen justify-center! items-center! overflow-x-hidden overflow-y-auto">
|
||||
<Logo />
|
||||
|
||||
<Border
|
||||
className={twMerge(
|
||||
"w-full lg:w-[600px] flex-col items-center",
|
||||
"px-4 sm:px-6 py-8 shadow-md relative",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{status?.error && status.msg ? (
|
||||
<Tag
|
||||
variant="outlined"
|
||||
color="error"
|
||||
className="w-full py-1.5 outline-error/50 bg-error/5!"
|
||||
>
|
||||
<Row>
|
||||
<TriangleAlert size={15} />
|
||||
<span>{status.msg}</span>
|
||||
</Row>
|
||||
</Tag>
|
||||
) : null}
|
||||
{status?.success && status.msg ? (
|
||||
<Tag
|
||||
variant="outlined"
|
||||
color="success"
|
||||
className="w-full py-1.5 outline-success/50 bg-success/5!"
|
||||
>
|
||||
<Row>
|
||||
<CircleCheck size={15} />
|
||||
<span>{status.msg}</span>
|
||||
</Row>
|
||||
</Tag>
|
||||
) : null}
|
||||
{children}
|
||||
</Border>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import Img from "../twui/layout/Img";
|
||||
|
||||
type Props = {
|
||||
icon_size?: number;
|
||||
img_props?: Omit<ComponentProps<typeof Img>, "alt">;
|
||||
white?: boolean;
|
||||
};
|
||||
|
||||
export default function LogoIcon({ icon_size = 45, img_props, white }: Props) {
|
||||
return (
|
||||
<Img
|
||||
size={icon_size}
|
||||
{...img_props}
|
||||
alt="Logo Icon"
|
||||
src={
|
||||
white ? "/logos/wgui-logo-white.webp" : "/logos/wgui-logo.webp"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import Row from "../twui/layout/Row";
|
||||
import Span from "../twui/layout/Span";
|
||||
import type { ComponentProps, PropsWithChildren } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Stack from "../twui/layout/Stack";
|
||||
import LogoIcon from "./logo-icon";
|
||||
import { SiteData } from "@/src/data/site-data";
|
||||
|
||||
type Props = {
|
||||
icon_size?: number;
|
||||
text_wrapper_props?: ComponentProps<typeof Stack>;
|
||||
text_props?: ComponentProps<typeof Span>;
|
||||
white?: boolean;
|
||||
};
|
||||
|
||||
export default function Logo({
|
||||
icon_size = 45,
|
||||
text_props,
|
||||
text_wrapper_props,
|
||||
white,
|
||||
}: Props) {
|
||||
return (
|
||||
<a href="/" className="logo-link">
|
||||
<Row className="gap-2! flex-nowrap">
|
||||
<LogoIcon icon_size={icon_size} white={white} />
|
||||
<Stack
|
||||
{...text_wrapper_props}
|
||||
className={twMerge("gap-1!", text_wrapper_props?.className)}
|
||||
>
|
||||
<Stack className="gap-0!">
|
||||
<HeroText text_props={text_props}>wg-ui</HeroText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Row>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function HeroText({
|
||||
text_props,
|
||||
children,
|
||||
}: PropsWithChildren & {
|
||||
text_props?: ComponentProps<typeof Span>;
|
||||
}) {
|
||||
return (
|
||||
<Span
|
||||
{...text_props}
|
||||
className={twMerge(
|
||||
"text-xl text-dark font-serif whitespace-nowrap",
|
||||
"heading leading-[24px]",
|
||||
text_props?.className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Span>
|
||||
);
|
||||
}
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
import React, { type ComponentProps, type ReactNode } from "react";
|
||||
import Section from "@/src/components/twui/layout/Section";
|
||||
import Container from "@/src/components/twui/layout/Container";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import H1 from "@/src/components/twui/layout/H1";
|
||||
import Row from "@/src/components/twui/layout/Row";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Span from "@/src/components/twui/layout/Span";
|
||||
import BlurredImageBG from "@/src/components/general/blured-image-bg";
|
||||
import ImageBG from "@/src/components/general/image-bg";
|
||||
import type Img from "../twui/layout/Img";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
image_url?: string;
|
||||
sub_title?: string;
|
||||
cta_buttons?: ReactNode;
|
||||
img_props?: Omit<ComponentProps<typeof Img>, "alt">;
|
||||
img_component?: ReactNode;
|
||||
};
|
||||
|
||||
export default function MainHeroSection({
|
||||
title,
|
||||
cta_buttons,
|
||||
sub_title,
|
||||
image_url,
|
||||
img_props,
|
||||
img_component,
|
||||
}: Props) {
|
||||
const img_comp = img_component ? (
|
||||
img_component
|
||||
) : image_url ? (
|
||||
<ImageBG src={image_url} img_props={img_props} />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Section className={twMerge("relative")}>
|
||||
<Container>
|
||||
<Row className="grid xl:grid-cols-2 relative z-10 max-w-xl xl:max-w-none lg:pb-20 pt-60 lg:pt-20">
|
||||
<Stack className="w-full gap-8">
|
||||
<H1
|
||||
className={twMerge(
|
||||
"text-center md:text-left w-full",
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</H1>
|
||||
{sub_title ? (
|
||||
<Span
|
||||
className={twMerge(
|
||||
"text-lg sm:text-2xl leading-snug text-center md:text-left",
|
||||
)}
|
||||
>
|
||||
{sub_title}
|
||||
</Span>
|
||||
) : null}
|
||||
{cta_buttons}
|
||||
</Stack>
|
||||
{/* <Stack className="relative w-full xl:w-[700px] xl:h-full overflow-visible"></Stack> */}
|
||||
</Row>
|
||||
</Container>
|
||||
</Section>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
import Img from "@/src/components/twui/layout/Img";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Stack from "../twui/layout/Stack";
|
||||
|
||||
type Props = {
|
||||
src: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
alt?: string;
|
||||
};
|
||||
|
||||
export default function RectImage({
|
||||
src,
|
||||
height = 300,
|
||||
width = 200,
|
||||
alt,
|
||||
}: Props) {
|
||||
const img_component = (
|
||||
<Img
|
||||
alt={alt || "image"}
|
||||
src={src}
|
||||
className={twMerge(`w-full h-full object-cover`)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
className={twMerge(`overflow-hidden hidden xl:flex`)}
|
||||
style={{
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
}}
|
||||
>
|
||||
{img_component}
|
||||
</Stack>
|
||||
<Stack className={twMerge(`overflow-hidden flex xl:hidden w-full`)}>
|
||||
{img_component}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
import Section from "@/src/components/twui/layout/Section";
|
||||
import Container from "@/src/components/twui/layout/Container";
|
||||
import H2 from "@/src/components/twui/layout/H2";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import type { TWUILucideIconName } from "../twui/elements/lucide-icon";
|
||||
import LucideIcon from "../twui/elements/lucide-icon";
|
||||
import Span from "../twui/layout/Span";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
tagline: string;
|
||||
icon_name?: TWUILucideIconName;
|
||||
sub_title?: string;
|
||||
post_component?: ReactNode;
|
||||
};
|
||||
|
||||
export default function TaglineSection({
|
||||
tagline,
|
||||
icon_name,
|
||||
sub_title,
|
||||
post_component,
|
||||
}: Props) {
|
||||
/**
|
||||
* Render component
|
||||
*/
|
||||
return (
|
||||
<Section className="bg-secondary relative">
|
||||
<Container className="xl:py-10">
|
||||
<Stack className="w-full items-center gap-6">
|
||||
{icon_name && (
|
||||
<LucideIcon
|
||||
name={icon_name}
|
||||
size={50}
|
||||
className="text-accent"
|
||||
/>
|
||||
)}
|
||||
<H2 className="text-center max-w-4xl xl:text-6xl! text-white">
|
||||
{tagline}
|
||||
</H2>
|
||||
{sub_title ? (
|
||||
<Span
|
||||
className="text-white/80 max-w-4xl text-center"
|
||||
size="larger"
|
||||
>
|
||||
{sub_title}
|
||||
</Span>
|
||||
) : null}
|
||||
{post_component}
|
||||
</Stack>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Form from "@/src/components/twui/form/Form";
|
||||
import LoadingOverlay from "@/src/components/twui/elements/LoadingOverlay";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import UserFormName from "./user-form-name";
|
||||
import Tag from "@/src/components/twui/elements/Tag";
|
||||
import UserFormAction from "./user-form-action";
|
||||
import UserFormEmail from "./user-form-email";
|
||||
import UserFormUsername from "./user-form-username";
|
||||
import type { UserFormObject } from "@/src/types";
|
||||
import submitUserForm from "./submit-user-form";
|
||||
import UserFormPassword from "./user-form-password";
|
||||
|
||||
type Props = {
|
||||
existing_user?: UserFormObject;
|
||||
existing_user_full?: BUN_SQLITE_WGUI_USERS;
|
||||
is_first_user?: boolean;
|
||||
};
|
||||
|
||||
export default function UserForm({
|
||||
existing_user,
|
||||
existing_user_full,
|
||||
is_first_user,
|
||||
}: Props) {
|
||||
const init = useFormInit<UserFormObject>({
|
||||
existing: existing_user,
|
||||
existing_full: existing_user_full,
|
||||
is_first_user,
|
||||
});
|
||||
|
||||
const { loading, status } = init;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
className="w-full gap-6"
|
||||
onSubmit={() => {
|
||||
submitUserForm(init);
|
||||
}}
|
||||
>
|
||||
{status?.error && <Tag color="error">{status.msg}</Tag>}
|
||||
{loading && <LoadingOverlay />}
|
||||
|
||||
<UserFormName {...init} />
|
||||
<UserFormEmail {...init} />
|
||||
<UserFormUsername {...init} />
|
||||
<UserFormPassword {...init} />
|
||||
{/* <UserFormUserTypes {...init} /> */}
|
||||
{/* <UserFormBio {...init} /> */}
|
||||
{/* <UserFormImage {...init} /> */}
|
||||
<UserFormAction {...init} />
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import type {
|
||||
BUN_SQLITE_WGUI_USER_TYPES,
|
||||
BUN_SQLITE_WGUI_USERS,
|
||||
} from "@/db/types/db";
|
||||
import adminCrudHandler from "@/src/functions/frontend/admin-crud-handler";
|
||||
import clientDeleteMediaHandler from "@/src/functions/frontend/client-delete-media-handler";
|
||||
import clientUploadMediaHandler from "@/src/functions/frontend/client-upload-media-handler";
|
||||
import type useFormInit from "@/src/hooks/use-form-init";
|
||||
import type { ApiReqParams, UserFormObject } from "@/src/types";
|
||||
import _ from "lodash";
|
||||
import fetchApi from "../../twui/utils/fetch/fetchApi";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
|
||||
export default async function submitUserForm({
|
||||
setLoading,
|
||||
setStatus,
|
||||
form,
|
||||
existing_full,
|
||||
existing,
|
||||
image,
|
||||
is_first_user,
|
||||
}: ReturnType<typeof useFormInit<UserFormObject>>) {
|
||||
try {
|
||||
const confirm_msg = existing_full?.id
|
||||
? `Update this user?`
|
||||
: `Create new user?`;
|
||||
|
||||
if (!window.confirm(confirm_msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const new_user_data: BUN_SQLITE_WGUI_USERS = _.pick<
|
||||
UserFormObject,
|
||||
keyof UserFormObject
|
||||
>(form, ["first_name", "last_name", "email", "username", "bio"]);
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const create_or_update_user_res = is_first_user
|
||||
? await fetchApi<
|
||||
ApiReqParams,
|
||||
APIResponseObject<BUN_SQLITE_WGUI_USERS>
|
||||
>(`/api/create-first-user.ts`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
insert_data: [new_user_data],
|
||||
},
|
||||
})
|
||||
: await adminCrudHandler({
|
||||
action: existing_full?.id ? "update" : "insert",
|
||||
table: "users",
|
||||
insert_data: [new_user_data],
|
||||
update_data: new_user_data,
|
||||
id: existing_full?.id,
|
||||
});
|
||||
|
||||
const target_user_id =
|
||||
existing_full?.id || create_or_update_user_res.singleRes?.id;
|
||||
|
||||
if (image?.imageBase64Full) {
|
||||
const is_user_settings =
|
||||
window.location.pathname.includes("admin/settings");
|
||||
|
||||
const delete_media_res = await clientDeleteMediaHandler({
|
||||
user_id: target_user_id,
|
||||
media_paradigm: "user-profile-image",
|
||||
});
|
||||
|
||||
if (!delete_media_res.success) {
|
||||
console.log("delete_media_res", delete_media_res);
|
||||
|
||||
throw new Error(
|
||||
delete_media_res.msg ||
|
||||
delete_media_res.error ||
|
||||
`Couldn't delete old media`,
|
||||
);
|
||||
}
|
||||
|
||||
const upload_image_res = await clientUploadMediaHandler({
|
||||
user_id: target_user_id,
|
||||
media_base_64_data_url: image.imageBase64Full,
|
||||
media_paradigms: ["user-profile-image"],
|
||||
media_type: "image",
|
||||
is_media_primary: true,
|
||||
update_on_duplicate: true,
|
||||
reauth: is_user_settings,
|
||||
});
|
||||
|
||||
console.log("upload_image_res", upload_image_res);
|
||||
|
||||
if (!upload_image_res.success) {
|
||||
throw new Error(
|
||||
upload_image_res.msg || "Couldn't upload Image",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (create_or_update_user_res.success && target_user_id) {
|
||||
const delete_user_types = await adminCrudHandler({
|
||||
action: "delete",
|
||||
table: "user_types",
|
||||
sql_query: {
|
||||
query: {
|
||||
user_id: {
|
||||
value: target_user_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const user_types = form.user_types;
|
||||
|
||||
if (user_types?.[0]) {
|
||||
const add_user_types =
|
||||
await adminCrudHandler<BUN_SQLITE_WGUI_USER_TYPES>({
|
||||
action: "insert",
|
||||
table: "user_types",
|
||||
insert_data: user_types.map((ut) => ({
|
||||
user_id: target_user_id,
|
||||
user_type: ut,
|
||||
})),
|
||||
});
|
||||
|
||||
if (add_user_types.success) {
|
||||
if (existing_full?.id) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.location.pathname = `/admin/people/${target_user_id}`;
|
||||
}
|
||||
} else {
|
||||
console.log("add_user_types", add_user_types);
|
||||
throw new Error(
|
||||
add_user_types.msg || "Couldn't add user types",
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("res", create_or_update_user_res);
|
||||
console.log("form", form);
|
||||
|
||||
throw new Error(
|
||||
create_or_update_user_res.msg || "Person Creation Failed",
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setStatus({
|
||||
error: true,
|
||||
msg: error.message || "User Form Error",
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import Button from "@/src/components/twui/layout/Button";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
|
||||
export default function UserFormAction({
|
||||
status,
|
||||
existing_full,
|
||||
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_USERS>>) {
|
||||
const title = existing_full?.id ? "Update User" : "Create New User";
|
||||
|
||||
if (status?.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button title={title}>{title}</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import TinyMCEEditor from "@/src/components/twui/editors/TinyMCE";
|
||||
import Button from "@/src/components/twui/layout/Button";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
|
||||
export default function UserFormBio({
|
||||
setForm,
|
||||
existing,
|
||||
existing_full,
|
||||
form,
|
||||
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_USERS>>) {
|
||||
const title = existing_full?.id ? "Update User" : "Create New User";
|
||||
|
||||
return (
|
||||
<>
|
||||
<TinyMCEEditor
|
||||
defaultValue={form.bio}
|
||||
changeHandler={(content) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
bio: content,
|
||||
}));
|
||||
}}
|
||||
placeholder="Write Something about yourself"
|
||||
options={{ height: 300 }}
|
||||
name="Bio"
|
||||
showLabel
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import Input from "@/src/components/twui/form/Input";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
|
||||
export default function UserFormEmail({
|
||||
setForm,
|
||||
form,
|
||||
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_USERS>>) {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email Addres"
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
email: e.target.value,
|
||||
}));
|
||||
}}
|
||||
defaultValue={form.email}
|
||||
showLabel
|
||||
required
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import ImageUpload from "@/src/components/twui/form/ImageUpload";
|
||||
import grabMediaURL from "@/src/functions/backend/media/grab-media-url";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
import type { UserFormObject } from "@/src/types";
|
||||
import type { BUN_SQLITE_WGUI_USERS_JOIN } from "@/src/types/sql-joins";
|
||||
|
||||
export default function UserFormImage({
|
||||
setForm,
|
||||
existing_full,
|
||||
setImage,
|
||||
}: ReturnType<typeof useFormInit<UserFormObject>>) {
|
||||
const title = existing_full?.id ? "Update User" : "Create New User";
|
||||
|
||||
const person = existing_full as BUN_SQLITE_WGUI_USERS_JOIN | undefined;
|
||||
|
||||
const user_image_url = person?.profile_media_id
|
||||
? grabMediaURL({
|
||||
media: { id: person?.profile_media_id },
|
||||
})
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImageUpload
|
||||
label="User Image"
|
||||
existingImageUrl={user_image_url?.media_url}
|
||||
onChangeHandler={(imgData) => {
|
||||
setImage(imgData);
|
||||
}}
|
||||
setImgURL={(imgURL) => {
|
||||
if (
|
||||
typeof imgURL !== "string" &&
|
||||
typeof imgURL !== "undefined"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
image: imgURL || "",
|
||||
image_thumbnail: imgURL || "",
|
||||
}));
|
||||
}}
|
||||
restoreImageFn={() => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
image: user_image_url || "",
|
||||
image_thumbnail: user_image_url || "",
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import Input from "@/src/components/twui/form/Input";
|
||||
import Row from "@/src/components/twui/layout/Row";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
|
||||
export default function UserFormName({
|
||||
setForm,
|
||||
form,
|
||||
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_USERS>>) {
|
||||
return (
|
||||
<Row className="w-full grid xl:grid-cols-2">
|
||||
<Input
|
||||
title="First Name"
|
||||
placeholder="Eg. John"
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
first_name: e.target.value,
|
||||
}));
|
||||
}}
|
||||
defaultValue={form.first_name}
|
||||
autoComplete="given-name"
|
||||
showLabel
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
title="Last Name"
|
||||
placeholder="Eg. Doe"
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
last_name: e.target.value,
|
||||
}));
|
||||
}}
|
||||
defaultValue={form.last_name}
|
||||
autoComplete="family-name"
|
||||
showLabel
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import Input from "@/src/components/twui/form/Input";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
|
||||
export default function UserFormPassword({
|
||||
setForm,
|
||||
form,
|
||||
existing_full,
|
||||
setStatus,
|
||||
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_USERS>>) {
|
||||
if (existing_full?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
password: e.target.value,
|
||||
}));
|
||||
}}
|
||||
defaultValue={form.username}
|
||||
showLabel
|
||||
/>
|
||||
<Input
|
||||
placeholder="Confirm Password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
onChange={(e) => {
|
||||
const confirm_psswd = e.target.value;
|
||||
|
||||
if (confirm_psswd && form.password !== confirm_psswd) {
|
||||
setStatus({
|
||||
error: true,
|
||||
msg: `Passwords don't match`,
|
||||
});
|
||||
} else {
|
||||
setStatus(undefined);
|
||||
}
|
||||
}}
|
||||
defaultValue={form.username}
|
||||
showLabel
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import SearchSelect from "@/src/components/twui/form/SearchSelect";
|
||||
import { UneditableUserTypes, UserTypes } from "@/src/dict/user-types-dict";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
import type { UserFormObject, UserType } from "@/src/types";
|
||||
|
||||
export default function UserFormUserTypes({
|
||||
setForm,
|
||||
form,
|
||||
}: ReturnType<typeof useFormInit<UserFormObject>>) {
|
||||
return (
|
||||
<>
|
||||
<SearchSelect
|
||||
multiple
|
||||
options={UserTypes.filter(
|
||||
(ut) => !UneditableUserTypes.includes(ut.value),
|
||||
).map((ut) => ({
|
||||
value: ut.value,
|
||||
}))}
|
||||
defaultValues={form.user_types || []}
|
||||
changeHandler={(value) => {
|
||||
const user_types = Array.isArray(value)
|
||||
? (value as UserType[])
|
||||
: value
|
||||
? ([value] as UserType[])
|
||||
: [];
|
||||
setForm({ ...form, user_types });
|
||||
}}
|
||||
title="User Types"
|
||||
showLabel
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
|
||||
import Input from "@/src/components/twui/form/Input";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
|
||||
export default function UserFormUsername({
|
||||
setForm,
|
||||
form,
|
||||
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_USERS>>) {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Username"
|
||||
onChange={(e) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
username: e.target.value,
|
||||
}));
|
||||
}}
|
||||
defaultValue={form.username}
|
||||
showLabel
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user