Initial commit of eigent-main

This commit is contained in:
puzhen 2025-08-12 01:16:39 +02:00
commit 723df5a03e
1144 changed files with 103478 additions and 0 deletions

View file

@ -0,0 +1,126 @@
// src/components-page/account-settings/active-sessions/active-sessions-page.tsx
import { fromNow } from "@stackframe/stack-shared/dist/utils/dates";
import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { ActionCell, Badge, Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
import { useEffect, useState } from "react";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { PageLayout } from "../page-layout";
import { jsx, jsxs } from "react/jsx-runtime";
function ActiveSessionsPage() {
const { t } = useTranslation();
const user = useUser({ or: "throw" });
const [isLoading, setIsLoading] = useState(true);
const [isRevokingAll, setIsRevokingAll] = useState(false);
const [sessions, setSessions] = useState([]);
const [showConfirmRevokeAll, setShowConfirmRevokeAll] = useState(false);
useEffect(() => {
runAsynchronously(async () => {
setIsLoading(true);
const sessionsData = await user.getActiveSessions();
const enhancedSessions = sessionsData;
setSessions(enhancedSessions);
setIsLoading(false);
});
}, [user]);
const handleRevokeSession = async (sessionId) => {
try {
await user.revokeSession(sessionId);
setSessions((prev) => prev.filter((session) => session.id !== sessionId));
} catch (error) {
captureError("Failed to revoke session", { sessionId, error });
throw error;
}
};
const handleRevokeAllSessions = async () => {
setIsRevokingAll(true);
try {
const deletionPromises = sessions.filter((session) => !session.isCurrentSession).map((session) => user.revokeSession(session.id));
await Promise.all(deletionPromises);
setSessions((prevSessions) => prevSessions.filter((session) => session.isCurrentSession));
} catch (error) {
captureError("Failed to revoke all sessions", { error, sessionIds: sessions.map((session) => session.id) });
throw error;
} finally {
setIsRevokingAll(false);
setShowConfirmRevokeAll(false);
}
};
return /* @__PURE__ */ jsx(PageLayout, { children: /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-2", children: [
/* @__PURE__ */ jsx(Typography, { className: "font-medium", children: t("Active Sessions") }),
sessions.filter((s) => !s.isCurrentSession).length > 0 && !isLoading && (showConfirmRevokeAll ? /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
Button,
{
variant: "destructive",
size: "sm",
loading: isRevokingAll,
onClick: handleRevokeAllSessions,
children: t("Confirm")
}
),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
size: "sm",
disabled: isRevokingAll,
onClick: () => setShowConfirmRevokeAll(false),
children: t("Cancel")
}
)
] }) : /* @__PURE__ */ jsx(
Button,
{
variant: "outline",
size: "sm",
onClick: () => setShowConfirmRevokeAll(true),
children: t("Revoke All Other Sessions")
}
))
] }),
/* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "footnote", className: "mb-4", children: t("These are devices where you're currently logged in. You can revoke access to end a session.") }),
isLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-[300px] w-full rounded-md" }) : /* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsxs(Table, { children: [
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: t("Session") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[150px]", children: t("IP Address") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[150px]", children: t("Location") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[150px]", children: t("Last used") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[80px]" })
] }) }),
/* @__PURE__ */ jsx(TableBody, { children: sessions.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: 5, className: "text-center py-6", children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: t("No active sessions found") }) }) }) : sessions.map((session) => /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
/* @__PURE__ */ jsx(Typography, { children: session.isCurrentSession ? t("Current Session") : t("Other Session") }),
session.isImpersonation && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "w-fit mt-1", children: t("Impersonation") }),
/* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "footnote", children: t("Signed in {time}", { time: new Date(session.createdAt).toLocaleDateString() }) })
] }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: session.geoInfo?.ip || t("-") }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: session.geoInfo?.cityName || t("Unknown") }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
/* @__PURE__ */ jsx(Typography, { children: session.lastUsedAt ? fromNow(new Date(session.lastUsedAt)) : t("Never") }),
/* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "footnote", title: session.lastUsedAt ? new Date(session.lastUsedAt).toLocaleString() : "", children: session.lastUsedAt ? new Date(session.lastUsedAt).toLocaleDateString() : "" })
] }) }),
/* @__PURE__ */ jsx(TableCell, { align: "right", children: /* @__PURE__ */ jsx(
ActionCell,
{
items: [
{
item: t("Revoke"),
onClick: () => handleRevokeSession(session.id),
danger: true,
disabled: session.isCurrentSession,
disabledTooltip: session.isCurrentSession ? t("You cannot revoke your current session") : void 0
}
]
}
) })
] }, session.id)) })
] }) })
] }) });
}
export {
ActiveSessionsPage
};
//# sourceMappingURL=active-sessions-page.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,45 @@
// src/components-page/account-settings/api-keys/api-keys-page.tsx
import { Button } from "@stackframe/stack-ui";
import { useState } from "react";
import { CreateApiKeyDialog, ShowApiKeyDialog } from "../../../components/api-key-dialogs";
import { ApiKeyTable } from "../../../components/api-key-table";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { PageLayout } from "../page-layout";
import { jsx, jsxs } from "react/jsx-runtime";
function ApiKeysPage() {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const apiKeys = user.useApiKeys();
const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);
const [returnedApiKey, setReturnedApiKey] = useState(null);
const CreateDialog = CreateApiKeyDialog;
const ShowDialog = ShowApiKeyDialog;
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(Button, { onClick: () => setIsNewApiKeyDialogOpen(true), children: t("Create API Key") }),
/* @__PURE__ */ jsx(ApiKeyTable, { apiKeys }),
/* @__PURE__ */ jsx(
CreateDialog,
{
open: isNewApiKeyDialogOpen,
onOpenChange: setIsNewApiKeyDialogOpen,
onKeyCreated: setReturnedApiKey,
createApiKey: async (data) => {
const apiKey = await user.createApiKey(data);
return apiKey;
}
}
),
/* @__PURE__ */ jsx(
ShowDialog,
{
apiKey: returnedApiKey,
onClose: () => setReturnedApiKey(null)
}
)
] });
}
export {
ApiKeysPage
};
//# sourceMappingURL=api-keys-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/api-keys/api-keys-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Button } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { CreateApiKeyDialog, ShowApiKeyDialog } from \"../../../components/api-key-dialogs\";\nimport { ApiKeyTable } from \"../../../components/api-key-table\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { ApiKey, ApiKeyCreationOptions } from \"../../../lib/stack-app/api-keys\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { PageLayout } from \"../page-layout\";\n\n\nexport function ApiKeysPage() {\n const { t } = useTranslation();\n\n const user = useUser({ or: 'redirect' });\n const apiKeys = user.useApiKeys();\n\n const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);\n const [returnedApiKey, setReturnedApiKey] = useState<ApiKey<\"user\", true> | null>(null);\n\n const CreateDialog = CreateApiKeyDialog<\"user\">;\n const ShowDialog = ShowApiKeyDialog<\"user\">;\n\n return (\n <PageLayout>\n <Button onClick={() => setIsNewApiKeyDialogOpen(true)}>\n {t(\"Create API Key\")}\n </Button>\n <ApiKeyTable apiKeys={apiKeys} />\n <CreateDialog\n open={isNewApiKeyDialogOpen}\n onOpenChange={setIsNewApiKeyDialogOpen}\n onKeyCreated={setReturnedApiKey}\n createApiKey={async (data: ApiKeyCreationOptions<\"user\">) => {\n const apiKey = await user.createApiKey(data);\n return apiKey;\n }}\n />\n <ShowDialog\n apiKey={returnedApiKey}\n onClose={() => setReturnedApiKey(null)}\n />\n </PageLayout>\n );\n}\n"],"mappings":";AAIA,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB,wBAAwB;AACrD,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAExB,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAgBvB,SACE,KADF;AAbG,SAAS,cAAc;AAC5B,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,UAAU,KAAK,WAAW;AAEhC,QAAM,CAAC,uBAAuB,wBAAwB,IAAI,SAAS,KAAK;AACxE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwC,IAAI;AAExF,QAAM,eAAe;AACrB,QAAM,aAAa;AAEnB,SACE,qBAAC,cACC;AAAA,wBAAC,UAAO,SAAS,MAAM,yBAAyB,IAAI,GACjD,YAAE,gBAAgB,GACrB;AAAA,IACA,oBAAC,eAAY,SAAkB;AAAA,IAC/B;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,cAAc;AAAA,QACd,cAAc;AAAA,QACd,cAAc,OAAO,SAAwC;AAC3D,gBAAM,SAAS,MAAM,KAAK,aAAa,IAAI;AAC3C,iBAAO;AAAA,QACT;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ;AAAA,QACR,SAAS,MAAM,kBAAkB,IAAI;AAAA;AAAA,IACvC;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,50 @@
// src/components-page/account-settings/editable-text.tsx
import { Button, Input, Typography } from "@stackframe/stack-ui";
import { Edit } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "../../lib/translations";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function EditableText(props) {
const [editing, setEditing] = useState(false);
const [editingValue, setEditingValue] = useState(props.value);
const { t } = useTranslation();
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
Input,
{
value: editingValue,
onChange: (e) => setEditingValue(e.target.value)
}
),
/* @__PURE__ */ jsx(
Button,
{
size: "sm",
onClick: async () => {
await props.onSave?.(editingValue);
setEditing(false);
},
children: t("Save")
}
),
/* @__PURE__ */ jsx(
Button,
{
size: "sm",
variant: "secondary",
onClick: () => {
setEditingValue(props.value);
setEditing(false);
},
children: t("Cancel")
}
)
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Typography, { children: props.value }),
/* @__PURE__ */ jsx(Button, { onClick: () => setEditing(true), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Edit, { className: "w-4 h-4" }) })
] }) });
}
export {
EditableText
};
//# sourceMappingURL=editable-text.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components-page/account-settings/editable-text.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Button, Input, Typography } from \"@stackframe/stack-ui\";\nimport { Edit } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { useTranslation } from \"../../lib/translations\";\n\n\nexport function EditableText(props: { value: string, onSave?: (value: string) => void | Promise<void> }) {\n const [editing, setEditing] = useState(false);\n const [editingValue, setEditingValue] = useState(props.value);\n const { t } = useTranslation();\n\n return (\n <div className='flex items-center gap-2'>\n {editing ? (\n <>\n <Input\n value={editingValue}\n onChange={(e) => setEditingValue(e.target.value)}\n />\n <Button\n size='sm'\n onClick={async () => {\n await props.onSave?.(editingValue);\n setEditing(false);\n }}\n >\n {t(\"Save\")}\n </Button>\n <Button\n size='sm'\n variant='secondary'\n onClick={() => {\n setEditingValue(props.value);\n setEditing(false);\n }}>\n {t(\"Cancel\")}\n </Button>\n </>\n ) : (\n <>\n <Typography>{props.value}</Typography>\n <Button onClick={() => setEditing(true)} size='icon' variant='ghost'>\n <Edit className=\"w-4 h-4\" />\n </Button>\n </>\n )}\n </div>\n );\n}\n"],"mappings":";AAIA,SAAS,QAAQ,OAAO,kBAAkB;AAC1C,SAAS,YAAY;AACrB,SAAS,gBAAgB;AACzB,SAAS,sBAAsB;AAWvB,mBACE,KADF;AARD,SAAS,aAAa,OAA4E;AACvG,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,MAAM,KAAK;AAC5D,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,SACE,oBAAC,SAAI,WAAU,2BACZ,oBACC,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,UAAU,CAAC,MAAM,gBAAgB,EAAE,OAAO,KAAK;AAAA;AAAA,IACjD;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,YAAY;AACnB,gBAAM,MAAM,SAAS,YAAY;AACjC,qBAAW,KAAK;AAAA,QAClB;AAAA,QAEC,YAAE,MAAM;AAAA;AAAA,IACX;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,SAAS,MAAM;AACb,0BAAgB,MAAM,KAAK;AAC3B,qBAAW,KAAK;AAAA,QAClB;AAAA,QACC,YAAE,QAAQ;AAAA;AAAA,IACb;AAAA,KACF,IAEA,iCACE;AAAA,wBAAC,cAAY,gBAAM,OAAM;AAAA,IACzB,oBAAC,UAAO,SAAS,MAAM,WAAW,IAAI,GAAG,MAAK,QAAO,SAAQ,SAC3D,8BAAC,QAAK,WAAU,WAAU,GAC5B;AAAA,KACF,GAEJ;AAEJ;","names":[]}

