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,151 @@
"use client";
"use client";
// src/components-page/account-settings.tsx
import { Skeleton, Typography } from "@stackframe/stack-ui";
import { icons } from "lucide-react";
import { Suspense } from "react";
import { useStackApp, useUser } from "..";
import { MaybeFullPage } from "../components/elements/maybe-full-page";
import { SidebarLayout } from "../components/elements/sidebar-layout";
import { TeamIcon } from "../components/team-icon";
import { useTranslation } from "../lib/translations";
import { ActiveSessionsPage } from "./account-settings/active-sessions/active-sessions-page";
import { ApiKeysPage } from "./account-settings/api-keys/api-keys-page";
import { EmailsAndAuthPage } from "./account-settings/email-and-auth/email-and-auth-page";
import { ProfilePage } from "./account-settings/profile-page/profile-page";
import { SettingsPage } from "./account-settings/settings/settings-page";
import { TeamCreationPage } from "./account-settings/teams/team-creation-page";
import { TeamPage } from "./account-settings/teams/team-page";
import { jsx, jsxs } from "react/jsx-runtime";
var Icon = ({ name }) => {
const LucideIcon = icons[name];
return /* @__PURE__ */ jsx(LucideIcon, { className: "mr-2 h-4 w-4" });
};
function AccountSettings(props) {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const teams = user.useTeams();
const stackApp = useStackApp();
const project = stackApp.useProject();
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("div", { className: "self-stretch flex-grow w-full", children: /* @__PURE__ */ jsx(
SidebarLayout,
{
items: [
{
title: t("My Profile"),
type: "item",
id: "profile",
icon: /* @__PURE__ */ jsx(Icon, { name: "Contact" }),
content: /* @__PURE__ */ jsx(ProfilePage, {})
},
{
title: t("Emails & Auth"),
type: "item",
id: "auth",
icon: /* @__PURE__ */ jsx(Icon, { name: "ShieldCheck" }),
content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(EmailsAndAuthPageSkeleton, {}), children: /* @__PURE__ */ jsx(EmailsAndAuthPage, {}) })
},
{
title: t("Active Sessions"),
type: "item",
id: "sessions",
icon: /* @__PURE__ */ jsx(Icon, { name: "Monitor" }),
content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ActiveSessionsPageSkeleton, {}), children: /* @__PURE__ */ jsx(ActiveSessionsPage, {}) })
},
...project.config.allowUserApiKeys ? [{
title: t("API Keys"),
type: "item",
id: "api-keys",
icon: /* @__PURE__ */ jsx(Icon, { name: "Key" }),
content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ApiKeysPageSkeleton, {}), children: /* @__PURE__ */ jsx(ApiKeysPage, {}) })
}] : [],
{
title: t("Settings"),
type: "item",
id: "settings",
icon: /* @__PURE__ */ jsx(Icon, { name: "Settings" }),
content: /* @__PURE__ */ jsx(SettingsPage, {})
},
...props.extraItems?.map((item) => ({
title: item.title,
type: "item",
id: item.id,
icon: (() => {
const iconName = item.iconName;
if (iconName) {
return /* @__PURE__ */ jsx(Icon, { name: iconName });
} else if (item.icon) {
return item.icon;
}
return null;
})(),
content: item.content
})) || [],
...teams.length > 0 || project.config.clientTeamCreationEnabled ? [{
title: t("Teams"),
type: "divider"
}] : [],
...teams.map((team) => ({
title: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center w-full", children: [
/* @__PURE__ */ jsx(TeamIcon, { team }),
/* @__PURE__ */ jsx(Typography, { className: "max-w-[320px] md:w-[90%] truncate", children: team.displayName })
] }),
type: "item",
id: `team-${team.id}`,
content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(TeamPageSkeleton, {}), children: /* @__PURE__ */ jsx(TeamPage, { team }) })
})),
...project.config.clientTeamCreationEnabled ? [{
title: t("Create a team"),
icon: /* @__PURE__ */ jsx(Icon, { name: "CirclePlus" }),
type: "item",
id: "team-creation",
content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(TeamCreationSkeleton, {}), children: /* @__PURE__ */ jsx(TeamCreationPage, {}) })
}] : []
].filter((p) => p.type === "divider" || p.content),
title: t("Account Settings")
}
) }) });
}
function PageLayout(props) {
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-6", children: props.children });
}
function EmailsAndAuthPageSkeleton() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" })
] });
}
function ActiveSessionsPageSkeleton() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-48 mb-2" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full mb-4" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-[200px] w-full mt-1 rounded-md" })
] });
}
function ApiKeysPageSkeleton() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-[200px] w-full mt-1 rounded-md" })
] });
}
function TeamPageSkeleton() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-[200px] w-full mt-1 rounded-md" })
] });
}
function TeamCreationSkeleton() {
return /* @__PURE__ */ jsxs(PageLayout, { children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" })
] });
}
export {
AccountSettings
};
//# sourceMappingURL=account-settings.js.map

File diff suppressed because one or more lines are too long

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":[]}

View file

