1
0
forked from baron/baron-sso

i18n refresh and frontend fixes

This commit is contained in:
Lectom C Han
2026-02-10 19:15:51 +09:00
parent 2441c64598
commit b6d3b69cda
44 changed files with 8603 additions and 1760 deletions

View File

@@ -14,19 +14,34 @@ import {
import { Input } from "../../../components/ui/input";
import { Label } from "../../../components/ui/label";
import { fetchTenant, updateTenant } from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
type SchemaFieldType = "text" | "number" | "boolean";
type SchemaField = {
id: string;
key: string;
label: string;
type: "text" | "number" | "boolean";
type: SchemaFieldType;
required: boolean;
};
function createFieldId() {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
return crypto.randomUUID();
}
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
export function TenantSchemaPage() {
const { tenantId } = useParams<{ tenantId: string }>();
const queryClient = useQueryClient();
if (!tenantId) return <div>Tenant ID missing</div>;
if (!tenantId) {
return (
<div>{t("msg.admin.tenants.schema.missing_id", "Tenant ID missing")}</div>
);
}
const tenantQuery = useQuery({
queryKey: ["tenant", tenantId],
@@ -36,8 +51,20 @@ export function TenantSchemaPage() {
const [fields, setFields] = useState<SchemaField[]>([]);
useEffect(() => {
if (tenantQuery.data?.config?.userSchema) {
setFields(tenantQuery.data.config.userSchema as SchemaField[]);
const rawSchema = tenantQuery.data?.config?.userSchema;
if (Array.isArray(rawSchema)) {
setFields(
rawSchema.map((field) => ({
id: typeof field?.id === "string" ? field.id : createFieldId(),
key: typeof field?.key === "string" ? field.key : "",
label: typeof field?.label === "string" ? field.label : "",
type:
field?.type === "number" || field?.type === "boolean"
? field.type
: "text",
required: Boolean(field?.required),
})),
);
}
}, [tenantQuery.data]);
@@ -51,15 +78,32 @@ export function TenantSchemaPage() {
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] });
alert("Schema updated successfully");
alert(
t(
"msg.admin.tenants.schema.update_success",
"Schema updated successfully",
),
);
},
onError: (err: AxiosError<{ error?: string }>) => {
alert(err.response?.data?.error || "Failed to update schema");
alert(
err.response?.data?.error ||
t("msg.admin.tenants.schema.update_error", "Failed to update schema"),
);
},
});
const addField = () => {
setFields([...fields, { key: "", label: "", type: "text", required: false }]);
setFields([
...fields,
{
id: createFieldId(),
key: "",
label: "",
type: "text",
required: false,
},
]);
};
const removeField = (index: number) => {
@@ -78,51 +122,89 @@ export function TenantSchemaPage() {
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>User Schema Extension</CardTitle>
<CardTitle>
{t("ui.admin.tenants.schema.title", "User Schema Extension")}
</CardTitle>
<CardDescription>
Define custom attributes for users in this tenant.
{t(
"msg.admin.tenants.schema.subtitle",
"Define custom attributes for users in this tenant.",
)}
</CardDescription>
</div>
<Button onClick={addField} size="sm">
<Plus size={16} className="mr-2" />
Add Field
{t("ui.admin.tenants.schema.add_field", "Add Field")}
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
{fields.length === 0 && (
<div className="py-8 text-center text-muted-foreground border border-dashed rounded-md">
No custom fields defined. Click "Add Field" to begin.
{t(
"msg.admin.tenants.schema.empty",
'No custom fields defined. Click "Add Field" to begin.',
)}
</div>
)}
{fields.map((field, index) => (
<div key={index} className="flex items-end gap-4 p-4 border rounded-md bg-muted/30">
<div
key={field.id}
className="flex items-end gap-4 p-4 border rounded-md bg-muted/30"
>
<div className="flex-1 space-y-2">
<Label>Field Key (ID)</Label>
<Label>
{t("ui.admin.tenants.schema.field.key", "Field Key (ID)")}
</Label>
<Input
value={field.key}
onChange={(e) => updateField(index, { key: e.target.value })}
placeholder="e.g. employee_id"
placeholder={t(
"ui.admin.tenants.schema.field.key_placeholder",
"e.g. employee_id",
)}
/>
</div>
<div className="flex-1 space-y-2">
<Label>Display Label</Label>
<Label>
{t("ui.admin.tenants.schema.field.label", "Display Label")}
</Label>
<Input
value={field.label}
onChange={(e) => updateField(index, { label: e.target.value })}
placeholder="e.g. 사번"
onChange={(e) =>
updateField(index, { label: e.target.value })
}
placeholder={t(
"ui.admin.tenants.schema.field.label_placeholder",
"e.g. 사번",
)}
/>
</div>
<div className="w-32 space-y-2">
<Label>Type</Label>
<Label>{t("ui.admin.tenants.schema.field.type", "Type")}</Label>
<select
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm"
value={field.type}
onChange={(e) => updateField(index, { type: e.target.value as any })}
onChange={(e) => {
const nextType = e.target.value;
if (
nextType === "text" ||
nextType === "number" ||
nextType === "boolean"
) {
updateField(index, { type: nextType });
}
}}
>
<option value="text">Text</option>
<option value="number">Number</option>
<option value="boolean">Boolean</option>
<option value="text">
{t("ui.admin.tenants.schema.field.type_text", "Text")}
</option>
<option value="number">
{t("ui.admin.tenants.schema.field.type_number", "Number")}
</option>
<option value="boolean">
{t("ui.admin.tenants.schema.field.type_boolean", "Boolean")}
</option>
</select>
</div>
<Button
@@ -144,7 +226,7 @@ export function TenantSchemaPage() {
disabled={updateMutation.isPending || tenantQuery.isLoading}
>
<Save size={16} className="mr-2" />
Save Schema Changes
{t("ui.admin.tenants.schema.save", "Save Schema Changes")}
</Button>
</div>
</div>