View file

@ -0,0 +1,21 @@
// src/components-page/account-settings/email-and-auth/email-and-auth-page.tsx
import { PageLayout } from "../page-layout";
import { EmailsSection } from "./emails-section";
import { MfaSection } from "./mfa-section";
import { OtpSection } from "./otp-section";
import { PasskeySection } from "./passkey-section";
import { PasswordSection } from "./password-section";
import { jsx, jsxs } from "react/jsx-runtime";
function EmailsAndAuthPage() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(EmailsSection, {}),
/* @__PURE__ */ jsx(PasswordSection, {}),
/* @__PURE__ */ jsx(PasskeySection, {}),
/* @__PURE__ */ jsx(OtpSection, {}),
/* @__PURE__ */ jsx(MfaSection, {})
] });
}
export {
EmailsAndAuthPage
};
//# sourceMappingURL=email-and-auth-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/email-and-auth/email-and-auth-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { PageLayout } from \"../page-layout\";\nimport { EmailsSection } from \"./emails-section\";\nimport { MfaSection } from \"./mfa-section\";\nimport { OtpSection } from \"./otp-section\";\nimport { PasskeySection } from \"./passkey-section\";\nimport { PasswordSection } from \"./password-section\";\n\nexport function EmailsAndAuthPage() {\n return (\n <PageLayout>\n <EmailsSection/>\n <PasswordSection />\n <PasskeySection />\n <OtpSection />\n <MfaSection />\n </PageLayout>\n );\n}\n"],"mappings":";AAIA,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAI5B,SACE,KADF;AAFG,SAAS,oBAAoB;AAClC,SACE,qBAAC,cACC;AAAA,wBAAC,iBAAa;AAAA,IACd,oBAAC,mBAAgB;AAAA,IACjB,oBAAC,kBAAe;AAAA,IAChB,oBAAC,cAAW;AAAA,IACZ,oBAAC,cAAW;AAAA,KACd;AAEJ;","names":[]}

View file

