Claude create settings page

This commit is contained in:
2026-03-11 13:28:52 +00:00
parent 1ecc1374d2
commit 6d2413a25f
7 changed files with 348 additions and 1 deletions
@@ -0,0 +1,31 @@
import { AppContext } from "@/src/pages/_app";
import useStatus from "@/twui/components/hooks/useStatus";
import { useContext, useState } from "react";
export type SettingsFormData = {
first_name?: string;
last_name?: string;
email?: string;
image?: string;
};
export default function useSettingsForm() {
const { pageProps } = useContext(AppContext);
const { user } = pageProps;
const { loading, setLoading } = useStatus();
const [formData, setFormData] = useState<SettingsFormData>({
first_name: user?.first_name || "",
last_name: user?.last_name || "",
email: user?.email || "",
image: user?.image || "",
});
return {
formData,
setFormData,
loading,
setLoading,
user,
};
}
@@ -0,0 +1,119 @@
import { AppContext } from "@/src/pages/_app";
import fetchApi from "@/twui/components/utils/fetch/fetchApi";
import ImageUpload from "@/twui/components/form/ImageUpload";
import Input from "@/twui/components/form/Input";
import Button from "@/twui/components/layout/Button";
import Divider from "@/twui/components/layout/Divider";
import Row from "@/twui/components/layout/Row";
import Span from "@/twui/components/layout/Span";
import Stack from "@/twui/components/layout/Stack";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import { useContext } from "react";
import useSettingsForm from "../(hooks)/use-settings-form";
export default function SettingsForm() {
const { setToast } = useContext(AppContext);
const { formData, setFormData, loading, setLoading, user } =
useSettingsForm();
async function handleSubmit() {
if (!formData.first_name?.match(/./)) return;
setLoading(true);
try {
const res = await fetchApi<typeof formData, APIResponseObject>(
`/api/admin/settings`,
{ method: "POST", body: formData },
);
if (res.success) {
setToast({
toastOpen: true,
toastMessage: "Settings saved",
toastStyle: "success",
closeDelay: 3000,
});
window.location.reload();
} else {
setToast({
toastOpen: true,
toastMessage: res.msg || "Failed to save settings",
toastStyle: "error",
closeDelay: 4000,
});
}
} finally {
setLoading(false);
}
}
return (
<Stack className="w-full max-w-[600px] items-stretch gap-6">
<Stack className="gap-1">
<Span size="small" variant="faded">
Username
</Span>
<Span>
<strong>{user?.username || "—"}</strong>
</Span>
</Stack>
<Divider />
<Row className="gap-4 items-stretch">
<Input
title="First Name"
placeholder="Eg. John"
value={formData.first_name}
changeHandler={(v) =>
setFormData((prev) => ({ ...prev, first_name: v }))
}
required
showLabel
/>
<Input
title="Last Name"
placeholder="Eg. Doe"
value={formData.last_name}
changeHandler={(v) =>
setFormData((prev) => ({ ...prev, last_name: v }))
}
showLabel
/>
</Row>
<Input
title="Email"
placeholder="Eg. [email protected]"
type="email"
value={formData.email}
changeHandler={(v) =>
setFormData((prev) => ({ ...prev, email: v }))
}
required
showLabel
/>
<ImageUpload
label="Profile Image"
existingImageUrl={formData.image || undefined}
onChangeHandler={(imgData) => {
setFormData((prev) => ({
...prev,
image: imgData?.imageBase64Full || prev.image,
}));
}}
className="h-[200px]"
/>
<Button
title="Save Settings"
loading={loading}
onClick={handleSubmit}
>
Save Settings
</Button>
</Stack>
);
}
@@ -0,0 +1,20 @@
import AdminHero from "@/src/components/general/admin/hero";
import Divider from "@/twui/components/layout/Divider";
import Stack from "@/twui/components/layout/Stack";
import { Fragment } from "react";
import SettingsForm from "./(sections)/settings-form";
export default function Main() {
return (
<Fragment>
<AdminHero
title="Settings"
description="Update your profile information"
/>
<Divider />
<Stack className="grid-cell-content">
<SettingsForm />
</Stack>
</Fragment>
);
}
@@ -46,6 +46,7 @@ export default async function setupDeploymentUser({ user_id }: Params) {
cmd += `chmod 600 "${ssh_dir}/authorized_keys"\n`;
cmd += `cat << 'EOF' > ${force_command_file}\n`;
cmd += `#!/bin/bash\n`;
cmd += `EOF\n`;
cmd += `cat << 'EOF' > ${sshd_config_file}\n`;
@@ -57,7 +58,10 @@ export default async function setupDeploymentUser({ user_id }: Params) {
cmd += ` # Restrict shell / tunneling\n`;
cmd += ` AllowTcpForwarding yes\n`;
cmd += ` X11Forwarding no\n`;
cmd += ` X11Forwarding no\n\n`;
cmd += ` # Restrict Commands\n`;
cmd += ` ForceCommand ${force_command_file}\n`;
cmd += `EOF\n`;
}
+16
View File
@@ -0,0 +1,16 @@
import Main from "@/src/components/pages/admin/settings";
import defaultAdminProps from "@/src/functions/pages/admin/default-admin-props";
import Layout from "@/src/layouts/admin";
import { GetServerSideProps } from "next";
export default function AdminSettings() {
return (
<Layout>
<Main />
</Layout>
);
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
return await defaultAdminProps({ ctx });
};
+60
View File
@@ -0,0 +1,60 @@
import loginUser from "@/src/functions/auth/login-user";
import { NSQLITE_TURBOCI_ADMIN_USERS } from "@/src/db/types";
import userAuth from "@/src/utils/user-auth";
import NSQLite from "@moduletrace/nsqlite";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import type { NextApiRequest, NextApiResponse } from "next";
type ReqBody = {
first_name?: string;
last_name?: string;
email?: string;
image?: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<APIResponseObject>,
) {
try {
if (req.method !== "POST") {
return res.json({
success: false,
msg: "Wrong Method",
});
}
const { singleRes: user } = await userAuth({ req });
if (!user?.id) {
return res.json({
success: false,
msg: "Unauthorized",
});
}
const { first_name, last_name, email, image } = req.body as ReqBody;
if (!first_name?.match(/./)) {
return res.json({ success: false, msg: "First name is required" });
}
const update: NSQLITE_TURBOCI_ADMIN_USERS = {};
if (first_name) update.first_name = first_name;
if (last_name !== undefined) update.last_name = last_name;
if (email) update.email = email;
if (image !== undefined) update.image = image;
await NSQLite.update({
table: "users",
targetId: user.id,
data: update,
});
const updated = await loginUser({ res, user_id: user.id });
return res.json(updated);
} catch (error: any) {
return res.json({ success: false, msg: error.message });
}
}