@ -0,0 +1,99 @@
"use client";
"use client";
// src/components-page/auth-page.tsx
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { Skeleton, Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from "@stackframe/stack-ui";
import { Suspense, useEffect } from "react";
import { useStackApp, useUser } from "..";
import { CredentialSignIn } from "../components/credential-sign-in";
import { CredentialSignUp } from "../components/credential-sign-up";
import { MaybeFullPage } from "../components/elements/maybe-full-page";
import { SeparatorWithText } from "../components/elements/separator-with-text";
import { StyledLink } from "../components/link";
import { MagicLinkSignIn } from "../components/magic-link-sign-in";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { OAuthButtonGroup } from "../components/oauth-button-group";
import { PasskeyButton } from "../components/passkey-button";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function AuthPage(props) {
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Fallback, { ...props }), children: /* @__PURE__ */ jsx(Inner, { ...props }) });
}
function Fallback(props) {
const { t } = useTranslation();
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("div", { className: "stack-scope flex flex-col items-stretch", style: { maxWidth: "380px", flexBasis: "380px", padding: props.fullPage ? "1rem" : 0 }, children: /* @__PURE__ */ jsxs("div", { className: "text-center mb-6 flex flex-col", children: [
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-2/3 self-center" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16 mt-8" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-24 mt-2" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-6" })
] }) }) });
}
function Inner(props) {
const stackApp = useStackApp();
const user = useUser();
const projectFromHook = stackApp.useProject();
const project = props.mockProject || projectFromHook;
const { t } = useTranslation();
useEffect(() => {
if (props.automaticRedirect && user && !props.mockProject) {
runAsynchronously(
props.type === "sign-in" ? stackApp.redirectToAfterSignIn({ replace: true }) : stackApp.redirectToAfterSignUp({ replace: true })
);
}
}, [user, props.mockProject, stackApp, props.automaticRedirect]);
if (user && !props.mockProject && !props.automaticRedirect) {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "signedIn", fullPage: props.fullPage });
}
if (props.type === "sign-up" && !project.config.signUpEnabled) {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "signUpDisabled", fullPage: props.fullPage });
}
const hasOAuthProviders = project.config.oauthProviders.length > 0;
const hasPasskey = project.config.passkeyEnabled === true && props.type === "sign-in";
const enableSeparator = (project.config.credentialEnabled || project.config.magicLinkEnabled) && (hasOAuthProviders || hasPasskey);
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsxs("div", { className: "stack-scope flex flex-col items-stretch", style: { maxWidth: "380px", flexBasis: "380px", padding: props.fullPage ? "1rem" : 0 }, children: [
/* @__PURE__ */ jsxs("div", { className: "text-center mb-6", children: [
/* @__PURE__ */ jsx(Typography, { type: "h2", children: props.type === "sign-in" ? t("Sign in to your account") : t("Create a new account") }),
props.type === "sign-in" ? project.config.signUpEnabled && /* @__PURE__ */ jsxs(Typography, { children: [
t("Don't have an account?"),
" ",
/* @__PURE__ */ jsx(StyledLink, { href: stackApp.urls.signUp, onClick: (e) => {
runAsynchronously(stackApp.redirectToSignUp());
e.preventDefault();
}, children: t("Sign up") })
] }) : /* @__PURE__ */ jsxs(Typography, { children: [
t("Already have an account?"),
" ",
/* @__PURE__ */ jsx(StyledLink, { href: stackApp.urls.signIn, onClick: (e) => {
runAsynchronously(stackApp.redirectToSignIn());
e.preventDefault();
}, children: t("Sign in") })
] })
] }),
(hasOAuthProviders || hasPasskey) && /* @__PURE__ */ jsxs("div", { className: "gap-4 flex flex-col items-stretch stack-scope", children: [
hasOAuthProviders && /* @__PURE__ */ jsx(OAuthButtonGroup, { type: props.type, mockProject: props.mockProject }),
hasPasskey && /* @__PURE__ */ jsx(PasskeyButton, { type: props.type })
] }),
enableSeparator && /* @__PURE__ */ jsx(SeparatorWithText, { text: t("Or continue with") }),
project.config.credentialEnabled && project.config.magicLinkEnabled ? /* @__PURE__ */ jsxs(Tabs, { defaultValue: props.firstTab || "magic-link", children: [
/* @__PURE__ */ jsxs(TabsList, { className: cn("w-full mb-2", {
"flex-row-reverse": props.firstTab === "password"
}), children: [
/* @__PURE__ */ jsx(TabsTrigger, { value: "magic-link", className: "flex-1", children: t("Email") }),
/* @__PURE__ */ jsx(TabsTrigger, { value: "password", className: "flex-1", children: t("Email & Password") })
] }),
/* @__PURE__ */ jsx(TabsContent, { value: "magic-link", children: /* @__PURE__ */ jsx(MagicLinkSignIn, {}) }),
/* @__PURE__ */ jsx(TabsContent, { value: "password", children: props.type === "sign-up" ? /* @__PURE__ */ jsx(CredentialSignUp, { noPasswordRepeat: props.noPasswordRepeat }) : /* @__PURE__ */ jsx(CredentialSignIn, {}) })
] }) : project.config.credentialEnabled ? props.type === "sign-up" ? /* @__PURE__ */ jsx(CredentialSignUp, { noPasswordRepeat: props.noPasswordRepeat }) : /* @__PURE__ */ jsx(CredentialSignIn, {}) : project.config.magicLinkEnabled ? /* @__PURE__ */ jsx(MagicLinkSignIn, {}) : !(hasOAuthProviders || hasPasskey) ? /* @__PURE__ */ jsx(Typography, { variant: "destructive", className: "text-center", children: t("No authentication method enabled.") }) : null,
props.extraInfo && /* @__PURE__ */ jsx("div", { className: cn("flex flex-col items-center text-center text-sm text-gray-500", {
"mt-2": project.config.credentialEnabled || project.config.magicLinkEnabled,
"mt-6": !(project.config.credentialEnabled || project.config.magicLinkEnabled)
}), children: /* @__PURE__ */ jsx("div", { children: props.extraInfo }) })
] }) });
}
export {
AuthPage
};
//# sourceMappingURL=auth-page.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,99 @@
"use client";
"use client";
// src/components-page/cli-auth-confirm.tsx
import { Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { stackAppInternalsSymbol, useStackApp } from "..";
import { MessageCard } from "../components/message-cards/message-card";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function CliAuthConfirmation({ fullPage = true }) {
const { t } = useTranslation();
const app = useStackApp();
const [authorizing, setAuthorizing] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(null);
const user = app.useUser({ or: "redirect" });
const handleAuthorize = async () => {
if (authorizing) return;
setAuthorizing(true);
try {
const urlParams = new URLSearchParams(window.location.search);
const loginCode = urlParams.get("login_code");
if (!loginCode) {
throw new Error("Missing login code in URL parameters");
}
const refreshToken = (await user.currentSession.getTokens()).refreshToken;
if (!refreshToken) {
throw new Error("You must be logged in to authorize CLI access");
}
const result = await app[stackAppInternalsSymbol].sendRequest("/auth/cli/complete", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
login_code: loginCode,
refresh_token: (await user.currentSession.getTokens()).refreshToken
})
});
if (!result.ok) {
throw new Error(`Authorization failed: ${result.status} ${await result.text()}`);
}
setSuccess(true);
} catch (err) {
setError(err);
} finally {
setAuthorizing(false);
}
};
if (success) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("CLI Authorization Successful"),
fullPage,
primaryButtonText: t("Close"),
primaryAction: () => window.close(),
children: /* @__PURE__ */ jsx(Typography, { children: t("The CLI application has been authorized successfully. You can now close this window and return to the command line.") })
}
);
}
if (error) {
return /* @__PURE__ */ jsxs(
MessageCard,
{
title: t("Authorization Failed"),
fullPage,
primaryButtonText: t("Try Again"),
primaryAction: () => setError(null),
secondaryButtonText: t("Cancel"),
secondaryAction: () => window.close(),
children: [
/* @__PURE__ */ jsx(Typography, { className: "text-red-600", children: t("Failed to authorize the CLI application:") }),
/* @__PURE__ */ jsx(Typography, { className: "text-red-600", children: error.message })
]
}
);
}
return /* @__PURE__ */ jsxs(
MessageCard,
{
title: t("Authorize CLI Application"),
fullPage,
primaryButtonText: authorizing ? t("Authorizing...") : t("Authorize"),
primaryAction: handleAuthorize,
secondaryButtonText: t("Cancel"),
secondaryAction: () => window.close(),
children: [
/* @__PURE__ */ jsx(Typography, { children: t("A command line application is requesting access to your account. Click the button below to authorize it.") }),
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("WARNING: Make sure you trust the command line application, as it will gain access to your account. If you did not initiate this request, you can close this page and ignore it. We will never send you this link via email or any other means.") })
]
}
);
}
export {
CliAuthConfirmation
};
//# sourceMappingURL=cli-auth-confirm.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,66 @@
"use client";
"use client";
// src/components-page/email-verification.tsx
import { KnownErrors } from "@stackframe/stack-shared";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import React from "react";
import { useStackApp, useUser } from "..";
import { MessageCard } from "../components/message-cards/message-card";
import { useTranslation } from "../lib/translations";
import { jsx } from "react/jsx-runtime";
function EmailVerification(props) {
const { t } = useTranslation();
const stackApp = useStackApp();
const user = useUser();
const [result, setResult] = React.useState(null);
const invalidJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Invalid Verification Link"), fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("p", { children: t("Please check if you have the correct link. If you continue to have issues, please contact support.") }) });
const expiredJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Expired Verification Link"), fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("p", { children: t("Your email verification link has expired. Please request a new verification link from your account settings.") }) });
if (!props.searchParams?.code) {
return invalidJsx;
}
if (!result) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Do you want to verify your email?"),
fullPage: !!props.fullPage,
primaryButtonText: t("Verify"),
primaryAction: async () => {
const result2 = await stackApp.verifyEmail(props.searchParams?.code || throwErr("No verification code provided"));
setResult(result2);
},
secondaryButtonText: t("Cancel"),
secondaryAction: async () => {
await stackApp.redirectToHome();
}
}
);
} else {
if (result.status === "error") {
if (KnownErrors.VerificationCodeNotFound.isInstance(result.error)) {
return invalidJsx;
} else if (KnownErrors.VerificationCodeExpired.isInstance(result.error)) {
return expiredJsx;
} else if (KnownErrors.VerificationCodeAlreadyUsed.isInstance(result.error)) {
} else {
throw result.error;
}
}
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("You email has been verified!"),
fullPage: !!props.fullPage,
primaryButtonText: t("Go home"),
primaryAction: async () => {
await stackApp.redirectToHome();
}
}
);
}
}
export {
EmailVerification
};
//# sourceMappingURL=email-verification.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/email-verification.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { KnownErrors } from \"@stackframe/stack-shared\";\nimport { throwErr } from \"@stackframe/stack-shared/dist/utils/errors\";\nimport React from \"react\";\nimport { useStackApp, useUser } from \"..\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function EmailVerification(props: {\n searchParams?: Record<string, string>,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const user = useUser();\n const [result, setResult] = React.useState<Awaited<ReturnType<typeof stackApp.verifyEmail>> | null>(null);\n\n const invalidJsx = (\n <MessageCard title={t(\"Invalid Verification Link\")} fullPage={!!props.fullPage}>\n <p>{t(\"Please check if you have the correct link. If you continue to have issues, please contact support.\")}</p>\n </MessageCard>\n );\n\n const expiredJsx = (\n <MessageCard title={t(\"Expired Verification Link\")} fullPage={!!props.fullPage}>\n <p>{t(\"Your email verification link has expired. Please request a new verification link from your account settings.\")}</p>\n </MessageCard>\n );\n\n if (!props.searchParams?.code) {\n return invalidJsx;\n }\n\n if (!result) {\n return <MessageCard\n title={t(\"Do you want to verify your email?\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Verify\")}\n primaryAction={async () => {\n const result = await stackApp.verifyEmail(props.searchParams?.code || throwErr(\"No verification code provided\"));\n setResult(result);\n }}\n secondaryButtonText={t(\"Cancel\")}\n secondaryAction={async () => {\n await stackApp.redirectToHome();\n }}\n />;\n } else {\n if (result.status === 'error') {\n if (KnownErrors.VerificationCodeNotFound.isInstance(result.error)) {\n return invalidJsx;\n } else if (KnownErrors.VerificationCodeExpired.isInstance(result.error)) {\n return expiredJsx;\n } else if (KnownErrors.VerificationCodeAlreadyUsed.isInstance(result.error)) {\n // everything fine, continue\n } else {\n throw result.error;\n }\n }\n\n return <MessageCard\n title={t(\"You email has been verified!\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Go home\")}\n primaryAction={async () => {\n await stackApp.redirectToHome();\n }}\n />;\n }\n}\n"],"mappings":";;;AAOA,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,OAAO,WAAW;AAClB,SAAS,aAAa,eAAe;AACrC,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAazB;AAXC,SAAS,kBAAkB,OAG/B;AACD,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,WAAW,YAAY;AAC7B,QAAM,OAAO,QAAQ;AACrB,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAkE,IAAI;AAExG,QAAM,aACJ,oBAAC,eAAY,OAAO,EAAE,2BAA2B,GAAG,UAAU,CAAC,CAAC,MAAM,UACpE,8BAAC,OAAG,YAAE,oGAAoG,GAAE,GAC9G;AAGF,QAAM,aACJ,oBAAC,eAAY,OAAO,EAAE,2BAA2B,GAAG,UAAU,CAAC,CAAC,MAAM,UACpE,8BAAC,OAAG,YAAE,8GAA8G,GAAE,GACxH;AAGF,MAAI,CAAC,MAAM,cAAc,MAAM;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MAAC;AAAA;AAAA,QACN,OAAO,EAAE,mCAAmC;AAAA,QAC5C,UAAU,CAAC,CAAC,MAAM;AAAA,QAClB,mBAAmB,EAAE,QAAQ;AAAA,QAC7B,eAAe,YAAY;AACzB,gBAAMA,UAAS,MAAM,SAAS,YAAY,MAAM,cAAc,QAAQ,SAAS,+BAA+B,CAAC;AAC/G,oBAAUA,OAAM;AAAA,QAClB;AAAA,QACA,qBAAqB,EAAE,QAAQ;AAAA,QAC/B,iBAAiB,YAAY;AAC3B,gBAAM,SAAS,eAAe;AAAA,QAChC;AAAA;AAAA,IACF;AAAA,EACF,OAAO;AACL,QAAI,OAAO,WAAW,SAAS;AAC7B,UAAI,YAAY,yBAAyB,WAAW,OAAO,KAAK,GAAG;AACjE,eAAO;AAAA,MACT,WAAW,YAAY,wBAAwB,WAAW,OAAO,KAAK,GAAG;AACvE,eAAO;AAAA,MACT,WAAW,YAAY,4BAA4B,WAAW,OAAO,KAAK,GAAG;AAAA,MAE7E,OAAO;AACL,cAAM,OAAO;AAAA,MACf;AAAA,IACF;AAEA,WAAO;AAAA,MAAC;AAAA;AAAA,QACN,OAAO,EAAE,8BAA8B;AAAA,QACvC,UAAU,CAAC,CAAC,MAAM;AAAA,QAClB,mBAAmB,EAAE,SAAS;AAAA,QAC9B,eAAe,YAAY;AACzB,gBAAM,SAAS,eAAe;AAAA,QAChC;AAAA;AAAA,IACF;AAAA,EACF;AACF;","names":["result"]}