@ -0,0 +1,163 @@
// src/components-page/account-settings/email-and-auth/emails-section.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { KnownErrors } from "@stackframe/stack-shared/dist/known-errors";
import { strictEmailSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { ActionCell, Badge, Button, Input, Table, TableBody, TableCell, TableRow, Typography } from "@stackframe/stack-ui";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { FormWarningText } from "../../../components/elements/form-warning";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function EmailsSection() {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const contactChannels = user.useContactChannels();
const [addingEmail, setAddingEmail] = useState(contactChannels.length === 0);
const [addingEmailLoading, setAddingEmailLoading] = useState(false);
const [addedEmail, setAddedEmail] = useState(null);
const isLastEmail = contactChannels.filter((x) => x.usedForAuth && x.type === "email").length === 1;
useEffect(() => {
if (addedEmail) {
runAsynchronously(async () => {
const cc = contactChannels.find((x) => x.value === addedEmail);
if (cc && !cc.isVerified) {
await cc.sendVerificationEmail();
}
setAddedEmail(null);
});
}
}, [contactChannels, addedEmail]);
const emailSchema = yupObject({
email: strictEmailSchema(t("Please enter a valid email address")).notOneOf(contactChannels.map((x) => x.value), t("Email already exists")).defined().nonEmpty(t("Email is required"))
});
const { register, handleSubmit, formState: { errors }, reset } = useForm({
resolver: yupResolver(emailSchema)
});
const onSubmit = async (data) => {
setAddingEmailLoading(true);
try {
await user.createContactChannel({ type: "email", value: data.email, usedForAuth: false });
setAddedEmail(data.email);
} finally {
setAddingEmailLoading(false);
}
setAddingEmail(false);
reset();
};
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row justify-between mb-4 gap-4", children: [
/* @__PURE__ */ jsx(Typography, { className: "font-medium", children: t("Emails") }),
addingEmail ? /* @__PURE__ */ jsxs(
"form",
{
onSubmit: (e) => {
e.preventDefault();
runAsynchronously(handleSubmit(onSubmit));
},
className: "flex flex-col",
children: [
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
Input,
{
...register("email"),
placeholder: t("Enter email")
}
),
/* @__PURE__ */ jsx(Button, { type: "submit", loading: addingEmailLoading, children: t("Add") }),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => {
setAddingEmail(false);
reset();
},
children: t("Cancel")
}
)
] }),
errors.email && /* @__PURE__ */ jsx(FormWarningText, { text: errors.email.message })
]
}
) : /* @__PURE__ */ jsx("div", { className: "flex md:justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setAddingEmail(true), children: t("Add an email") }) })
] }),
contactChannels.length > 0 ? /* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsx(TableBody, { children: contactChannels.filter((x) => x.type === "email").sort((a, b) => {
if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
return 0;
}).map((x) => /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row gap-2 md:gap-4", children: [
x.value,
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
x.isPrimary ? /* @__PURE__ */ jsx(Badge, { children: t("Primary") }) : null,
!x.isVerified ? /* @__PURE__ */ jsx(Badge, { variant: "destructive", children: t("Unverified") }) : null,
x.usedForAuth ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t("Used for sign-in") }) : null
] })
] }) }),
/* @__PURE__ */ jsx(TableCell, { className: "flex justify-end", children: /* @__PURE__ */ jsx(ActionCell, { items: [
...!x.isVerified ? [{
item: t("Send verification email"),
onClick: async () => {
await x.sendVerificationEmail();
}
}] : [],
...!x.isPrimary && x.isVerified ? [{
item: t("Set as primary"),
onClick: async () => {
await x.update({ isPrimary: true });
}
}] : !x.isPrimary ? [{
item: t("Set as primary"),
onClick: async () => {
},
disabled: true,
disabledTooltip: t("Please verify your email first")
}] : [],
...!x.usedForAuth && x.isVerified ? [{
item: t("Use for sign-in"),
onClick: async () => {
try {
await x.update({ usedForAuth: true });
} catch (e) {
if (KnownErrors.ContactChannelAlreadyUsedForAuthBySomeoneElse.isInstance(e)) {
alert(t("This email is already used for sign-in by another user."));
}
}
}
}] : [],
...x.usedForAuth && !isLastEmail ? [{
item: t("Stop using for sign-in"),
onClick: async () => {
await x.update({ usedForAuth: false });
}
}] : x.usedForAuth ? [{
item: t("Stop using for sign-in"),
onClick: async () => {
},
disabled: true,
disabledTooltip: t("You can not remove your last sign-in email")
}] : [],
...!isLastEmail || !x.usedForAuth ? [{
item: t("Remove"),
onClick: async () => {
await x.delete();
},
danger: true
}] : [{
item: t("Remove"),
onClick: async () => {
},
disabled: true,
disabledTooltip: t("You can not remove your last sign-in email")
}]
] }) })
] }, x.id)) }) }) }) : null
] });
}
export {
EmailsSection
};
//# sourceMappingURL=emails-section.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,111 @@
// src/components-page/account-settings/email-and-auth/mfa-section.tsx
import { createTOTPKeyURI, verifyTOTP } from "@oslojs/otp";
import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, Typography } from "@stackframe/stack-ui";
import * as QRCode from "qrcode";
import { useEffect, useState } from "react";
import { useStackApp, useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function MfaSection() {
const { t } = useTranslation();
const project = useStackApp().useProject();
const user = useUser({ or: "throw" });
const [generatedSecret, setGeneratedSecret] = useState(null);
const [qrCodeUrl, setQrCodeUrl] = useState(null);
const [mfaCode, setMfaCode] = useState("");
const [isMaybeWrong, setIsMaybeWrong] = useState(false);
const isEnabled = user.isMultiFactorRequired;
const [handleSubmit, isLoading] = useAsyncCallback(async () => {
await user.update({
totpMultiFactorSecret: generatedSecret
});
setGeneratedSecret(null);
setQrCodeUrl(null);
setMfaCode("");
}, [generatedSecret, user]);
useEffect(() => {
setIsMaybeWrong(false);
runAsynchronouslyWithAlert(async () => {
if (generatedSecret && verifyTOTP(generatedSecret, 30, 6, mfaCode)) {
await handleSubmit();
}
setIsMaybeWrong(true);
});
}, [mfaCode, generatedSecret, handleSubmit]);
return /* @__PURE__ */ jsx(
Section,
{
title: t("Multi-factor authentication"),
description: isEnabled ? t("Multi-factor authentication is currently enabled.") : t("Multi-factor authentication is currently disabled."),
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
!isEnabled && generatedSecret && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Typography, { children: t("Scan this QR code with your authenticator app:") }),
/* @__PURE__ */ jsx("img", { width: 200, height: 200, src: qrCodeUrl ?? throwErr("TOTP QR code failed to generate"), alt: t("TOTP multi-factor authentication QR code") }),
/* @__PURE__ */ jsx(Typography, { children: t("Then, enter your six-digit MFA code:") }),
/* @__PURE__ */ jsx(
Input,
{
value: mfaCode,
onChange: (e) => {
setIsMaybeWrong(false);
setMfaCode(e.target.value);
},
placeholder: "123456",
maxLength: 6,
disabled: isLoading
}
),
isMaybeWrong && mfaCode.length === 6 && /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Incorrect code. Please try again.") }),
/* @__PURE__ */ jsx("div", { className: "flex", children: /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => {
setGeneratedSecret(null);
setQrCodeUrl(null);
setMfaCode("");
},
children: t("Cancel")
}
) })
] }),
/* @__PURE__ */ jsx("div", { className: "flex gap-2", children: isEnabled ? /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: async () => {
await user.update({
totpMultiFactorSecret: null
});
},
children: t("Disable MFA")
}
) : !generatedSecret && /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: async () => {
const secret = generateRandomValues(new Uint8Array(20));
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
setGeneratedSecret(secret);
},
children: t("Enable MFA")
}
) })
] })
}
);
}
async function generateTotpQrCode(project, user, secret) {
const uri = createTOTPKeyURI(project.displayName, user.primaryEmail ?? user.id, secret, 30, 6);
return await QRCode.toDataURL(uri);
}
export {
MfaSection
};
//# sourceMappingURL=mfa-section.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,64 @@
// src/components-page/account-settings/email-and-auth/otp-section.tsx
import { Button, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useStackApp, useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { jsx, jsxs } from "react/jsx-runtime";
function OtpSection() {
const { t } = useTranslation();
const user = useUser({ or: "throw" });
const project = useStackApp().useProject();
const contactChannels = user.useContactChannels();
const isLastAuth = user.otpAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.passkeyAuthEnabled;
const [disabling, setDisabling] = useState(false);
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
if (!project.config.magicLinkEnabled) {
return null;
}
const handleDisableOTP = async () => {
await user.update({ otpAuthEnabled: false });
setDisabling(false);
};
return /* @__PURE__ */ jsx(Section, { title: t("OTP sign-in"), description: user.otpAuthEnabled ? t("OTP/magic link sign-in is currently enabled.") : t("Enable sign-in via magic link or OTP sent to your sign-in emails."), children: /* @__PURE__ */ jsx("div", { className: "flex md:justify-end", children: hasValidEmail ? user.otpAuthEnabled ? !isLastAuth ? !disabling ? /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setDisabling(true),
children: t("Disable OTP")
}
) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to disable OTP sign-in? You will not be able to sign in with only emails anymore.") }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
Button,
{
variant: "destructive",
onClick: handleDisableOTP,
children: t("Disable")
}
),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setDisabling(false),
children: t("Cancel")
}
)
] })
] }) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("OTP sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }) : /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: async () => {
await user.update({ otpAuthEnabled: true });
},
children: t("Enable OTP")
}
) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To enable OTP sign-in, please add a verified sign-in email.") }) }) });
}
export {
OtpSection
};
//# sourceMappingURL=otp-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/email-and-auth/otp-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Button, Typography } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { useStackApp, useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function OtpSection() {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\" });\n const project = useStackApp().useProject();\n const contactChannels = user.useContactChannels();\n const isLastAuth = user.otpAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.passkeyAuthEnabled;\n const [disabling, setDisabling] = useState(false);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const hasValidEmail = contactChannels.filter(x => x.type === 'email' && x.isVerified && x.usedForAuth).length > 0;\n\n if (!project.config.magicLinkEnabled) {\n return null;\n }\n\n const handleDisableOTP = async () => {\n await user.update({ otpAuthEnabled: false });\n setDisabling(false);\n };\n\n return (\n <Section title={t(\"OTP sign-in\")} description={user.otpAuthEnabled ? t(\"OTP/magic link sign-in is currently enabled.\") : t(\"Enable sign-in via magic link or OTP sent to your sign-in emails.\")}>\n <div className='flex md:justify-end'>\n {hasValidEmail ? (\n user.otpAuthEnabled ? (\n !isLastAuth ? (\n !disabling ? (\n <Button\n variant='secondary'\n onClick={() => setDisabling(true)}\n >\n {t(\"Disable OTP\")}\n </Button>\n ) : (\n <div className='flex flex-col gap-2'>\n <Typography variant='destructive'>\n {t(\"Are you sure you want to disable OTP sign-in? You will not be able to sign in with only emails anymore.\")}\n </Typography>\n <div className='flex gap-2'>\n <Button\n variant='destructive'\n onClick={handleDisableOTP}\n >\n {t(\"Disable\")}\n </Button>\n <Button\n variant='secondary'\n onClick={() => setDisabling(false)}\n >\n {t(\"Cancel\")}\n </Button>\n </div>\n </div>\n )\n ) : (\n <Typography variant='secondary' type='label'>{t(\"OTP sign-in is enabled and cannot be disabled as it is currently the only sign-in method\")}</Typography>\n )\n ) : (\n <Button\n variant='secondary'\n onClick={async () => {\n await user.update({ otpAuthEnabled: true });\n }}\n >\n {t(\"Enable OTP\")}\n </Button>\n )\n ) : (\n <Typography variant='secondary' type='label'>{t(\"To enable OTP sign-in, please add a verified sign-in email.\")}</Typography>\n )}\n </div>\n </Section>\n );\n}\n"],"mappings":";AAIA,SAAS,QAAQ,kBAAkB;AACnC,SAAS,gBAAgB;AACzB,SAAS,aAAa,eAAe;AACrC,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AA6BR,cAWE,YAXF;AA3BT,SAAS,aAAa;AAC3B,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,QAAQ,CAAC;AACpC,QAAM,UAAU,YAAY,EAAE,WAAW;AACzC,QAAM,kBAAkB,KAAK,mBAAmB;AAChD,QAAM,aAAa,KAAK,kBAAkB,CAAC,KAAK,eAAe,KAAK,eAAe,WAAW,KAAK,CAAC,KAAK;AACzG,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAGhD,QAAM,gBAAgB,gBAAgB,OAAO,OAAK,EAAE,SAAS,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS;AAEhH,MAAI,CAAC,QAAQ,OAAO,kBAAkB;AACpC,WAAO;AAAA,EACT;AAEA,QAAM,mBAAmB,YAAY;AACnC,UAAM,KAAK,OAAO,EAAE,gBAAgB,MAAM,CAAC;AAC3C,iBAAa,KAAK;AAAA,EACpB;AAEA,SACE,oBAAC,WAAQ,OAAO,EAAE,aAAa,GAAG,aAAa,KAAK,iBAAiB,EAAE,8CAA8C,IAAI,EAAE,mEAAmE,GAC5L,8BAAC,SAAI,WAAU,uBACZ,0BACC,KAAK,iBACH,CAAC,aACC,CAAC,YACC;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,SAAS,MAAM,aAAa,IAAI;AAAA,MAE/B,YAAE,aAAa;AAAA;AAAA,EAClB,IAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,wBAAC,cAAW,SAAQ,eACjB,YAAE,yGAAyG,GAC9G;AAAA,IACA,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS;AAAA,UAER,YAAE,SAAS;AAAA;AAAA,MACd;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,MAAM,aAAa,KAAK;AAAA,UAEhC,YAAE,QAAQ;AAAA;AAAA,MACb;AAAA,OACF;AAAA,KACF,IAGF,oBAAC,cAAW,SAAQ,aAAY,MAAK,SAAS,YAAE,0FAA0F,GAAE,IAG9I;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,SAAS,YAAY;AACnB,cAAM,KAAK,OAAO,EAAE,gBAAgB,KAAK,CAAC;AAAA,MAC5C;AAAA,MAEC,YAAE,YAAY;AAAA;AAAA,EACjB,IAGF,oBAAC,cAAW,SAAQ,aAAY,MAAK,SAAS,YAAE,6DAA6D,GAAE,GAEnH,GACF;AAEJ;","names":[]}

View file

