@stackframe/stack 2.8.11 → 2.8.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/components/api-key-dialogs.js +5 -4
- package/dist/components/api-key-dialogs.js.map +1 -1
- package/dist/components/credential-sign-in.js +4 -4
- package/dist/components/credential-sign-up.js +3 -3
- package/dist/components/elements/maybe-full-page.js +1 -1
- package/dist/components/elements/sidebar-layout.js +1 -1
- package/dist/components/magic-link-sign-in.js +6 -6
- package/dist/components/magic-link-sign-in.js.map +1 -1
- package/dist/components/message-cards/known-error-message-card.js +2 -2
- package/dist/components/message-cards/message-card.js +1 -1
- package/dist/components/message-cards/predefined-message-card.js +3 -3
- package/dist/components/oauth-button-group.js +2 -2
- package/dist/components/oauth-button.js +27 -16
- package/dist/components/oauth-button.js.map +1 -1
- package/dist/components/passkey-button.js +2 -2
- package/dist/components/profile-image-editor.js +87 -34
- package/dist/components/profile-image-editor.js.map +1 -1
- package/dist/components/selected-team-switcher.js +41 -9
- package/dist/components/selected-team-switcher.js.map +1 -1
- package/dist/components/{iframe-preventer.js → use-in-iframe.js} +9 -19
- package/dist/components/use-in-iframe.js.map +1 -0
- package/dist/components/user-button.js +41 -8
- package/dist/components/user-button.js.map +1 -1
- package/dist/components-page/account-settings/active-sessions/active-sessions-page.js +57 -12
- package/dist/components-page/account-settings/active-sessions/active-sessions-page.js.map +1 -1
- package/dist/components-page/account-settings/api-keys/api-keys-page.js +100 -12
- package/dist/components-page/account-settings/api-keys/api-keys-page.js.map +1 -1
- package/dist/components-page/account-settings/editable-text.js +1 -1
- package/dist/components-page/account-settings/email-and-auth/email-and-auth-page.js +12 -12
- package/dist/components-page/account-settings/email-and-auth/email-and-auth-page.js.map +1 -1
- package/dist/components-page/account-settings/email-and-auth/emails-section.js +15 -6
- package/dist/components-page/account-settings/email-and-auth/emails-section.js.map +1 -1
- package/dist/components-page/account-settings/email-and-auth/mfa-section.js +18 -5
- package/dist/components-page/account-settings/email-and-auth/mfa-section.js.map +1 -1
- package/dist/components-page/account-settings/email-and-auth/otp-section.js +18 -5
- package/dist/components-page/account-settings/email-and-auth/otp-section.js.map +1 -1
- package/dist/components-page/account-settings/email-and-auth/passkey-section.js +19 -6
- package/dist/components-page/account-settings/email-and-auth/passkey-section.js.map +1 -1
- package/dist/components-page/account-settings/email-and-auth/password-section.js +20 -7
- package/dist/components-page/account-settings/email-and-auth/password-section.js.map +1 -1
- package/dist/components-page/account-settings/profile-page/profile-page.js +18 -8
- package/dist/components-page/account-settings/profile-page/profile-page.js.map +1 -1
- package/dist/components-page/account-settings/settings/delete-account-section.js +19 -10
- package/dist/components-page/account-settings/settings/delete-account-section.js.map +1 -1
- package/dist/components-page/account-settings/settings/settings-page.js +6 -6
- package/dist/components-page/account-settings/settings/settings-page.js.map +1 -1
- package/dist/components-page/account-settings/settings/sign-out-section.js +15 -6
- package/dist/components-page/account-settings/settings/sign-out-section.js.map +1 -1
- package/dist/components-page/account-settings/teams/leave-team-section.js +3 -3
- package/dist/components-page/account-settings/teams/team-api-keys-section.js +9 -6
- package/dist/components-page/account-settings/teams/team-api-keys-section.js.map +1 -1
- package/dist/components-page/account-settings/teams/team-creation-page.js +19 -10
- package/dist/components-page/account-settings/teams/team-creation-page.js.map +1 -1
- package/dist/components-page/account-settings/teams/team-display-name-section.js +4 -4
- package/dist/components-page/account-settings/teams/team-member-invitation-section.js +4 -4
- package/dist/components-page/account-settings/teams/team-member-list-section.js +7 -4
- package/dist/components-page/account-settings/teams/team-member-list-section.js.map +1 -1
- package/dist/components-page/account-settings/teams/team-page.js +8 -8
- package/dist/components-page/account-settings/teams/team-profile-image-section.js +4 -4
- package/dist/components-page/account-settings/teams/team-profile-user-section.js +4 -4
- package/dist/components-page/account-settings.js +29 -21
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/components-page/auth-page.js +16 -17
- package/dist/components-page/auth-page.js.map +1 -1
- package/dist/components-page/cli-auth-confirm.js +3 -3
- package/dist/components-page/email-verification.js +6 -6
- package/dist/components-page/email-verification.js.map +1 -1
- package/dist/components-page/error-page.js +9 -9
- package/dist/components-page/error-page.js.map +1 -1
- package/dist/components-page/forgot-password.js +6 -6
- package/dist/components-page/magic-link-callback.js +7 -7
- package/dist/components-page/magic-link-callback.js.map +1 -1
- package/dist/components-page/mfa.js +190 -0
- package/dist/components-page/mfa.js.map +1 -0
- package/dist/components-page/oauth-callback.js +4 -4
- package/dist/components-page/password-reset.js +9 -9
- package/dist/components-page/password-reset.js.map +1 -1
- package/dist/components-page/sign-in.js +3 -2
- package/dist/components-page/sign-in.js.map +1 -1
- package/dist/components-page/sign-out.js +2 -2
- package/dist/components-page/sign-up.js +1 -1
- package/dist/components-page/stack-handler.js +25 -14
- package/dist/components-page/stack-handler.js.map +1 -1
- package/dist/components-page/team-creation.js +4 -4
- package/dist/components-page/team-invitation.js +6 -6
- package/dist/components-page/team-invitation.js.map +1 -1
- package/dist/esm/components/api-key-dialogs.js +5 -4
- package/dist/esm/components/api-key-dialogs.js.map +1 -1
- package/dist/esm/components/credential-sign-in.js +4 -4
- package/dist/esm/components/credential-sign-up.js +3 -3
- package/dist/esm/components/elements/maybe-full-page.js +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +1 -1
- package/dist/esm/components/magic-link-sign-in.js +6 -6
- package/dist/esm/components/magic-link-sign-in.js.map +1 -1
- package/dist/esm/components/message-cards/known-error-message-card.js +2 -2
- package/dist/esm/components/message-cards/message-card.js +1 -1
- package/dist/esm/components/message-cards/predefined-message-card.js +3 -3
- package/dist/esm/components/oauth-button-group.js +2 -2
- package/dist/esm/components/oauth-button.js +28 -17
- package/dist/esm/components/oauth-button.js.map +1 -1
- package/dist/esm/components/passkey-button.js +2 -2
- package/dist/esm/components/profile-image-editor.js +86 -34
- package/dist/esm/components/profile-image-editor.js.map +1 -1
- package/dist/esm/components/selected-team-switcher.js +41 -9
- package/dist/esm/components/selected-team-switcher.js.map +1 -1
- package/dist/esm/components/use-in-iframe.js +18 -0
- package/dist/esm/components/use-in-iframe.js.map +1 -0
- package/dist/esm/components/user-button.js +41 -8
- package/dist/esm/components/user-button.js.map +1 -1
- package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js +57 -12
- package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js.map +1 -1
- package/dist/esm/components-page/account-settings/api-keys/api-keys-page.js +100 -12
- package/dist/esm/components-page/account-settings/api-keys/api-keys-page.js.map +1 -1
- package/dist/esm/components-page/account-settings/editable-text.js +1 -1
- package/dist/esm/components-page/account-settings/email-and-auth/email-and-auth-page.js +12 -12
- package/dist/esm/components-page/account-settings/email-and-auth/email-and-auth-page.js.map +1 -1
- package/dist/esm/components-page/account-settings/email-and-auth/emails-section.js +15 -6
- package/dist/esm/components-page/account-settings/email-and-auth/emails-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/email-and-auth/mfa-section.js +18 -5
- package/dist/esm/components-page/account-settings/email-and-auth/mfa-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/email-and-auth/otp-section.js +18 -5
- package/dist/esm/components-page/account-settings/email-and-auth/otp-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/email-and-auth/passkey-section.js +19 -6
- package/dist/esm/components-page/account-settings/email-and-auth/passkey-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/email-and-auth/password-section.js +20 -7
- package/dist/esm/components-page/account-settings/email-and-auth/password-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/profile-page/profile-page.js +18 -8
- package/dist/esm/components-page/account-settings/profile-page/profile-page.js.map +1 -1
- package/dist/esm/components-page/account-settings/settings/delete-account-section.js +19 -10
- package/dist/esm/components-page/account-settings/settings/delete-account-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/settings/settings-page.js +6 -6
- package/dist/esm/components-page/account-settings/settings/settings-page.js.map +1 -1
- package/dist/esm/components-page/account-settings/settings/sign-out-section.js +15 -6
- package/dist/esm/components-page/account-settings/settings/sign-out-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/teams/leave-team-section.js +3 -3
- package/dist/esm/components-page/account-settings/teams/team-api-keys-section.js +9 -6
- package/dist/esm/components-page/account-settings/teams/team-api-keys-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/teams/team-creation-page.js +19 -10
- package/dist/esm/components-page/account-settings/teams/team-creation-page.js.map +1 -1
- package/dist/esm/components-page/account-settings/teams/team-display-name-section.js +4 -4
- package/dist/esm/components-page/account-settings/teams/team-member-invitation-section.js +4 -4
- package/dist/esm/components-page/account-settings/teams/team-member-list-section.js +7 -4
- package/dist/esm/components-page/account-settings/teams/team-member-list-section.js.map +1 -1
- package/dist/esm/components-page/account-settings/teams/team-page.js +8 -8
- package/dist/esm/components-page/account-settings/teams/team-profile-image-section.js +4 -4
- package/dist/esm/components-page/account-settings/teams/team-profile-user-section.js +4 -4
- package/dist/esm/components-page/account-settings.js +29 -21
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/auth-page.js +16 -17
- package/dist/esm/components-page/auth-page.js.map +1 -1
- package/dist/esm/components-page/cli-auth-confirm.js +3 -3
- package/dist/esm/components-page/email-verification.js +6 -6
- package/dist/esm/components-page/email-verification.js.map +1 -1
- package/dist/esm/components-page/error-page.js +9 -9
- package/dist/esm/components-page/error-page.js.map +1 -1
- package/dist/esm/components-page/forgot-password.js +6 -6
- package/dist/esm/components-page/magic-link-callback.js +7 -7
- package/dist/esm/components-page/magic-link-callback.js.map +1 -1
- package/dist/esm/components-page/mfa.js +174 -0
- package/dist/esm/components-page/mfa.js.map +1 -0
- package/dist/esm/components-page/oauth-callback.js +4 -4
- package/dist/esm/components-page/password-reset.js +9 -9
- package/dist/esm/components-page/password-reset.js.map +1 -1
- package/dist/esm/components-page/sign-in.js +3 -2
- package/dist/esm/components-page/sign-in.js.map +1 -1
- package/dist/esm/components-page/sign-out.js +2 -2
- package/dist/esm/components-page/sign-up.js +1 -1
- package/dist/esm/components-page/stack-handler.js +25 -14
- package/dist/esm/components-page/stack-handler.js.map +1 -1
- package/dist/esm/components-page/team-creation.js +4 -4
- package/dist/esm/components-page/team-invitation.js +6 -6
- package/dist/esm/components-page/team-invitation.js.map +1 -1
- package/dist/esm/generated/global-css.js +1 -1
- package/dist/esm/generated/global-css.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.js +3574 -2364
- package/dist/esm/generated/quetzal-translations.js.map +1 -1
- package/dist/esm/index.js +22 -22
- package/dist/esm/lib/auth.js +3 -3
- package/dist/esm/lib/auth.js.map +1 -1
- package/dist/esm/lib/cookie.js +1 -129
- package/dist/esm/lib/cookie.js.map +1 -1
- package/dist/esm/lib/hooks.js +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +8 -8
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +57 -26
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js +2 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/index.js +3 -3
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +35 -17
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/index.js +3 -3
- package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/client-app.js +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/server-app.js +1 -1
- package/dist/esm/lib/stack-app/common.js.map +1 -1
- package/dist/esm/lib/stack-app/index.js +2 -2
- package/dist/esm/lib/stack-app/internal-api-keys/index.js.map +1 -1
- package/dist/esm/lib/stack-app/users/index.js.map +1 -1
- package/dist/esm/lib/translations.js +1 -1
- package/dist/esm/providers/stack-provider-client.js +2 -2
- package/dist/esm/providers/stack-provider.js +3 -3
- package/dist/esm/providers/theme-provider.js +3 -3
- package/dist/esm/providers/translation-provider.js +2 -2
- package/dist/esm/utils/browser-script.js +1 -1
- package/dist/generated/global-css.js +1 -1
- package/dist/generated/global-css.js.map +1 -1
- package/dist/generated/quetzal-translations.js +3574 -2364
- package/dist/generated/quetzal-translations.js.map +1 -1
- package/dist/index.d.mts +73 -2
- package/dist/index.d.ts +73 -2
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/dist/lib/auth.js +3 -3
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/cookie.js +4 -132
- package/dist/lib/cookie.js.map +1 -1
- package/dist/lib/hooks.js +1 -1
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +8 -8
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js +57 -26
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js +2 -1
- package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/index.js +3 -3
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js +35 -17
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/index.js +3 -3
- package/dist/lib/stack-app/apps/interfaces/admin-app.js +1 -1
- package/dist/lib/stack-app/apps/interfaces/client-app.js +1 -1
- package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/server-app.js +1 -1
- package/dist/lib/stack-app/common.js.map +1 -1
- package/dist/lib/stack-app/index.js +2 -2
- package/dist/lib/stack-app/internal-api-keys/index.js.map +1 -1
- package/dist/lib/stack-app/users/index.js.map +1 -1
- package/dist/lib/translations.js +1 -1
- package/dist/providers/stack-provider-client.js +2 -2
- package/dist/providers/stack-provider.js +3 -3
- package/dist/providers/theme-provider.js +3 -3
- package/dist/providers/translation-provider.js +2 -2
- package/dist/utils/browser-script.js +1 -1
- package/package.json +5 -5
- package/dist/components/iframe-preventer.js.map +0 -1
- package/dist/esm/components/iframe-preventer.js +0 -28
- package/dist/esm/components/iframe-preventer.js.map +0 -1
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
} from "@stackframe/stack-ui";
|
|
19
19
|
import { PlusCircle, Settings } from "lucide-react";
|
|
20
20
|
import { Suspense, useEffect, useMemo } from "react";
|
|
21
|
-
import { useStackApp, useUser } from "
|
|
22
|
-
import { useTranslation } from "../lib/translations";
|
|
23
|
-
import { TeamIcon } from "./team-icon";
|
|
21
|
+
import { useStackApp, useUser } from "../index.js";
|
|
22
|
+
import { useTranslation } from "../lib/translations.js";
|
|
23
|
+
import { TeamIcon } from "./team-icon.js";
|
|
24
24
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
25
25
|
function SelectedTeamSwitcher(props) {
|
|
26
26
|
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Fallback, {}), children: /* @__PURE__ */ jsx(Inner, { ...props }) });
|
|
@@ -30,23 +30,38 @@ function Fallback() {
|
|
|
30
30
|
}
|
|
31
31
|
function Inner(props) {
|
|
32
32
|
const { t } = useTranslation();
|
|
33
|
-
const
|
|
34
|
-
const
|
|
33
|
+
const appFromHook = useStackApp();
|
|
34
|
+
const userFromHook = useUser();
|
|
35
|
+
const app = props.mockUser ? {
|
|
36
|
+
useProject: () => props.mockProject || { config: { clientTeamCreationEnabled: false } },
|
|
37
|
+
useNavigate: () => () => {
|
|
38
|
+
},
|
|
39
|
+
// Mock navigate function
|
|
40
|
+
urls: { accountSettings: "/account-settings" }
|
|
41
|
+
} : appFromHook;
|
|
42
|
+
const user = props.mockUser ? {
|
|
43
|
+
selectedTeam: props.mockUser.selectedTeam,
|
|
44
|
+
useTeams: () => props.mockTeams || [],
|
|
45
|
+
setSelectedTeam: async () => {
|
|
46
|
+
}
|
|
47
|
+
// Mock function
|
|
48
|
+
} : userFromHook;
|
|
35
49
|
const project = app.useProject();
|
|
36
50
|
const navigate = app.useNavigate();
|
|
37
51
|
const selectedTeam = user?.selectedTeam || props.selectedTeam;
|
|
38
52
|
const rawTeams = user?.useTeams();
|
|
39
53
|
const teams = useMemo(() => rawTeams?.sort((a, b) => b.id === selectedTeam?.id ? 1 : -1), [rawTeams, selectedTeam]);
|
|
40
54
|
useEffect(() => {
|
|
41
|
-
if (!props.noUpdateSelectedTeam && props.selectedTeam) {
|
|
55
|
+
if (!props.noUpdateSelectedTeam && props.selectedTeam && !props.mockUser) {
|
|
42
56
|
runAsynchronouslyWithAlert(user?.setSelectedTeam(props.selectedTeam));
|
|
43
57
|
}
|
|
44
|
-
}, [props.noUpdateSelectedTeam, props.selectedTeam]);
|
|
58
|
+
}, [props.noUpdateSelectedTeam, props.selectedTeam, props.mockUser]);
|
|
45
59
|
return /* @__PURE__ */ jsxs(
|
|
46
60
|
Select,
|
|
47
61
|
{
|
|
48
62
|
value: selectedTeam?.id,
|
|
49
63
|
onValueChange: (value) => {
|
|
64
|
+
if (props.mockUser) return;
|
|
50
65
|
runAsynchronouslyWithAlert(async () => {
|
|
51
66
|
const team = teams?.find((team2) => team2.id === value);
|
|
52
67
|
if (!team) {
|
|
@@ -66,7 +81,20 @@ function Inner(props) {
|
|
|
66
81
|
user?.selectedTeam ? /* @__PURE__ */ jsxs(SelectGroup, { children: [
|
|
67
82
|
/* @__PURE__ */ jsx(SelectLabel, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
68
83
|
/* @__PURE__ */ jsx("span", { children: t("Current team") }),
|
|
69
|
-
/* @__PURE__ */ jsx(
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
Button,
|
|
86
|
+
{
|
|
87
|
+
variant: "ghost",
|
|
88
|
+
size: "icon",
|
|
89
|
+
className: "h-6 w-6",
|
|
90
|
+
onClick: () => {
|
|
91
|
+
if (!props.mockUser) {
|
|
92
|
+
navigate(`${app.urls.accountSettings}#team-${user.selectedTeam?.id}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
children: /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" })
|
|
96
|
+
}
|
|
97
|
+
)
|
|
70
98
|
] }) }),
|
|
71
99
|
/* @__PURE__ */ jsx(SelectItem, { value: user.selectedTeam.id, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
72
100
|
/* @__PURE__ */ jsx(TeamIcon, { team: user.selectedTeam }),
|
|
@@ -85,7 +113,11 @@ function Inner(props) {
|
|
|
85
113
|
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
|
|
86
114
|
Button,
|
|
87
115
|
{
|
|
88
|
-
onClick: () =>
|
|
116
|
+
onClick: () => {
|
|
117
|
+
if (!props.mockUser) {
|
|
118
|
+
navigate(`${app.urls.accountSettings}#team-creation`);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
89
121
|
className: "w-full",
|
|
90
122
|
variant: "ghost",
|
|
91
123
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/selected-team-switcher.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport {\n Button,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n Skeleton,\n Typography\n} from \"@stackframe/stack-ui\";\nimport { PlusCircle, Settings } from \"lucide-react\";\nimport { Suspense, useEffect, useMemo } from \"react\";\nimport { Team, useStackApp, useUser } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { TeamIcon } from \"./team-icon\";\n\ntype SelectedTeamSwitcherProps = {\n urlMap?: (team: Team) => string,\n selectedTeam?: Team,\n noUpdateSelectedTeam?: boolean,\n};\n\nexport function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {\n return <Suspense fallback={<Fallback />}>\n <Inner {...props} />\n </Suspense>;\n}\n\nfunction Fallback() {\n return <Skeleton className=\"h-9 w-full max-w-64 stack-scope\" />;\n}\n\nfunction Inner(props: SelectedTeamSwitcherProps) {\n const { t } = useTranslation();\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/components/selected-team-switcher.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport {\n Button,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n Skeleton,\n Typography\n} from \"@stackframe/stack-ui\";\nimport { PlusCircle, Settings } from \"lucide-react\";\nimport { Suspense, useEffect, useMemo } from \"react\";\nimport { Team, useStackApp, useUser } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { TeamIcon } from \"./team-icon\";\n\ntype MockTeam = {\n id: string,\n displayName: string,\n profileImageUrl?: string | null,\n};\n\ntype SelectedTeamSwitcherProps = {\n urlMap?: (team: Team) => string,\n selectedTeam?: Team,\n noUpdateSelectedTeam?: boolean,\n // Mock data props\n mockUser?: {\n selectedTeam?: MockTeam,\n },\n mockTeams?: MockTeam[],\n mockProject?: {\n config: {\n clientTeamCreationEnabled: boolean,\n },\n },\n};\n\nexport function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {\n return <Suspense fallback={<Fallback />}>\n <Inner {...props} />\n </Suspense>;\n}\n\nfunction Fallback() {\n return <Skeleton className=\"h-9 w-full max-w-64 stack-scope\" />;\n}\n\nfunction Inner(props: SelectedTeamSwitcherProps) {\n const { t } = useTranslation();\n const appFromHook = useStackApp();\n const userFromHook = useUser();\n\n // Use mock data if provided, otherwise use real data\n const app = props.mockUser ? {\n useProject: () => props.mockProject || { config: { clientTeamCreationEnabled: false } },\n useNavigate: () => () => {}, // Mock navigate function\n urls: { accountSettings: '/account-settings' },\n } : appFromHook;\n\n const user = props.mockUser ? {\n selectedTeam: props.mockUser.selectedTeam,\n useTeams: () => props.mockTeams || [],\n setSelectedTeam: async () => {}, // Mock function\n } : userFromHook;\n\n const project = app.useProject();\n const navigate = app.useNavigate();\n const selectedTeam = user?.selectedTeam || props.selectedTeam;\n const rawTeams = user?.useTeams();\n const teams = useMemo(() => rawTeams?.sort((a, b) => b.id === selectedTeam?.id ? 1 : -1), [rawTeams, selectedTeam]);\n\n useEffect(() => {\n if (!props.noUpdateSelectedTeam && props.selectedTeam && !props.mockUser) {\n runAsynchronouslyWithAlert(user?.setSelectedTeam(props.selectedTeam));\n }\n }, [props.noUpdateSelectedTeam, props.selectedTeam, props.mockUser]);\n\n return (\n <Select\n value={selectedTeam?.id}\n onValueChange={(value) => {\n // Skip actual navigation/updates in mock mode\n if (props.mockUser) return;\n\n runAsynchronouslyWithAlert(async () => {\n const team = teams?.find(team => team.id === value);\n if (!team) {\n throw new Error('Team not found, this should not happen');\n }\n\n if (!props.noUpdateSelectedTeam) {\n await user?.setSelectedTeam(team as Team);\n }\n if (props.urlMap) {\n navigate(props.urlMap(team as Team));\n }\n });\n }}\n >\n <SelectTrigger className=\"stack-scope max-w-64\">\n <SelectValue placeholder=\"Select team\"/>\n </SelectTrigger>\n <SelectContent className=\"stack-scope\">\n {user?.selectedTeam ? <SelectGroup>\n <SelectLabel>\n <div className=\"flex items-center justify-between\">\n <span>\n {t('Current team')}\n </span>\n <Button\n variant='ghost'\n size='icon'\n className=\"h-6 w-6\"\n onClick={() => {\n // Skip navigation in mock mode\n if (!props.mockUser) {\n navigate(`${app.urls.accountSettings}#team-${user.selectedTeam?.id}`);\n }\n }}\n >\n <Settings className=\"h-4 w-4\"/>\n </Button>\n </div>\n </SelectLabel>\n <SelectItem value={user.selectedTeam.id}>\n <div className=\"flex items-center gap-2\">\n <TeamIcon team={user.selectedTeam as Team} />\n <Typography className=\"max-w-40 truncate\">{user.selectedTeam.displayName}</Typography>\n </div>\n </SelectItem>\n </SelectGroup> : undefined}\n\n {teams?.length ?\n <SelectGroup>\n <SelectLabel>{t('Other teams')}</SelectLabel>\n {teams.filter(team => team.id !== user?.selectedTeam?.id)\n .map(team => (\n <SelectItem value={team.id} key={team.id}>\n <div className=\"flex items-center gap-2\">\n <TeamIcon team={team as Team} />\n <Typography className=\"max-w-64 truncate\">{team.displayName}</Typography>\n </div>\n </SelectItem>\n ))}\n </SelectGroup> :\n <SelectGroup>\n <SelectLabel>{t('No teams yet')}</SelectLabel>\n </SelectGroup>}\n\n {project.config.clientTeamCreationEnabled && <>\n <SelectSeparator/>\n <div>\n <Button\n onClick={() => {\n // Skip navigation in mock mode\n if (!props.mockUser) {\n navigate(`${app.urls.accountSettings}#team-creation`);\n }\n }}\n className=\"w-full\"\n variant='ghost'\n >\n <PlusCircle className=\"mr-2 h-4 w-4\"/> {t('Create a team')}\n </Button>\n </div>\n </>}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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","names":["team"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/use-in-iframe.tsx
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
function useInIframe() {
|
|
7
|
+
const [isIframe, setIsIframe] = useState(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (window.self !== window.top) {
|
|
10
|
+
setIsIframe(true);
|
|
11
|
+
}
|
|
12
|
+
}, []);
|
|
13
|
+
return isIframe;
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
useInIframe
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=use-in-iframe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/use-in-iframe.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { useEffect, useState } from \"react\";\n\nexport function useInIframe() {\n const [isIframe, setIsIframe] = useState(false);\n useEffect(() => {\n if (window.self !== window.top) {\n setIsIframe(true);\n }\n }, []);\n\n return isIframe;\n}\n"],"mappings":";;;AAMA,SAAS,WAAW,gBAAgB;AAE7B,SAAS,cAAc;AAC5B,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,YAAU,MAAM;AACd,QAAI,OAAO,SAAS,OAAO,KAAK;AAC9B,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;","names":[]}
|
|
@@ -6,9 +6,9 @@ import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/
|
|
|
6
6
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Skeleton, Typography } from "@stackframe/stack-ui";
|
|
7
7
|
import { CircleUser, LogIn, LogOut, SunMoon, UserPlus } from "lucide-react";
|
|
8
8
|
import { Suspense } from "react";
|
|
9
|
-
import { useStackApp, useUser } from "
|
|
10
|
-
import { useTranslation } from "../lib/translations";
|
|
11
|
-
import { UserAvatar } from "./elements/user-avatar";
|
|
9
|
+
import { useStackApp, useUser } from "../index.js";
|
|
10
|
+
import { useTranslation } from "../lib/translations.js";
|
|
11
|
+
import { UserAvatar } from "./elements/user-avatar.js";
|
|
12
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
function Item(props) {
|
|
14
14
|
return /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => runAsynchronouslyWithAlert(props.onClick), children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
@@ -20,7 +20,16 @@ function UserButton(props) {
|
|
|
20
20
|
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Skeleton, { className: "h-[34px] w-[34px] rounded-full stack-scope" }), children: /* @__PURE__ */ jsx(UserButtonInner, { ...props }) });
|
|
21
21
|
}
|
|
22
22
|
function UserButtonInner(props) {
|
|
23
|
-
const
|
|
23
|
+
const userFromHook = useUser();
|
|
24
|
+
const user = props.mockUser ? {
|
|
25
|
+
displayName: props.mockUser.displayName || "Mock User",
|
|
26
|
+
primaryEmail: props.mockUser.primaryEmail || "mock@example.com",
|
|
27
|
+
profileImageUrl: props.mockUser.profileImageUrl,
|
|
28
|
+
signOut: () => {
|
|
29
|
+
console.log("Mock sign out - no action taken in demo mode");
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
} : userFromHook;
|
|
24
33
|
return /* @__PURE__ */ jsx(UserButtonInnerInner, { ...props, user });
|
|
25
34
|
}
|
|
26
35
|
function UserButtonInnerInner(props) {
|
|
@@ -50,7 +59,13 @@ function UserButtonInnerInner(props) {
|
|
|
50
59
|
Item,
|
|
51
60
|
{
|
|
52
61
|
text: t("Account settings"),
|
|
53
|
-
onClick: async () =>
|
|
62
|
+
onClick: async () => {
|
|
63
|
+
if (props.mockUser) {
|
|
64
|
+
console.log("Mock account settings - no navigation in demo mode");
|
|
65
|
+
} else {
|
|
66
|
+
await app.redirectToAccountSettings();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
54
69
|
icon: /* @__PURE__ */ jsx(CircleUser, { ...iconProps })
|
|
55
70
|
}
|
|
56
71
|
),
|
|
@@ -58,7 +73,13 @@ function UserButtonInnerInner(props) {
|
|
|
58
73
|
Item,
|
|
59
74
|
{
|
|
60
75
|
text: t("Sign in"),
|
|
61
|
-
onClick: async () =>
|
|
76
|
+
onClick: async () => {
|
|
77
|
+
if (props.mockUser) {
|
|
78
|
+
console.log("Mock sign in - no navigation in demo mode");
|
|
79
|
+
} else {
|
|
80
|
+
await app.redirectToSignIn();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
62
83
|
icon: /* @__PURE__ */ jsx(LogIn, { ...iconProps })
|
|
63
84
|
}
|
|
64
85
|
),
|
|
@@ -66,7 +87,13 @@ function UserButtonInnerInner(props) {
|
|
|
66
87
|
Item,
|
|
67
88
|
{
|
|
68
89
|
text: t("Sign up"),
|
|
69
|
-
onClick: async () =>
|
|
90
|
+
onClick: async () => {
|
|
91
|
+
if (props.mockUser) {
|
|
92
|
+
console.log("Mock sign up - no navigation in demo mode");
|
|
93
|
+
} else {
|
|
94
|
+
await app.redirectToSignUp();
|
|
95
|
+
}
|
|
96
|
+
},
|
|
70
97
|
icon: /* @__PURE__ */ jsx(UserPlus, { ...iconProps })
|
|
71
98
|
}
|
|
72
99
|
),
|
|
@@ -83,7 +110,13 @@ function UserButtonInnerInner(props) {
|
|
|
83
110
|
Item,
|
|
84
111
|
{
|
|
85
112
|
text: t("Sign out"),
|
|
86
|
-
onClick: () =>
|
|
113
|
+
onClick: async () => {
|
|
114
|
+
if (props.mockUser) {
|
|
115
|
+
console.log("Mock sign out - no action taken in demo mode");
|
|
116
|
+
} else {
|
|
117
|
+
await user.signOut();
|
|
118
|
+
}
|
|
119
|
+
},
|
|
87
120
|
icon: /* @__PURE__ */ jsx(LogOut, { ...iconProps })
|
|
88
121
|
}
|
|
89
122
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/user-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Skeleton, Typography } from \"@stackframe/stack-ui\";\nimport { CircleUser, LogIn, LogOut, SunMoon, UserPlus } from \"lucide-react\";\nimport React, { Suspense } from \"react\";\nimport { CurrentUser, useStackApp, useUser } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { UserAvatar } from \"./elements/user-avatar\";\n\nfunction Item(props: { text: string, icon: React.ReactNode, onClick: () => void | Promise<void> }) {\n return (\n <DropdownMenuItem onClick={() => runAsynchronouslyWithAlert(props.onClick)}>\n <div className=\"flex gap-2 items-center\">\n {props.icon}\n <Typography>{props.text}</Typography>\n </div>\n </DropdownMenuItem>\n );\n}\n\ntype UserButtonProps = {\n showUserInfo?: boolean,\n colorModeToggle?: () => void | Promise<void>,\n extraItems?: {\n text: string,\n icon: React.ReactNode,\n onClick: () => void | Promise<void>,\n }[],\n};\n\nexport function UserButton(props: UserButtonProps) {\n return (\n <Suspense fallback={<Skeleton className=\"h-[34px] w-[34px] rounded-full stack-scope\" />}>\n <UserButtonInner {...props} />\n </Suspense>\n );\n}\n\nfunction UserButtonInner(props: UserButtonProps) {\n const
|
|
1
|
+
{"version":3,"sources":["../../../src/components/user-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Skeleton, Typography } from \"@stackframe/stack-ui\";\nimport { CircleUser, LogIn, LogOut, SunMoon, UserPlus } from \"lucide-react\";\nimport React, { Suspense } from \"react\";\nimport { CurrentUser, useStackApp, useUser } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { UserAvatar } from \"./elements/user-avatar\";\n\nfunction Item(props: { text: string, icon: React.ReactNode, onClick: () => void | Promise<void> }) {\n return (\n <DropdownMenuItem onClick={() => runAsynchronouslyWithAlert(props.onClick)}>\n <div className=\"flex gap-2 items-center\">\n {props.icon}\n <Typography>{props.text}</Typography>\n </div>\n </DropdownMenuItem>\n );\n}\n\ntype UserButtonProps = {\n showUserInfo?: boolean,\n colorModeToggle?: () => void | Promise<void>,\n extraItems?: {\n text: string,\n icon: React.ReactNode,\n onClick: () => void | Promise<void>,\n }[],\n mockUser?: {\n displayName?: string,\n primaryEmail?: string,\n profileImageUrl?: string,\n },\n};\n\nexport function UserButton(props: UserButtonProps) {\n return (\n <Suspense fallback={<Skeleton className=\"h-[34px] w-[34px] rounded-full stack-scope\" />}>\n <UserButtonInner {...props} />\n </Suspense>\n );\n}\n\nfunction UserButtonInner(props: UserButtonProps) {\n const userFromHook = useUser();\n\n // Use mock user if provided, otherwise use real user\n const user = props.mockUser ? {\n displayName: props.mockUser.displayName || 'Mock User',\n primaryEmail: props.mockUser.primaryEmail || 'mock@example.com',\n profileImageUrl: props.mockUser.profileImageUrl,\n signOut: () => {\n console.log('Mock sign out - no action taken in demo mode');\n return Promise.resolve();\n }\n } as CurrentUser : userFromHook;\n\n return <UserButtonInnerInner {...props} user={user} />;\n}\n\n\nfunction UserButtonInnerInner(props: UserButtonProps & { user: CurrentUser | null }) {\n const { t } = useTranslation();\n const user = props.user;\n const app = useStackApp();\n\n const iconProps = { size: 20, className: 'h-4 w-4' };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger className=\"outline-none stack-scope\">\n <div className=\"flex gap-2 items-center\">\n <UserAvatar user={user} />\n {user && props.showUserInfo &&\n <div className=\"flex flex-col justify-center text-left\">\n <Typography className=\"max-w-40 truncate\">{user.displayName}</Typography>\n <Typography className=\"max-w-40 truncate\" variant=\"secondary\" type='label'>{user.primaryEmail}</Typography>\n </div>\n }\n </div>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"stack-scope\">\n <DropdownMenuLabel>\n <div className=\"flex gap-2 items-center\">\n <UserAvatar user={user} />\n <div>\n {user && <Typography className=\"max-w-40 truncate\">{user.displayName}</Typography>}\n {user && <Typography className=\"max-w-40 truncate\" variant=\"secondary\" type='label'>{user.primaryEmail}</Typography>}\n {!user && <Typography>{t('Not signed in')}</Typography>}\n </div>\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n {user && <Item\n text={t('Account settings')}\n onClick={async () => {\n if (props.mockUser) {\n console.log('Mock account settings - no navigation in demo mode');\n } else {\n await app.redirectToAccountSettings();\n }\n }}\n icon={<CircleUser {...iconProps} />}\n />}\n {!user && <Item\n text={t('Sign in')}\n onClick={async () => {\n if (props.mockUser) {\n console.log('Mock sign in - no navigation in demo mode');\n } else {\n await app.redirectToSignIn();\n }\n }}\n icon={<LogIn {...iconProps} />}\n />}\n {!user && <Item\n text={t('Sign up')}\n onClick={async () => {\n if (props.mockUser) {\n console.log('Mock sign up - no navigation in demo mode');\n } else {\n await app.redirectToSignUp();\n }\n }}\n icon={<UserPlus {...iconProps}/> }\n />}\n {user && props.extraItems && props.extraItems.map((item, index) => (\n <Item key={index} {...item} />\n ))}\n {props.colorModeToggle && (\n <Item\n text={t('Toggle theme')}\n onClick={props.colorModeToggle}\n icon={<SunMoon {...iconProps} />}\n />\n )}\n {user && <Item\n text={t('Sign out')}\n onClick={async () => {\n if (props.mockUser) {\n console.log('Mock sign out - no action taken in demo mode');\n } else {\n await user.signOut();\n }\n }}\n icon={<LogOut {...iconProps} />}\n />}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,29 +4,70 @@ import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
|
|
|
4
4
|
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
|
5
5
|
import { ActionCell, Badge, Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
-
import { useUser } from "../../../lib/hooks";
|
|
8
|
-
import { useTranslation } from "../../../lib/translations";
|
|
9
|
-
import { PageLayout } from "../page-layout";
|
|
7
|
+
import { useUser } from "../../../lib/hooks.js";
|
|
8
|
+
import { useTranslation } from "../../../lib/translations.js";
|
|
9
|
+
import { PageLayout } from "../page-layout.js";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
function ActiveSessionsPage() {
|
|
11
|
+
function ActiveSessionsPage(props) {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
|
-
const
|
|
14
|
-
const [isLoading, setIsLoading] = useState(
|
|
13
|
+
const userFromHook = useUser({ or: props?.mockSessions || props?.mockMode ? "return-null" : "throw" });
|
|
14
|
+
const [isLoading, setIsLoading] = useState(!props?.mockSessions);
|
|
15
15
|
const [isRevokingAll, setIsRevokingAll] = useState(false);
|
|
16
16
|
const [sessions, setSessions] = useState([]);
|
|
17
17
|
const [showConfirmRevokeAll, setShowConfirmRevokeAll] = useState(false);
|
|
18
|
+
const mockSessionsData = props?.mockSessions ? props.mockSessions.map((session) => ({
|
|
19
|
+
id: session.id,
|
|
20
|
+
isCurrentSession: session.isCurrentSession,
|
|
21
|
+
isImpersonation: session.isImpersonation || false,
|
|
22
|
+
createdAt: session.createdAt,
|
|
23
|
+
lastUsedAt: session.lastUsedAt,
|
|
24
|
+
geoInfo: session.geoInfo
|
|
25
|
+
})) : [
|
|
26
|
+
{
|
|
27
|
+
id: "current-session",
|
|
28
|
+
isCurrentSession: true,
|
|
29
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30
|
+
lastUsedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31
|
+
geoInfo: { ip: "192.168.1.1", cityName: "San Francisco" }
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "mobile-session",
|
|
35
|
+
isCurrentSession: false,
|
|
36
|
+
createdAt: new Date(Date.now() - 864e5).toISOString(),
|
|
37
|
+
// 1 day ago
|
|
38
|
+
lastUsedAt: new Date(Date.now() - 72e5).toISOString(),
|
|
39
|
+
// 2 hours ago
|
|
40
|
+
geoInfo: { ip: "10.0.0.1", cityName: "New York" }
|
|
41
|
+
}
|
|
42
|
+
];
|
|
18
43
|
useEffect(() => {
|
|
44
|
+
if (props?.mockSessions) {
|
|
45
|
+
setSessions(mockSessionsData);
|
|
46
|
+
setIsLoading(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (props?.mockMode && !userFromHook) {
|
|
50
|
+
setSessions(mockSessionsData);
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!userFromHook) return;
|
|
19
55
|
runAsynchronously(async () => {
|
|
20
56
|
setIsLoading(true);
|
|
21
|
-
const sessionsData = await
|
|
57
|
+
const sessionsData = await userFromHook.getActiveSessions();
|
|
22
58
|
const enhancedSessions = sessionsData;
|
|
23
59
|
setSessions(enhancedSessions);
|
|
24
60
|
setIsLoading(false);
|
|
25
61
|
});
|
|
26
|
-
}, [
|
|
62
|
+
}, [userFromHook, props?.mockSessions]);
|
|
27
63
|
const handleRevokeSession = async (sessionId) => {
|
|
64
|
+
if (props?.mockSessions) {
|
|
65
|
+
setSessions((prev) => prev.filter((session) => session.id !== sessionId));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!userFromHook) return;
|
|
28
69
|
try {
|
|
29
|
-
await
|
|
70
|
+
await userFromHook.revokeSession(sessionId);
|
|
30
71
|
setSessions((prev) => prev.filter((session) => session.id !== sessionId));
|
|
31
72
|
} catch (error) {
|
|
32
73
|
captureError("Failed to revoke session", { sessionId, error });
|
|
@@ -36,9 +77,13 @@ function ActiveSessionsPage() {
|
|
|
36
77
|
const handleRevokeAllSessions = async () => {
|
|
37
78
|
setIsRevokingAll(true);
|
|
38
79
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
80
|
+
if (props?.mockSessions) {
|
|
81
|
+
setSessions((prevSessions) => prevSessions.filter((session) => session.isCurrentSession));
|
|
82
|
+
} else if (userFromHook) {
|
|
83
|
+
const deletionPromises = sessions.filter((session) => !session.isCurrentSession).map((session) => userFromHook.revokeSession(session.id));
|
|
84
|
+
await Promise.all(deletionPromises);
|
|
85
|
+
setSessions((prevSessions) => prevSessions.filter((session) => session.isCurrentSession));
|
|
86
|
+
}
|
|
42
87
|
} catch (error) {
|
|
43
88
|
captureError("Failed to revoke all sessions", { error, sessionIds: sessions.map((session) => session.id) });
|
|
44
89
|
throw error;
|
package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components-page/account-settings/active-sessions/active-sessions-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { fromNow } from \"@stackframe/stack-shared/dist/utils/dates\";\nimport { captureError } from \"@stackframe/stack-shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { ActionCell, Badge, Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from \"@stackframe/stack-ui\";\nimport { useEffect, useState } from \"react\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { ActiveSession } from \"../../../lib/stack-app/users\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { PageLayout } from \"../page-layout\";\n\nexport function ActiveSessionsPage() {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\" });\n const [isLoading, setIsLoading] = useState(true);\n const [isRevokingAll, setIsRevokingAll] = useState(false);\n const [sessions, setSessions] = useState<ActiveSession[]>([]);\n const [showConfirmRevokeAll, setShowConfirmRevokeAll] = useState(false);\n\n // Fetch sessions when component mounts\n useEffect(() => {\n runAsynchronously(async () => {\n setIsLoading(true);\n const sessionsData = await user.getActiveSessions();\n const enhancedSessions = sessionsData;\n setSessions(enhancedSessions);\n setIsLoading(false);\n });\n }, [user]);\n\n const handleRevokeSession = async (sessionId: string) => {\n try {\n await user.revokeSession(sessionId);\n\n // Remove the session from the list\n setSessions(prev => prev.filter(session => session.id !== sessionId));\n } catch (error) {\n captureError(\"Failed to revoke session\", { sessionId ,error });\n throw error;\n }\n };\n\n const handleRevokeAllSessions = async () => {\n setIsRevokingAll(true);\n try {\n const deletionPromises = sessions\n .filter(session => !session.isCurrentSession)\n .map(session => user.revokeSession(session.id));\n await Promise.all(deletionPromises);\n setSessions(prevSessions => prevSessions.filter(session => session.isCurrentSession));\n } catch (error) {\n captureError(\"Failed to revoke all sessions\", { error, sessionIds: sessions.map(session => session.id) });\n throw error;\n } finally {\n setIsRevokingAll(false);\n setShowConfirmRevokeAll(false);\n }\n };\n\n return (\n <PageLayout>\n <div>\n <div className=\"flex justify-between items-center mb-2\">\n <Typography className='font-medium'>{t(\"Active Sessions\")}</Typography>\n {sessions.filter(s => !s.isCurrentSession).length > 0 && !isLoading && (\n showConfirmRevokeAll ? (\n <div className=\"flex gap-2\">\n <Button\n variant=\"destructive\"\n size=\"sm\"\n loading={isRevokingAll}\n onClick={handleRevokeAllSessions}\n >\n {t(\"Confirm\")}\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={isRevokingAll}\n onClick={() => setShowConfirmRevokeAll(false)}\n >\n {t(\"Cancel\")}\n </Button>\n </div>\n ) : (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setShowConfirmRevokeAll(true)}\n >\n {t(\"Revoke All Other Sessions\")}\n </Button>\n )\n )}\n </div>\n <Typography variant='secondary' type='footnote' className=\"mb-4\">\n {t(\"These are devices where you're currently logged in. You can revoke access to end a session.\")}\n </Typography>\n\n {isLoading ? (\n <Skeleton className=\"h-[300px] w-full rounded-md\" />\n ) : (\n <div className='border rounded-md'>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-[200px]\">{t(\"Session\")}</TableHead>\n <TableHead className=\"w-[150px]\">{t(\"IP Address\")}</TableHead>\n <TableHead className=\"w-[150px]\">{t(\"Location\")}</TableHead>\n <TableHead className=\"w-[150px]\">{t(\"Last used\")}</TableHead>\n <TableHead className=\"w-[80px]\"></TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sessions.length === 0 ? (\n <TableRow>\n <TableCell colSpan={5} className=\"text-center py-6\">\n <Typography variant=\"secondary\">{t(\"No active sessions found\")}</Typography>\n </TableCell>\n </TableRow>\n ) : (\n sessions.map((session) => (\n <TableRow key={session.id}>\n <TableCell>\n <div className=\"flex flex-col\">\n {/* We currently do not save any usefull information about the user, in the future, the name should probably say what kind of session it is (e.g. cli, browser, maybe what auth method was used) */}\n <Typography>{session.isCurrentSession ? t(\"Current Session\") : t(\"Other Session\")}</Typography>\n {session.isImpersonation && <Badge variant=\"secondary\" className=\"w-fit mt-1\">{t(\"Impersonation\")}</Badge>}\n <Typography variant='secondary' type='footnote'>\n {t(\"Signed in {time}\", { time: new Date(session.createdAt).toLocaleDateString() })}\n </Typography>\n </div>\n </TableCell>\n <TableCell>\n <Typography>{session.geoInfo?.ip || t('-')}</Typography>\n </TableCell>\n <TableCell>\n <Typography>{session.geoInfo?.cityName || t('Unknown')}</Typography>\n </TableCell>\n <TableCell>\n <div className=\"flex flex-col\">\n <Typography>{session.lastUsedAt ? fromNow(new Date(session.lastUsedAt)) : t(\"Never\")}</Typography>\n <Typography variant='secondary' type='footnote' title={session.lastUsedAt ? new Date(session.lastUsedAt).toLocaleString() : \"\"}>\n {session.lastUsedAt ? new Date(session.lastUsedAt).toLocaleDateString() : \"\"}\n </Typography>\n </div>\n </TableCell>\n <TableCell align=\"right\">\n <ActionCell\n items={[\n {\n item: t(\"Revoke\"),\n onClick: () => handleRevokeSession(session.id),\n danger: true,\n disabled: session.isCurrentSession,\n disabledTooltip: session.isCurrentSession ? t(\"You cannot revoke your current session\") : undefined,\n },\n ]}\n />\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </Table>\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components-page/account-settings/active-sessions/active-sessions-page.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { fromNow } from \"@stackframe/stack-shared/dist/utils/dates\";\nimport { captureError } from \"@stackframe/stack-shared/dist/utils/errors\";\nimport { runAsynchronously } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { ActionCell, Badge, Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from \"@stackframe/stack-ui\";\nimport { useEffect, useState } from \"react\";\nimport { useUser } from \"../../../lib/hooks\";\nimport { ActiveSession } from \"../../../lib/stack-app/users\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { PageLayout } from \"../page-layout\";\n\nexport function ActiveSessionsPage(props?: {\n mockSessions?: Array<{\n id: string,\n isCurrentSession: boolean,\n isImpersonation?: boolean,\n createdAt: string,\n lastUsedAt?: string,\n geoInfo?: {\n ip?: string,\n cityName?: string,\n },\n }>,\n mockMode?: boolean,\n}) {\n const { t } = useTranslation();\n const userFromHook = useUser({ or: (props?.mockSessions || props?.mockMode) ? 'return-null' : 'throw' });\n const [isLoading, setIsLoading] = useState(!props?.mockSessions);\n const [isRevokingAll, setIsRevokingAll] = useState(false);\n const [sessions, setSessions] = useState<ActiveSession[]>([]);\n const [showConfirmRevokeAll, setShowConfirmRevokeAll] = useState(false);\n\n // Use mock data if provided\n const mockSessionsData = props?.mockSessions ? props.mockSessions.map(session => ({\n id: session.id,\n isCurrentSession: session.isCurrentSession,\n isImpersonation: session.isImpersonation || false,\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n geoInfo: session.geoInfo,\n })) : [\n {\n id: 'current-session',\n isCurrentSession: true,\n createdAt: new Date().toISOString(),\n lastUsedAt: new Date().toISOString(),\n geoInfo: { ip: '192.168.1.1', cityName: 'San Francisco' }\n },\n {\n id: 'mobile-session',\n isCurrentSession: false,\n createdAt: new Date(Date.now() - 86400000).toISOString(), // 1 day ago\n lastUsedAt: new Date(Date.now() - 7200000).toISOString(), // 2 hours ago\n geoInfo: { ip: '10.0.0.1', cityName: 'New York' }\n }\n ];\n\n // Fetch sessions when component mounts (only if not using mock data)\n useEffect(() => {\n if (props?.mockSessions) {\n setSessions(mockSessionsData as any);\n setIsLoading(false);\n return;\n }\n\n // If in mock mode but no mock sessions provided, use default mock data\n if (props?.mockMode && !userFromHook) {\n setSessions(mockSessionsData as any);\n setIsLoading(false);\n return;\n }\n\n if (!userFromHook) return;\n\n runAsynchronously(async () => {\n setIsLoading(true);\n const sessionsData = await userFromHook.getActiveSessions();\n const enhancedSessions = sessionsData;\n setSessions(enhancedSessions);\n setIsLoading(false);\n });\n }, [userFromHook, props?.mockSessions]);\n\n const handleRevokeSession = async (sessionId: string) => {\n if (props?.mockSessions) {\n // Mock revoke - just remove from list\n setSessions(prev => prev.filter(session => session.id !== sessionId));\n return;\n }\n\n if (!userFromHook) return;\n\n try {\n await userFromHook.revokeSession(sessionId);\n setSessions(prev => prev.filter(session => session.id !== sessionId));\n } catch (error) {\n captureError(\"Failed to revoke session\", { sessionId ,error });\n throw error;\n }\n };\n\n const handleRevokeAllSessions = async () => {\n setIsRevokingAll(true);\n try {\n if (props?.mockSessions) {\n // Mock revoke all - just keep current session\n setSessions(prevSessions => prevSessions.filter(session => session.isCurrentSession));\n } else if (userFromHook) {\n const deletionPromises = sessions\n .filter(session => !session.isCurrentSession)\n .map(session => userFromHook.revokeSession(session.id));\n await Promise.all(deletionPromises);\n setSessions(prevSessions => prevSessions.filter(session => session.isCurrentSession));\n }\n } catch (error) {\n captureError(\"Failed to revoke all sessions\", { error, sessionIds: sessions.map(session => session.id) });\n throw error;\n } finally {\n setIsRevokingAll(false);\n setShowConfirmRevokeAll(false);\n }\n };\n\n return (\n <PageLayout>\n <div>\n <div className=\"flex justify-between items-center mb-2\">\n <Typography className='font-medium'>{t(\"Active Sessions\")}</Typography>\n {sessions.filter(s => !s.isCurrentSession).length > 0 && !isLoading && (\n showConfirmRevokeAll ? (\n <div className=\"flex gap-2\">\n <Button\n variant=\"destructive\"\n size=\"sm\"\n loading={isRevokingAll}\n onClick={handleRevokeAllSessions}\n >\n {t(\"Confirm\")}\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={isRevokingAll}\n onClick={() => setShowConfirmRevokeAll(false)}\n >\n {t(\"Cancel\")}\n </Button>\n </div>\n ) : (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setShowConfirmRevokeAll(true)}\n >\n {t(\"Revoke All Other Sessions\")}\n </Button>\n )\n )}\n </div>\n <Typography variant='secondary' type='footnote' className=\"mb-4\">\n {t(\"These are devices where you're currently logged in. You can revoke access to end a session.\")}\n </Typography>\n\n {isLoading ? (\n <Skeleton className=\"h-[300px] w-full rounded-md\" />\n ) : (\n <div className='border rounded-md'>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-[200px]\">{t(\"Session\")}</TableHead>\n <TableHead className=\"w-[150px]\">{t(\"IP Address\")}</TableHead>\n <TableHead className=\"w-[150px]\">{t(\"Location\")}</TableHead>\n <TableHead className=\"w-[150px]\">{t(\"Last used\")}</TableHead>\n <TableHead className=\"w-[80px]\"></TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {sessions.length === 0 ? (\n <TableRow>\n <TableCell colSpan={5} className=\"text-center py-6\">\n <Typography variant=\"secondary\">{t(\"No active sessions found\")}</Typography>\n </TableCell>\n </TableRow>\n ) : (\n sessions.map((session) => (\n <TableRow key={session.id}>\n <TableCell>\n <div className=\"flex flex-col\">\n {/* We currently do not save any usefull information about the user, in the future, the name should probably say what kind of session it is (e.g. cli, browser, maybe what auth method was used) */}\n <Typography>{session.isCurrentSession ? t(\"Current Session\") : t(\"Other Session\")}</Typography>\n {session.isImpersonation && <Badge variant=\"secondary\" className=\"w-fit mt-1\">{t(\"Impersonation\")}</Badge>}\n <Typography variant='secondary' type='footnote'>\n {t(\"Signed in {time}\", { time: new Date(session.createdAt).toLocaleDateString() })}\n </Typography>\n </div>\n </TableCell>\n <TableCell>\n <Typography>{session.geoInfo?.ip || t('-')}</Typography>\n </TableCell>\n <TableCell>\n <Typography>{session.geoInfo?.cityName || t('Unknown')}</Typography>\n </TableCell>\n <TableCell>\n <div className=\"flex flex-col\">\n <Typography>{session.lastUsedAt ? fromNow(new Date(session.lastUsedAt)) : t(\"Never\")}</Typography>\n <Typography variant='secondary' type='footnote' title={session.lastUsedAt ? new Date(session.lastUsedAt).toLocaleString() : \"\"}>\n {session.lastUsedAt ? new Date(session.lastUsedAt).toLocaleDateString() : \"\"}\n </Typography>\n </div>\n </TableCell>\n <TableCell align=\"right\">\n <ActionCell\n items={[\n {\n item: t(\"Revoke\"),\n onClick: () => handleRevokeSession(session.id),\n danger: true,\n disabled: session.isCurrentSession,\n disabledTooltip: session.isCurrentSession ? t(\"You cannot revoke your current session\") : undefined,\n },\n ]}\n />\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </Table>\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,20 +1,110 @@
|
|
|
1
1
|
// src/components-page/account-settings/api-keys/api-keys-page.tsx
|
|
2
2
|
import { Button } from "@stackframe/stack-ui";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { CreateApiKeyDialog, ShowApiKeyDialog } from "../../../components/api-key-dialogs";
|
|
5
|
-
import { ApiKeyTable } from "../../../components/api-key-table";
|
|
6
|
-
import { useUser } from "../../../lib/hooks";
|
|
7
|
-
import { useTranslation } from "../../../lib/translations";
|
|
8
|
-
import { PageLayout } from "../page-layout";
|
|
4
|
+
import { CreateApiKeyDialog, ShowApiKeyDialog } from "../../../components/api-key-dialogs.js";
|
|
5
|
+
import { ApiKeyTable } from "../../../components/api-key-table.js";
|
|
6
|
+
import { useUser } from "../../../lib/hooks.js";
|
|
7
|
+
import { useTranslation } from "../../../lib/translations.js";
|
|
8
|
+
import { PageLayout } from "../page-layout.js";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
function ApiKeysPage() {
|
|
10
|
+
function ApiKeysPage(props) {
|
|
11
11
|
const { t } = useTranslation();
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const isInMockMode = !!(props?.mockApiKeys || props?.mockMode);
|
|
13
|
+
const userFromHook = useUser({ or: isInMockMode ? "return-null" : "redirect" });
|
|
14
|
+
if (isInMockMode && !userFromHook) {
|
|
15
|
+
}
|
|
16
|
+
if (!isInMockMode && !userFromHook) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const mockApiKeysData = props?.mockApiKeys ? props.mockApiKeys.map((mockKey) => ({
|
|
20
|
+
id: mockKey.id,
|
|
21
|
+
description: mockKey.description,
|
|
22
|
+
createdAt: new Date(mockKey.createdAt),
|
|
23
|
+
expiresAt: mockKey.expiresAt ? new Date(mockKey.expiresAt) : void 0,
|
|
24
|
+
manuallyRevokedAt: mockKey.manuallyRevokedAt ? new Date(mockKey.manuallyRevokedAt) : null,
|
|
25
|
+
value: {
|
|
26
|
+
lastFour: mockKey.id.slice(-4).padStart(4, "0")
|
|
27
|
+
// Use last 4 chars of ID or pad with zeros
|
|
28
|
+
},
|
|
29
|
+
type: "user",
|
|
30
|
+
userId: "mock-user-id",
|
|
31
|
+
update: async () => {
|
|
32
|
+
console.log("Mock API key update called");
|
|
33
|
+
},
|
|
34
|
+
revoke: async () => {
|
|
35
|
+
console.log("Mock API key revoke called");
|
|
36
|
+
},
|
|
37
|
+
isValid: () => {
|
|
38
|
+
const now = /* @__PURE__ */ new Date();
|
|
39
|
+
const isExpired = mockKey.expiresAt ? new Date(mockKey.expiresAt) < now : false;
|
|
40
|
+
const isRevoked = !!mockKey.manuallyRevokedAt;
|
|
41
|
+
return !isExpired && !isRevoked;
|
|
42
|
+
},
|
|
43
|
+
whyInvalid: () => {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
if (mockKey.manuallyRevokedAt) return "manually-revoked";
|
|
46
|
+
if (mockKey.expiresAt && new Date(mockKey.expiresAt) < now) return "expired";
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
})) : [
|
|
50
|
+
{
|
|
51
|
+
id: "key-1",
|
|
52
|
+
description: "Development Key",
|
|
53
|
+
createdAt: new Date(Date.now() - 1728e5),
|
|
54
|
+
// 2 days ago
|
|
55
|
+
expiresAt: void 0,
|
|
56
|
+
manuallyRevokedAt: null,
|
|
57
|
+
value: {
|
|
58
|
+
lastFour: "ey-1".slice(-4).padStart(4, "0")
|
|
59
|
+
},
|
|
60
|
+
type: "user",
|
|
61
|
+
userId: "mock-user-id",
|
|
62
|
+
update: async () => {
|
|
63
|
+
console.log("Mock API key update called");
|
|
64
|
+
},
|
|
65
|
+
revoke: async () => {
|
|
66
|
+
console.log("Mock API key revoke called");
|
|
67
|
+
},
|
|
68
|
+
isValid: () => true,
|
|
69
|
+
whyInvalid: () => null
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
let apiKeys;
|
|
73
|
+
if (isInMockMode) {
|
|
74
|
+
apiKeys = mockApiKeysData;
|
|
75
|
+
} else if (userFromHook) {
|
|
76
|
+
apiKeys = userFromHook.useApiKeys();
|
|
77
|
+
} else {
|
|
78
|
+
apiKeys = [];
|
|
79
|
+
}
|
|
14
80
|
const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);
|
|
15
81
|
const [returnedApiKey, setReturnedApiKey] = useState(null);
|
|
16
82
|
const CreateDialog = CreateApiKeyDialog;
|
|
17
83
|
const ShowDialog = ShowApiKeyDialog;
|
|
84
|
+
const handleCreateApiKey = async (data) => {
|
|
85
|
+
if (isInMockMode) {
|
|
86
|
+
const mockApiKey = {
|
|
87
|
+
id: `key-${Date.now()}`,
|
|
88
|
+
description: data.description,
|
|
89
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
90
|
+
expiresAt: data.expiresAt?.toISOString(),
|
|
91
|
+
value: "sk_dev_mock_key_" + Math.random().toString(36).substring(2),
|
|
92
|
+
update: async () => {
|
|
93
|
+
console.log("Mock API key update called");
|
|
94
|
+
},
|
|
95
|
+
revoke: async () => {
|
|
96
|
+
console.log("Mock API key revoke called");
|
|
97
|
+
},
|
|
98
|
+
isValid: () => true,
|
|
99
|
+
whyInvalid: () => null,
|
|
100
|
+
type: "user",
|
|
101
|
+
userId: "mock-user-id"
|
|
102
|
+
};
|
|
103
|
+
return mockApiKey;
|
|
104
|
+
}
|
|
105
|
+
if (!userFromHook) throw new Error("User not available");
|
|
106
|
+
return await userFromHook.createApiKey(data);
|
|
107
|
+
};
|
|
18
108
|
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
19
109
|
/* @__PURE__ */ jsx(Button, { onClick: () => setIsNewApiKeyDialogOpen(true), children: t("Create API Key") }),
|
|
20
110
|
/* @__PURE__ */ jsx(ApiKeyTable, { apiKeys }),
|
|
@@ -24,10 +114,8 @@ function ApiKeysPage() {
|
|
|
24
114
|
open: isNewApiKeyDialogOpen,
|
|
25
115
|
onOpenChange: setIsNewApiKeyDialogOpen,
|
|
26
116
|
onKeyCreated: setReturnedApiKey,
|
|
27
|
-
createApiKey:
|
|
28
|
-
|
|
29
|
-
return apiKey;
|
|
30
|
-
}
|
|
117
|
+
createApiKey: handleCreateApiKey,
|
|
118
|
+
mockMode: isInMockMode
|
|
31
119
|
}
|
|
32
120
|
),
|
|
33
121
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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(props?: {\n mockApiKeys?: Array<{\n id: string,\n description: string,\n createdAt: string,\n expiresAt?: string,\n manuallyRevokedAt?: string,\n }>,\n mockMode?: boolean,\n}) {\n const { t } = useTranslation();\n\n // Check if we're in any kind of mock mode first\n const isInMockMode = !!(props?.mockApiKeys || props?.mockMode);\n\n const userFromHook = useUser({ or: isInMockMode ? 'return-null' : 'redirect' });\n\n // In mock mode, we don't need a user - just show mock data\n if (isInMockMode && !userFromHook) {\n // This is expected in mock mode, continue with mock data\n }\n\n // Only return null if we're not in mock mode and don't have a user\n if (!isInMockMode && !userFromHook) {\n return null; // This shouldn't happen due to redirect, but just in case\n }\n\n // Use mock data if provided\n const mockApiKeysData = props?.mockApiKeys ? props.mockApiKeys.map(mockKey => ({\n id: mockKey.id,\n description: mockKey.description,\n createdAt: new Date(mockKey.createdAt),\n expiresAt: mockKey.expiresAt ? new Date(mockKey.expiresAt) : undefined,\n manuallyRevokedAt: mockKey.manuallyRevokedAt ? new Date(mockKey.manuallyRevokedAt) : null,\n value: {\n lastFour: mockKey.id.slice(-4).padStart(4, '0'), // Use last 4 chars of ID or pad with zeros\n },\n type: 'user' as const,\n userId: 'mock-user-id',\n update: async () => {\n console.log('Mock API key update called');\n },\n revoke: async () => {\n console.log('Mock API key revoke called');\n },\n isValid: () => {\n const now = new Date();\n const isExpired = mockKey.expiresAt ? new Date(mockKey.expiresAt) < now : false;\n const isRevoked = !!mockKey.manuallyRevokedAt;\n return !isExpired && !isRevoked;\n },\n whyInvalid: () => {\n const now = new Date();\n if (mockKey.manuallyRevokedAt) return 'manually-revoked';\n if (mockKey.expiresAt && new Date(mockKey.expiresAt) < now) return 'expired';\n return null;\n },\n })) : [\n {\n id: 'key-1',\n description: 'Development Key',\n createdAt: new Date(Date.now() - 172800000), // 2 days ago\n expiresAt: undefined,\n manuallyRevokedAt: null,\n value: {\n lastFour: 'ey-1'.slice(-4).padStart(4, '0'),\n },\n type: 'user' as const,\n userId: 'mock-user-id',\n update: async () => {\n console.log('Mock API key update called');\n },\n revoke: async () => {\n console.log('Mock API key revoke called');\n },\n isValid: () => true,\n whyInvalid: () => null,\n }\n ];\n\n // Determine which API keys to use\n let apiKeys: any[];\n if (isInMockMode) {\n apiKeys = mockApiKeysData;\n } else if (userFromHook) {\n apiKeys = userFromHook.useApiKeys();\n } else {\n apiKeys = [];\n }\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 const handleCreateApiKey = async (data: ApiKeyCreationOptions<\"user\">) => {\n if (isInMockMode) {\n // Mock API key creation\n const mockApiKey = {\n id: `key-${Date.now()}`,\n description: data.description,\n createdAt: new Date().toISOString(),\n expiresAt: data.expiresAt?.toISOString(),\n value: 'sk_dev_mock_key_' + Math.random().toString(36).substring(2),\n update: async () => {\n console.log('Mock API key update called');\n },\n revoke: async () => {\n console.log('Mock API key revoke called');\n },\n isValid: () => true,\n whyInvalid: () => null,\n type: 'user' as const,\n userId: 'mock-user-id',\n };\n return mockApiKey as any;\n }\n\n if (!userFromHook) throw new Error('User not available');\n return await userFromHook.createApiKey(data);\n };\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={handleCreateApiKey}\n mockMode={isInMockMode}\n />\n <ShowDialog\n apiKey={returnedApiKey}\n onClose={() => setReturnedApiKey(null)}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Button, Input, Typography } from "@stackframe/stack-ui";
|
|
3
3
|
import { Edit } from "lucide-react";
|
|
4
4
|
import { useState } from "react";
|
|
5
|
-
import { useTranslation } from "../../lib/translations";
|
|
5
|
+
import { useTranslation } from "../../lib/translations.js";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
function EditableText(props) {
|
|
8
8
|
const [editing, setEditing] = useState(false);
|