View file

@ -0,0 +1,73 @@
"use client";
"use client";
// src/components-page/error-page.tsx
import { KnownError, KnownErrors } from "@stackframe/stack-shared";
import { Typography } from "@stackframe/stack-ui";
import { useStackApp } from "..";
import { KnownErrorMessageCard } from "../components/message-cards/known-error-message-card";
import { MessageCard } from "../components/message-cards/message-card";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { useTranslation } from "../lib/translations";
import { jsx } from "react/jsx-runtime";
function ErrorPage(props) {
const { t } = useTranslation();
const stackApp = useStackApp();
const errorCode = props.searchParams.errorCode;
const message = props.searchParams.message;
const details = props.searchParams.details;
const unknownErrorCard = /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "unknownError", fullPage: !!props.fullPage });
if (!errorCode || !message) {
return unknownErrorCard;
}
let error;
try {
const detailJson = details ? JSON.parse(details) : {};
error = KnownError.fromJson({ code: errorCode, message, details: detailJson });
} catch (e) {
return unknownErrorCard;
}
if (KnownErrors.OAuthConnectionAlreadyConnectedToAnotherUser.isInstance(error)) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Failed to connect account"),
fullPage: !!props.fullPage,
primaryButtonText: t("Go Home"),
primaryAction: () => stackApp.redirectToHome(),
children: /* @__PURE__ */ jsx(Typography, { children: t("This account is already connected to another user. Please connect a different account.") })
}
);
}
if (KnownErrors.UserAlreadyConnectedToAnotherOAuthConnection.isInstance(error)) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Failed to connect account"),
fullPage: !!props.fullPage,
primaryButtonText: t("Go Home"),
primaryAction: () => stackApp.redirectToHome(),
children: /* @__PURE__ */ jsx(Typography, { children: t("The user is already connected to another OAuth account. Did you maybe selected the wrong account on the OAuth provider page?") })
}
);
}
if (KnownErrors.OAuthProviderAccessDenied.isInstance(error)) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("OAuth provider access denied"),
fullPage: !!props.fullPage,
primaryButtonText: t("Sign in again"),
primaryAction: () => stackApp.redirectToSignIn(),
secondaryButtonText: t("Go Home"),
secondaryAction: () => stackApp.redirectToHome(),
children: /* @__PURE__ */ jsx(Typography, { children: t("The sign-in operation has been cancelled. Please try again. [access_denied]") })
}
);
}
return /* @__PURE__ */ jsx(KnownErrorMessageCard, { error, fullPage: !!props.fullPage });
}
export {
ErrorPage
};
//# sourceMappingURL=error-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/error-page.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { KnownError, KnownErrors } from \"@stackframe/stack-shared\";\nimport { Typography } from \"@stackframe/stack-ui\";\nimport { useStackApp } from \"..\";\nimport { KnownErrorMessageCard } from \"../components/message-cards/known-error-message-card\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { PredefinedMessageCard } from \"../components/message-cards/predefined-message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\n\nexport function ErrorPage(props: { fullPage?: boolean, searchParams: Record<string, string> }) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const errorCode = props.searchParams.errorCode;\n const message = props.searchParams.message;\n const details = props.searchParams.details;\n\n const unknownErrorCard = <PredefinedMessageCard type='unknownError' fullPage={!!props.fullPage} />;\n\n if (!errorCode || !message) {\n return unknownErrorCard;\n }\n\n let error;\n try {\n const detailJson = details ? JSON.parse(details) : {};\n error = KnownError.fromJson({ code: errorCode, message, details: detailJson });\n } catch (e) {\n return unknownErrorCard;\n }\n\n if (KnownErrors.OAuthConnectionAlreadyConnectedToAnotherUser.isInstance(error)) {\n // TODO: add \"Connect a different account\" button\n return (\n <MessageCard\n title={t(\"Failed to connect account\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Go Home\")}\n primaryAction={() => stackApp.redirectToHome()}\n >\n <Typography>\n {t(\"This account is already connected to another user. Please connect a different account.\")}\n </Typography>\n </MessageCard>\n );\n }\n\n if (KnownErrors.UserAlreadyConnectedToAnotherOAuthConnection.isInstance(error)) {\n // TODO: add \"Connect again\" button\n return (\n <MessageCard\n title={t(\"Failed to connect account\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Go Home\")}\n primaryAction={() => stackApp.redirectToHome()}\n >\n <Typography>\n {t(\"The user is already connected to another OAuth account. Did you maybe selected the wrong account on the OAuth provider page?\")}\n </Typography>\n </MessageCard>\n );\n }\n\n if (KnownErrors.OAuthProviderAccessDenied.isInstance(error)) {\n return (\n <MessageCard\n title={t(\"OAuth provider access denied\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Sign in again\")}\n primaryAction={() => stackApp.redirectToSignIn()}\n secondaryButtonText={t(\"Go Home\")}\n secondaryAction={() => stackApp.redirectToHome()}\n >\n <Typography>\n {t(\"The sign-in operation has been cancelled. Please try again. [access_denied]\")}\n </Typography>\n </MessageCard>\n );\n }\n\n return <KnownErrorMessageCard error={error} fullPage={!!props.fullPage} />;\n}\n"],"mappings":";;;AAOA,SAAS,YAAY,mBAAmB;AACxC,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,6BAA6B;AACtC,SAAS,mBAAmB;AAC5B,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAUJ;AAPpB,SAAS,UAAU,OAAqE;AAC7F,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAY,MAAM,aAAa;AACrC,QAAM,UAAU,MAAM,aAAa;AACnC,QAAM,UAAU,MAAM,aAAa;AAEnC,QAAM,mBAAmB,oBAAC,yBAAsB,MAAK,gBAAe,UAAU,CAAC,CAAC,MAAM,UAAU;AAEhG,MAAI,CAAC,aAAa,CAAC,SAAS;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,UAAU,KAAK,MAAM,OAAO,IAAI,CAAC;AACpD,YAAQ,WAAW,SAAS,EAAE,MAAM,WAAW,SAAS,SAAS,WAAW,CAAC;AAAA,EAC/E,SAAS,GAAG;AACV,WAAO;AAAA,EACT;AAEA,MAAI,YAAY,6CAA6C,WAAW,KAAK,GAAG;AAE9E,WACE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,2BAA2B;AAAA,QACpC,UAAU,CAAC,CAAC,MAAM;AAAA,QAClB,mBAAmB,EAAE,SAAS;AAAA,QAC9B,eAAe,MAAM,SAAS,eAAe;AAAA,QAE7C,8BAAC,cACE,YAAE,wFAAwF,GAC7F;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,MAAI,YAAY,6CAA6C,WAAW,KAAK,GAAG;AAE9E,WACE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,2BAA2B;AAAA,QACpC,UAAU,CAAC,CAAC,MAAM;AAAA,QAClB,mBAAmB,EAAE,SAAS;AAAA,QAC9B,eAAe,MAAM,SAAS,eAAe;AAAA,QAE7C,8BAAC,cACE,YAAE,8HAA8H,GACnI;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,MAAI,YAAY,0BAA0B,WAAW,KAAK,GAAG;AAC3D,WACE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,8BAA8B;AAAA,QACvC,UAAU,CAAC,CAAC,MAAM;AAAA,QAClB,mBAAmB,EAAE,eAAe;AAAA,QACpC,eAAe,MAAM,SAAS,iBAAiB;AAAA,QAC/C,qBAAqB,EAAE,SAAS;AAAA,QAChC,iBAAiB,MAAM,SAAS,eAAe;AAAA,QAE/C,8BAAC,cACE,YAAE,6EAA6E,GAClF;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,SAAO,oBAAC,yBAAsB,OAAc,UAAU,CAAC,CAAC,MAAM,UAAU;AAC1E;","names":[]}