@ -0,0 +1,67 @@
// src/components-page/account-settings/email-and-auth/passkey-section.tsx
import { Button, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useStackApp } from "../../..";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function PasskeySection() {
const { t } = useTranslation();
const user = useUser({ or: "throw" });
const stackApp = useStackApp();
const project = stackApp.useProject();
const contactChannels = user.useContactChannels();
const hasPasskey = user.passkeyAuthEnabled;
const isLastAuth = user.passkeyAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.otpAuthEnabled;
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
if (!project.config.passkeyEnabled) {
return null;
}
const handleDeletePasskey = async () => {
await user.update({ passkeyAuthEnabled: false });
setShowConfirmationModal(false);
};
const handleAddNewPasskey = async () => {
await user.registerPasskey();
};
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Section, { title: t("Passkey"), description: hasPasskey ? t("Passkey registered") : t("Register a passkey"), children: /* @__PURE__ */ jsxs("div", { className: "flex md:justify-end gap-2", children: [
!hasValidEmail && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To enable Passkey sign-in, please add a verified sign-in email.") }),
hasValidEmail && hasPasskey && isLastAuth && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("Passkey sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }),
!hasPasskey && hasValidEmail && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: handleAddNewPasskey, variant: "secondary", children: t("Add new passkey") }) }),
hasValidEmail && hasPasskey && !isLastAuth && !showConfirmationModal && /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setShowConfirmationModal(true),
children: t("Delete Passkey")
}
),
hasValidEmail && hasPasskey && !isLastAuth && showConfirmationModal && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to disable Passkey sign-in? You will not be able to sign in with your passkey anymore.") }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
Button,
{
variant: "destructive",
onClick: handleDeletePasskey,
children: t("Disable")
}
),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setShowConfirmationModal(false),
children: t("Cancel")
}
)
] })
] })
] }) }) });
}
export {
PasskeySection
};
//# sourceMappingURL=passkey-section.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,146 @@
// src/components-page/account-settings/email-and-auth/password-section.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
import { passwordSchema as schemaFieldsPasswordSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, Label, PasswordInput, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { useStackApp } from "../../..";
import { FormWarningText } from "../../../components/elements/form-warning";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function PasswordSection() {
const { t } = useTranslation();
const user = useUser({ or: "throw" });
const contactChannels = user.useContactChannels();
const [changingPassword, setChangingPassword] = useState(false);
const [loading, setLoading] = useState(false);
const project = useStackApp().useProject();
const passwordSchema = yupObject({
oldPassword: user.hasPassword ? schemaFieldsPasswordSchema.defined().nonEmpty(t("Please enter your old password")) : yupString(),
newPassword: schemaFieldsPasswordSchema.defined().nonEmpty(t("Please enter your password")).test({
name: "is-valid-password",
test: (value, ctx) => {
const error = getPasswordError(value);
if (error) {
return ctx.createError({ message: error.message });
} else {
return true;
}
}
}),
newPasswordRepeat: yupString().nullable().oneOf([yup.ref("newPassword"), "", null], t("Passwords do not match")).defined().nonEmpty(t("Please repeat your password"))
});
const { register, handleSubmit, setError, formState: { errors }, clearErrors, reset } = useForm({
resolver: yupResolver(passwordSchema)
});
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.usedForAuth).length > 0;
const onSubmit = async (data) => {
setLoading(true);
try {
const { oldPassword, newPassword } = data;
const error = user.hasPassword ? await user.updatePassword({ oldPassword, newPassword }) : await user.setPassword({ password: newPassword });
if (error) {
setError("oldPassword", { type: "manual", message: t("Incorrect password") });
} else {
reset();
setChangingPassword(false);
}
} finally {
setLoading(false);
}
};
const registerPassword = register("newPassword");
const registerPasswordRepeat = register("newPasswordRepeat");
if (!project.config.credentialEnabled) {
return null;
}
return /* @__PURE__ */ jsx(
Section,
{
title: t("Password"),
description: user.hasPassword ? t("Update your password") : t("Set a password for your account"),
children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: !changingPassword ? hasValidEmail ? /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setChangingPassword(true),
children: user.hasPassword ? t("Update password") : t("Set password")
}
) : /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: t("To set a password, please add a sign-in email.") }) : /* @__PURE__ */ jsxs(
"form",
{
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
user.hasPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "old-password", className: "mb-1", children: t("Old password") }),
/* @__PURE__ */ jsx(
Input,
{
id: "old-password",
type: "password",
autoComplete: "current-password",
...register("oldPassword")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.oldPassword?.message?.toString() })
] }),
/* @__PURE__ */ jsx(Label, { htmlFor: "new-password", className: "mt-4 mb-1", children: t("New password") }),
/* @__PURE__ */ jsx(
PasswordInput,
{
id: "new-password",
autoComplete: "new-password",
...registerPassword,
onChange: (e) => {
clearErrors("newPassword");
clearErrors("newPasswordRepeat");
runAsynchronously(registerPassword.onChange(e));
}
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.newPassword?.message?.toString() }),
/* @__PURE__ */ jsx(Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: t("Repeat new password") }),
/* @__PURE__ */ jsx(
PasswordInput,
{
id: "repeat-password",
autoComplete: "new-password",
...registerPasswordRepeat,
onChange: (e) => {
clearErrors("newPassword");
clearErrors("newPasswordRepeat");
runAsynchronously(registerPasswordRepeat.onChange(e));
}
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.newPasswordRepeat?.message?.toString() }),
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex gap-4", children: [
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: user.hasPassword ? t("Update Password") : t("Set Password") }),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => {
setChangingPassword(false);
reset();
},
children: t("Cancel")
}
)
] })
]
}
) })
}
);
}
export {
PasswordSection
};
//# sourceMappingURL=password-section.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,9 @@
// src/components-page/account-settings/page-layout.tsx
import { jsx } from "react/jsx-runtime";
function PageLayout(props) {
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-6", children: props.children });
}
export {
PageLayout
};
//# sourceMappingURL=page-layout.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components-page/account-settings/page-layout.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nexport function PageLayout(props: { children: React.ReactNode }) {\n return (\n <div className='flex flex-col gap-6'>\n {props.children}\n </div>\n );\n}\n"],"mappings":";AAMI;AAFG,SAAS,WAAW,OAAsC;AAC/D,SACE,oBAAC,SAAI,WAAU,uBACZ,gBAAM,UACT;AAEJ;","names":[]}

View file

@ -0,0 +1,50 @@
// src/components-page/account-settings/profile-page/profile-page.tsx
import { ProfileImageEditor } from "../../../components/profile-image-editor";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { EditableText } from "../editable-text";
import { PageLayout } from "../page-layout";
import { Section } from "../section";
import { jsx, jsxs } from "react/jsx-runtime";
function ProfilePage() {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(
Section,
{
title: t("User name"),
description: t("This is a display name and is not used for authentication"),
children: /* @__PURE__ */ jsx(
EditableText,
{
value: user.displayName || "",
onSave: async (newDisplayName) => {
await user.update({ displayName: newDisplayName });
}
}
)
}
),
/* @__PURE__ */ jsx(
Section,
{
title: t("Profile image"),
description: t("Upload your own image as your avatar"),
children: /* @__PURE__ */ jsx(
ProfileImageEditor,
{
user,
onProfileImageUrlChange: async (profileImageUrl) => {
await user.update({ profileImageUrl });
}
}
)
}
)
] });
}
export {
ProfilePage
};
//# sourceMappingURL=profile-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/profile-page/profile-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { ProfileImageEditor } from \"../../../components/profile-image-editor\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { EditableText } from \"../editable-text\";\nimport { PageLayout } from \"../page-layout\";\nimport { Section } from \"../section\";\n\nexport function ProfilePage() {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n\n return (\n <PageLayout>\n <Section\n title={t(\"User name\")}\n description={t(\"This is a display name and is not used for authentication\")}\n >\n <EditableText\n value={user.displayName || ''}\n onSave={async (newDisplayName) => {\n await user.update({ displayName: newDisplayName });\n }}/>\n </Section>\n\n <Section\n title={t(\"Profile image\")}\n description={t(\"Upload your own image as your avatar\")}\n >\n <ProfileImageEditor\n user={user}\n onProfileImageUrlChange={async (profileImageUrl) => {\n await user.update({ profileImageUrl });\n }}\n />\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":";AAIA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAOpB,SAKI,KALJ;AALG,SAAS,cAAc;AAC5B,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AAEvC,SACE,qBAAC,cACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,WAAW;AAAA,QACpB,aAAa,EAAE,2DAA2D;AAAA,QAE1E;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,KAAK,eAAe;AAAA,YAC3B,QAAQ,OAAO,mBAAmB;AAChC,oBAAM,KAAK,OAAO,EAAE,aAAa,eAAe,CAAC;AAAA,YACnD;AAAA;AAAA,QAAE;AAAA;AAAA,IACN;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,eAAe;AAAA,QACxB,aAAa,EAAE,sCAAsC;AAAA,QAErD;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,yBAAyB,OAAO,oBAAoB;AAClD,oBAAM,KAAK,OAAO,EAAE,gBAAgB,CAAC;AAAA,YACvC;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,19 @@
// src/components-page/account-settings/section.tsx
import { Separator, Typography } from "@stackframe/stack-ui";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function Section(props) {
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Separator, {}),
/* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
/* @__PURE__ */ jsxs("div", { className: "sm:flex-1 flex flex-col justify-center", children: [
/* @__PURE__ */ jsx(Typography, { className: "font-medium", children: props.title }),
props.description && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "footnote", children: props.description })
] }),
/* @__PURE__ */ jsx("div", { className: "sm:flex-1 sm:items-end flex flex-col gap-2 ", children: props.children })
] })
] });
}
export {
Section
};
//# sourceMappingURL=section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components-page/account-settings/section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Separator, Typography } from \"@stackframe/stack-ui\";\n\nexport function Section(props: { title: string, description?: string, children: React.ReactNode }) {\n return (\n <>\n <Separator />\n <div className='flex flex-col sm:flex-row gap-2'>\n <div className='sm:flex-1 flex flex-col justify-center'>\n <Typography className='font-medium'>\n {props.title}\n </Typography>\n {props.description && <Typography variant='secondary' type='footnote'>\n {props.description}\n </Typography>}\n </div>\n <div className='sm:flex-1 sm:items-end flex flex-col gap-2 '>\n {props.children}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":";AAIA,SAAS,WAAW,kBAAkB;AAIlC,mBACE,KAEE,YAHJ;AAFG,SAAS,QAAQ,OAA2E;AACjG,SACE,iCACE;AAAA,wBAAC,aAAU;AAAA,IACX,qBAAC,SAAI,WAAU,mCACb;AAAA,2BAAC,SAAI,WAAU,0CACb;AAAA,4BAAC,cAAW,WAAU,eACnB,gBAAM,OACT;AAAA,QACC,MAAM,eAAe,oBAAC,cAAW,SAAQ,aAAY,MAAK,YACxD,gBAAM,aACT;AAAA,SACF;AAAA,MACA,oBAAC,SAAI,WAAU,+CACZ,gBAAM,UACT;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,62 @@
// src/components-page/account-settings/settings/delete-account-section.tsx
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useStackApp, useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { jsx, jsxs } from "react/jsx-runtime";
function DeleteAccountSection() {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const app = useStackApp();
const project = app.useProject();
const [deleting, setDeleting] = useState(false);
if (!project.config.clientUserDeletionEnabled) {
return null;
}
return /* @__PURE__ */ jsx(
Section,
{
title: t("Delete Account"),
description: t("Permanently remove your account and all associated data"),
children: /* @__PURE__ */ jsx("div", { className: "stack-scope flex flex-col items-stretch", children: /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "w-full", children: /* @__PURE__ */ jsxs(AccordionItem, { value: "item-1", children: [
/* @__PURE__ */ jsx(AccordionTrigger, { children: t("Danger zone") }),
/* @__PURE__ */ jsx(AccordionContent, { children: !deleting ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
Button,
{
variant: "destructive",
onClick: () => setDeleting(true),
children: t("Delete account")
}
) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to delete your account? This action is IRREVERSIBLE and will delete ALL associated data.") }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
Button,
{
variant: "destructive",
onClick: async () => {
await user.delete();
await app.redirectToHome();
},
children: t("Delete Account")
}
),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setDeleting(false),
children: t("Cancel")
}
)
] })
] }) })
] }) }) })
}
);
}
export {
DeleteAccountSection
};
//# sourceMappingURL=delete-account-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/settings/delete-account-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Typography } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { useStackApp, useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function DeleteAccountSection() {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n const app = useStackApp();\n const project = app.useProject();\n const [deleting, setDeleting] = useState(false);\n if (!project.config.clientUserDeletionEnabled) {\n return null;\n }\n\n return (\n <Section\n title={t(\"Delete Account\")}\n description={t(\"Permanently remove your account and all associated data\")}\n >\n <div className='stack-scope flex flex-col items-stretch'>\n <Accordion type=\"single\" collapsible className=\"w-full\">\n <AccordionItem value=\"item-1\">\n <AccordionTrigger>{t(\"Danger zone\")}</AccordionTrigger>\n <AccordionContent>\n {!deleting ? (\n <div>\n <Button\n variant='destructive'\n onClick={() => setDeleting(true)}\n >\n {t(\"Delete account\")}\n </Button>\n </div>\n ) : (\n <div className='flex flex-col gap-2'>\n <Typography variant='destructive'>\n {t(\"Are you sure you want to delete your account? This action is IRREVERSIBLE and will delete ALL associated data.\")}\n </Typography>\n <div className='flex gap-2'>\n <Button\n variant='destructive'\n onClick={async () => {\n await user.delete();\n await app.redirectToHome();\n }}\n >\n {t(\"Delete Account\")}\n </Button>\n <Button\n variant='secondary'\n onClick={() => setDeleting(false)}\n >\n {t(\"Cancel\")}\n </Button>\n </div>\n </div>\n )}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n </div>\n </Section>\n );\n}\n"],"mappings":";AAIA,SAAS,WAAW,kBAAkB,eAAe,kBAAkB,QAAQ,kBAAkB;AACjG,SAAS,gBAAgB;AACzB,SAAS,aAAa,eAAe;AACrC,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AAoBZ,cAgBM,YAhBN;AAlBL,SAAS,uBAAuB;AACrC,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,MAAM,YAAY;AACxB,QAAM,UAAU,IAAI,WAAW;AAC/B,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,MAAI,CAAC,QAAQ,OAAO,2BAA2B;AAC7C,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,gBAAgB;AAAA,MACzB,aAAa,EAAE,yDAAyD;AAAA,MAExE,8BAAC,SAAI,WAAU,2CACb,8BAAC,aAAU,MAAK,UAAS,aAAW,MAAC,WAAU,UAC7C,+BAAC,iBAAc,OAAM,UACnB;AAAA,4BAAC,oBAAkB,YAAE,aAAa,GAAE;AAAA,QACpC,oBAAC,oBACE,WAAC,WACA,oBAAC,SACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,SAAS,MAAM,YAAY,IAAI;AAAA,YAE9B,YAAE,gBAAgB;AAAA;AAAA,QACrB,GACF,IAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,8BAAC,cAAW,SAAQ,eACjB,YAAE,gHAAgH,GACrH;AAAA,UACA,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,SAAS,YAAY;AACnB,wBAAM,KAAK,OAAO;AAClB,wBAAM,IAAI,eAAe;AAAA,gBAC3B;AAAA,gBAEC,YAAE,gBAAgB;AAAA;AAAA,YACrB;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,SAAS,MAAM,YAAY,KAAK;AAAA,gBAE/B,YAAE,QAAQ;AAAA;AAAA,YACb;AAAA,aACF;AAAA,WACF,GAEJ;AAAA,SACF,GACF,GACF;AAAA;AAAA,EACF;AAEJ;","names":[]}

