mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-05 05:55:21 +02:00
feat(user): update user schema to include firstName and lastName fields, modify related components and forms for user registration and profile management
This commit is contained in:
@@ -103,7 +103,7 @@ export const ImpersonationBar = () => {
|
||||
setOpen(false);
|
||||
|
||||
toast.success("Successfully impersonating user", {
|
||||
description: `You are now viewing as ${selectedUser.name || selectedUser.email}`,
|
||||
description: `You are now viewing as ${`${selectedUser.name} ${selectedUser.lastName}`.trim() || selectedUser.email}`,
|
||||
});
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
@@ -195,7 +195,8 @@ export const ImpersonationBar = () => {
|
||||
<UserIcon className="mr-2 h-4 w-4 flex-shrink-0" />
|
||||
<span className="truncate flex flex-col items-start">
|
||||
<span className="text-sm font-medium">
|
||||
{selectedUser.name || ""}
|
||||
{`${selectedUser.name} ${selectedUser.lastName}`.trim() ||
|
||||
""}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{selectedUser.email}
|
||||
@@ -242,7 +243,8 @@ export const ImpersonationBar = () => {
|
||||
<UserIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<span className="flex flex-col items-start">
|
||||
<span className="text-sm font-medium">
|
||||
{user.name || ""}
|
||||
{`${user.name} ${user.lastName}`.trim() ||
|
||||
""}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{user.email} • {user.role}
|
||||
@@ -283,10 +285,14 @@ export const ImpersonationBar = () => {
|
||||
<AvatarImage
|
||||
className="object-cover"
|
||||
src={data?.user?.image || ""}
|
||||
alt={data?.user?.name || ""}
|
||||
alt={
|
||||
`${data?.user?.firstName} ${data?.user?.lastName}`.trim() ||
|
||||
""
|
||||
}
|
||||
/>
|
||||
<AvatarFallback>
|
||||
{data?.user?.name?.slice(0, 2).toUpperCase() || "U"}
|
||||
{`${data?.user?.firstName?.[0] || ""}${data?.user?.lastName?.[0] || ""}`.toUpperCase() ||
|
||||
"U"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -299,7 +305,8 @@ export const ImpersonationBar = () => {
|
||||
Impersonating
|
||||
</Badge>
|
||||
<span className="font-medium">
|
||||
{data?.user?.name || ""}
|
||||
{`${data?.user?.firstName} ${data?.user?.lastName}`.trim() ||
|
||||
""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm text-muted-foreground flex-wrap">
|
||||
|
||||
@@ -41,6 +41,7 @@ const profileSchema = z.object({
|
||||
currentPassword: z.string().nullable(),
|
||||
image: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
allowImpersonation: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
@@ -88,7 +89,8 @@ export const ProfileForm = () => {
|
||||
image: data?.user?.image || "",
|
||||
currentPassword: "",
|
||||
allowImpersonation: data?.user?.allowImpersonation || false,
|
||||
name: data?.user?.name || "",
|
||||
name: data?.user?.firstName || "",
|
||||
lastName: data?.user?.lastName || "",
|
||||
},
|
||||
resolver: zodResolver(profileSchema),
|
||||
});
|
||||
@@ -102,7 +104,8 @@ export const ProfileForm = () => {
|
||||
image: data?.user?.image || "",
|
||||
currentPassword: form.getValues("currentPassword") || "",
|
||||
allowImpersonation: data?.user?.allowImpersonation,
|
||||
name: data?.user?.name || "",
|
||||
name: data?.user?.firstName || "",
|
||||
lastName: data?.user?.lastName || "",
|
||||
},
|
||||
{
|
||||
keepValues: true,
|
||||
@@ -127,6 +130,7 @@ export const ProfileForm = () => {
|
||||
currentPassword: values.currentPassword || undefined,
|
||||
allowImpersonation: values.allowImpersonation,
|
||||
name: values.name || undefined,
|
||||
lastName: values.lastName || undefined,
|
||||
});
|
||||
await refetch();
|
||||
toast.success("Profile Updated");
|
||||
@@ -136,6 +140,7 @@ export const ProfileForm = () => {
|
||||
image: values.image,
|
||||
currentPassword: "",
|
||||
name: values.name || "",
|
||||
lastName: values.lastName || "",
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error("Error updating the profile");
|
||||
@@ -180,9 +185,22 @@ export const ProfileForm = () => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>First Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Name" {...field} />
|
||||
<Input placeholder="John" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Doe" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -280,7 +298,7 @@ export const ProfileForm = () => {
|
||||
<Avatar className="default-avatar h-12 w-12 rounded-full border hover:p-px hover:border-primary transition-transform">
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{getFallbackAvatarInitials(
|
||||
data?.user?.name,
|
||||
`${data?.user?.firstName} ${data?.user?.lastName}`.trim(),
|
||||
)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
@@ -49,7 +49,9 @@ export const UserNav = () => {
|
||||
alt={data?.user?.image || ""}
|
||||
/>
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{getFallbackAvatarInitials(data?.user?.name)}
|
||||
{getFallbackAvatarInitials(
|
||||
`${data?.user?.firstName} ${data?.user?.lastName}`.trim(),
|
||||
)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
|
||||
2
apps/dokploy/drizzle/0128_hard_falcon.sql
Normal file
2
apps/dokploy/drizzle/0128_hard_falcon.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "user" RENAME COLUMN "name" TO "firstName";--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN "lastName" text DEFAULT '' NOT NULL;
|
||||
6864
apps/dokploy/drizzle/meta/0128_snapshot.json
Normal file
6864
apps/dokploy/drizzle/meta/0128_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -897,6 +897,13 @@
|
||||
"when": 1765095189368,
|
||||
"tag": "0127_superb_alice",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 128,
|
||||
"version": "7",
|
||||
"when": 1765101709413,
|
||||
"tag": "0128_hard_falcon",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { betterAuthInstance } from "@dokploy/server";
|
||||
import {
|
||||
adminClient,
|
||||
apiKeyClient,
|
||||
inferAdditionalFields,
|
||||
organizationClient,
|
||||
twoFactorClient,
|
||||
} from "better-auth/client/plugins";
|
||||
@@ -13,5 +15,6 @@ export const authClient = createAuthClient({
|
||||
twoFactorClient(),
|
||||
apiKeyClient(),
|
||||
adminClient(),
|
||||
inferAdditionalFields<typeof betterAuthInstance>(),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -27,7 +27,10 @@ import { api } from "@/utils/api";
|
||||
const registerSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
message: "First name is required",
|
||||
}),
|
||||
lastName: z.string().min(1, {
|
||||
message: "Last name is required",
|
||||
}),
|
||||
email: z
|
||||
.string()
|
||||
@@ -92,6 +95,7 @@ const Invitation = ({
|
||||
const form = useForm<Register>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
@@ -115,6 +119,7 @@ const Invitation = ({
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
name: values.name,
|
||||
lastName: values.lastName,
|
||||
fetchOptions: {
|
||||
headers: {
|
||||
"x-dokploy-token": token,
|
||||
@@ -197,12 +202,22 @@ const Invitation = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>First Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter your name"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="John" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Doe" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -27,7 +27,10 @@ import { authClient } from "@/lib/auth-client";
|
||||
const registerSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
message: "First name is required",
|
||||
}),
|
||||
lastName: z.string().min(1, {
|
||||
message: "Last name is required",
|
||||
}),
|
||||
email: z
|
||||
.string()
|
||||
@@ -79,6 +82,7 @@ const Register = ({ isCloud }: Props) => {
|
||||
const form = useForm<Register>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
@@ -95,6 +99,7 @@ const Register = ({ isCloud }: Props) => {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
name: values.name,
|
||||
lastName: values.lastName,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
@@ -158,9 +163,22 @@ const Register = ({ isCloud }: Props) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>First Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="name" {...field} />
|
||||
<Input placeholder="John" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Doe" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -31,7 +31,8 @@ export const user = pgTable("user", {
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull().default(""),
|
||||
firstName: text("firstName").notNull().default(""),
|
||||
lastName: text("lastName").notNull().default(""),
|
||||
isRegistered: boolean("isRegistered").notNull().default(false),
|
||||
expirationDate: text("expirationDate")
|
||||
.notNull()
|
||||
@@ -332,6 +333,7 @@ export const apiUpdateUser = createSchema.partial().extend({
|
||||
password: z.string().optional(),
|
||||
currentPassword: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
metricsConfig: z
|
||||
.object({
|
||||
server: z.object({
|
||||
|
||||
@@ -14,7 +14,7 @@ import { getHubSpotUTK, submitToHubSpot } from "../utils/tracking/hubspot";
|
||||
import { sendEmail } from "../verification/send-verification-email";
|
||||
import { getPublicIpWithFallback } from "../wss/utils";
|
||||
|
||||
const { handler, api } = betterAuth({
|
||||
export const betterAuthInstance = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg",
|
||||
schema: schema,
|
||||
@@ -127,16 +127,22 @@ const { handler, api } = betterAuth({
|
||||
});
|
||||
}
|
||||
|
||||
console.log(user);
|
||||
|
||||
if (IS_CLOUD) {
|
||||
try {
|
||||
const hutk = getHubSpotUTK(
|
||||
context?.request?.headers?.get("cookie") || undefined,
|
||||
);
|
||||
// Cast to include additional fields
|
||||
const userWithFields = user as typeof user & {
|
||||
lastName?: string;
|
||||
};
|
||||
const hubspotSuccess = await submitToHubSpot(
|
||||
{
|
||||
email: user.email,
|
||||
firstName: user.name,
|
||||
lastName: user.name,
|
||||
firstName: user.name || "", // name is mapped to firstName column
|
||||
lastName: userWithFields.lastName || "",
|
||||
},
|
||||
hutk,
|
||||
);
|
||||
@@ -204,6 +210,9 @@ const { handler, api } = betterAuth({
|
||||
},
|
||||
user: {
|
||||
modelName: "user",
|
||||
fields: {
|
||||
name: "firstName", // Map better-auth's default 'name' field to 'firstName' column
|
||||
},
|
||||
additionalFields: {
|
||||
role: {
|
||||
type: "string",
|
||||
@@ -220,6 +229,12 @@ const { handler, api } = betterAuth({
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
},
|
||||
lastName: {
|
||||
type: "string",
|
||||
required: false,
|
||||
input: true,
|
||||
defaultValue: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
@@ -257,10 +272,12 @@ const { handler, api } = betterAuth({
|
||||
});
|
||||
|
||||
export const auth = {
|
||||
handler,
|
||||
createApiKey: api.createApiKey,
|
||||
handler: betterAuthInstance.handler,
|
||||
createApiKey: betterAuthInstance.api.createApiKey,
|
||||
};
|
||||
|
||||
export const { handler, api } = betterAuthInstance;
|
||||
|
||||
export const validateRequest = async (request: IncomingMessage) => {
|
||||
const apiKey = request.headers["x-api-key"] as string;
|
||||
if (apiKey) {
|
||||
@@ -316,16 +333,11 @@ export const validateRequest = async (request: IncomingMessage) => {
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
emailVerified,
|
||||
image,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
twoFactorEnabled,
|
||||
} = apiKeyRecord.user;
|
||||
// When accessing from DB, use actual column names
|
||||
const userFromDb = apiKeyRecord.user as typeof apiKeyRecord.user & {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
|
||||
const mockSession = {
|
||||
session: {
|
||||
@@ -333,14 +345,14 @@ export const validateRequest = async (request: IncomingMessage) => {
|
||||
activeOrganizationId: organizationId || "",
|
||||
},
|
||||
user: {
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
emailVerified,
|
||||
image,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
twoFactorEnabled,
|
||||
id: userFromDb.id,
|
||||
name: userFromDb.firstName, // Map firstName back to name for better-auth
|
||||
email: userFromDb.email,
|
||||
emailVerified: userFromDb.emailVerified,
|
||||
image: userFromDb.image,
|
||||
createdAt: userFromDb.createdAt,
|
||||
updatedAt: userFromDb.updatedAt,
|
||||
twoFactorEnabled: userFromDb.twoFactorEnabled,
|
||||
role: member?.role || "member",
|
||||
ownerId: member?.organization.ownerId || apiKeyRecord.user.id,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user