diff --git a/adminfront/src/features/users/UserCreatePage.tsx b/adminfront/src/features/users/UserCreatePage.tsx index 6e3b6a1a..c2cb8c6a 100644 --- a/adminfront/src/features/users/UserCreatePage.tsx +++ b/adminfront/src/features/users/UserCreatePage.tsx @@ -8,6 +8,8 @@ import { Plus, Save, Trash2, + Mail, + X, } from "lucide-react"; import * as React from "react"; import { useForm } from "react-hook-form"; @@ -60,7 +62,7 @@ import { resolvePersonalTenant } from "./utils/personalTenant"; type UserFormValues = UserCreateRequest & { metadata: Record & { - sub_email?: string; + sub_email?: string[]; }; }; type UserCategory = "hanmac" | "external" | "personal"; @@ -136,6 +138,8 @@ function UserCreatePage() { ); const [isResolvingTenant, setIsResolvingTenant] = React.useState(false); + const [newSubEmail, setNewSubEmail] = React.useState(""); + const { data: tenantsData } = useQuery({ queryKey: ["tenants", "all"], queryFn: () => fetchAllTenants(), @@ -166,11 +170,34 @@ function UserCreatePage() { jobTitle: "", role: "user", metadata: { - sub_email: "", + sub_email: [], }, }, }); + const currentSubEmails = (watch("metadata.sub_email") as string[]) || []; + + const handleAddSubEmail = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === "," || e.key === " ") { + e.preventDefault(); + const value = newSubEmail.trim().replace(/,/g, ""); + if (value && value.includes("@") && !currentSubEmails.includes(value)) { + setValue("metadata.sub_email", [...currentSubEmails, value], { + shouldDirty: true, + }); + setNewSubEmail(""); + } + } + }; + + const handleRemoveSubEmail = (emailToRemove: string) => { + setValue( + "metadata.sub_email", + currentSubEmails.filter((e) => e !== emailToRemove), + { shouldDirty: true }, + ); + }; + // Lock company for tenant_admin React.useEffect(() => { if (profile?.role === "tenant_admin" && profile.tenantSlug) { @@ -377,14 +404,7 @@ function UserCreatePage() { ...formMetadata } = data.metadata ?? {}; - // Parse sub_email - let sub_email: string[] = []; - if (typeof rawSubEmail === "string" && rawSubEmail.trim() !== "") { - sub_email = rawSubEmail - .split(/[;,\n\r\t]/) - .map((e) => e.trim()) - .filter((e) => e.includes("@")); - } + const sub_email = Array.isArray(rawSubEmail) ? rawSubEmail : []; const metadata: Record = { ...formMetadata, @@ -600,22 +620,69 @@ function UserCreatePage() {
- +
+
+ {currentSubEmails.map((email) => ( +
+ {email} + +
+ ))} +
+
+ setNewSubEmail(e.target.value)} + onKeyDown={handleAddSubEmail} + className="pr-20" + placeholder={t( + "ui.admin.users.create.form.sub_email_placeholder", + "추가할 이메일 입력 후 Enter", + )} + /> + +
+

+ * 여러 개 입력 가능. 입력 후 엔터를 눌러 추가하세요. +

+
diff --git a/adminfront/src/features/users/UserDetailPage.tsx b/adminfront/src/features/users/UserDetailPage.tsx index 124ae3e9..38144170 100644 --- a/adminfront/src/features/users/UserDetailPage.tsx +++ b/adminfront/src/features/users/UserDetailPage.tsx @@ -17,6 +17,7 @@ import { Shield, Trash2, Users, + X, } from "lucide-react"; import * as React from "react"; import { @@ -95,7 +96,7 @@ import { resolvePersonalTenant } from "./utils/personalTenant"; type UserFormValues = Omit & { email: string; metadata: Record> & { - sub_email?: string; + sub_email?: string[]; }; }; type UserCategory = "hanmac" | "external" | "personal"; @@ -409,6 +410,30 @@ function UserDetailPage() { const isSelf = Boolean(profile?.id && user?.id && profile.id === user.id); const watchedStatus = watch("status"); + const [newSubEmail, setNewSubEmail] = React.useState(""); + const currentSubEmails = (watch("metadata.sub_email") as string[]) || []; + + const handleAddSubEmail = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === "," || e.key === " ") { + e.preventDefault(); + const value = newSubEmail.trim().replace(/,/g, ""); + if (value && value.includes("@") && !currentSubEmails.includes(value)) { + setValue("metadata.sub_email", [...currentSubEmails, value], { + shouldDirty: true, + }); + setNewSubEmail(""); + } + } + }; + + const handleRemoveSubEmail = (emailToRemove: string) => { + setValue( + "metadata.sub_email", + currentSubEmails.filter((e) => e !== emailToRemove), + { shouldDirty: true }, + ); + }; + const resetMutation = useMutation({ mutationFn: (newPass: string) => updateUser(userId, { password: newPass }), onSuccess: (_, newPass) => { @@ -636,10 +661,13 @@ function UserDetailPage() { Record >) || {}), sub_email: Array.isArray(user.metadata?.sub_email) - ? user.metadata.sub_email.join(", ") + ? user.metadata.sub_email : typeof user.metadata?.sub_email === "string" ? user.metadata.sub_email - : "", + .split(/[;,\n\r\t]/) + .map((e) => e.trim()) + .filter((e) => e.includes("@")) + : [], }, }); const isUserHanmacFamily = isHanmacFamilyUser( @@ -1087,27 +1115,74 @@ function UserDetailPage() {
- -

- {t( - "msg.admin.users.detail.sub_email_help", - "* 보조 이메일로도 로그인이 가능하며 계정 찾기 등에 활용될 수 있습니다.", - )} -

+
+
+ {currentSubEmails.map((email) => ( + + {email} + + + ))} +
+
+ setNewSubEmail(e.target.value)} + onKeyDown={handleAddSubEmail} + className="h-11 shadow-sm pr-20" + placeholder={t( + "ui.admin.users.detail.form.sub_email_placeholder", + "추가할 이메일 입력 후 Enter", + )} + /> + +
+

+ {t( + "msg.admin.users.detail.sub_email_help", + "* 보조 이메일로도 로그인이 가능하며 계정 찾기 등에 활용될 수 있습니다.", + )} +

+