View file

@ -0,0 +1,92 @@
"use client";
"use client";
// src/components-page/forgot-password.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, Label, Typography, cn } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useStackApp, useUser } from "..";
import { FormWarningText } from "../components/elements/form-warning";
import { MaybeFullPage } from "../components/elements/maybe-full-page";
import { StyledLink } from "../components/link";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function ForgotPasswordForm({ onSent }) {
const { t } = useTranslation();
const schema = yupObject({
email: strictEmailSchema(t("Please enter a valid email")).defined().nonEmpty(t("Please enter your email"))
});
const { register, handleSubmit, formState: { errors }, clearErrors } = useForm({
resolver: yupResolver(schema)
});
const stackApp = useStackApp();
const [loading, setLoading] = useState(false);
const onSubmit = async (data) => {
setLoading(true);
try {
const { email } = data;
await stackApp.sendForgotPasswordEmail(email);
onSent?.();
} finally {
setLoading(false);
}
};
return /* @__PURE__ */ jsxs(
"form",
{
className: "flex flex-col items-stretch stack-scope",
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "email", className: "mb-1", children: t("Your Email") }),
/* @__PURE__ */ jsx(
Input,
{
id: "email",
type: "email",
autoComplete: "email",
...register("email"),
onChange: () => clearErrors("email")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: t("Send Email") })
]
}
);
}
function ForgotPassword(props) {
const { t } = useTranslation();
const stackApp = useStackApp();
const user = useUser();
const [sent, setSent] = useState(false);
if (user) {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "signedIn", fullPage: !!props.fullPage });
}
if (sent) {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "emailSent", fullPage: !!props.fullPage });
}
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsxs("div", { className: cn(
"stack-scope max-w-[380px] flex-basis-[380px]",
props.fullPage ? "p-4" : "p-0"
), children: [
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsx(Typography, { type: "h2", children: t("Reset Your Password") }),
/* @__PURE__ */ jsxs(Typography, { children: [
t("Don't need to reset?"),
" ",
/* @__PURE__ */ jsx(StyledLink, { href: stackApp.urls["signIn"], children: t("Sign in") })
] })
] }),
/* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(ForgotPasswordForm, { onSent: () => setSent(true) }) })
] }) });
}
export {
ForgotPassword,
ForgotPasswordForm
};
//# sourceMappingURL=forgot-password.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,76 @@
"use client";
"use client";
// src/components-page/magic-link-callback.tsx
import { KnownErrors } from "@stackframe/stack-shared";
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import React from "react";
import { useStackApp, useUser } from "..";
import { MessageCard } from "../components/message-cards/message-card";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { useTranslation } from "../lib/translations";
import { jsx } from "react/jsx-runtime";
var cacheSignInWithMagicLink = cacheFunction(async (stackApp, code) => {
return await stackApp.signInWithMagicLink(code);
});
function MagicLinkCallback(props) {
const { t } = useTranslation();
const stackApp = useStackApp();
const user = useUser();
const [result, setResult] = React.useState(null);
if (user) {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "signedIn", fullPage: !!props.fullPage });
}
const invalidJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Invalid Magic Link"), fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("p", { children: t("Please check if you have the correct link. If you continue to have issues, please contact support.") }) });
const expiredJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Expired Magic Link"), fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("p", { children: t("Your magic link has expired. Please request a new magic link if you need to sign-in.") }) });
const alreadyUsedJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Magic Link Already Used"), fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("p", { children: t("The magic link has already been used. The link can only be used once. Please request a new magic link if you need to sign-in again.") }) });
if (!props.searchParams?.code) {
return invalidJsx;
}
if (!result) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Do you want to sign in?"),
fullPage: !!props.fullPage,
primaryButtonText: t("Sign in"),
primaryAction: async () => {
const result2 = await stackApp.signInWithMagicLink(props.searchParams?.code || throwErr("No magic link provided"));
setResult(result2);
},
secondaryButtonText: t("Cancel"),
secondaryAction: async () => {
await stackApp.redirectToHome();
}
}
);
} else {
if (result.status === "error") {
if (KnownErrors.VerificationCodeNotFound.isInstance(result.error)) {
return invalidJsx;
} else if (KnownErrors.VerificationCodeExpired.isInstance(result.error)) {
return expiredJsx;
} else if (KnownErrors.VerificationCodeAlreadyUsed.isInstance(result.error)) {
return alreadyUsedJsx;
} else {
throw result.error;
}
}
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Signed in successfully!"),
fullPage: !!props.fullPage,
primaryButtonText: t("Go home"),
primaryAction: async () => {
await stackApp.redirectToHome();
}
}
);
}
}
export {
MagicLinkCallback
};
//# sourceMappingURL=magic-link-callback.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,69 @@
"use client";
"use client";
// src/components-page/oauth-callback.tsx
import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { Spinner, cn } from "@stackframe/stack-ui";
import { useEffect, useRef, useState } from "react";
import { useStackApp } from "..";
import { MaybeFullPage } from "../components/elements/maybe-full-page";
import { StyledLink } from "../components/link";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function OAuthCallback({ fullPage }) {
const { t } = useTranslation();
const app = useStackApp();
const called = useRef(false);
const [error, setError] = useState(null);
const [showRedirectLink, setShowRedirectLink] = useState(false);
useEffect(() => runAsynchronously(async () => {
if (called.current) return;
called.current = true;
let hasRedirected = false;
try {
hasRedirected = await app.callOAuthCallback();
} catch (e) {
captureError("<OAuthCallback />", e);
setError(e);
}
if (!hasRedirected && (!error || process.env.NODE_ENV === "production")) {
await app.redirectToSignIn({ noRedirectBack: true });
}
}), []);
useEffect(() => {
setTimeout(() => setShowRedirectLink(true), 3e3);
}, []);
return /* @__PURE__ */ jsx(
MaybeFullPage,
{
fullPage: fullPage ?? false,
containerClassName: "flex items-center justify-center",
children: /* @__PURE__ */ jsxs(
"div",
{
className: cn(
"text-center justify-center items-center stack-scope flex flex-col gap-4 max-w-[380px]",
fullPage ? "p-4" : "p-0"
),
children: [
/* @__PURE__ */ jsx("div", { className: "flex flex-col justify-center items-center gap-4", children: /* @__PURE__ */ jsx(Spinner, { size: 20 }) }),
showRedirectLink ? /* @__PURE__ */ jsxs("p", { children: [
t("If you are not redirected automatically, "),
/* @__PURE__ */ jsx(StyledLink, { className: "whitespace-nowrap", href: app.urls.home, children: t("click here") })
] }) : null,
error ? /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("p", { children: t("Something went wrong while processing the OAuth callback:") }),
/* @__PURE__ */ jsx("pre", { children: JSON.stringify(error, null, 2) }),
/* @__PURE__ */ jsx("p", { children: t("This is most likely an error in Stack. Please report it.") })
] }) : null
]
}
)
}
);
}
export {
OAuthCallback
};
//# sourceMappingURL=oauth-callback.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/oauth-callback.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { captureError } from \"@stackframe/stack-shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { Spinner, cn } from \"@stackframe/stack-ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useStackApp } from \"..\";\nimport { MaybeFullPage } from \"../components/elements/maybe-full-page\";\nimport { StyledLink } from \"../components/link\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function OAuthCallback({ fullPage }: { fullPage?: boolean }) {\n const { t } = useTranslation();\n const app = useStackApp();\n const called = useRef(false);\n const [error, setError] = useState<unknown>(null);\n const [showRedirectLink, setShowRedirectLink] = useState(false);\n\n useEffect(() => runAsynchronously(async () => {\n if (called.current) return;\n called.current = true;\n let hasRedirected = false;\n try {\n hasRedirected = await app.callOAuthCallback();\n } catch (e) {\n captureError(\"<OAuthCallback />\", e);\n setError(e);\n }\n if (!hasRedirected && (!error || process.env.NODE_ENV === 'production')) {\n await app.redirectToSignIn({ noRedirectBack: true });\n }\n }), []);\n\n useEffect(() => {\n setTimeout(() => setShowRedirectLink(true), 3000);\n }, []);\n\n return (\n <MaybeFullPage\n fullPage={fullPage ?? false}\n containerClassName=\"flex items-center justify-center\"\n >\n <div\n className={cn(\n \"text-center justify-center items-center stack-scope flex flex-col gap-4 max-w-[380px]\",\n fullPage ? \"p-4\" : \"p-0\"\n )}\n >\n <div className=\"flex flex-col justify-center items-center gap-4\">\n <Spinner size={20} />\n </div>\n {showRedirectLink ? <p>{t('If you are not redirected automatically, ')}<StyledLink className=\"whitespace-nowrap\" href={app.urls.home}>{t(\"click here\")}</StyledLink></p> : null}\n {error ? <div>\n <p>{t(\"Something went wrong while processing the OAuth callback:\")}</p>\n <pre>{JSON.stringify(error, null, 2)}</pre>\n <p>{t(\"This is most likely an error in Stack. Please report it.\")}</p>\n </div> : null}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;AAOA,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AAClC,SAAS,SAAS,UAAU;AAC5B,SAAS,WAAW,QAAQ,gBAAgB;AAC5C,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAwCrB,cAEkB,YAFlB;AAtCH,SAAS,cAAc,EAAE,SAAS,GAA2B;AAClE,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,MAAM,YAAY;AACxB,QAAM,SAAS,OAAO,KAAK;AAC3B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB,IAAI;AAChD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,SAAS,KAAK;AAE9D,YAAU,MAAM,kBAAkB,YAAY;AAC5C,QAAI,OAAO,QAAS;AACpB,WAAO,UAAU;AACjB,QAAI,gBAAgB;AACpB,QAAI;AACF,sBAAgB,MAAM,IAAI,kBAAkB;AAAA,IAC9C,SAAS,GAAG;AACV,mBAAa,qBAAqB,CAAC;AACnC,eAAS,CAAC;AAAA,IACZ;AACA,QAAI,CAAC,kBAAkB,CAAC,SAAS,QAAQ,IAAI,aAAa,eAAe;AACvE,YAAM,IAAI,iBAAiB,EAAE,gBAAgB,KAAK,CAAC;AAAA,IACrD;AAAA,EACF,CAAC,GAAG,CAAC,CAAC;AAEN,YAAU,MAAM;AACd,eAAW,MAAM,oBAAoB,IAAI,GAAG,GAAI;AAAA,EAClD,GAAG,CAAC,CAAC;AAEL,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,YAAY;AAAA,MACtB,oBAAmB;AAAA,MAEnB;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,WAAW,QAAQ;AAAA,UACrB;AAAA,UAEA;AAAA,gCAAC,SAAI,WAAU,mDACb,8BAAC,WAAQ,MAAM,IAAI,GACrB;AAAA,YACC,mBAAmB,qBAAC,OAAG;AAAA,gBAAE,2CAA2C;AAAA,cAAE,oBAAC,cAAW,WAAU,qBAAoB,MAAM,IAAI,KAAK,MAAO,YAAE,YAAY,GAAE;AAAA,eAAa,IAAO;AAAA,YAC1K,QAAQ,qBAAC,SACR;AAAA,kCAAC,OAAG,YAAE,2DAA2D,GAAE;AAAA,cACnE,oBAAC,SAAK,eAAK,UAAU,OAAO,MAAM,CAAC,GAAE;AAAA,cACrC,oBAAC,OAAG,YAAE,0DAA0D,GAAE;AAAA,eACpE,IAAS;AAAA;AAAA;AAAA,MACX;AAAA;AAAA,EACF;AAEJ;","names":[]}