View file

@ -0,0 +1,15 @@
// src/components-page/account-settings/settings/settings-page.tsx
import { PageLayout } from "../page-layout";
import { DeleteAccountSection } from "./delete-account-section";
import { SignOutSection } from "./sign-out-section";
import { jsx, jsxs } from "react/jsx-runtime";
function SettingsPage() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(DeleteAccountSection, {}),
/* @__PURE__ */ jsx(SignOutSection, {})
] });
}
export {
SettingsPage
};
//# sourceMappingURL=settings-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/settings/settings-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { PageLayout } from \"../page-layout\";\nimport { DeleteAccountSection } from \"./delete-account-section\";\nimport { SignOutSection } from \"./sign-out-section\";\n\n\nexport function SettingsPage() {\n return (\n <PageLayout>\n <DeleteAccountSection />\n <SignOutSection />\n </PageLayout>\n );\n}\n"],"mappings":";AAIA,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AACrC,SAAS,sBAAsB;AAK3B,SACE,KADF;AAFG,SAAS,eAAe;AAC7B,SACE,qBAAC,cACC;AAAA,wBAAC,wBAAqB;AAAA,IACtB,oBAAC,kBAAe;AAAA,KAClB;AAEJ;","names":[]}

View file

@ -0,0 +1,29 @@
// src/components-page/account-settings/settings/sign-out-section.tsx
import { Button } from "@stackframe/stack-ui";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { jsx } from "react/jsx-runtime";
function SignOutSection() {
const { t } = useTranslation();
const user = useUser({ or: "throw" });
return /* @__PURE__ */ jsx(
Section,
{
title: t("Sign out"),
description: t("End your current session"),
children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => user.signOut(),
children: t("Sign out")
}
) })
}
);
}
export {
SignOutSection
};
//# sourceMappingURL=sign-out-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/settings/sign-out-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Button } from \"@stackframe/stack-ui\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function SignOutSection() {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\" });\n\n return (\n <Section\n title={t(\"Sign out\")}\n description={t(\"End your current session\")}\n >\n <div>\n <Button\n variant='secondary'\n onClick={() => user.signOut()}\n >\n {t(\"Sign out\")}\n </Button>\n </div>\n </Section>\n );\n}\n"],"mappings":";AAIA,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AAYhB;AAVD,SAAS,iBAAiB;AAC/B,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,QAAQ,CAAC;AAEpC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,UAAU;AAAA,MACnB,aAAa,EAAE,0BAA0B;AAAA,MAEzC,8BAAC,SACC;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,MAAM,KAAK,QAAQ;AAAA,UAE3B,YAAE,UAAU;AAAA;AAAA,MACf,GACF;AAAA;AAAA,EACF;AAEJ;","names":[]}

View file

@ -0,0 +1,54 @@
// src/components-page/account-settings/teams/leave-team-section.tsx
import { Button, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { jsx, jsxs } from "react/jsx-runtime";
function LeaveTeamSection(props) {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const [leaving, setLeaving] = useState(false);
return /* @__PURE__ */ jsx(
Section,
{
title: t("Leave Team"),
description: t("leave this team and remove your team profile"),
children: !leaving ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setLeaving(true),
children: t("Leave team")
}
) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to leave the team?") }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsx(
Button,
{
variant: "destructive",
onClick: async () => {
await user.leaveTeam(props.team);
window.location.reload();
},
children: t("Leave")
}
),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: () => setLeaving(false),
children: t("Cancel")
}
)
] })
] })
}
);
}
export {
LeaveTeamSection
};
//# sourceMappingURL=leave-team-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/leave-team-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Button, Typography } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { Team } from \"../../..\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function LeaveTeamSection(props: { team: Team }) {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n const [leaving, setLeaving] = useState(false);\n\n return (\n <Section\n title={t(\"Leave Team\")}\n description={t(\"leave this team and remove your team profile\")}\n >\n {!leaving ? (\n <div>\n <Button\n variant='secondary'\n onClick={() => setLeaving(true)}\n >\n {t(\"Leave team\")}\n </Button>\n </div>\n ) : (\n <div className='flex flex-col gap-2'>\n <Typography variant='destructive'>\n {t(\"Are you sure you want to leave the team?\")}\n </Typography>\n <div className='flex gap-2'>\n <Button\n variant='destructive'\n onClick={async () => {\n await user.leaveTeam(props.team);\n window.location.reload();\n }}\n >\n {t(\"Leave\")}\n </Button>\n <Button\n variant='secondary'\n onClick={() => setLeaving(false)}\n >\n {t(\"Cancel\")}\n </Button>\n </div>\n </div>\n )}\n </Section>\n );\n}\n"],"mappings":";AAIA,SAAS,QAAQ,kBAAkB;AACnC,SAAS,gBAAgB;AAEzB,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AAcd,cAYA,YAZA;AAZH,SAAS,iBAAiB,OAAuB;AACtD,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,YAAY;AAAA,MACrB,aAAa,EAAE,8CAA8C;AAAA,MAE5D,WAAC,UACA,oBAAC,SACC;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,MAAM,WAAW,IAAI;AAAA,UAE7B,YAAE,YAAY;AAAA;AAAA,MACjB,GACF,IAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,4BAAC,cAAW,SAAQ,eACjB,YAAE,0CAA0C,GAC/C;AAAA,QACA,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,SAAS,YAAY;AACnB,sBAAM,KAAK,UAAU,MAAM,IAAI;AAC/B,uBAAO,SAAS,OAAO;AAAA,cACzB;AAAA,cAEC,YAAE,OAAO;AAAA;AAAA,UACZ;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,SAAS,MAAM,WAAW,KAAK;AAAA,cAE9B,YAAE,QAAQ;AAAA;AAAA,UACb;AAAA,WACF;AAAA,SACF;AAAA;AAAA,EAEJ;AAEJ;","names":[]}

View file

