@stackframe/stack 2.8.22 → 2.8.27

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 CHANGED
@@ -1,5 +1,55 @@
1
1
  # @stackframe/stack
2
2
 
3
+ ## 2.8.27
4
+
5
+ ### Patch Changes
6
+
7
+ - Various changes
8
+ - Updated dependencies
9
+ - @stackframe/stack-shared@2.8.27
10
+ - @stackframe/stack-ui@2.8.27
11
+ - @stackframe/stack-sc@2.8.27
12
+
13
+ ## 2.8.26
14
+
15
+ ### Patch Changes
16
+
17
+ - Various changes
18
+ - Updated dependencies
19
+ - @stackframe/stack-shared@2.8.26
20
+ - @stackframe/stack-ui@2.8.26
21
+ - @stackframe/stack-sc@2.8.26
22
+
23
+ ## 2.8.25
24
+
25
+ ### Patch Changes
26
+
27
+ - Various changes
28
+ - Updated dependencies
29
+ - @stackframe/stack-shared@2.8.25
30
+ - @stackframe/stack-ui@2.8.25
31
+ - @stackframe/stack-sc@2.8.25
32
+
33
+ ## 2.8.24
34
+
35
+ ### Patch Changes
36
+
37
+ - Various changes
38
+ - Updated dependencies
39
+ - @stackframe/stack-shared@2.8.24
40
+ - @stackframe/stack-ui@2.8.24
41
+ - @stackframe/stack-sc@2.8.24
42
+
43
+ ## 2.8.23
44
+
45
+ ### Patch Changes
46
+
47
+ - Various changes
48
+ - Updated dependencies
49
+ - @stackframe/stack-shared@2.8.23
50
+ - @stackframe/stack-ui@2.8.23
51
+ - @stackframe/stack-sc@2.8.23
52
+
3
53
  ## 2.8.22
4
54
 
5
55
  ### Patch Changes
@@ -168,6 +168,15 @@ function OAuthButton({
168
168
  };
169
169
  break;
170
170
  }