View file

@ -0,0 +1,145 @@
"use client";
"use client";
// src/components-page/password-reset.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { KnownErrors } from "@stackframe/stack-shared";
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
import { passwordSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Label, PasswordInput, Typography, cn } from "@stackframe/stack-ui";
import React, { 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 { MaybeFullPage } from "../components/elements/maybe-full-page";
import { MessageCard } from "../components/message-cards/message-card";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function PasswordResetForm(props) {
const { t } = useTranslation();
const schema = yupObject({
password: passwordSchema.defined(t("Please enter your password")).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;
}
}
}),
passwordRepeat: yupString().nullable().oneOf([yup.ref("password"), null], t("Passwords do not match")).defined().nonEmpty(t("Please repeat your password"))
});
const { register, handleSubmit, formState: { errors }, clearErrors } = useForm({
resolver: yupResolver(schema)
});
const stackApp = useStackApp();
const [finished, setFinished] = useState(false);
const [resetError, setResetError] = useState(false);
const [loading, setLoading] = useState(false);
const onSubmit = async (data) => {
setLoading(true);
try {
const { password } = data;
const result = await stackApp.resetPassword({ password, code: props.code });
if (result.status === "error") {
setResetError(true);
return;
}
setFinished(true);
} finally {
setLoading(false);
}
};
if (finished) {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "passwordReset", fullPage: !!props.fullPage });
}
if (resetError) {
return /* @__PURE__ */ jsx(MessageCard, { title: t("Failed to reset password"), fullPage: !!props.fullPage, children: t("Failed to reset password. Please request a new password reset link") });
}
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsxs("div", { className: cn(
"flex flex-col items-stretch max-w-[380px] flex-basis-[380px]",
props.fullPage ? "p-4" : "p-0"
), children: [
/* @__PURE__ */ jsx("div", { className: "text-center mb-6", children: /* @__PURE__ */ jsx(Typography, { type: "h2", children: t("Reset Your Password") }) }),
/* @__PURE__ */ jsxs(
"form",
{
className: "flex flex-col items-stretch",
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "password", className: "mb-1", children: t("New Password") }),
/* @__PURE__ */ jsx(
PasswordInput,
{
id: "password",
autoComplete: "new-password",
...register("password"),
onChange: () => {
clearErrors("password");
clearErrors("passwordRepeat");
}
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.password?.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",
...register("passwordRepeat"),
onChange: () => {
clearErrors("password");
clearErrors("passwordRepeat");
}
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.passwordRepeat?.message?.toString() }),
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: t("Reset Password") })
]
}
)
] }) });
}
var cachedVerifyPasswordResetCode = cacheFunction(async (stackApp, code) => {
return await stackApp.verifyPasswordResetCode(code);
});
function PasswordReset({
searchParams,
fullPage = false
}) {
const { t } = useTranslation();
const stackApp = useStackApp();
const invalidJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Invalid Password Reset Link"), fullPage, children: /* @__PURE__ */ jsx(Typography, { children: t("Please double check if you have the correct password reset link.") }) });
const expiredJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Expired Password Reset Link"), fullPage, children: /* @__PURE__ */ jsx(Typography, { children: t("Your password reset link has expired. Please request a new password reset link from the login page.") }) });
const usedJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Used Password Reset Link"), fullPage, children: /* @__PURE__ */ jsx(Typography, { children: t("This password reset link has already been used. If you need to reset your password again, please request a new password reset link from the login page.") }) });
const code = searchParams.code;
if (!code) {
return invalidJsx;
}
const result = React.use(cachedVerifyPasswordResetCode(stackApp, code));
if (result.status === "error") {
if (KnownErrors.VerificationCodeNotFound.isInstance(result.error)) {
return invalidJsx;
} else if (KnownErrors.VerificationCodeExpired.isInstance(result.error)) {
return expiredJsx;
} else if (KnownErrors.VerificationCodeAlreadyUsed.isInstance(result.error)) {
return usedJsx;
} else {
throw result.error;
}
}
return /* @__PURE__ */ jsx(PasswordResetForm, { code, fullPage });
}
export {
PasswordReset,
PasswordResetForm as default
};
//# sourceMappingURL=password-reset.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
// src/components-page/section.tsx
import { Separator, Typography } from "@stackframe/stack-ui";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
//# sourceMappingURL=section.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/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\n\nfunction 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;AAKlC,mBACE,KAEE,YAHJ;","names":[]}