@ -0,0 +1,67 @@
// src/components-page/account-settings/teams/team-api-keys-section.tsx
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { Button } from "@stackframe/stack-ui";
import { useState } from "react";
import { CreateApiKeyDialog, ShowApiKeyDialog } from "../../../components/api-key-dialogs";
import { ApiKeyTable } from "../../../components/api-key-table";
import { useStackApp, useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function TeamApiKeysSection(props) {
const user = useUser({ or: "redirect" });
const team = user.useTeam(props.team.id);
const stackApp = useStackApp();
const project = stackApp.useProject();
if (!team) {
throw new StackAssertionError("Team not found");
}
const teamApiKeysEnabled = project.config.allowTeamApiKeys;
const manageApiKeysPermission = user.usePermission(props.team, "$manage_api_keys");
if (!manageApiKeysPermission || !teamApiKeysEnabled) {
return null;
}
return /* @__PURE__ */ jsx(TeamApiKeysSectionInner, { team: props.team });
}
function TeamApiKeysSectionInner(props) {
const { t } = useTranslation();
const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);
const [returnedApiKey, setReturnedApiKey] = useState(null);
const apiKeys = props.team.useApiKeys();
const CreateDialog = CreateApiKeyDialog;
const ShowDialog = ShowApiKeyDialog;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
Section,
{
title: t("API Keys"),
description: t("API keys grant programmatic access to your team."),
children: /* @__PURE__ */ jsx(Button, { onClick: () => setIsNewApiKeyDialogOpen(true), children: t("Create API Key") })
}
),
/* @__PURE__ */ jsx(ApiKeyTable, { apiKeys }),
/* @__PURE__ */ jsx(
CreateDialog,
{
open: isNewApiKeyDialogOpen,
onOpenChange: setIsNewApiKeyDialogOpen,
onKeyCreated: setReturnedApiKey,
createApiKey: async (data) => {
const apiKey = await props.team.createApiKey(data);
return apiKey;
}
}
),
/* @__PURE__ */ jsx(
ShowDialog,
{
apiKey: returnedApiKey,
onClose: () => setReturnedApiKey(null)
}
)
] });
}
export {
TeamApiKeysSection
};
//# sourceMappingURL=team-api-keys-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-api-keys-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { StackAssertionError } from \"@stackframe/stack-shared/dist/utils/errors\";\nimport { Button } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { CreateApiKeyDialog, ShowApiKeyDialog } from \"../../../components/api-key-dialogs\";\nimport { ApiKeyTable } from \"../../../components/api-key-table\";\nimport { useStackApp, useUser } from \"../../../lib/hooks\";\nimport { TeamApiKeyFirstView } from \"../../../lib/stack-app/api-keys\";\nimport { Team } from \"../../../lib/stack-app/teams\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\n\nexport function TeamApiKeysSection(props: { team: Team }) {\n const user = useUser({ or: 'redirect' });\n const team = user.useTeam(props.team.id);\n const stackApp = useStackApp();\n const project = stackApp.useProject();\n\n if (!team) {\n throw new StackAssertionError(\"Team not found\");\n }\n\n const teamApiKeysEnabled = project.config.allowTeamApiKeys;\n const manageApiKeysPermission = user.usePermission(props.team, '$manage_api_keys');\n if (!manageApiKeysPermission || !teamApiKeysEnabled) {\n return null;\n }\n\n return <TeamApiKeysSectionInner team={props.team} />;\n}\n\nfunction TeamApiKeysSectionInner(props: { team: Team }) {\n const { t } = useTranslation();\n\n const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);\n const [returnedApiKey, setReturnedApiKey] = useState<TeamApiKeyFirstView | null>(null);\n\n const apiKeys = props.team.useApiKeys();\n\n const CreateDialog = CreateApiKeyDialog<\"team\">;\n const ShowDialog = ShowApiKeyDialog<\"team\">;\n\n return (\n <>\n <Section\n title={t(\"API Keys\")}\n description={t(\"API keys grant programmatic access to your team.\")}\n >\n <Button onClick={() => setIsNewApiKeyDialogOpen(true)}>\n {t(\"Create API Key\")}\n </Button>\n </Section>\n <ApiKeyTable apiKeys={apiKeys} />\n\n <CreateDialog\n open={isNewApiKeyDialogOpen}\n onOpenChange={setIsNewApiKeyDialogOpen}\n onKeyCreated={setReturnedApiKey}\n createApiKey={async (data) => {\n const apiKey = await props.team.createApiKey(data);\n return apiKey;\n }}\n />\n <ShowDialog\n apiKey={returnedApiKey}\n onClose={() => setReturnedApiKey(null)}\n />\n </>\n );\n}\n"],"mappings":";AAIA,SAAS,2BAA2B;AACpC,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB,wBAAwB;AACrD,SAAS,mBAAmB;AAC5B,SAAS,aAAa,eAAe;AAGrC,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AAmBf,SAeL,UAfK,KAeL,YAfK;AAhBF,SAAS,mBAAmB,OAAuB;AACxD,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,OAAO,KAAK,QAAQ,MAAM,KAAK,EAAE;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,SAAS,WAAW;AAEpC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,oBAAoB,gBAAgB;AAAA,EAChD;AAEA,QAAM,qBAAqB,QAAQ,OAAO;AAC1C,QAAM,0BAA0B,KAAK,cAAc,MAAM,MAAM,kBAAkB;AACjF,MAAI,CAAC,2BAA2B,CAAC,oBAAoB;AACnD,WAAO;AAAA,EACT;AAEA,SAAO,oBAAC,2BAAwB,MAAM,MAAM,MAAM;AACpD;AAEA,SAAS,wBAAwB,OAAuB;AACtD,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,QAAM,CAAC,uBAAuB,wBAAwB,IAAI,SAAS,KAAK;AACxE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAqC,IAAI;AAErF,QAAM,UAAU,MAAM,KAAK,WAAW;AAEtC,QAAM,eAAe;AACrB,QAAM,aAAa;AAEnB,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,UAAU;AAAA,QACnB,aAAa,EAAE,kDAAkD;AAAA,QAEjE,8BAAC,UAAO,SAAS,MAAM,yBAAyB,IAAI,GACjD,YAAE,gBAAgB,GACrB;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,eAAY,SAAkB;AAAA,IAE/B;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,cAAc;AAAA,QACd,cAAc;AAAA,QACd,cAAc,OAAO,SAAS;AAC5B,gBAAM,SAAS,MAAM,MAAM,KAAK,aAAa,IAAI;AACjD,iBAAO;AAAA,QACT;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ;AAAA,QACR,SAAS,MAAM,kBAAkB,IAAI;AAAA;AAAA,IACvC;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,67 @@
// src/components-page/account-settings/teams/team-creation-page.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { FormWarningText } from "../../../components/elements/form-warning";
import { MessageCard } from "../../../components/message-cards/message-card";
import { useStackApp, useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { PageLayout } from "../page-layout";
import { Section } from "../section";
import { jsx, jsxs } from "react/jsx-runtime";
function TeamCreationPage() {
const { t } = useTranslation();
const teamCreationSchema = yupObject({
displayName: yupString().defined().nonEmpty(t("Please enter a team name"))
});
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(teamCreationSchema)
});
const app = useStackApp();
const project = app.useProject();
const user = useUser({ or: "redirect" });
const navigate = app.useNavigate();
const [loading, setLoading] = useState(false);
if (!project.config.clientTeamCreationEnabled) {
return /* @__PURE__ */ jsx(MessageCard, { title: t("Team creation is not enabled") });
}
const onSubmit = async (data) => {
setLoading(true);
let team;
try {
team = await user.createTeam({ displayName: data.displayName });
} finally {
setLoading(false);
}
navigate(`#team-${team.id}`);
};
return /* @__PURE__ */ jsx(PageLayout, { children: /* @__PURE__ */ jsx(Section, { title: t("Create a Team"), description: t("Enter a display name for your new team"), children: /* @__PURE__ */ jsxs(
"form",
{
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
className: "flex gap-2 flex-col sm:flex-row",
children: [
/* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1", children: [
/* @__PURE__ */ jsx(
Input,
{
id: "displayName",
type: "text",
...register("displayName")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.displayName?.message?.toString() })
] }),
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Create") })
]
}
) }) });
}
export {
TeamCreationPage
};
//# sourceMappingURL=team-creation-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-creation-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { yupObject, yupString } from \"@stackframe/stack-shared/dist/schema-fields\";\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { Button, Input } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { FormWarningText } from \"../../../components/elements/form-warning\";\nimport { MessageCard } from \"../../../components/message-cards/message-card\";\nimport { useStackApp, useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { PageLayout } from \"../page-layout\";\nimport { Section } from \"../section\";\n\nexport function TeamCreationPage() {\n const { t } = useTranslation();\n\n const teamCreationSchema = yupObject({\n displayName: yupString().defined().nonEmpty(t(\"Please enter a team name\")),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(teamCreationSchema)\n });\n const app = useStackApp();\n const project = app.useProject();\n const user = useUser({ or: 'redirect' });\n const navigate = app.useNavigate();\n const [loading, setLoading] = useState(false);\n\n if (!project.config.clientTeamCreationEnabled) {\n return <MessageCard title={t(\"Team creation is not enabled\")} />;\n }\n\n const onSubmit = async (data: yup.InferType<typeof teamCreationSchema>) => {\n setLoading(true);\n\n let team;\n try {\n team = await user.createTeam({ displayName: data.displayName });\n } finally {\n setLoading(false);\n }\n\n navigate(`#team-${team.id}`);\n };\n\n return (\n <PageLayout>\n <Section title={t(\"Create a Team\")} description={t(\"Enter a display name for your new team\")}>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex gap-2 flex-col sm:flex-row'\n >\n <div className='flex flex-col flex-1'>\n <Input\n id=\"displayName\"\n type=\"text\"\n {...register(\"displayName\")}\n />\n <FormWarningText text={errors.displayName?.message?.toString()} />\n </div>\n <Button type=\"submit\" loading={loading}>{t(\"Create\")}</Button>\n </form>\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":";AAIA,SAAS,mBAAmB;AAC5B,SAAS,WAAW,iBAAiB;AACrC,SAAS,kCAAkC;AAC3C,SAAS,QAAQ,aAAa;AAC9B,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAExB,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,aAAa,eAAe;AACrC,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAmBb,cAwBD,YAxBC;AAjBJ,SAAS,mBAAmB;AACjC,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,QAAM,qBAAqB,UAAU;AAAA,IACnC,aAAa,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,0BAA0B,CAAC;AAAA,EAC3E,CAAC;AAED,QAAM,EAAE,UAAU,cAAc,WAAW,EAAE,OAAO,EAAE,IAAI,QAAQ;AAAA,IAChE,UAAU,YAAY,kBAAkB;AAAA,EAC1C,CAAC;AACD,QAAM,MAAM,YAAY;AACxB,QAAM,UAAU,IAAI,WAAW;AAC/B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,WAAW,IAAI,YAAY;AACjC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,MAAI,CAAC,QAAQ,OAAO,2BAA2B;AAC7C,WAAO,oBAAC,eAAY,OAAO,EAAE,8BAA8B,GAAG;AAAA,EAChE;AAEA,QAAM,WAAW,OAAO,SAAmD;AACzE,eAAW,IAAI;AAEf,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,KAAK,WAAW,EAAE,aAAa,KAAK,YAAY,CAAC;AAAA,IAChE,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAEA,aAAS,SAAS,KAAK,EAAE,EAAE;AAAA,EAC7B;AAEA,SACE,oBAAC,cACC,8BAAC,WAAQ,OAAO,EAAE,eAAe,GAAG,aAAa,EAAE,wCAAwC,GACzF;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,OAAK,2BAA2B,aAAa,QAAQ,EAAE,CAAC,CAAC;AAAA,MACnE,YAAU;AAAA,MACV,WAAU;AAAA,MAEV;AAAA,6BAAC,SAAI,WAAU,wBACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACJ,GAAG,SAAS,aAAa;AAAA;AAAA,UAC5B;AAAA,UACA,oBAAC,mBAAgB,MAAM,OAAO,aAAa,SAAS,SAAS,GAAG;AAAA,WAClE;AAAA,QACA,oBAAC,UAAO,MAAK,UAAS,SAAmB,YAAE,QAAQ,GAAE;AAAA;AAAA;AAAA,EACvD,GACF,GACF;AAEJ;","names":[]}

