+
+
+
{t("admin.users.title")}
+
{t("admin.users.lead")}
+
+
+
+
+ {state.status === "loading" && (
+
{t("admin.common.loading")}
+ )}
+ {state.status === "error" && (
+
{t("admin.common.loadError")}
+ )}
+ {state.status === "loaded" && state.rows.length === 0 && (
+
{t("admin.users.empty")}
+ )}
+ {state.status === "loaded" && state.rows.length > 0 && (
+
+
+
+
+ | {t("admin.users.col.date")} |
+ {t("admin.users.col.name")} |
+ {t("admin.users.col.email")} |
+ {t("admin.users.col.household")} |
+
+
+
+ {state.rows.map((row) => (
+
+ | {formatCreatedAt(row.createdAt)} |
+ {fullName(row)} |
+ {row.email} |
+ {row.householdName ?? t("admin.users.noHousehold")} |
+
+ ))}
+
+
+
+ )}
+
+ {isCreating && (
+
{
+ setIsCreating(false);
+ if (created) load();
+ }}
+ />
+ )}
+
+ );
+}
+
+/**
+ * Two-step create dialog: the form, then — on success — a one-time reveal of
+ * the generated temporary password. `onClose(created)` tells the parent
+ * whether to refetch the list (`true` once an account was created, even if
+ * the operator then closes from the reveal step).
+ */
+function CreateUserDialog({ onClose }: { onClose: (created: boolean) => void }) {
+ const { t } = useTranslation();
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [email, setEmail] = useState("");
+ const [fieldErrors, setFieldErrors] = useState