171
+ case "twitch": {
172
+ style = {
173
+ backgroundColor: "#6441a5",
174
+ textColor: "#fff",
175
+ name: "Twitch",
176
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.BrandIcons.Twitch, { iconSize })
177
+ };
178
+ break;
179
+ }
171
180
  default: {
172
181
  style = {
173
182
  name: provider,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/oauth-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { BrandIcons, Button, SimpleTooltip } from '@stackframe/stack-ui';\nimport Color from 'color';\nimport { useEffect, useId, useState } from 'react';\nimport { useStackApp } from '..';\nimport { useTranslation } from '../lib/translations';\nimport { useInIframe } from './use-in-iframe';\n\nconst iconSize = 22;\n\nconst changeColor = (c: Color, value: number) => {\n if (c.isLight()) {\n value = -value;\n }\n return c.hsl(c.hue(), c.saturationl(), c.lightness() + value).toString();\n};\n\nexport function OAuthButton({\n provider,\n type,\n isMock = false,\n}: {\n provider: string,\n type: 'sign-in' | 'sign-up',\n isMock?: boolean,\n}) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const styleId = useId().replaceAll(':', '-');\n const isIframe = useInIframe();\n\n const [lastUsed, setLastUsed] = useState<string | null>(null);\n useEffect(() => {\n setLastUsed(localStorage.getItem('_STACK_AUTH.lastUsed'));\n }, []);\n\n let style : {\n backgroundColor?: string,\n textColor?: string,\n name: string,\n icon: JSX.Element | null,\n border?: string,\n };\n switch (provider) {\n case 'google': {\n style = {\n backgroundColor: '#fff',\n textColor: '#000',\n name: 'Google',\n border: '1px solid #ddd',\n icon: <BrandIcons.Google iconSize={iconSize} />,\n };\n break;\n }\n case 'github': {\n style = {\n backgroundColor: '#111',\n textColor: '#fff',\n border: '1px solid #333',\n name: 'GitHub',\n icon: <BrandIcons.GitHub iconSize={iconSize} />,\n };\n break;\n }\n case 'facebook': {\n style = {\n backgroundColor: '#1877F2',\n textColor: '#fff',\n name: 'Facebook',\n icon: <BrandIcons.Facebook iconSize={iconSize} />,\n };\n break;\n }\n case 'microsoft': {\n style = {\n backgroundColor: '#2f2f2f',\n textColor: '#fff',\n name: 'Microsoft',\n icon: <BrandIcons.Microsoft iconSize={iconSize} />,\n };\n break;\n }\n case 'spotify': {\n style = {\n backgroundColor: '#1DB954',\n textColor: '#fff',\n name: 'Spotify',\n icon: <BrandIcons.Spotify iconSize={iconSize} />,\n };\n break;\n }\n case 'discord': {\n style = {\n backgroundColor: '#5865F2',\n textColor: '#fff',\n name: 'Discord',\n icon: <BrandIcons.Discord iconSize={iconSize} />,\n };\n break;\n }\n case 'gitlab': {\n style = {\n backgroundColor: \"#111\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Gitlab\",\n icon: <BrandIcons.Gitlab iconSize={iconSize} />,\n };\n break;\n }\n case 'apple': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Apple\",\n icon: <BrandIcons.Apple iconSize={iconSize} />,\n };\n break;\n }\n case \"bitbucket\": {\n style = {\n backgroundColor: \"#fff\",\n textColor: \"#000\",\n border: \"1px solid #ddd\",\n name: \"Bitbucket\",\n icon: <BrandIcons.Bitbucket iconSize={iconSize} />,\n };\n break;\n }\n case 'linkedin': {\n style = {\n backgroundColor: \"#0073b1\",\n textColor: \"#fff\",\n name: \"LinkedIn\",\n icon: <BrandIcons.LinkedIn iconSize={iconSize} />,\n };\n break;\n }\n case 'x': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n name: \"X\",\n icon: <BrandIcons.X iconSize={iconSize} />,\n };\n break;\n }\n default: {\n style = {\n name: provider,\n icon: null,\n };\n }\n }\n\n const styleSheet = `\n .stack-oauth-button-${styleId} {\n background-color: ${style.backgroundColor} !important;\n color: ${style.textColor} !important;\n border: ${style.border} !important;\n }\n .stack-oauth-button-${styleId}:hover {\n background-color: ${changeColor(Color(style.backgroundColor), 10)} !important;\n }\n `;\n\n return (\n <>\n <style>{styleSheet}</style>\n <SimpleTooltip\n disabled={!isIframe}\n tooltip={isIframe ? \"This auth provider is not supported in an iframe for security reasons.\" : undefined}\n className='stack-scope w-full inline-flex'\n >\n <Button\n onClick={async () => {\n localStorage.setItem('_STACK_AUTH.lastUsed', provider);\n await stackApp.signInWithOAuth(provider);\n }}\n className={`stack-oauth-button-${styleId} stack-scope relative w-full`}\n disabled={isIframe}\n >\n {!isMock && lastUsed === provider && (\n <span className=\"absolute -top-2 -right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-md\">\n last\n </span>\n )}\n <div className='flex items-center w-full gap-4'>\n {style.icon}\n <span className='flex-1'>\n {type === 'sign-up' ?\n t('Sign up with {provider}', { provider: style.name }) :\n t('Sign in with {provider}', { provider: style.name })\n }\n </span>\n </div>\n </Button>\n </SimpleTooltip>\n </>\n );\n}\n"],"mappings":"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","names":["Color"]}
1
+ {"version":3,"sources":["../../src/components/oauth-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { BrandIcons, Button, SimpleTooltip } from '@stackframe/stack-ui';\nimport Color from 'color';\nimport { useEffect, useId, useState } from 'react';\nimport { useStackApp } from '..';\nimport { useTranslation } from '../lib/translations';\nimport { useInIframe } from './use-in-iframe';\n\nconst iconSize = 22;\n\nconst changeColor = (c: Color, value: number) => {\n if (c.isLight()) {\n value = -value;\n }\n return c.hsl(c.hue(), c.saturationl(), c.lightness() + value).toString();\n};\n\nexport function OAuthButton({\n provider,\n type,\n isMock = false,\n}: {\n provider: string,\n type: 'sign-in' | 'sign-up',\n isMock?: boolean,\n}) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const styleId = useId().replaceAll(':', '-');\n const isIframe = useInIframe();\n\n const [lastUsed, setLastUsed] = useState<string | null>(null);\n useEffect(() => {\n setLastUsed(localStorage.getItem('_STACK_AUTH.lastUsed'));\n }, []);\n\n let style : {\n backgroundColor?: string,\n textColor?: string,\n name: string,\n icon: JSX.Element | null,\n border?: string,\n };\n switch (provider) {\n case 'google': {\n style = {\n backgroundColor: '#fff',\n textColor: '#000',\n name: 'Google',\n border: '1px solid #ddd',\n icon: <BrandIcons.Google iconSize={iconSize} />,\n };\n break;\n }\n case 'github': {\n style = {\n backgroundColor: '#111',\n textColor: '#fff',\n border: '1px solid #333',\n name: 'GitHub',\n icon: <BrandIcons.GitHub iconSize={iconSize} />,\n };\n break;\n }\n case 'facebook': {\n style = {\n backgroundColor: '#1877F2',\n textColor: '#fff',\n name: 'Facebook',\n icon: <BrandIcons.Facebook iconSize={iconSize} />,\n };\n break;\n }\n case 'microsoft': {\n style = {\n backgroundColor: '#2f2f2f',\n textColor: '#fff',\n name: 'Microsoft',\n icon: <BrandIcons.Microsoft iconSize={iconSize} />,\n };\n break;\n }\n case 'spotify': {\n style = {\n backgroundColor: '#1DB954',\n textColor: '#fff',\n name: 'Spotify',\n icon: <BrandIcons.Spotify iconSize={iconSize} />,\n };\n break;\n }\n case 'discord': {\n style = {\n backgroundColor: '#5865F2',\n textColor: '#fff',\n name: 'Discord',\n icon: <BrandIcons.Discord iconSize={iconSize} />,\n };\n break;\n }\n case 'gitlab': {\n style = {\n backgroundColor: \"#111\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Gitlab\",\n icon: <BrandIcons.Gitlab iconSize={iconSize} />,\n };\n break;\n }\n case 'apple': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Apple\",\n icon: <BrandIcons.Apple iconSize={iconSize} />,\n };\n break;\n }\n case \"bitbucket\": {\n style = {\n backgroundColor: \"#fff\",\n textColor: \"#000\",\n border: \"1px solid #ddd\",\n name: \"Bitbucket\",\n icon: <BrandIcons.Bitbucket iconSize={iconSize} />,\n };\n break;\n }\n case 'linkedin': {\n style = {\n backgroundColor: \"#0073b1\",\n textColor: \"#fff\",\n name: \"LinkedIn\",\n icon: <BrandIcons.LinkedIn iconSize={iconSize} />,\n };\n break;\n }\n case 'x': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n name: \"X\",\n icon: <BrandIcons.X iconSize={iconSize} />,\n };\n break;\n }\n case 'twitch': {\n style = {\n backgroundColor: \"#6441a5\",\n textColor: \"#fff\",\n name: \"Twitch\",\n icon: <BrandIcons.Twitch iconSize={iconSize} />,\n };\n break;\n }\n default: {\n style = {\n name: provider,\n icon: null,\n };\n }\n }\n\n const styleSheet = `\n .stack-oauth-button-${styleId} {\n background-color: ${style.backgroundColor} !important;\n color: ${style.textColor} !important;\n border: ${style.border} !important;\n }\n .stack-oauth-button-${styleId}:hover {\n background-color: ${changeColor(Color(style.backgroundColor), 10)} !important;\n }\n `;\n\n return (\n <>\n <style>{styleSheet}</style>\n <SimpleTooltip\n disabled={!isIframe}\n tooltip={isIframe ? \"This auth provider is not supported in an iframe for security reasons.\" : undefined}\n className='stack-scope w-full inline-flex'\n >\n <Button\n onClick={async () => {\n localStorage.setItem('_STACK_AUTH.lastUsed', provider);\n await stackApp.signInWithOAuth(provider);\n }}\n className={`stack-oauth-button-${styleId} stack-scope relative w-full`}\n disabled={isIframe}\n >\n {!isMock && lastUsed === provider && (\n <span className=\"absolute -top-2 -right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-md\">\n last\n </span>\n )}\n <div className='flex items-center w-full gap-4'>\n {style.icon}\n <span className='flex-1'>\n {type === 'sign-up' ?\n t('Sign up with {provider}', { provider: style.name }) :\n t('Sign in with {provider}', { provider: style.name })\n }\n </span>\n </div>\n </Button>\n </SimpleTooltip>\n </>\n );\n}\n"],"mappings":"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","names":["Color"]}
@@ -94,7 +94,7 @@ function ActiveSessionsPage(props) {
94
94
  await userFromHook.revokeSession(sessionId);
95
95
  setSessions((prev) => prev.filter((session) => session.id !== sessionId));
96
96
  } catch (error) {
97
- (0, import_errors.captureError)("Failed to revoke session", { sessionId, error });
97
+ (0, import_errors.captureError)("session-revoke", { sessionId, error });
98
98
  throw error;
99
99
  }
100
100
  };
@@ -109,7 +109,7 @@ function ActiveSessionsPage(props) {
109
109
  setSessions((prevSessions) => prevSessions.filter((session) => session.isCurrentSession));
110
110
  }
111
111
  } catch (error) {
112
- (0, import_errors.captureError)("Failed to revoke all sessions", { error, sessionIds: sessions.map((session) => session.id) });
112
+ (0, import_errors.captureError)("all-sessions-revoke", { error, sessionIds: sessions.map((session) => session.id) });
113
113
  throw error;
114
114
  } finally {
115
115
  setIsRevokingAll(false);
@@ -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(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
+ {"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(\"session-revoke\", { 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(\"all-sessions-revoke\", { 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":[]}
@@ -135,6 +135,15 @@ function OAuthButton({
135
135
  };
136
136
  break;
137
137
  }
138
+ case "twitch": {
139
+ style = {
140
+ backgroundColor: "#6441a5",
141
+ textColor: "#fff",
142
+ name: "Twitch",
143
+ icon: /* @__PURE__ */ jsx(BrandIcons.Twitch, { iconSize })
144
+ };
145
+ break;
146
+ }
138
147
  default: {
139
148
  style = {
140
149
  name: provider,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/oauth-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { BrandIcons, Button, SimpleTooltip } from '@stackframe/stack-ui';\nimport Color from 'color';\nimport { useEffect, useId, useState } from 'react';\nimport { useStackApp } from '..';\nimport { useTranslation } from '../lib/translations';\nimport { useInIframe } from './use-in-iframe';\n\nconst iconSize = 22;\n\nconst changeColor = (c: Color, value: number) => {\n if (c.isLight()) {\n value = -value;\n }\n return c.hsl(c.hue(), c.saturationl(), c.lightness() + value).toString();\n};\n\nexport function OAuthButton({\n provider,\n type,\n isMock = false,\n}: {\n provider: string,\n type: 'sign-in' | 'sign-up',\n isMock?: boolean,\n}) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const styleId = useId().replaceAll(':', '-');\n const isIframe = useInIframe();\n\n const [lastUsed, setLastUsed] = useState<string | null>(null);\n useEffect(() => {\n setLastUsed(localStorage.getItem('_STACK_AUTH.lastUsed'));\n }, []);\n\n let style : {\n backgroundColor?: string,\n textColor?: string,\n name: string,\n icon: JSX.Element | null,\n border?: string,\n };\n switch (provider) {\n case 'google': {\n style = {\n backgroundColor: '#fff',\n textColor: '#000',\n name: 'Google',\n border: '1px solid #ddd',\n icon: <BrandIcons.Google iconSize={iconSize} />,\n };\n break;\n }\n case 'github': {\n style = {\n backgroundColor: '#111',\n textColor: '#fff',\n border: '1px solid #333',\n name: 'GitHub',\n icon: <BrandIcons.GitHub iconSize={iconSize} />,\n };\n break;\n }\n case 'facebook': {\n style = {\n backgroundColor: '#1877F2',\n textColor: '#fff',\n name: 'Facebook',\n icon: <BrandIcons.Facebook iconSize={iconSize} />,\n };\n break;\n }\n case 'microsoft': {\n style = {\n backgroundColor: '#2f2f2f',\n textColor: '#fff',\n name: 'Microsoft',\n icon: <BrandIcons.Microsoft iconSize={iconSize} />,\n };\n break;\n }\n case 'spotify': {\n style = {\n backgroundColor: '#1DB954',\n textColor: '#fff',\n name: 'Spotify',\n icon: <BrandIcons.Spotify iconSize={iconSize} />,\n };\n break;\n }\n case 'discord': {\n style = {\n backgroundColor: '#5865F2',\n textColor: '#fff',\n name: 'Discord',\n icon: <BrandIcons.Discord iconSize={iconSize} />,\n };\n break;\n }\n case 'gitlab': {\n style = {\n backgroundColor: \"#111\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Gitlab\",\n icon: <BrandIcons.Gitlab iconSize={iconSize} />,\n };\n break;\n }\n case 'apple': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Apple\",\n icon: <BrandIcons.Apple iconSize={iconSize} />,\n };\n break;\n }\n case \"bitbucket\": {\n style = {\n backgroundColor: \"#fff\",\n textColor: \"#000\",\n border: \"1px solid #ddd\",\n name: \"Bitbucket\",\n icon: <BrandIcons.Bitbucket iconSize={iconSize} />,\n };\n break;\n }\n case 'linkedin': {\n style = {\n backgroundColor: \"#0073b1\",\n textColor: \"#fff\",\n name: \"LinkedIn\",\n icon: <BrandIcons.LinkedIn iconSize={iconSize} />,\n };\n break;\n }\n case 'x': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n name: \"X\",\n icon: <BrandIcons.X iconSize={iconSize} />,\n };\n break;\n }\n default: {\n style = {\n name: provider,\n icon: null,\n };\n }\n }\n\n const styleSheet = `\n .stack-oauth-button-${styleId} {\n background-color: ${style.backgroundColor} !important;\n color: ${style.textColor} !important;\n border: ${style.border} !important;\n }\n .stack-oauth-button-${styleId}:hover {\n background-color: ${changeColor(Color(style.backgroundColor), 10)} !important;\n }\n `;\n\n return (\n <>\n <style>{styleSheet}</style>\n <SimpleTooltip\n disabled={!isIframe}\n tooltip={isIframe ? \"This auth provider is not supported in an iframe for security reasons.\" : undefined}\n className='stack-scope w-full inline-flex'\n >\n <Button\n onClick={async () => {\n localStorage.setItem('_STACK_AUTH.lastUsed', provider);\n await stackApp.signInWithOAuth(provider);\n }}\n className={`stack-oauth-button-${styleId} stack-scope relative w-full`}\n disabled={isIframe}\n >\n {!isMock && lastUsed === provider && (\n <span className=\"absolute -top-2 -right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-md\">\n last\n </span>\n )}\n <div className='flex items-center w-full gap-4'>\n {style.icon}\n <span className='flex-1'>\n {type === 'sign-up' ?\n t('Sign up with {provider}', { provider: style.name }) :\n t('Sign in with {provider}', { provider: style.name })\n }\n </span>\n </div>\n </Button>\n </SimpleTooltip>\n </>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/oauth-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { BrandIcons, Button, SimpleTooltip } from '@stackframe/stack-ui';\nimport Color from 'color';\nimport { useEffect, useId, useState } from 'react';\nimport { useStackApp } from '..';\nimport { useTranslation } from '../lib/translations';\nimport { useInIframe } from './use-in-iframe';\n\nconst iconSize = 22;\n\nconst changeColor = (c: Color, value: number) => {\n if (c.isLight()) {\n value = -value;\n }\n return c.hsl(c.hue(), c.saturationl(), c.lightness() + value).toString();\n};\n\nexport function OAuthButton({\n provider,\n type,\n isMock = false,\n}: {\n provider: string,\n type: 'sign-in' | 'sign-up',\n isMock?: boolean,\n}) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const styleId = useId().replaceAll(':', '-');\n const isIframe = useInIframe();\n\n const [lastUsed, setLastUsed] = useState<string | null>(null);\n useEffect(() => {\n setLastUsed(localStorage.getItem('_STACK_AUTH.lastUsed'));\n }, []);\n\n let style : {\n backgroundColor?: string,\n textColor?: string,\n name: string,\n icon: JSX.Element | null,\n border?: string,\n };\n switch (provider) {\n case 'google': {\n style = {\n backgroundColor: '#fff',\n textColor: '#000',\n name: 'Google',\n border: '1px solid #ddd',\n icon: <BrandIcons.Google iconSize={iconSize} />,\n };\n break;\n }\n case 'github': {\n style = {\n backgroundColor: '#111',\n textColor: '#fff',\n border: '1px solid #333',\n name: 'GitHub',\n icon: <BrandIcons.GitHub iconSize={iconSize} />,\n };\n break;\n }\n case 'facebook': {\n style = {\n backgroundColor: '#1877F2',\n textColor: '#fff',\n name: 'Facebook',\n icon: <BrandIcons.Facebook iconSize={iconSize} />,\n };\n break;\n }\n case 'microsoft': {\n style = {\n backgroundColor: '#2f2f2f',\n textColor: '#fff',\n name: 'Microsoft',\n icon: <BrandIcons.Microsoft iconSize={iconSize} />,\n };\n break;\n }\n case 'spotify': {\n style = {\n backgroundColor: '#1DB954',\n textColor: '#fff',\n name: 'Spotify',\n icon: <BrandIcons.Spotify iconSize={iconSize} />,\n };\n break;\n }\n case 'discord': {\n style = {\n backgroundColor: '#5865F2',\n textColor: '#fff',\n name: 'Discord',\n icon: <BrandIcons.Discord iconSize={iconSize} />,\n };\n break;\n }\n case 'gitlab': {\n style = {\n backgroundColor: \"#111\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Gitlab\",\n icon: <BrandIcons.Gitlab iconSize={iconSize} />,\n };\n break;\n }\n case 'apple': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n border: \"1px solid #333\",\n name: \"Apple\",\n icon: <BrandIcons.Apple iconSize={iconSize} />,\n };\n break;\n }\n case \"bitbucket\": {\n style = {\n backgroundColor: \"#fff\",\n textColor: \"#000\",\n border: \"1px solid #ddd\",\n name: \"Bitbucket\",\n icon: <BrandIcons.Bitbucket iconSize={iconSize} />,\n };\n break;\n }\n case 'linkedin': {\n style = {\n backgroundColor: \"#0073b1\",\n textColor: \"#fff\",\n name: \"LinkedIn\",\n icon: <BrandIcons.LinkedIn iconSize={iconSize} />,\n };\n break;\n }\n case 'x': {\n style = {\n backgroundColor: \"#000\",\n textColor: \"#fff\",\n name: \"X\",\n icon: <BrandIcons.X iconSize={iconSize} />,\n };\n break;\n }\n case 'twitch': {\n style = {\n backgroundColor: \"#6441a5\",\n textColor: \"#fff\",\n name: \"Twitch\",\n icon: <BrandIcons.Twitch iconSize={iconSize} />,\n };\n break;\n }\n default: {\n style = {\n name: provider,\n icon: null,\n };\n }\n }\n\n const styleSheet = `\n .stack-oauth-button-${styleId} {\n background-color: ${style.backgroundColor} !important;\n color: ${style.textColor} !important;\n border: ${style.border} !important;\n }\n .stack-oauth-button-${styleId}:hover {\n background-color: ${changeColor(Color(style.backgroundColor), 10)} !important;\n }\n `;\n\n return (\n <>\n <style>{styleSheet}</style>\n <SimpleTooltip\n disabled={!isIframe}\n tooltip={isIframe ? \"This auth provider is not supported in an iframe for security reasons.\" : undefined}\n className='stack-scope w-full inline-flex'\n >\n <Button\n onClick={async () => {\n localStorage.setItem('_STACK_AUTH.lastUsed', provider);\n await stackApp.signInWithOAuth(provider);\n }}\n className={`stack-oauth-button-${styleId} stack-scope relative w-full`}\n disabled={isIframe}\n >\n {!isMock && lastUsed === provider && (\n <span className=\"absolute -top-2 -right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-md\">\n last\n </span>\n )}\n <div className='flex items-center w-full gap-4'>\n {style.icon}\n <span className='flex-1'>\n {type === 'sign-up' ?\n t('Sign up with {provider}', { provider: style.name }) :\n t('Sign in with {provider}', { provider: style.name })\n }\n </span>\n </div>\n </Button>\n </SimpleTooltip>\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -70,7 +70,7 @@ function ActiveSessionsPage(props) {
70
70
  await userFromHook.revokeSession(sessionId);
71
71
  setSessions((prev) => prev.filter((session) => session.id !== sessionId));
72
72
  } catch (error) {
73
- captureError("Failed to revoke session", { sessionId, error });
73
+ captureError("session-revoke", { sessionId, error });
74
74
  throw error;
75
75
  }
76
76
  };
@@ -85,7 +85,7 @@ function ActiveSessionsPage(props) {
85
85
  setSessions((prevSessions) => prevSessions.filter((session) => session.isCurrentSession));
86
86
  }
87
87
  } catch (error) {
88
- captureError("Failed to revoke all sessions", { error, sessionIds: sessions.map((session) => session.id) });
88
+ captureError("all-sessions-revoke", { error, sessionIds: sessions.map((session) => session.id) });
89
89
  throw error;
90
90
  } finally {
91
91
  setIsRevokingAll(false);
@@ -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(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
+ {"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(\"session-revoke\", { 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(\"all-sessions-revoke\", { 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":[]}
@@ -6,7 +6,6 @@ import { pick } from "@stackframe/stack-shared/dist/utils/objects";
6
6
  import { Result } from "@stackframe/stack-shared/dist/utils/results";
7
7
  import { useMemo } from "react";
8
8
  import { stackAppInternalsSymbol } from "../../common.js";
9
- import { adminEmailTemplateUpdateOptionsToCrud } from "../../email-templates/index.js";
10
9
  import { internalApiKeyCreateOptionsToCrud } from "../../internal-api-keys/index.js";
11
10
  import { adminProjectPermissionDefinitionCreateOptionsToCrud, adminProjectPermissionDefinitionUpdateOptionsToCrud, adminTeamPermissionDefinitionCreateOptionsToCrud, adminTeamPermissionDefinitionUpdateOptionsToCrud } from "../../permissions/index.js";
12
11
  import { adminProjectUpdateOptionsToCrud } from "../../projects/index.js";
@@ -44,15 +43,15 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
44
43
  const res = await this._interface.listInternalApiKeys();
45
44
  return res;
46
45
  });
47
- this._adminEmailTemplatesCache = createCache(async () => {
48
- return await this._interface.listEmailTemplates();
49
- });
50
46
  this._adminEmailThemeCache = createCache(async ([id]) => {
51
47
  return await this._interface.getEmailTheme(id);
52
48
  });
53
49
  this._adminEmailThemesCache = createCache(async () => {
54
50
  return await this._interface.listEmailThemes();
55
51
  });
52
+ this._adminEmailTemplatesCache = createCache(async () => {
53
+ return await this._interface.listInternalEmailTemplates();
54
+ });
56
55
  this._adminTeamPermissionDefinitionsCache = createCache(async () => {
57
56
  return await this._interface.listTeamPermissionDefinitions();
58
57
  });
@@ -65,8 +64,8 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
65
64
  this._metricsCache = createCache(async () => {
66
65
  return await this._interface.getMetrics();
67
66
  });
68
- this._emailThemePreviewCache = createCache(async ([theme, content]) => {
69
- return await this._interface.renderEmailThemePreview(theme, content);
67
+ this._emailPreviewCache = createCache(async ([themeId, themeTsxSource, templateId, templateTsxSource]) => {
68
+ return await this._interface.renderEmailPreview({ themeId, themeTsxSource, templateId, templateTsxSource });
70
69
  });
71
70
  }
72
71
  _adminOwnedProjectFromCrud(data, onRefresh) {
@@ -222,16 +221,6 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
222
221
  await this._refreshInternalApiKeys();
223
222
  return this._createInternalApiKeyFirstViewFromCrud(crud);
224
223
  }
225
- useEmailTemplates() {
226
- const crud = useAsyncCache(this._adminEmailTemplatesCache, [], "useEmailTemplates()");
227
- return useMemo(() => {
228
- return crud.map((j) => this._adminEmailTemplateFromCrud(j));
229
- }, [crud]);
230
- }
231
- async listEmailTemplates() {
232
- const crud = Result.orThrow(await this._adminEmailTemplatesCache.getOrWait([], "write-only"));
233
- return crud.map((j) => this._adminEmailTemplateFromCrud(j));
234
- }
235
224
  useEmailThemes() {
236
225
  const crud = useAsyncCache(this._adminEmailThemesCache, [], "useEmailThemes()");
237
226
  return useMemo(() => {
@@ -241,6 +230,17 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
241
230
  }));
242
231
  }, [crud]);
243
232
  }
233
+ useEmailTemplates() {
234
+ const crud = useAsyncCache(this._adminEmailTemplatesCache, [], "useEmailTemplates()");
235
+ return useMemo(() => {
236
+ return crud.map((template) => ({
237
+ id: template.id,
238
+ displayName: template.display_name,
239
+ themeId: template.theme_id,
240
+ tsxSource: template.tsx_source
241
+ }));
242
+ }, [crud]);
243
+ }
244
244
  async listEmailThemes() {
245
245
  const crud = Result.orThrow(await this._adminEmailThemesCache.getOrWait([], "write-only"));
246
246
  return crud.map((theme) => ({
@@ -248,13 +248,14 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
248
248
  displayName: theme.display_name
249
249
  }));
250
250
  }
251
- async updateEmailTemplate(type, data) {
252
- await this._interface.updateEmailTemplate(type, adminEmailTemplateUpdateOptionsToCrud(data));
253
- await this._adminEmailTemplatesCache.refresh([]);
254
- }
255
- async resetEmailTemplate(type) {
256
- await this._interface.resetEmailTemplate(type);
257
- await this._adminEmailTemplatesCache.refresh([]);
251
+ async listEmailTemplates() {
252
+ const crud = Result.orThrow(await this._adminEmailTemplatesCache.getOrWait([], "write-only"));
253
+ return crud.map((template) => ({
254
+ id: template.id,
255
+ displayName: template.display_name,
256
+ themeId: template.theme_id,
257
+ tsxSource: template.tsx_source
258
+ }));
258
259
  }
259
260
  async createTeamPermissionDefinition(data) {
260
261
  const crud = await this._interface.createTeamPermissionDefinition(adminTeamPermissionDefinitionCreateOptionsToCrud(data));
@@ -360,21 +361,30 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
360
361
  async sendSignInInvitationEmail(email, callbackUrl) {
361
362
  await this._interface.sendSignInInvitationEmail(email, callbackUrl);
362
363
  }
363
- async sendEmailThemeChatMessage(themeId, currentEmailTheme, messages, abortSignal) {
364
- return await this._interface.sendEmailThemeChatMessage(themeId, currentEmailTheme, messages, abortSignal);
364
+ async createEmailTemplate(displayName) {
365
+ const result = await this._interface.createEmailTemplate(displayName);
366
+ await this._adminEmailTemplatesCache.refresh([]);
367
+ return result;
368
+ }
369
+ async sendChatMessage(threadId, contextType, messages, abortSignal) {
370
+ return await this._interface.sendChatMessage(threadId, contextType, messages, abortSignal);
371
+ }
372
+ async saveChatMessage(threadId, message) {
373
+ await this._interface.saveChatMessage(threadId, message);
365
374
  }
366
- async listEmailThemeChatMessages(themeId) {
367
- return await this._interface.listEmailThemeChatMessages(themeId);
375
+ async listChatMessages(threadId) {
376
+ return await this._interface.listChatMessages(threadId);
368
377
  }
369
378
  async createEmailTheme(displayName) {
370
- this._adminEmailThemesCache.invalidate([]);
371
- return await this._interface.createEmailTheme(displayName);
379
+ const result = await this._interface.createEmailTheme(displayName);
380
+ await this._adminEmailThemesCache.refresh([]);
381
+ return result;
372
382
  }
373
- async getEmailThemePreview(themeId, content) {
374
- return (await this._interface.renderEmailThemePreview(themeId, content)).html;
383
+ async getEmailPreview(options) {
384
+ return (await this._interface.renderEmailPreview(options)).html;
375
385
  }
376
- useEmailThemePreview(themeId, content) {
377
- const crud = useAsyncCache(this._emailThemePreviewCache, [themeId, content], "useEmailThemePreview()");
386
+ useEmailPreview(options) {
387
+ const crud = useAsyncCache(this._emailPreviewCache, [options.themeId, options.themeTsxSource, options.templateId, options.templateTsxSource], "useEmailPreview()");
378
388
  return crud.html;
379
389
  }
380
390
  useEmailTheme(id) {
@@ -384,9 +394,13 @@ var _StackAdminAppImplIncomplete = class extends _StackServerAppImplIncomplete {
384
394
  tsxSource: crud.tsx_source
385
395
  };
386
396
  }
387
- async updateEmailTheme(id, tsxSource, previewHtml) {
388
- const result = await this._interface.updateEmailTheme(id, tsxSource, previewHtml);
389
- return result;
397
+ async updateEmailTheme(id, tsxSource) {
398
+ await this._interface.updateEmailTheme(id, tsxSource);
399
+ }
400
+ async updateEmailTemplate(id, tsxSource, themeId) {
401
+ const result = await this._interface.updateEmailTemplate(id, tsxSource, themeId);
402
+ await this._adminEmailTemplatesCache.refresh([]);
403
+ return { renderedHtml: result.rendered_html };
390
404
  }
391
405
  };
392
406
  export {