View file

@ -0,0 +1,32 @@
// src/components-page/account-settings/teams/team-display-name-section.tsx
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { EditableText } from "../editable-text";
import { Section } from "../section";
import { jsx } from "react/jsx-runtime";
function TeamDisplayNameSection(props) {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const updateTeamPermission = user.usePermission(props.team, "$update_team");
if (!updateTeamPermission) {
return null;
}
return /* @__PURE__ */ jsx(
Section,
{
title: t("Team display name"),
description: t("Change the display name of your team"),
children: /* @__PURE__ */ jsx(
EditableText,
{
value: props.team.displayName,
onSave: async (newDisplayName) => await props.team.update({ displayName: newDisplayName })
}
)
}
);
}
export {
TeamDisplayNameSection
};
//# sourceMappingURL=team-display-name-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-display-name-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Team } from \"../../..\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { EditableText } from \"../editable-text\";\nimport { Section } from \"../section\";\n\nexport function TeamDisplayNameSection(props: { team: Team }) {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n const updateTeamPermission = user.usePermission(props.team, '$update_team');\n\n if (!updateTeamPermission) {\n return null;\n }\n\n return (\n <Section\n title={t(\"Team display name\")}\n description={t(\"Change the display name of your team\")}\n >\n <EditableText\n value={props.team.displayName}\n onSave={async (newDisplayName) => await props.team.update({ displayName: newDisplayName })}\n />\n </Section>\n );\n}\n"],"mappings":";AAKA,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAgBlB;AAdC,SAAS,uBAAuB,OAAuB;AAC5D,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,uBAAuB,KAAK,cAAc,MAAM,MAAM,cAAc;AAE1E,MAAI,CAAC,sBAAsB;AACzB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,mBAAmB;AAAA,MAC5B,aAAa,EAAE,sCAAsC;AAAA,MAErD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM,KAAK;AAAA,UAClB,QAAQ,OAAO,mBAAmB,MAAM,MAAM,KAAK,OAAO,EAAE,aAAa,eAAe,CAAC;AAAA;AAAA,MAC3F;AAAA;AAAA,EACF;AAEJ;","names":[]}

View file

@ -0,0 +1,106 @@
// src/components-page/account-settings/teams/team-member-invitation-section.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { strictEmailSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
import { Trash } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { FormWarningText } from "../../../components/elements/form-warning";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function TeamMemberInvitationSection(props) {
const user = useUser({ or: "redirect" });
const inviteMemberPermission = user.usePermission(props.team, "$invite_members");
if (!inviteMemberPermission) {
return null;
}
return /* @__PURE__ */ jsx(MemberInvitationSectionInner, { team: props.team });
}
function MemberInvitationsSectionInvitationsList(props) {
const user = useUser({ or: "redirect" });
const { t } = useTranslation();
const invitationsToShow = props.team.useInvitations();
const removeMemberPermission = user.usePermission(props.team, "$remove_members");
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Table, { className: "mt-6", children: [
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: t("Outstanding invitations") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[60px]", children: t("Expires") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[36px] max-w-[36px]" })
] }) }),
/* @__PURE__ */ jsxs(TableBody, { children: [
invitationsToShow.map((invitation, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: invitation.recipientEmail }) }),
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: invitation.expiresAt.toLocaleString() }) }),
/* @__PURE__ */ jsx(TableCell, { align: "right", className: "max-w-[36px]", children: removeMemberPermission && /* @__PURE__ */ jsx(Button, { onClick: async () => await invitation.revoke(), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Trash, { className: "w-4 h-4" }) }) })
] }, invitation.id)),
invitationsToShow.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: 3, children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: t("No outstanding invitations") }) }) })
] })
] }) });
}
function MemberInvitationSectionInner(props) {
const user = useUser({ or: "redirect" });
const { t } = useTranslation();
const readMemberPermission = user.usePermission(props.team, "$read_members");
const invitationSchema = yupObject({
email: strictEmailSchema(t("Please enter a valid email address")).defined().nonEmpty(t("Please enter an email address"))
});
const { register, handleSubmit, formState: { errors }, watch } = useForm({
resolver: yupResolver(invitationSchema)
});
const [loading, setLoading] = useState(false);
const [invitedEmail, setInvitedEmail] = useState(null);
const onSubmit = async (data) => {
setLoading(true);
try {
await props.team.inviteUser({ email: data.email });
setInvitedEmail(data.email);
} finally {
setLoading(false);
}
};
useEffect(() => {
setInvitedEmail(null);
}, [watch("email")]);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
Section,
{
title: t("Invite member"),
description: t("Invite a user to your team through email"),
children: /* @__PURE__ */ jsxs(
"form",
{
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
className: "w-full",
children: [
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row w-full", children: [
/* @__PURE__ */ jsx(
Input,
{
placeholder: t("Email"),
...register("email")
}
),
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Invite User") })
] }),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
invitedEmail && /* @__PURE__ */ jsxs(Typography, { type: "label", variant: "secondary", children: [
"Invited ",
invitedEmail
] })
]
}
)
}
),
readMemberPermission && /* @__PURE__ */ jsx(MemberInvitationsSectionInvitationsList, { team: props.team })
] });
}
export {
TeamMemberInvitationSection
};
//# sourceMappingURL=team-member-invitation-section.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,39 @@
// src/components-page/account-settings/teams/team-member-list-section.tsx
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
import { UserAvatar } from "../../../components/elements/user-avatar";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function TeamMemberListSection(props) {
const user = useUser({ or: "redirect" });
const readMemberPermission = user.usePermission(props.team, "$read_members");
const inviteMemberPermission = user.usePermission(props.team, "$invite_members");
if (!readMemberPermission && !inviteMemberPermission) {
return null;
}
return /* @__PURE__ */ jsx(MemberListSectionInner, { team: props.team });
}
function MemberListSectionInner(props) {
const { t } = useTranslation();
const users = props.team.useUsers();
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Typography, { className: "font-medium mb-2", children: t("Members") }),
/* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsxs(Table, { children: [
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: t("User") }),
/* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: t("Name") })
] }) }),
/* @__PURE__ */ jsx(TableBody, { children: users.map(({ id, teamProfile }, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(UserAvatar, { user: teamProfile }) }),
/* @__PURE__ */ jsxs(TableCell, { children: [
teamProfile.displayName && /* @__PURE__ */ jsx(Typography, { children: teamProfile.displayName }),
!teamProfile.displayName && /* @__PURE__ */ jsx(Typography, { className: "text-muted-foreground italic", children: t("No display name set") })
] })
] }, id)) })
] }) })
] });
}
export {
TeamMemberListSection
};
//# sourceMappingURL=team-member-list-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-member-list-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from \"@stackframe/stack-ui\";\nimport { Team } from \"../../..\";\nimport { UserAvatar } from \"../../../components/elements/user-avatar\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\n\nexport function TeamMemberListSection(props: { team: Team }) {\n const user = useUser({ or: 'redirect' });\n const readMemberPermission = user.usePermission(props.team, '$read_members');\n const inviteMemberPermission = user.usePermission(props.team, '$invite_members');\n\n if (!readMemberPermission && !inviteMemberPermission) {\n return null;\n }\n\n return <MemberListSectionInner team={props.team} />;\n}\n\nfunction MemberListSectionInner(props: { team: Team }) {\n const { t } = useTranslation();\n const users = props.team.useUsers();\n\n return (\n <div>\n <Typography className='font-medium mb-2'>{t(\"Members\")}</Typography>\n <div className='border rounded-md'>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-[100px]\">{t(\"User\")}</TableHead>\n <TableHead className=\"w-[200px]\">{t(\"Name\")}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {users.map(({ id, teamProfile }, i) => (\n <TableRow key={id}>\n <TableCell>\n <UserAvatar user={teamProfile} />\n </TableCell>\n <TableCell>\n {teamProfile.displayName && (\n <Typography>{teamProfile.displayName}</Typography>\n )}\n {!teamProfile.displayName && (\n <Typography className=\"text-muted-foreground italic\">{t(\"No display name set\")}</Typography>\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n </div>\n );\n}\n"],"mappings":";AAIA,SAAS,OAAO,WAAW,WAAW,WAAW,aAAa,UAAU,kBAAkB;AAE1F,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAWtB,cAaG,YAbH;AATF,SAAS,sBAAsB,OAAuB;AAC3D,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,uBAAuB,KAAK,cAAc,MAAM,MAAM,eAAe;AAC3E,QAAM,yBAAyB,KAAK,cAAc,MAAM,MAAM,iBAAiB;AAE/E,MAAI,CAAC,wBAAwB,CAAC,wBAAwB;AACpD,WAAO;AAAA,EACT;AAEA,SAAO,oBAAC,0BAAuB,MAAM,MAAM,MAAM;AACnD;AAEA,SAAS,uBAAuB,OAAuB;AACrD,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,QAAQ,MAAM,KAAK,SAAS;AAElC,SACE,qBAAC,SACC;AAAA,wBAAC,cAAW,WAAU,oBAAoB,YAAE,SAAS,GAAE;AAAA,IACvD,oBAAC,SAAI,WAAU,qBACb,+BAAC,SACC;AAAA,0BAAC,eACC,+BAAC,YACC;AAAA,4BAAC,aAAU,WAAU,aAAa,YAAE,MAAM,GAAE;AAAA,QAC5C,oBAAC,aAAU,WAAU,aAAa,YAAE,MAAM,GAAE;AAAA,SAC9C,GACF;AAAA,MACA,oBAAC,aACE,gBAAM,IAAI,CAAC,EAAE,IAAI,YAAY,GAAG,MAC/B,qBAAC,YACC;AAAA,4BAAC,aACC,8BAAC,cAAW,MAAM,aAAa,GACjC;AAAA,QACA,qBAAC,aACE;AAAA,sBAAY,eACX,oBAAC,cAAY,sBAAY,aAAY;AAAA,UAEtC,CAAC,YAAY,eACZ,oBAAC,cAAW,WAAU,gCAAgC,YAAE,qBAAqB,GAAE;AAAA,WAEnF;AAAA,WAXa,EAYf,CACD,GACH;AAAA,OACF,GACF;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,25 @@
// src/components-page/account-settings/teams/team-page.tsx
import { PageLayout } from "../page-layout";
import { LeaveTeamSection } from "./leave-team-section";
import { TeamApiKeysSection } from "./team-api-keys-section";
import { TeamDisplayNameSection } from "./team-display-name-section";
import { TeamMemberInvitationSection } from "./team-member-invitation-section";
import { TeamMemberListSection } from "./team-member-list-section";
import { TeamProfileImageSection } from "./team-profile-image-section";
import { TeamUserProfileSection } from "./team-profile-user-section";
import { jsx, jsxs } from "react/jsx-runtime";
function TeamPage(props) {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(TeamUserProfileSection, { team: props.team }, `user-profile-${props.team.id}`),
/* @__PURE__ */ jsx(TeamProfileImageSection, { team: props.team }, `profile-image-${props.team.id}`),
/* @__PURE__ */ jsx(TeamDisplayNameSection, { team: props.team }, `display-name-${props.team.id}`),
/* @__PURE__ */ jsx(TeamMemberListSection, { team: props.team }, `member-list-${props.team.id}`),
/* @__PURE__ */ jsx(TeamMemberInvitationSection, { team: props.team }, `member-invitation-${props.team.id}`),
/* @__PURE__ */ jsx(TeamApiKeysSection, { team: props.team }, `api-keys-${props.team.id}`),
/* @__PURE__ */ jsx(LeaveTeamSection, { team: props.team }, `leave-team-${props.team.id}`)
] });
}
export {
TeamPage
};
//# sourceMappingURL=team-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Team } from \"../../..\";\nimport { PageLayout } from \"../page-layout\";\nimport { LeaveTeamSection } from \"./leave-team-section\";\nimport { TeamApiKeysSection } from \"./team-api-keys-section\";\nimport { TeamDisplayNameSection } from \"./team-display-name-section\";\nimport { TeamMemberInvitationSection } from \"./team-member-invitation-section\";\nimport { TeamMemberListSection } from \"./team-member-list-section\";\nimport { TeamProfileImageSection } from \"./team-profile-image-section\";\nimport { TeamUserProfileSection } from \"./team-profile-user-section\";\n\n\nexport function TeamPage(props: { team: Team }) {\n return (\n <PageLayout>\n <TeamUserProfileSection key={`user-profile-${props.team.id}`} team={props.team} />\n <TeamProfileImageSection key={`profile-image-${props.team.id}`} team={props.team} />\n <TeamDisplayNameSection key={`display-name-${props.team.id}`} team={props.team} />\n <TeamMemberListSection key={`member-list-${props.team.id}`} team={props.team} />\n <TeamMemberInvitationSection key={`member-invitation-${props.team.id}`} team={props.team} />\n <TeamApiKeysSection key={`api-keys-${props.team.id}`} team={props.team} />\n <LeaveTeamSection key={`leave-team-${props.team.id}`} team={props.team} />\n </PageLayout>\n );\n}\n"],"mappings":";AAKA,SAAS,kBAAkB;AAC3B,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,mCAAmC;AAC5C,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AAKnC,SACE,KADF;AAFG,SAAS,SAAS,OAAuB;AAC9C,SACE,qBAAC,cACC;AAAA,wBAAC,0BAA6D,MAAM,MAAM,QAA7C,gBAAgB,MAAM,KAAK,EAAE,EAAsB;AAAA,IAChF,oBAAC,2BAA+D,MAAM,MAAM,QAA9C,iBAAiB,MAAM,KAAK,EAAE,EAAsB;AAAA,IAClF,oBAAC,0BAA6D,MAAM,MAAM,QAA7C,gBAAgB,MAAM,KAAK,EAAE,EAAsB;AAAA,IAChF,oBAAC,yBAA2D,MAAM,MAAM,QAA5C,eAAe,MAAM,KAAK,EAAE,EAAsB;AAAA,IAC9E,oBAAC,+BAAuE,MAAM,MAAM,QAAlD,qBAAqB,MAAM,KAAK,EAAE,EAAsB;AAAA,IAC1F,oBAAC,sBAAqD,MAAM,MAAM,QAAzC,YAAY,MAAM,KAAK,EAAE,EAAsB;AAAA,IACxE,oBAAC,oBAAqD,MAAM,MAAM,QAA3C,cAAc,MAAM,KAAK,EAAE,EAAsB;AAAA,KAC1E;AAEJ;","names":[]}