View file

@ -0,0 +1,19 @@
// src/components-page/sign-in.tsx
import { AuthPage } from "./auth-page";
import { jsx } from "react/jsx-runtime";
function SignIn(props) {
return /* @__PURE__ */ jsx(
AuthPage,
{
fullPage: !!props.fullPage,
type: "sign-in",
automaticRedirect: !!props.automaticRedirect,
extraInfo: props.extraInfo,
firstTab: props.firstTab
}
);
}
export {
SignIn
};
//# sourceMappingURL=sign-in.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/sign-in.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { AuthPage } from \"./auth-page\";\n\nexport function SignIn(props: {\n fullPage?: boolean,\n automaticRedirect?: boolean,\n extraInfo?: React.ReactNode,\n firstTab?: 'magic-link' | 'password',\n}) {\n return (\n <AuthPage\n fullPage={!!props.fullPage}\n type=\"sign-in\"\n automaticRedirect={!!props.automaticRedirect}\n extraInfo={props.extraInfo}\n firstTab={props.firstTab}\n />\n );\n}\n"],"mappings":";AAIA,SAAS,gBAAgB;AASrB;AAPG,SAAS,OAAO,OAKpB;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,CAAC,CAAC,MAAM;AAAA,MAClB,MAAK;AAAA,MACL,mBAAmB,CAAC,CAAC,MAAM;AAAA,MAC3B,WAAW,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA;AAAA,EAClB;AAEJ;","names":[]}

View file

@ -0,0 +1,23 @@
"use client";
"use client";
// src/components-page/sign-out.tsx
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import React from "react";
import { useUser } from "..";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { jsx } from "react/jsx-runtime";
var cacheSignOut = cacheFunction(async (user) => {
return await user.signOut();
});
function SignOut(props) {
const user = useUser();
if (user) {
React.use(cacheSignOut(user));
}
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "signedOut", fullPage: props.fullPage });
}
export {
SignOut
};
//# sourceMappingURL=sign-out.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/sign-out.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { cacheFunction } from \"@stackframe/stack-shared/dist/utils/caches\";\nimport React from \"react\";\nimport { CurrentUser, useUser } from \"..\";\nimport { PredefinedMessageCard } from \"../components/message-cards/predefined-message-card\";\n\nconst cacheSignOut = cacheFunction(async (user: CurrentUser) => {\n return await user.signOut();\n});\n\nexport function SignOut(props: { fullPage?: boolean }) {\n const user = useUser();\n\n if (user) {\n React.use(cacheSignOut(user));\n }\n\n return <PredefinedMessageCard type='signedOut' fullPage={props.fullPage} />;\n}\n"],"mappings":";;;AAOA,SAAS,qBAAqB;AAC9B,OAAO,WAAW;AAClB,SAAsB,eAAe;AACrC,SAAS,6BAA6B;AAa7B;AAXT,IAAM,eAAe,cAAc,OAAO,SAAsB;AAC9D,SAAO,MAAM,KAAK,QAAQ;AAC5B,CAAC;AAEM,SAAS,QAAQ,OAA+B;AACrD,QAAM,OAAO,QAAQ;AAErB,MAAI,MAAM;AACR,UAAM,IAAI,aAAa,IAAI,CAAC;AAAA,EAC9B;AAEA,SAAO,oBAAC,yBAAsB,MAAK,aAAY,UAAU,MAAM,UAAU;AAC3E;","names":[]}

View file

@ -0,0 +1,23 @@
"use client";
"use client";
// src/components-page/sign-up.tsx
import { AuthPage } from "./auth-page";
import { jsx } from "react/jsx-runtime";
function SignUp(props) {
return /* @__PURE__ */ jsx(
AuthPage,
{
fullPage: !!props.fullPage,
type: "sign-up",
automaticRedirect: !!props.automaticRedirect,
noPasswordRepeat: props.noPasswordRepeat,
extraInfo: props.extraInfo,
firstTab: props.firstTab
}
);
}
export {
SignUp
};
//# sourceMappingURL=sign-up.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/sign-up.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { AuthPage } from './auth-page';\n\nexport function SignUp(props: {\n fullPage?: boolean,\n automaticRedirect?: boolean,\n noPasswordRepeat?: boolean,\n extraInfo?: React.ReactNode,\n firstTab?: 'magic-link' | 'password',\n}) {\n return <AuthPage\n fullPage={!!props.fullPage}\n type='sign-up'\n automaticRedirect={!!props.automaticRedirect}\n noPasswordRepeat={props.noPasswordRepeat}\n extraInfo={props.extraInfo}\n firstTab={props.firstTab}\n />;\n}\n"],"mappings":";;;AAMA,SAAS,gBAAgB;AAShB;AAPF,SAAS,OAAO,OAMpB;AACD,SAAO;AAAA,IAAC;AAAA;AAAA,MACN,UAAU,CAAC,CAAC,MAAM;AAAA,MAClB,MAAK;AAAA,MACL,mBAAmB,CAAC,CAAC,MAAM;AAAA,MAC3B,kBAAkB,MAAM;AAAA,MACxB,WAAW,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA;AAAA,EAClB;AACF;","names":[]}

View file