View file

@ -0,0 +1,34 @@
// src/components-page/account-settings/teams/team-profile-image-section.tsx
import { ProfileImageEditor } from "../../../components/profile-image-editor";
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { Section } from "../section";
import { jsx } from "react/jsx-runtime";
function TeamProfileImageSection(props) {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const updateTeamPermission = user.usePermission(props.team, "$update_team");
if (!updateTeamPermission) {
return null;
}
return /* @__PURE__ */ jsx(
Section,
{
title: t("Team profile image"),
description: t("Upload an image for your team"),
children: /* @__PURE__ */ jsx(
ProfileImageEditor,
{
user: props.team,
onProfileImageUrlChange: async (profileImageUrl) => {
await props.team.update({ profileImageUrl });
}
}
)
}
);
}
export {
TeamProfileImageSection
};
//# sourceMappingURL=team-profile-image-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-profile-image-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Team } from \"../../..\";\nimport { ProfileImageEditor } from \"../../../components/profile-image-editor\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\n\nexport function TeamProfileImageSection(props: { team: Team }) {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n const updateTeamPermission = user.usePermission(props.team, '$update_team');\n\n if (!updateTeamPermission) {\n return null;\n }\n\n return (\n <Section\n title={t(\"Team profile image\")}\n description={t(\"Upload an image for your team\")}\n >\n <ProfileImageEditor\n user={props.team}\n onProfileImageUrlChange={async (profileImageUrl) => {\n await props.team.update({ profileImageUrl });\n }}\n />\n </Section>\n );\n}\n"],"mappings":";AAKA,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AAgBlB;AAdC,SAAS,wBAAwB,OAAuB;AAC7D,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,uBAAuB,KAAK,cAAc,MAAM,MAAM,cAAc;AAE1E,MAAI,CAAC,sBAAsB;AACzB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,oBAAoB;AAAA,MAC7B,aAAa,EAAE,+BAA+B;AAAA,MAE9C;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,MAAM;AAAA,UACZ,yBAAyB,OAAO,oBAAoB;AAClD,kBAAM,MAAM,KAAK,OAAO,EAAE,gBAAgB,CAAC;AAAA,UAC7C;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;","names":[]}

View file

@ -0,0 +1,31 @@
// src/components-page/account-settings/teams/team-profile-user-section.tsx
import { useUser } from "../../../lib/hooks";
import { useTranslation } from "../../../lib/translations";
import { EditableText } from "../editable-text";
import { Section } from "../section";
import { jsx } from "react/jsx-runtime";
function TeamUserProfileSection(props) {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const profile = user.useTeamProfile(props.team);
return /* @__PURE__ */ jsx(
Section,
{
title: t("Team user name"),
description: t("Overwrite your user display name in this team"),
children: /* @__PURE__ */ jsx(
EditableText,
{
value: profile.displayName || "",
onSave: async (newDisplayName) => {
await profile.update({ displayName: newDisplayName });
}
}
)
}
);
}
export {
TeamUserProfileSection
};
//# sourceMappingURL=team-profile-user-section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/components-page/account-settings/teams/team-profile-user-section.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Team } from \"../../..\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { EditableText } from \"../editable-text\";\nimport { Section } from \"../section\";\n\nexport function TeamUserProfileSection(props: { team: Team }) {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n const profile = user.useTeamProfile(props.team);\n\n return (\n <Section\n title={t(\"Team user name\")}\n description={t(\"Overwrite your user display name in this team\")}\n >\n <EditableText\n value={profile.displayName || ''}\n onSave={async (newDisplayName) => {\n await profile.update({ displayName: newDisplayName });\n }}\n />\n </Section>\n );\n}\n"],"mappings":";AAKA,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAYlB;AAVC,SAAS,uBAAuB,OAAuB;AAC5D,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,UAAU,KAAK,eAAe,MAAM,IAAI;AAE9C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,gBAAgB;AAAA,MACzB,aAAa,EAAE,+CAA+C;AAAA,MAE9D;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,QAAQ,eAAe;AAAA,UAC9B,QAAQ,OAAO,mBAAmB;AAChC,kBAAM,QAAQ,OAAO,EAAE,aAAa,eAAe,CAAC;AAAA,UACtD;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;","names":[]}