@ -0,0 +1,234 @@
// src/components-page/stack-handler.tsx
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { filterUndefined } from "@stackframe/stack-shared/dist/utils/objects";
import { getRelativePart } from "@stackframe/stack-shared/dist/utils/urls";
import { useMemo } from "react";
import { SignIn, SignUp } from "..";
import { IframePreventer } from "../components/iframe-preventer";
import { MessageCard } from "../components/message-cards/message-card";
import { AccountSettings } from "./account-settings";
import { CliAuthConfirmation } from "./cli-auth-confirm";
import { EmailVerification } from "./email-verification";
import { ErrorPage } from "./error-page";
import { ForgotPassword } from "./forgot-password";
import { MagicLinkCallback } from "./magic-link-callback";
import { OAuthCallback } from "./oauth-callback";
import { PasswordReset } from "./password-reset";
import { SignOut } from "./sign-out";
import { TeamInvitation } from "./team-invitation";
import { jsx } from "react/jsx-runtime";
var availablePaths = {
signIn: "sign-in",
signUp: "sign-up",
emailVerification: "email-verification",
passwordReset: "password-reset",
forgotPassword: "forgot-password",
signOut: "sign-out",
oauthCallback: "oauth-callback",
magicLinkCallback: "magic-link-callback",
teamInvitation: "team-invitation",
accountSettings: "account-settings",
cliAuthConfirm: "cli-auth-confirm",
error: "error"
};
var pathAliases = {
// also includes the uppercase and non-dashed versions
...Object.fromEntries(Object.entries(availablePaths).map(([key, value]) => [value, value])),
"log-in": availablePaths.signIn,
"register": availablePaths.signUp
};
function renderComponent(props) {
const { path, searchParams, fullPage, componentProps, redirectIfNotHandler, onNotFound, app } = props;
switch (path) {
case availablePaths.signIn: {
redirectIfNotHandler?.("signIn");
return /* @__PURE__ */ jsx(
SignIn,
{
fullPage,
automaticRedirect: true,
...filterUndefinedINU(componentProps?.SignIn)
}
);
}
case availablePaths.signUp: {
redirectIfNotHandler?.("signUp");
return /* @__PURE__ */ jsx(
SignUp,
{
fullPage,
automaticRedirect: true,
...filterUndefinedINU(componentProps?.SignUp)
}
);
}
case availablePaths.emailVerification: {
redirectIfNotHandler?.("emailVerification");
return /* @__PURE__ */ jsx(
EmailVerification,
{
searchParams,
fullPage,
...filterUndefinedINU(componentProps?.EmailVerification)
}
);
}
case availablePaths.passwordReset: {
redirectIfNotHandler?.("passwordReset");
return /* @__PURE__ */ jsx(
PasswordReset,
{
searchParams,
fullPage,
...filterUndefinedINU(componentProps?.PasswordReset)
}
);
}
case availablePaths.forgotPassword: {
redirectIfNotHandler?.("forgotPassword");
return /* @__PURE__ */ jsx(
ForgotPassword,
{
fullPage,
...filterUndefinedINU(componentProps?.ForgotPassword)
}
);
}
case availablePaths.signOut: {
redirectIfNotHandler?.("signOut");
return /* @__PURE__ */ jsx(
SignOut,
{
fullPage,
...filterUndefinedINU(componentProps?.SignOut)
}
);
}
case availablePaths.oauthCallback: {
redirectIfNotHandler?.("oauthCallback");
return /* @__PURE__ */ jsx(
OAuthCallback,
{
fullPage,
...filterUndefinedINU(componentProps?.OAuthCallback)
}
);
}
case availablePaths.magicLinkCallback: {
redirectIfNotHandler?.("magicLinkCallback");
return /* @__PURE__ */ jsx(
MagicLinkCallback,
{
searchParams,
fullPage,
...filterUndefinedINU(componentProps?.MagicLinkCallback)
}
);
}
case availablePaths.teamInvitation: {
redirectIfNotHandler?.("teamInvitation");
return /* @__PURE__ */ jsx(
TeamInvitation,
{
searchParams,
fullPage,
...filterUndefinedINU(componentProps?.TeamInvitation)
}
);
}
case availablePaths.accountSettings: {
return /* @__PURE__ */ jsx(
AccountSettings,
{
fullPage,
...filterUndefinedINU(componentProps?.AccountSettings)
}
);
}
case availablePaths.error: {
return /* @__PURE__ */ jsx(
ErrorPage,
{
searchParams,
fullPage,
...filterUndefinedINU(componentProps?.ErrorPage)
}
);
}
case availablePaths.cliAuthConfirm: {
return /* @__PURE__ */ jsx(
CliAuthConfirmation,
{
fullPage,
...filterUndefinedINU(componentProps?.CliAuthConfirmation)
}
);
}
default: {
if (Object.values(availablePaths).includes(path)) {
throw new StackAssertionError(`Path alias ${path} not included in switch statement, but in availablePaths?`, { availablePaths });
}
for (const [key, value] of Object.entries(pathAliases)) {
if (path === key.toLowerCase().replaceAll("-", "")) {
const redirectUrl = `${app.urls.handler}/${value}?${new URLSearchParams(searchParams).toString()}`;
return { redirect: redirectUrl };
}
}
return onNotFound();
}
}
}
function ReactStackHandler(props) {
const { path, searchParams } = useMemo(() => {
const search = window.location.search;
const handlerPath = new URL(props.app.urls.handler, window.location.origin).pathname;
const relativePath = props.location.startsWith(handlerPath) ? props.location.slice(handlerPath.length).replace(/^\/+/, "") : props.location.replace(/^\/+/, "");
return {
path: relativePath,
searchParams: Object.fromEntries(new URLSearchParams(search).entries())
};
}, [props.location, props.app.urls.handler]);
const redirectIfNotHandler = (name) => {
const url = props.app.urls[name];
const handlerUrl = props.app.urls.handler;
if (url !== handlerUrl && url.startsWith(handlerUrl + "/")) {
return;
}
const urlObj = new URL(url, window.location.origin);
for (const [key, value] of Object.entries(searchParams)) {
urlObj.searchParams.set(key, value);
}
window.location.href = getRelativePart(urlObj);
};
const result = renderComponent({
path,
searchParams,
fullPage: props.fullPage,
componentProps: props.componentProps,
redirectIfNotHandler,
onNotFound: () => /* @__PURE__ */ jsx(
MessageCard,
{
title: "Page does not exist",
fullPage: props.fullPage,
primaryButtonText: "Go to Home",
primaryAction: () => props.app.redirectToHome(),
children: "The page you are looking for could not be found. Please check the URL and try again."
}
),
app: props.app
});
if (result && "redirect" in result) {
window.location.href = result.redirect;
return null;
}
return /* @__PURE__ */ jsx(IframePreventer, { children: result });
}
var stack_handler_default = ReactStackHandler;
function filterUndefinedINU(value) {
return value === void 0 ? value : filterUndefined(value);
}
export {
stack_handler_default as default
};
//# sourceMappingURL=stack-handler.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,68 @@
"use client";
"use client";
// src/components-page/team-creation.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, Label, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { MessageCard, useStackApp, useUser } from "..";
import { FormWarningText } from "../components/elements/form-warning";
import { MaybeFullPage } from "../components/elements/maybe-full-page";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
function TeamCreation(props) {
const { t } = useTranslation();
const schema = yupObject({
displayName: yupString().defined().nonEmpty(t("Please enter a team name"))
});
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
const app = useStackApp();
const project = app.useProject();
const user = useUser({ or: "redirect" });
const [loading, setLoading] = useState(false);
const navigate = app.useNavigate();
if (!project.config.clientTeamCreationEnabled) {
return /* @__PURE__ */ jsx(MessageCard, { title: t("Team creation is not enabled") });
}
const onSubmit = async (data) => {
setLoading(true);
try {
const team = await user.createTeam({ displayName: data.displayName });
navigate(`${app.urls.handler}/team-settings/${team.id}`);
} finally {
setLoading(false);
}
};
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsxs("div", { className: "stack-scope flex flex-col items-stretch", style: { maxWidth: "380px", flexBasis: "380px", padding: props.fullPage ? "1rem" : 0 }, children: [
/* @__PURE__ */ jsx("div", { className: "text-center mb-6", children: /* @__PURE__ */ jsx(Typography, { type: "h2", children: t("Create a Team") }) }),
/* @__PURE__ */ jsxs(
"form",
{
className: "flex flex-col items-stretch stack-scope",
onSubmit: (e) => runAsynchronously(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "display-name", className: "mb-1", children: t("Display name") }),
/* @__PURE__ */ jsx(
Input,
{
id: "display-name",
...register("displayName")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.displayName?.message?.toString() }),
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: t("Create") })
]
}
)
] }) });
}
export {
TeamCreation
};
//# sourceMappingURL=team-creation.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components-page/team-creation.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { yupObject, yupString } from \"@stackframe/stack-shared/dist/schema-fields\";\nimport { runAsynchronously } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { Button, Input, Label, Typography } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { MessageCard, useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MaybeFullPage } from \"../components/elements/maybe-full-page\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function TeamCreation(props: { fullPage?: boolean }) {\n const { t } = useTranslation();\n\n const schema = yupObject({\n displayName: yupString().defined().nonEmpty(t('Please enter a team name')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(schema)\n });\n const app = useStackApp();\n const project = app.useProject();\n const user = useUser({ or: 'redirect' });\n const [loading, setLoading] = useState(false);\n const navigate = app.useNavigate();\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 schema>) => {\n setLoading(true);\n\n try {\n const team = await user.createTeam({ displayName: data.displayName });\n navigate(`${app.urls.handler}/team-settings/${team.id}`);\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MaybeFullPage fullPage={!!props.fullPage}>\n <div className='stack-scope flex flex-col items-stretch' style={{ maxWidth: '380px', flexBasis: '380px', padding: props.fullPage ? '1rem' : 0 }}>\n <div className=\"text-center mb-6\">\n <Typography type='h2'>\n {t('Create a Team')}\n </Typography>\n </div>\n <form\n className=\"flex flex-col items-stretch stack-scope\"\n onSubmit={e => runAsynchronously(handleSubmit(onSubmit)(e))}\n noValidate\n >\n <Label htmlFor=\"display-name\" className=\"mb-1\">{t('Display name')}</Label>\n <Input\n id=\"display-name\"\n {...register('displayName')}\n />\n <FormWarningText text={errors.displayName?.message?.toString()} />\n\n <Button type=\"submit\" className=\"mt-6\" loading={loading}>\n {t('Create')}\n </Button>\n </form>\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;AAOA,SAAS,mBAAmB;AAC5B,SAAS,WAAW,iBAAiB;AACrC,SAAS,yBAAyB;AAClC,SAAS,QAAQ,OAAO,OAAO,kBAAkB;AACjD,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAExB,SAAS,aAAa,aAAa,eAAe;AAClD,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAmBpB,cAsBH,YAtBG;AAjBJ,SAAS,aAAa,OAA+B;AAC1D,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,QAAM,SAAS,UAAU;AAAA,IACvB,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,MAAM;AAAA,EAC9B,CAAC;AACD,QAAM,MAAM,YAAY;AACxB,QAAM,UAAU,IAAI,WAAW;AAC/B,QAAM,OAAO,QAAQ,EAAE,IAAI,WAAW,CAAC;AACvC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,WAAW,IAAI,YAAY;AAEjC,MAAI,CAAC,QAAQ,OAAO,2BAA2B;AAC7C,WAAO,oBAAC,eAAY,OAAO,EAAE,8BAA8B,GAAG;AAAA,EAChE;AAEA,QAAM,WAAW,OAAO,SAAuC;AAC7D,eAAW,IAAI;AAEf,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,WAAW,EAAE,aAAa,KAAK,YAAY,CAAC;AACpE,eAAS,GAAG,IAAI,KAAK,OAAO,kBAAkB,KAAK,EAAE,EAAE;AAAA,IACzD,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,SACE,oBAAC,iBAAc,UAAU,CAAC,CAAC,MAAM,UAC/B,+BAAC,SAAI,WAAU,2CAA0C,OAAO,EAAE,UAAU,SAAS,WAAW,SAAS,SAAS,MAAM,WAAW,SAAS,EAAE,GAC5I;AAAA,wBAAC,SAAI,WAAU,oBACb,8BAAC,cAAW,MAAK,MACd,YAAE,eAAe,GACpB,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,UAAU,OAAK,kBAAkB,aAAa,QAAQ,EAAE,CAAC,CAAC;AAAA,QAC1D,YAAU;AAAA,QAEV;AAAA,8BAAC,SAAM,SAAQ,gBAAe,WAAU,QAAQ,YAAE,cAAc,GAAE;AAAA,UAClE;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACF,GAAG,SAAS,aAAa;AAAA;AAAA,UAC5B;AAAA,UACA,oBAAC,mBAAgB,MAAM,OAAO,aAAa,SAAS,SAAS,GAAG;AAAA,UAEhE,oBAAC,UAAO,MAAK,UAAS,WAAU,QAAO,SACpC,YAAE,QAAQ,GACb;AAAA;AAAA;AAAA,IACF;AAAA,KACF,GACF;AAEJ;","names":[]}

View file

@ -0,0 +1,110 @@
"use client";
"use client";
// src/components-page/team-invitation.tsx
import { KnownErrors } from "@stackframe/stack-shared";
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Typography } from "@stackframe/stack-ui";
import React from "react";
import { MessageCard, useStackApp, useUser } from "..";
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
var cachedVerifyInvitation = cacheFunction(async (stackApp, code) => {
return await stackApp.verifyTeamInvitationCode(code);
});
var cachedGetInvitationDetails = cacheFunction(async (stackApp, code) => {
return await stackApp.getTeamInvitationDetails(code);
});
function TeamInvitationInner(props) {
const { t } = useTranslation();
const stackApp = useStackApp();
const [success, setSuccess] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState(null);
const details = React.use(cachedGetInvitationDetails(stackApp, props.searchParams.code || ""));
if (errorMessage || details.status === "error") {
return /* @__PURE__ */ jsx(PredefinedMessageCard, { type: "unknownError", fullPage: props.fullPage });
}
if (success) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Team invitation"),
fullPage: props.fullPage,
primaryButtonText: "Go home",
primaryAction: () => stackApp.redirectToHome(),
children: /* @__PURE__ */ jsxs(Typography, { children: [
"You have successfully joined ",
details.data.teamDisplayName
] })
}
);
}
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Team invitation"),
fullPage: props.fullPage,
primaryButtonText: t("Join"),
primaryAction: () => runAsynchronouslyWithAlert(async () => {
const result = await stackApp.acceptTeamInvitation(props.searchParams.code || "");
if (result.status === "error") {
setErrorMessage(result.error.message);
} else {
setSuccess(true);
}
}),
secondaryButtonText: t("Ignore"),
secondaryAction: () => stackApp.redirectToHome(),
children: /* @__PURE__ */ jsxs(Typography, { children: [
"You are invited to join ",
details.data.teamDisplayName
] })
}
);
}
function TeamInvitation({ fullPage = false, searchParams }) {
const { t } = useTranslation();
const user = useUser();
const stackApp = useStackApp();
const invalidJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Invalid Team Invitation Link"), fullPage, children: /* @__PURE__ */ jsx(Typography, { children: t("Please double check if you have the correct team invitation link.") }) });
const expiredJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Expired Team Invitation Link"), fullPage, children: /* @__PURE__ */ jsx(Typography, { children: t("Your team invitation link has expired. Please request a new team invitation link ") }) });
const usedJsx = /* @__PURE__ */ jsx(MessageCard, { title: t("Used Team Invitation Link"), fullPage, children: /* @__PURE__ */ jsx(Typography, { children: t("This team invitation link has already been used.") }) });
const code = searchParams.code;
if (!code) {
return invalidJsx;
}
if (!user) {
return /* @__PURE__ */ jsx(
MessageCard,
{
title: t("Team invitation"),
fullPage,
primaryButtonText: t("Sign in"),
primaryAction: () => stackApp.redirectToSignIn(),
secondaryButtonText: t("Cancel"),
secondaryAction: () => stackApp.redirectToHome(),
children: /* @__PURE__ */ jsx(Typography, { children: t("Sign in or create an account to join the team.") })
}
);
}
const verificationResult = React.use(cachedVerifyInvitation(stackApp, searchParams.code || ""));
if (verificationResult.status === "error") {
const error = verificationResult.error;
if (KnownErrors.VerificationCodeNotFound.isInstance(error)) {
return invalidJsx;
} else if (KnownErrors.VerificationCodeExpired.isInstance(error)) {
return expiredJsx;
} else if (KnownErrors.VerificationCodeAlreadyUsed.isInstance(error)) {
return usedJsx;
} else {
throw error;
}
}
return /* @__PURE__ */ jsx(TeamInvitationInner, { fullPage, searchParams });
}
export {
TeamInvitation
};
//# sourceMappingURL=team-invitation.js.map

File diff suppressed because one or more lines are too long