@stackframe/stack 2.8.12 → 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.
Files changed (228) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/components/api-key-dialogs.js +5 -4
  3. package/dist/components/api-key-dialogs.js.map +1 -1
  4. package/dist/components/credential-sign-in.js +4 -4
  5. package/dist/components/credential-sign-up.js +3 -3
  6. package/dist/components/elements/maybe-full-page.js +1 -1
  7. package/dist/components/elements/sidebar-layout.js +1 -1
  8. package/dist/components/magic-link-sign-in.js +3 -3
  9. package/dist/components/message-cards/known-error-message-card.js +2 -2
  10. package/dist/components/message-cards/message-card.js +1 -1
  11. package/dist/components/message-cards/predefined-message-card.js +3 -3
  12. package/dist/components/oauth-button-group.js +2 -2
  13. package/dist/components/oauth-button.js +27 -16
  14. package/dist/components/oauth-button.js.map +1 -1
  15. package/dist/components/passkey-button.js +2 -2
  16. package/dist/components/profile-image-editor.js +87 -34
  17. package/dist/components/profile-image-editor.js.map +1 -1
  18. package/dist/components/selected-team-switcher.js +41 -9
  19. package/dist/components/selected-team-switcher.js.map +1 -1
  20. package/dist/components/{iframe-preventer.js → use-in-iframe.js} +9 -19
  21. package/dist/components/use-in-iframe.js.map +1 -0
  22. package/dist/components/user-button.js +41 -8
  23. package/dist/components/user-button.js.map +1 -1
  24. package/dist/components-page/account-settings/active-sessions/active-sessions-page.js +57 -12
  25. package/dist/components-page/account-settings/active-sessions/active-sessions-page.js.map +1 -1
  26. package/dist/components-page/account-settings/api-keys/api-keys-page.js +100 -12
  27. package/dist/components-page/account-settings/api-keys/api-keys-page.js.map +1 -1
  28. package/dist/components-page/account-settings/editable-text.js +1 -1
  29. package/dist/components-page/account-settings/email-and-auth/email-and-auth-page.js +12 -12
  30. package/dist/components-page/account-settings/email-and-auth/email-and-auth-page.js.map +1 -1
  31. package/dist/components-page/account-settings/email-and-auth/emails-section.js +14 -5
  32. package/dist/components-page/account-settings/email-and-auth/emails-section.js.map +1 -1
  33. package/dist/components-page/account-settings/email-and-auth/mfa-section.js +18 -5
  34. package/dist/components-page/account-settings/email-and-auth/mfa-section.js.map +1 -1
  35. package/dist/components-page/account-settings/email-and-auth/otp-section.js +18 -5
  36. package/dist/components-page/account-settings/email-and-auth/otp-section.js.map +1 -1
  37. package/dist/components-page/account-settings/email-and-auth/passkey-section.js +19 -6
  38. package/dist/components-page/account-settings/email-and-auth/passkey-section.js.map +1 -1
  39. package/dist/components-page/account-settings/email-and-auth/password-section.js +20 -7
  40. package/dist/components-page/account-settings/email-and-auth/password-section.js.map +1 -1
  41. package/dist/components-page/account-settings/profile-page/profile-page.js +18 -8
  42. package/dist/components-page/account-settings/profile-page/profile-page.js.map +1 -1
  43. package/dist/components-page/account-settings/settings/delete-account-section.js +19 -10
  44. package/dist/components-page/account-settings/settings/delete-account-section.js.map +1 -1
  45. package/dist/components-page/account-settings/settings/settings-page.js +6 -6
  46. package/dist/components-page/account-settings/settings/settings-page.js.map +1 -1
  47. package/dist/components-page/account-settings/settings/sign-out-section.js +15 -6
  48. package/dist/components-page/account-settings/settings/sign-out-section.js.map +1 -1
  49. package/dist/components-page/account-settings/teams/leave-team-section.js +3 -3
  50. package/dist/components-page/account-settings/teams/team-api-keys-section.js +5 -5
  51. package/dist/components-page/account-settings/teams/team-creation-page.js +19 -10
  52. package/dist/components-page/account-settings/teams/team-creation-page.js.map +1 -1
  53. package/dist/components-page/account-settings/teams/team-display-name-section.js +4 -4
  54. package/dist/components-page/account-settings/teams/team-member-invitation-section.js +4 -4
  55. package/dist/components-page/account-settings/teams/team-member-list-section.js +3 -3
  56. package/dist/components-page/account-settings/teams/team-page.js +8 -8
  57. package/dist/components-page/account-settings/teams/team-profile-image-section.js +4 -4
  58. package/dist/components-page/account-settings/teams/team-profile-user-section.js +4 -4
  59. package/dist/components-page/account-settings.js +29 -21
  60. package/dist/components-page/account-settings.js.map +1 -1
  61. package/dist/components-page/auth-page.js +11 -12
  62. package/dist/components-page/auth-page.js.map +1 -1
  63. package/dist/components-page/cli-auth-confirm.js +3 -3
  64. package/dist/components-page/email-verification.js +3 -3
  65. package/dist/components-page/error-page.js +6 -6
  66. package/dist/components-page/error-page.js.map +1 -1
  67. package/dist/components-page/forgot-password.js +6 -6
  68. package/dist/components-page/magic-link-callback.js +4 -4
  69. package/dist/components-page/mfa.js +190 -0
  70. package/dist/components-page/mfa.js.map +1 -0
  71. package/dist/components-page/oauth-callback.js +4 -4
  72. package/dist/components-page/password-reset.js +6 -6
  73. package/dist/components-page/sign-in.js +3 -2
  74. package/dist/components-page/sign-in.js.map +1 -1
  75. package/dist/components-page/sign-out.js +2 -2
  76. package/dist/components-page/sign-up.js +1 -1
  77. package/dist/components-page/stack-handler.js +25 -14
  78. package/dist/components-page/stack-handler.js.map +1 -1
  79. package/dist/components-page/team-creation.js +4 -4
  80. package/dist/components-page/team-invitation.js +3 -3
  81. package/dist/esm/components/api-key-dialogs.js +5 -4
  82. package/dist/esm/components/api-key-dialogs.js.map +1 -1
  83. package/dist/esm/components/credential-sign-in.js +4 -4
  84. package/dist/esm/components/credential-sign-up.js +3 -3
  85. package/dist/esm/components/elements/maybe-full-page.js +1 -1
  86. package/dist/esm/components/elements/sidebar-layout.js +1 -1
  87. package/dist/esm/components/magic-link-sign-in.js +3 -3
  88. package/dist/esm/components/message-cards/known-error-message-card.js +2 -2
  89. package/dist/esm/components/message-cards/message-card.js +1 -1
  90. package/dist/esm/components/message-cards/predefined-message-card.js +3 -3
  91. package/dist/esm/components/oauth-button-group.js +2 -2
  92. package/dist/esm/components/oauth-button.js +28 -17
  93. package/dist/esm/components/oauth-button.js.map +1 -1
  94. package/dist/esm/components/passkey-button.js +2 -2
  95. package/dist/esm/components/profile-image-editor.js +86 -34
  96. package/dist/esm/components/profile-image-editor.js.map +1 -1
  97. package/dist/esm/components/selected-team-switcher.js +41 -9
  98. package/dist/esm/components/selected-team-switcher.js.map +1 -1
  99. package/dist/esm/components/use-in-iframe.js +18 -0
  100. package/dist/esm/components/use-in-iframe.js.map +1 -0
  101. package/dist/esm/components/user-button.js +41 -8
  102. package/dist/esm/components/user-button.js.map +1 -1
  103. package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js +57 -12
  104. package/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js.map +1 -1
  105. package/dist/esm/components-page/account-settings/api-keys/api-keys-page.js +100 -12
  106. package/dist/esm/components-page/account-settings/api-keys/api-keys-page.js.map +1 -1
  107. package/dist/esm/components-page/account-settings/editable-text.js +1 -1
  108. package/dist/esm/components-page/account-settings/email-and-auth/email-and-auth-page.js +12 -12
  109. package/dist/esm/components-page/account-settings/email-and-auth/email-and-auth-page.js.map +1 -1
  110. package/dist/esm/components-page/account-settings/email-and-auth/emails-section.js +14 -5
  111. package/dist/esm/components-page/account-settings/email-and-auth/emails-section.js.map +1 -1
  112. package/dist/esm/components-page/account-settings/email-and-auth/mfa-section.js +18 -5
  113. package/dist/esm/components-page/account-settings/email-and-auth/mfa-section.js.map +1 -1
  114. package/dist/esm/components-page/account-settings/email-and-auth/otp-section.js +18 -5
  115. package/dist/esm/components-page/account-settings/email-and-auth/otp-section.js.map +1 -1
  116. package/dist/esm/components-page/account-settings/email-and-auth/passkey-section.js +19 -6
  117. package/dist/esm/components-page/account-settings/email-and-auth/passkey-section.js.map +1 -1
  118. package/dist/esm/components-page/account-settings/email-and-auth/password-section.js +20 -7
  119. package/dist/esm/components-page/account-settings/email-and-auth/password-section.js.map +1 -1
  120. package/dist/esm/components-page/account-settings/profile-page/profile-page.js +18 -8
  121. package/dist/esm/components-page/account-settings/profile-page/profile-page.js.map +1 -1
  122. package/dist/esm/components-page/account-settings/settings/delete-account-section.js +19 -10
  123. package/dist/esm/components-page/account-settings/settings/delete-account-section.js.map +1 -1
  124. package/dist/esm/components-page/account-settings/settings/settings-page.js +6 -6
  125. package/dist/esm/components-page/account-settings/settings/settings-page.js.map +1 -1
  126. package/dist/esm/components-page/account-settings/settings/sign-out-section.js +15 -6
  127. package/dist/esm/components-page/account-settings/settings/sign-out-section.js.map +1 -1
  128. package/dist/esm/components-page/account-settings/teams/leave-team-section.js +3 -3
  129. package/dist/esm/components-page/account-settings/teams/team-api-keys-section.js +5 -5
  130. package/dist/esm/components-page/account-settings/teams/team-creation-page.js +19 -10
  131. package/dist/esm/components-page/account-settings/teams/team-creation-page.js.map +1 -1
  132. package/dist/esm/components-page/account-settings/teams/team-display-name-section.js +4 -4
  133. package/dist/esm/components-page/account-settings/teams/team-member-invitation-section.js +4 -4
  134. package/dist/esm/components-page/account-settings/teams/team-member-list-section.js +3 -3
  135. package/dist/esm/components-page/account-settings/teams/team-page.js +8 -8
  136. package/dist/esm/components-page/account-settings/teams/team-profile-image-section.js +4 -4
  137. package/dist/esm/components-page/account-settings/teams/team-profile-user-section.js +4 -4
  138. package/dist/esm/components-page/account-settings.js +29 -21
  139. package/dist/esm/components-page/account-settings.js.map +1 -1
  140. package/dist/esm/components-page/auth-page.js +11 -12
  141. package/dist/esm/components-page/auth-page.js.map +1 -1
  142. package/dist/esm/components-page/cli-auth-confirm.js +3 -3
  143. package/dist/esm/components-page/email-verification.js +3 -3
  144. package/dist/esm/components-page/error-page.js +6 -6
  145. package/dist/esm/components-page/error-page.js.map +1 -1
  146. package/dist/esm/components-page/forgot-password.js +6 -6
  147. package/dist/esm/components-page/magic-link-callback.js +4 -4
  148. package/dist/esm/components-page/mfa.js +174 -0
  149. package/dist/esm/components-page/mfa.js.map +1 -0
  150. package/dist/esm/components-page/oauth-callback.js +4 -4
  151. package/dist/esm/components-page/password-reset.js +6 -6
  152. package/dist/esm/components-page/sign-in.js +3 -2
  153. package/dist/esm/components-page/sign-in.js.map +1 -1
  154. package/dist/esm/components-page/sign-out.js +2 -2
  155. package/dist/esm/components-page/sign-up.js +1 -1
  156. package/dist/esm/components-page/stack-handler.js +25 -14
  157. package/dist/esm/components-page/stack-handler.js.map +1 -1
  158. package/dist/esm/components-page/team-creation.js +4 -4
  159. package/dist/esm/components-page/team-invitation.js +3 -3
  160. package/dist/esm/generated/global-css.js +1 -1
  161. package/dist/esm/generated/global-css.js.map +1 -1
  162. package/dist/esm/generated/quetzal-translations.js +3574 -2364
  163. package/dist/esm/generated/quetzal-translations.js.map +1 -1
  164. package/dist/esm/index.js +22 -22
  165. package/dist/esm/lib/auth.js +2 -2
  166. package/dist/esm/lib/cookie.js +1 -129
  167. package/dist/esm/lib/cookie.js.map +1 -1
  168. package/dist/esm/lib/hooks.js +1 -1
  169. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +8 -8
  170. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  171. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +52 -21
  172. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  173. package/dist/esm/lib/stack-app/apps/implementations/common.js +2 -1
  174. package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
  175. package/dist/esm/lib/stack-app/apps/implementations/index.js +3 -3
  176. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +8 -8
  177. package/dist/esm/lib/stack-app/apps/index.js +3 -3
  178. package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js +1 -1
  179. package/dist/esm/lib/stack-app/apps/interfaces/client-app.js +1 -1
  180. package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  181. package/dist/esm/lib/stack-app/apps/interfaces/server-app.js +1 -1
  182. package/dist/esm/lib/stack-app/common.js.map +1 -1
  183. package/dist/esm/lib/stack-app/index.js +2 -2
  184. package/dist/esm/lib/stack-app/internal-api-keys/index.js.map +1 -1
  185. package/dist/esm/lib/translations.js +1 -1
  186. package/dist/esm/providers/stack-provider-client.js +2 -2
  187. package/dist/esm/providers/stack-provider.js +3 -3
  188. package/dist/esm/providers/theme-provider.js +3 -3
  189. package/dist/esm/providers/translation-provider.js +2 -2
  190. package/dist/esm/utils/browser-script.js +1 -1
  191. package/dist/generated/global-css.js +1 -1
  192. package/dist/generated/global-css.js.map +1 -1
  193. package/dist/generated/quetzal-translations.js +3574 -2364
  194. package/dist/generated/quetzal-translations.js.map +1 -1
  195. package/dist/index.d.mts +71 -2
  196. package/dist/index.d.ts +71 -2
  197. package/dist/index.js +23 -23
  198. package/dist/index.js.map +1 -1
  199. package/dist/lib/auth.js +2 -2
  200. package/dist/lib/cookie.js +4 -132
  201. package/dist/lib/cookie.js.map +1 -1
  202. package/dist/lib/hooks.js +1 -1
  203. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +8 -8
  204. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  205. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +52 -21
  206. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  207. package/dist/lib/stack-app/apps/implementations/common.js +2 -1
  208. package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
  209. package/dist/lib/stack-app/apps/implementations/index.js +3 -3
  210. package/dist/lib/stack-app/apps/implementations/server-app-impl.js +8 -8
  211. package/dist/lib/stack-app/apps/index.js +3 -3
  212. package/dist/lib/stack-app/apps/interfaces/admin-app.js +1 -1
  213. package/dist/lib/stack-app/apps/interfaces/client-app.js +1 -1
  214. package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  215. package/dist/lib/stack-app/apps/interfaces/server-app.js +1 -1
  216. package/dist/lib/stack-app/common.js.map +1 -1
  217. package/dist/lib/stack-app/index.js +2 -2
  218. package/dist/lib/stack-app/internal-api-keys/index.js.map +1 -1
  219. package/dist/lib/translations.js +1 -1
  220. package/dist/providers/stack-provider-client.js +2 -2
  221. package/dist/providers/stack-provider.js +3 -3
  222. package/dist/providers/theme-provider.js +3 -3
  223. package/dist/providers/translation-provider.js +2 -2
  224. package/dist/utils/browser-script.js +1 -1
  225. package/package.json +5 -5
  226. package/dist/components/iframe-preventer.js.map +0 -1
  227. package/dist/esm/components/iframe-preventer.js +0 -28
  228. package/dist/esm/components/iframe-preventer.js.map +0 -1
@@ -9,9 +9,9 @@ import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises"
9
9
  import { ActionDialog, Button, CopyField, Input, Label, Typography } from "@stackframe/stack-ui";
10
10
  import { useState } from "react";
11
11
  import { useForm } from "react-hook-form";
12
- import { useUser } from "..";
13
- import { FormWarningText } from "../components/elements/form-warning";
14
- import { useTranslation } from "../lib/translations";
12
+ import { useUser } from "../index.js";
13
+ import { FormWarningText } from "../components/elements/form-warning.js";
14
+ import { useTranslation } from "../lib/translations.js";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
  var neverInMs = 1e3 * 60 * 60 * 24 * 365 * 200;
17
17
  var expiresInOptions = {
@@ -24,7 +24,7 @@ var expiresInOptions = {
24
24
  };
25
25
  function CreateApiKeyDialog(props) {
26
26
  const { t } = useTranslation();
27
- const user = useUser({ or: "redirect" });
27
+ const user = useUser({ or: props.mockMode ? "return-null" : "redirect" });
28
28
  const [loading, setLoading] = useState(false);
29
29
  const apiKeySchema = yupObject({
30
30
  description: yupString().defined().nonEmpty(t("Description is required")),
@@ -139,6 +139,7 @@ function ShowApiKeyDialog(props) {
139
139
  /* @__PURE__ */ jsx(
140
140
  CopyField,
141
141
  {
142
+ type: "input",
142
143
  monospace: true,
143
144
  value: props.apiKey?.value ?? "",
144
145
  label: t("Secret API Key")
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/api-key-dialogs.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { yupObject, yupString } from '@stackframe/stack-shared/dist/schema-fields';\nimport { captureError } from '@stackframe/stack-shared/dist/utils/errors';\nimport { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises';\nimport { ActionDialog, Button, CopyField, Input, Label, Typography } from '@stackframe/stack-ui';\nimport { useState } from \"react\";\nimport { useForm } from 'react-hook-form';\nimport * as yup from \"yup\";\nimport { useUser } from '..';\nimport { FormWarningText } from '../components/elements/form-warning';\nimport { ApiKey, ApiKeyCreationOptions, ApiKeyType } from \"../lib/stack-app/api-keys\";\nimport { useTranslation } from \"../lib/translations\";\n\n// Constants for expiration options\nexport const neverInMs = 1000 * 60 * 60 * 24 * 365 * 200;\nexport const expiresInOptions = {\n [1000 * 60 * 60 * 24 * 1]: \"1 day\",\n [1000 * 60 * 60 * 24 * 7]: \"7 days\",\n [1000 * 60 * 60 * 24 * 30]: \"30 days\",\n [1000 * 60 * 60 * 24 * 90]: \"90 days\",\n [1000 * 60 * 60 * 24 * 365]: \"1 year\",\n [neverInMs]: \"Never\",\n} as const;\n\n/**\n * Dialog for creating a new API key\n */\nexport function CreateApiKeyDialog<Type extends ApiKeyType = ApiKeyType>(props: {\n open: boolean,\n onOpenChange: (open: boolean) => void,\n onKeyCreated?: (key: ApiKey<Type, true>) => void,\n createApiKey: (data: ApiKeyCreationOptions<Type>) => Promise<ApiKey<Type, true>>,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: 'redirect' });\n const [loading, setLoading] = useState(false);\n\n const apiKeySchema = yupObject({\n description: yupString().defined().nonEmpty(t('Description is required')),\n expiresIn: yupString().defined(),\n });\n\n const { register, handleSubmit, formState: { errors }, reset } = useForm({\n resolver: yupResolver(apiKeySchema),\n defaultValues: {\n description: '',\n expiresIn: Object.keys(expiresInOptions)[2], // Default to 30 days\n }\n });\n\n const onSubmit = async (data: yup.InferType<typeof apiKeySchema>) => {\n setLoading(true);\n try {\n const expiresAt = new Date(Date.now() + parseInt(data.expiresIn));\n const apiKey = await props.createApiKey({\n description: data.description,\n expiresAt,\n });\n\n if (props.onKeyCreated) {\n props.onKeyCreated(apiKey);\n }\n\n reset();\n props.onOpenChange(false);\n } catch (error) {\n captureError(\"Failed to create API key\", { error });\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <ActionDialog\n open={props.open}\n onOpenChange={props.onOpenChange}\n title={t('Create API Key')}\n description={t('API keys grant programmatic access to your account.')}\n >\n <form\n onSubmit={(e) => {\n e.preventDefault();\n runAsynchronously(handleSubmit(onSubmit));\n }}\n className=\"space-y-4\"\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"description\">{t('Description')}</Label>\n <Input\n id=\"description\"\n placeholder={t('e.g. Development, Production, CI/CD')}\n {...register('description')}\n />\n {errors.description && <FormWarningText text={errors.description.message} />}\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"expiresIn\">{t('Expires In')}</Label>\n <select\n id=\"expiresIn\"\n className=\"w-full p-2 border border-input rounded-md bg-background\"\n {...register('expiresIn')}\n >\n {Object.entries(expiresInOptions).map(([value, label]) => (\n <option key={value} value={value}>{t(label)}</option>\n ))}\n </select>\n {errors.expiresIn && <FormWarningText text={errors.expiresIn.message} />}\n </div>\n\n <div className=\"flex justify-end gap-2 pt-4\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n reset();\n props.onOpenChange(false);\n }}\n >\n {t('Cancel')}\n </Button>\n <Button type=\"submit\" loading={loading}>\n {t('Create')}\n </Button>\n </div>\n </form>\n </ActionDialog>\n );\n}\n\n/**\n * Dialog for showing the newly created API key\n */\nexport function ShowApiKeyDialog<Type extends ApiKeyType = ApiKeyType>(props: {\n apiKey: ApiKey<Type, true> | null,\n onClose?: () => void,\n}) {\n const { t } = useTranslation();\n\n return (\n <ActionDialog\n open={!!props.apiKey}\n title={t(\"API Key\")}\n okButton={{ label: t(\"Close\") }}\n onClose={props.onClose}\n preventClose\n confirmText={t(\"I understand that I will not be able to view this key again.\")}\n >\n <div className=\"flex flex-col gap-4\">\n <Typography>\n {t(\"Here is your API key.\")}{\" \"}\n <span className=\"font-bold\">\n {t(\"Copy it to a safe place. You will not be able to view it again.\")}\n </span>\n </Typography>\n <CopyField\n monospace\n value={props.apiKey?.value ?? ''}\n label={t(\"Secret API Key\")}\n />\n </div>\n </ActionDialog>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/api-key-dialogs.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { yupObject, yupString } from '@stackframe/stack-shared/dist/schema-fields';\nimport { captureError } from '@stackframe/stack-shared/dist/utils/errors';\nimport { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises';\nimport { ActionDialog, Button, CopyField, Input, Label, Typography } from '@stackframe/stack-ui';\nimport { useState } from \"react\";\nimport { useForm } from 'react-hook-form';\nimport * as yup from \"yup\";\nimport { useUser } from '..';\nimport { FormWarningText } from '../components/elements/form-warning';\nimport { ApiKey, ApiKeyCreationOptions, ApiKeyType } from \"../lib/stack-app/api-keys\";\nimport { useTranslation } from \"../lib/translations\";\n\n// Constants for expiration options\nexport const neverInMs = 1000 * 60 * 60 * 24 * 365 * 200;\nexport const expiresInOptions = {\n [1000 * 60 * 60 * 24 * 1]: \"1 day\",\n [1000 * 60 * 60 * 24 * 7]: \"7 days\",\n [1000 * 60 * 60 * 24 * 30]: \"30 days\",\n [1000 * 60 * 60 * 24 * 90]: \"90 days\",\n [1000 * 60 * 60 * 24 * 365]: \"1 year\",\n [neverInMs]: \"Never\",\n} as const;\n\n/**\n * Dialog for creating a new API key\n */\nexport function CreateApiKeyDialog<Type extends ApiKeyType = ApiKeyType>(props: {\n open: boolean,\n onOpenChange: (open: boolean) => void,\n onKeyCreated?: (key: ApiKey<Type, true>) => void,\n createApiKey: (data: ApiKeyCreationOptions<Type>) => Promise<ApiKey<Type, true>>,\n mockMode?: boolean,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: props.mockMode ? 'return-null' : 'redirect' });\n const [loading, setLoading] = useState(false);\n\n const apiKeySchema = yupObject({\n description: yupString().defined().nonEmpty(t('Description is required')),\n expiresIn: yupString().defined(),\n });\n\n const { register, handleSubmit, formState: { errors }, reset } = useForm({\n resolver: yupResolver(apiKeySchema),\n defaultValues: {\n description: '',\n expiresIn: Object.keys(expiresInOptions)[2], // Default to 30 days\n }\n });\n\n const onSubmit = async (data: yup.InferType<typeof apiKeySchema>) => {\n setLoading(true);\n try {\n const expiresAt = new Date(Date.now() + parseInt(data.expiresIn));\n const apiKey = await props.createApiKey({\n description: data.description,\n expiresAt,\n });\n\n if (props.onKeyCreated) {\n props.onKeyCreated(apiKey);\n }\n\n reset();\n props.onOpenChange(false);\n } catch (error) {\n captureError(\"Failed to create API key\", { error });\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <ActionDialog\n open={props.open}\n onOpenChange={props.onOpenChange}\n title={t('Create API Key')}\n description={t('API keys grant programmatic access to your account.')}\n >\n <form\n onSubmit={(e) => {\n e.preventDefault();\n runAsynchronously(handleSubmit(onSubmit));\n }}\n className=\"space-y-4\"\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"description\">{t('Description')}</Label>\n <Input\n id=\"description\"\n placeholder={t('e.g. Development, Production, CI/CD')}\n {...register('description')}\n />\n {errors.description && <FormWarningText text={errors.description.message} />}\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"expiresIn\">{t('Expires In')}</Label>\n <select\n id=\"expiresIn\"\n className=\"w-full p-2 border border-input rounded-md bg-background\"\n {...register('expiresIn')}\n >\n {Object.entries(expiresInOptions).map(([value, label]) => (\n <option key={value} value={value}>{t(label)}</option>\n ))}\n </select>\n {errors.expiresIn && <FormWarningText text={errors.expiresIn.message} />}\n </div>\n\n <div className=\"flex justify-end gap-2 pt-4\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n reset();\n props.onOpenChange(false);\n }}\n >\n {t('Cancel')}\n </Button>\n <Button type=\"submit\" loading={loading}>\n {t('Create')}\n </Button>\n </div>\n </form>\n </ActionDialog>\n );\n}\n\n/**\n * Dialog for showing the newly created API key\n */\nexport function ShowApiKeyDialog<Type extends ApiKeyType = ApiKeyType>(props: {\n apiKey: ApiKey<Type, true> | null,\n onClose?: () => void,\n}) {\n const { t } = useTranslation();\n\n return (\n <ActionDialog\n open={!!props.apiKey}\n title={t(\"API Key\")}\n okButton={{ label: t(\"Close\") }}\n onClose={props.onClose}\n preventClose\n confirmText={t(\"I understand that I will not be able to view this key again.\")}\n >\n <div className=\"flex flex-col gap-4\">\n <Typography>\n {t(\"Here is your API key.\")}{\" \"}\n <span className=\"font-bold\">\n {t(\"Copy it to a safe place. You will not be able to view it again.\")}\n </span>\n </Typography>\n <CopyField\n type=\"input\"\n monospace\n value={props.apiKey?.value ?? ''}\n label={t(\"Secret API Key\")}\n />\n </div>\n </ActionDialog>\n );\n}\n"],"mappings":";;;AAOA,SAAS,mBAAmB;AAC5B,SAAS,WAAW,iBAAiB;AACrC,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AAClC,SAAS,cAAc,QAAQ,WAAW,OAAO,OAAO,kBAAkB;AAC1E,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAExB,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAEhC,SAAS,sBAAsB;AA4EvB,SACE,KADF;AAzED,IAAM,YAAY,MAAO,KAAK,KAAK,KAAK,MAAM;AAC9C,IAAM,mBAAmB;AAAA,EAC9B,CAAC,MAAO,KAAK,KAAK,KAAK,CAAC,GAAG;AAAA,EAC3B,CAAC,MAAO,KAAK,KAAK,KAAK,CAAC,GAAG;AAAA,EAC3B,CAAC,MAAO,KAAK,KAAK,KAAK,EAAE,GAAG;AAAA,EAC5B,CAAC,MAAO,KAAK,KAAK,KAAK,EAAE,GAAG;AAAA,EAC5B,CAAC,MAAO,KAAK,KAAK,KAAK,GAAG,GAAG;AAAA,EAC7B,CAAC,SAAS,GAAG;AACf;AAKO,SAAS,mBAAyD,OAMtE;AACD,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,OAAO,QAAQ,EAAE,IAAI,MAAM,WAAW,gBAAgB,WAAW,CAAC;AACxE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,QAAM,eAAe,UAAU;AAAA,IAC7B,aAAa,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,yBAAyB,CAAC;AAAA,IACxE,WAAW,UAAU,EAAE,QAAQ;AAAA,EACjC,CAAC;AAED,QAAM,EAAE,UAAU,cAAc,WAAW,EAAE,OAAO,GAAG,MAAM,IAAI,QAAQ;AAAA,IACvE,UAAU,YAAY,YAAY;AAAA,IAClC,eAAe;AAAA,MACb,aAAa;AAAA,MACb,WAAW,OAAO,KAAK,gBAAgB,EAAE,CAAC;AAAA;AAAA,IAC5C;AAAA,EACF,CAAC;AAED,QAAM,WAAW,OAAO,SAA6C;AACnE,eAAW,IAAI;AACf,QAAI;AACF,YAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,CAAC;AAChE,YAAM,SAAS,MAAM,MAAM,aAAa;AAAA,QACtC,aAAa,KAAK;AAAA,QAClB;AAAA,MACF,CAAC;AAED,UAAI,MAAM,cAAc;AACtB,cAAM,aAAa,MAAM;AAAA,MAC3B;AAEA,YAAM;AACN,YAAM,aAAa,KAAK;AAAA,IAC1B,SAAS,OAAO;AACd,mBAAa,4BAA4B,EAAE,MAAM,CAAC;AAAA,IACpD,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB,OAAO,EAAE,gBAAgB;AAAA,MACzB,aAAa,EAAE,qDAAqD;AAAA,MAEpE;AAAA,QAAC;AAAA;AAAA,UACC,UAAU,CAAC,MAAM;AACf,cAAE,eAAe;AACjB,8BAAkB,aAAa,QAAQ,CAAC;AAAA,UAC1C;AAAA,UACA,WAAU;AAAA,UAEV;AAAA,iCAAC,SAAI,WAAU,aACb;AAAA,kCAAC,SAAM,SAAQ,eAAe,YAAE,aAAa,GAAE;AAAA,cAC/C;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,aAAa,EAAE,qCAAqC;AAAA,kBACnD,GAAG,SAAS,aAAa;AAAA;AAAA,cAC5B;AAAA,cACC,OAAO,eAAe,oBAAC,mBAAgB,MAAM,OAAO,YAAY,SAAS;AAAA,eAC5E;AAAA,YAEA,qBAAC,SAAI,WAAU,aACb;AAAA,kCAAC,SAAM,SAAQ,aAAa,YAAE,YAAY,GAAE;AAAA,cAC5C;AAAA,gBAAC;AAAA;AAAA,kBACC,IAAG;AAAA,kBACH,WAAU;AAAA,kBACT,GAAG,SAAS,WAAW;AAAA,kBAEvB,iBAAO,QAAQ,gBAAgB,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,MAClD,oBAAC,YAAmB,OAAe,YAAE,KAAK,KAA7B,KAA+B,CAC7C;AAAA;AAAA,cACH;AAAA,cACC,OAAO,aAAa,oBAAC,mBAAgB,MAAM,OAAO,UAAU,SAAS;AAAA,eACxE;AAAA,YAEA,qBAAC,SAAI,WAAU,+BACb;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAQ;AAAA,kBACR,SAAS,MAAM;AACb,0BAAM;AACN,0BAAM,aAAa,KAAK;AAAA,kBAC1B;AAAA,kBAEC,YAAE,QAAQ;AAAA;AAAA,cACb;AAAA,cACA,oBAAC,UAAO,MAAK,UAAS,SACnB,YAAE,QAAQ,GACb;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAKO,SAAS,iBAAuD,OAGpE;AACD,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,CAAC,CAAC,MAAM;AAAA,MACd,OAAO,EAAE,SAAS;AAAA,MAClB,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE;AAAA,MAC9B,SAAS,MAAM;AAAA,MACf,cAAY;AAAA,MACZ,aAAa,EAAE,8DAA8D;AAAA,MAE7E,+BAAC,SAAI,WAAU,uBACb;AAAA,6BAAC,cACE;AAAA,YAAE,uBAAuB;AAAA,UAAG;AAAA,UAC7B,oBAAC,UAAK,WAAU,aACb,YAAE,iEAAiE,GACtE;AAAA,WACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAS;AAAA,YACT,OAAO,MAAM,QAAQ,SAAS;AAAA,YAC9B,OAAO,EAAE,gBAAgB;AAAA;AAAA,QAC3B;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -8,10 +8,10 @@ import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/
8
8
  import { Button, Input, Label, PasswordInput } from "@stackframe/stack-ui";
9
9
  import { useState } from "react";
10
10
  import { useForm } from "react-hook-form";
11
- import { useStackApp } from "..";
12
- import { useTranslation } from "../lib/translations";
13
- import { FormWarningText } from "./elements/form-warning";
14
- import { StyledLink } from "./link";
11
+ import { useStackApp } from "../index.js";
12
+ import { useTranslation } from "../lib/translations.js";
13
+ import { FormWarningText } from "./elements/form-warning.js";
14
+ import { StyledLink } from "./link.js";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
  function CredentialSignIn() {
17
17
  const { t } = useTranslation();
@@ -10,9 +10,9 @@ import { Button, Input, Label, PasswordInput } from "@stackframe/stack-ui";
10
10
  import { useState } from "react";
11
11
  import { useForm } from "react-hook-form";
12
12
  import * as yup from "yup";
13
- import { useStackApp } from "..";
14
- import { useTranslation } from "../lib/translations";
15
- import { FormWarningText } from "./elements/form-warning";
13
+ import { useStackApp } from "../index.js";
14
+ import { useTranslation } from "../lib/translations.js";
15
+ import { FormWarningText } from "./elements/form-warning.js";
16
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
17
  function CredentialSignUp(props) {
18
18
  const { t } = useTranslation();
@@ -3,7 +3,7 @@
3
3
 
4
4
  // src/components/elements/maybe-full-page.tsx
5
5
  import { useId } from "react";
6
- import { SsrScript } from "./ssr-layout-effect";
6
+ import { SsrScript } from "./ssr-layout-effect.js";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  function MaybeFullPage({
9
9
  children,
@@ -5,7 +5,7 @@
5
5
  import { useHash } from "@stackframe/stack-shared/dist/hooks/use-hash";
6
6
  import { Button, Typography, cn } from "@stackframe/stack-ui";
7
7
  import { XIcon } from "lucide-react";
8
- import { useStackApp } from "../..";
8
+ import { useStackApp } from "../../index.js";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  function SidebarLayout(props) {
11
11
  const hash = useHash();
@@ -9,9 +9,9 @@ import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/
9
9
  import { Button, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, Typography } from "@stackframe/stack-ui";
10
10
  import { useEffect, useState } from "react";
11
11
  import { useForm } from "react-hook-form";
12
- import { useStackApp } from "..";
13
- import { useTranslation } from "../lib/translations";
14
- import { FormWarningText } from "./elements/form-warning";
12
+ import { useStackApp } from "../index.js";
13
+ import { useTranslation } from "../lib/translations.js";
14
+ import { FormWarningText } from "./elements/form-warning.js";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
  function OTP(props) {
17
17
  const { t } = useTranslation();
@@ -3,8 +3,8 @@
3
3
 
4
4
  // src/components/message-cards/known-error-message-card.tsx
5
5
  import { Typography } from "@stackframe/stack-ui";
6
- import { useStackApp } from "../..";
7
- import { MessageCard } from "./message-card";
6
+ import { useStackApp } from "../../index.js";
7
+ import { MessageCard } from "./message-card.js";
8
8
  import { jsxs } from "react/jsx-runtime";
9
9
  function KnownErrorMessageCard({
10
10
  error,
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
 
4
4
  // src/components/message-cards/message-card.tsx
5
- import { MaybeFullPage } from "../elements/maybe-full-page";
5
+ import { MaybeFullPage } from "../elements/maybe-full-page.js";
6
6
  import { Button, Typography } from "@stackframe/stack-ui";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  function MessageCard({ fullPage = false, ...props }) {
@@ -3,9 +3,9 @@
3
3
 
4
4
  // src/components/message-cards/predefined-message-card.tsx
5
5
  import { Typography } from "@stackframe/stack-ui";
6
- import { useStackApp } from "../..";
7
- import { useTranslation } from "../../lib/translations";
8
- import { MessageCard } from "./message-card";
6
+ import { useStackApp } from "../../index.js";
7
+ import { useTranslation } from "../../lib/translations.js";
8
+ import { MessageCard } from "./message-card.js";
9
9
  import { jsx } from "react/jsx-runtime";
10
10
  function PredefinedMessageCard({
11
11
  type,
@@ -2,8 +2,8 @@
2
2
  "use client";
3
3
 
4
4
  // src/components/oauth-button-group.tsx
5
- import { useStackApp } from "../lib/hooks";
6
- import { OAuthButton } from "./oauth-button";
5
+ import { useStackApp } from "../lib/hooks.js";
6
+ import { OAuthButton } from "./oauth-button.js";
7
7
  import { jsx } from "react/jsx-runtime";
8
8
  function OAuthButtonGroup({
9
9
  type,
@@ -2,11 +2,12 @@
2
2
  "use client";
3
3
 
4
4
  // src/components/oauth-button.tsx
5
- import { BrandIcons, Button } from "@stackframe/stack-ui";
5
+ import { BrandIcons, Button, SimpleTooltip } from "@stackframe/stack-ui";
6
6
  import Color from "color";
7
7
  import { useEffect, useId, useState } from "react";
8
- import { useStackApp } from "..";
9
- import { useTranslation } from "../lib/translations";
8
+ import { useStackApp } from "../index.js";
9
+ import { useTranslation } from "../lib/translations.js";
10
+ import { useInIframe } from "./use-in-iframe.js";
10
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
12
  var iconSize = 22;
12
13
  var changeColor = (c, value) => {
@@ -23,6 +24,7 @@ function OAuthButton({
23
24
  const { t } = useTranslation();
24
25
  const stackApp = useStackApp();
25
26
  const styleId = useId().replaceAll(":", "-");
27
+ const isIframe = useInIframe();
26
28
  const [lastUsed, setLastUsed] = useState(null);
27
29
  useEffect(() => {
28
30
  setLastUsed(localStorage.getItem("_STACK_AUTH.lastUsed"));
@@ -152,21 +154,30 @@ function OAuthButton({
152
154
  `;
153
155
  return /* @__PURE__ */ jsxs(Fragment, { children: [
154
156
  /* @__PURE__ */ jsx("style", { children: styleSheet }),
155
- /* @__PURE__ */ jsxs(
156
- Button,
157
+ /* @__PURE__ */ jsx(
158
+ SimpleTooltip,
157
159
  {
158
- onClick: async () => {
159
- localStorage.setItem("_STACK_AUTH.lastUsed", provider);
160
- await stackApp.signInWithOAuth(provider);
161
- },
162
- className: `stack-oauth-button-${styleId} stack-scope relative`,
163
- children: [
164
- !isMock && lastUsed === provider && /* @__PURE__ */ jsx("span", { className: "absolute -top-2 -right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-md", children: "last" }),
165
- /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full gap-4", children: [
166
- style.icon,
167
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: type === "sign-up" ? t("Sign up with {provider}", { provider: style.name }) : t("Sign in with {provider}", { provider: style.name }) })
168
- ] })
169
- ]
160
+ disabled: !isIframe,
161
+ tooltip: isIframe ? "This auth provider is not supported in an iframe for security reasons." : void 0,
162
+ className: "stack-scope w-full inline-flex",
163
+ children: /* @__PURE__ */ jsxs(
164
+ Button,
165
+ {
166
+ onClick: async () => {
167
+ localStorage.setItem("_STACK_AUTH.lastUsed", provider);
168
+ await stackApp.signInWithOAuth(provider);
169
+ },
170
+ className: `stack-oauth-button-${styleId} stack-scope relative w-full`,
171
+ disabled: isIframe,
172
+ children: [
173
+ !isMock && lastUsed === provider && /* @__PURE__ */ jsx("span", { className: "absolute -top-2 -right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-md", children: "last" }),
174
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full gap-4", children: [
175
+ style.icon,
176
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: type === "sign-up" ? t("Sign up with {provider}", { provider: style.name }) : t("Sign in with {provider}", { provider: style.name }) })
177
+ ] })
178
+ ]
179
+ }
180
+ )
170
181
  }
171
182
  )
172
183
  ] });
@@ -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 } from '@stackframe/stack-ui';\nimport Color from 'color';\nimport { useEffect, useId, useState } from 'react';\nimport { useStackApp } from '..';\nimport { useTranslation } from '../lib/translations';\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\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 <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`}\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 </>\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 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":[]}
@@ -4,8 +4,8 @@
4
4
  // src/components/passkey-button.tsx
5
5
  import { Button } from "@stackframe/stack-ui";
6
6
  import { useId } from "react";
7
- import { useStackApp } from "..";
8
- import { useTranslation } from "../lib/translations";
7
+ import { useStackApp } from "../index.js";
8
+ import { useTranslation } from "../lib/translations.js";
9
9
  import { KeyRound } from "lucide-react";
10
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
11
  function PasskeyButton({
@@ -4,10 +4,10 @@ import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/
4
4
  import { Button, Slider, Typography } from "@stackframe/stack-ui";
5
5
  import imageCompression from "browser-image-compression";
6
6
  import { Upload } from "lucide-react";
7
- import { useRef, useState } from "react";
8
- import AvatarEditor from "react-avatar-editor";
9
- import { useTranslation } from "../lib/translations";
10
- import { UserAvatar } from "./elements/user-avatar";
7
+ import { useCallback, useState } from "react";
8
+ import Cropper from "react-easy-crop";
9
+ import { useTranslation } from "../lib/translations.js";
10
+ import { UserAvatar } from "./elements/user-avatar.js";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
12
  async function checkImageUrl(url) {
13
13
  try {
@@ -18,17 +18,64 @@ async function checkImageUrl(url) {
18
18
  return false;
19
19
  }
20
20
  }
21
+ var createImage = (url) => new Promise((resolve, reject) => {
22
+ const image = new Image();
23
+ image.addEventListener("load", () => resolve(image));
24
+ image.addEventListener("error", (error) => reject(error));
25
+ image.setAttribute("crossOrigin", "anonymous");
26
+ image.src = url;
27
+ });
28
+ async function getCroppedImg(imageSrc, pixelCrop) {
29
+ const image = await createImage(imageSrc);
30
+ const canvas = document.createElement("canvas");
31
+ const ctx = canvas.getContext("2d");
32
+ if (!ctx) {
33
+ return null;
34
+ }
35
+ const safeCrop = {
36
+ x: Math.max(0, pixelCrop.x),
37
+ y: Math.max(0, pixelCrop.y),
38
+ width: Math.max(1, pixelCrop.width),
39
+ height: Math.max(1, pixelCrop.height)
40
+ };
41
+ canvas.width = safeCrop.width;
42
+ canvas.height = safeCrop.height;
43
+ ctx.drawImage(
44
+ image,
45
+ safeCrop.x,
46
+ safeCrop.y,
47
+ safeCrop.width,
48
+ safeCrop.height,
49
+ 0,
50
+ 0,
51
+ safeCrop.width,
52
+ safeCrop.height
53
+ );
54
+ return canvas.toDataURL("image/jpeg");
55
+ }
21
56
  function ProfileImageEditor(props) {
22
57
  const { t } = useTranslation();
23
- const cropRef = useRef(null);
24
- const [slideValue, setSlideValue] = useState(1);
25
58
  const [rawUrl, setRawUrl] = useState(null);
26
59
  const [error, setError] = useState(null);
60
+ const [crop, setCrop] = useState({ x: 0, y: 0 });
61
+ const [zoom, setZoom] = useState(1);
62
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
27
63
  function reset() {
28
- setSlideValue(1);
29
64
  setRawUrl(null);
30
65
  setError(null);
66
+ setCrop({ x: 0, y: 0 });
67
+ setZoom(1);
68
+ setCroppedAreaPixels(null);
31
69
  }
70
+ const onCropChange = useCallback((crop2) => {
71
+ setCrop(crop2);
72
+ }, []);
73
+ const onCropComplete = useCallback((croppedArea, croppedAreaPixels2) => {
74
+ setCroppedAreaPixels(croppedAreaPixels2);
75
+ }, []);
76
+ const onZoomChange = useCallback((zoom2) => {
77
+ setZoom(zoom2);
78
+ }, []);
32
79
  function upload() {
33
80
  const input = document.createElement("input");
34
81
  input.type = "file";
@@ -65,28 +112,28 @@ function ProfileImageEditor(props) {
65
112
  ] });
66
113
  }
67
114
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
68
- /* @__PURE__ */ jsx(
69
- AvatarEditor,
115
+ /* @__PURE__ */ jsx("div", { className: "relative w-64 h-64", children: /* @__PURE__ */ jsx(
116
+ Cropper,
70
117
  {
71
- ref: cropRef,
72
118
  image: rawUrl || props.user.profileImageUrl || "",
73
- borderRadius: 1e3,
74
- color: [0, 0, 0, 0.72],
75
- scale: slideValue,
76
- rotate: 0,
77
- border: 20,
78
- className: "border"
119
+ crop,
120
+ zoom,
121
+ aspect: 1,
122
+ cropShape: "round",
123
+ showGrid: false,
124
+ onCropChange,
125
+ onCropComplete,
126
+ onZoomChange
79
127
  }
80
- ),
128
+ ) }),
81
129
  /* @__PURE__ */ jsx(
82
130
  Slider,
83
131
  {
84
132
  min: 1,
85
- max: 5,
133
+ max: 3,
86
134
  step: 0.1,
87
- defaultValue: [slideValue],
88
- value: [slideValue],
89
- onValueChange: (v) => setSlideValue(v[0])
135
+ value: [zoom],
136
+ onValueChange: (v) => onZoomChange(v[0])
90
137
  }
91
138
  ),
92
139
  /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2", children: [
@@ -94,18 +141,22 @@ function ProfileImageEditor(props) {
94
141
  Button,
95
142
  {
96
143
  onClick: async () => {
97
- if (cropRef.current && rawUrl) {
98
- const croppedUrl = cropRef.current.getImage().toDataURL("image/jpeg");
99
- const compressedFile = await imageCompression(
100
- await imageCompression.getFilefromDataUrl(croppedUrl, "profile-image"),
101
- {
102
- maxSizeMB: 0.1,
103
- fileType: "image/jpeg"
104
- }
105
- );
106
- const compressedUrl = await imageCompression.getDataUrlFromFile(compressedFile);
107
- await props.onProfileImageUrlChange(compressedUrl);
108
- reset();
144
+ if (rawUrl && croppedAreaPixels) {
145
+ const croppedImageUrl = await getCroppedImg(rawUrl, croppedAreaPixels);
146
+ if (croppedImageUrl) {
147
+ const compressedFile = await imageCompression(
148
+ await imageCompression.getFilefromDataUrl(croppedImageUrl, "profile-image"),
149
+ {
150
+ maxSizeMB: 0.1,
151
+ fileType: "image/jpeg"
152
+ }
153
+ );
154
+ const compressedUrl = await imageCompression.getDataUrlFromFile(compressedFile);
155
+ await props.onProfileImageUrlChange(compressedUrl);
156
+ reset();
157
+ } else {
158
+ setError(t("Could not crop image."));
159
+ }
109
160
  }
110
161
  },
111
162
  children: t("Save")
@@ -124,6 +175,7 @@ function ProfileImageEditor(props) {
124
175
  }
125
176
  export {
126
177
  ProfileImageEditor,
127
- checkImageUrl
178
+ checkImageUrl,
179
+ getCroppedImg
128
180
  };
129
181
  //# sourceMappingURL=profile-image-editor.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/profile-image-editor.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { fileToBase64 } from '@stackframe/stack-shared/dist/utils/base64';\nimport { runAsynchronouslyWithAlert } from '@stackframe/stack-shared/dist/utils/promises';\nimport { Button, Slider, Typography } from '@stackframe/stack-ui';\nimport imageCompression from 'browser-image-compression';\nimport { Upload } from 'lucide-react';\nimport { ComponentProps, useRef, useState } from 'react';\nimport AvatarEditor from 'react-avatar-editor';\nimport { useTranslation } from '../lib/translations';\nimport { UserAvatar } from './elements/user-avatar';\n\nexport async function checkImageUrl(url: string){\n try {\n const res = await fetch(url, { method: 'HEAD' });\n const buff = await res.blob();\n return buff.type.startsWith('image/');\n } catch (e) {\n return false;\n }\n}\n\nexport function ProfileImageEditor(props: {\n user: NonNullable<ComponentProps<typeof UserAvatar>['user']>,\n onProfileImageUrlChange: (profileImageUrl: string | null) => void | Promise<void>,\n}) {\n const { t } = useTranslation();\n const cropRef = useRef<AvatarEditor>(null);\n const [slideValue, setSlideValue] = useState(1);\n const [rawUrl, setRawUrl] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n function reset() {\n setSlideValue(1);\n setRawUrl(null);\n setError(null);\n }\n\n function upload() {\n const input = document.createElement('input');\n input.type = 'file';\n input.onchange = (e) => {\n const file = (e.target as HTMLInputElement).files?.[0];\n if (!file) return;\n runAsynchronouslyWithAlert(async () => {\n const rawUrl = await fileToBase64(file);\n if (await checkImageUrl(rawUrl)) {\n setRawUrl(rawUrl);\n setError(null);\n } else {\n setError(t('Invalid image'));\n }\n input.remove();\n });\n };\n input.click();\n }\n\n if (!rawUrl) {\n return <div className='flex flex-col'>\n <div className='cursor-pointer relative' onClick={upload}>\n <UserAvatar\n size={60}\n user={props.user}\n border\n />\n <div className='absolute top-0 left-0 h-[60px] w-[60px] bg-gray-500/20 backdrop-blur-sm items-center justify-center rounded-full flex opacity-0 hover:opacity-100 transition-opacity'>\n <div className='bg-background p-2 rounded-full'>\n <Upload className='h-5 w-5' />\n </div>\n </div>\n </div>\n {error && <Typography variant='destructive' type='label'>{error}</Typography>}\n </div>;\n }\n\n return (\n <div className='flex flex-col items-center gap-4'>\n <AvatarEditor\n ref={cropRef}\n image={rawUrl || props.user.profileImageUrl || \"\"}\n borderRadius={1000}\n color={[0, 0, 0, 0.72]}\n scale={slideValue}\n rotate={0}\n border={20}\n className='border'\n />\n <Slider\n min={1}\n max={5}\n step={0.1}\n defaultValue={[slideValue]}\n value={[slideValue]}\n onValueChange={(v) => setSlideValue(v[0])}\n />\n\n <div className='flex flex-row gap-2'>\n <Button\n onClick={async () => {\n if (cropRef.current && rawUrl) {\n const croppedUrl = cropRef.current.getImage().toDataURL('image/jpeg');\n const compressedFile = await imageCompression(\n await imageCompression.getFilefromDataUrl(croppedUrl, 'profile-image'),\n {\n maxSizeMB: 0.1,\n fileType: \"image/jpeg\",\n }\n );\n const compressedUrl = await imageCompression.getDataUrlFromFile(compressedFile);\n await props.onProfileImageUrlChange(compressedUrl);\n reset();\n }\n }}\n >\n {t('Save')}\n </Button>\n <Button\n variant=\"secondary\"\n onClick={reset}\n >\n {t('Cancel')}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["rawUrl"]}
1
+ {"version":3,"sources":["../../../src/components/profile-image-editor.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { fileToBase64 } from '@stackframe/stack-shared/dist/utils/base64';\nimport { runAsynchronouslyWithAlert } from '@stackframe/stack-shared/dist/utils/promises';\nimport { Button, Slider, Typography } from '@stackframe/stack-ui';\nimport imageCompression from 'browser-image-compression';\nimport { Upload } from 'lucide-react';\nimport { ComponentProps, useCallback, useState } from 'react';\nimport Cropper, { Area } from 'react-easy-crop';\nimport { useTranslation } from '../lib/translations';\nimport { UserAvatar } from './elements/user-avatar';\n\nexport async function checkImageUrl(url: string){\n try {\n const res = await fetch(url, { method: 'HEAD' });\n const buff = await res.blob();\n return buff.type.startsWith('image/');\n } catch (e) {\n return false;\n }\n}\n\nconst createImage = (url: string): Promise<HTMLImageElement> =>\n new Promise((resolve, reject) => {\n const image = new Image();\n image.addEventListener('load', () => resolve(image));\n image.addEventListener('error', (error) => reject(error));\n image.setAttribute('crossOrigin', 'anonymous');\n image.src = url;\n });\n\nexport async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise<string | null> {\n const image = await createImage(imageSrc);\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n\n if (!ctx) {\n return null;\n }\n\n const safeCrop = {\n x: Math.max(0, pixelCrop.x),\n y: Math.max(0, pixelCrop.y),\n width: Math.max(1, pixelCrop.width),\n height: Math.max(1, pixelCrop.height),\n };\n\n canvas.width = safeCrop.width;\n canvas.height = safeCrop.height;\n\n ctx.drawImage(\n image,\n safeCrop.x,\n safeCrop.y,\n safeCrop.width,\n safeCrop.height,\n 0,\n 0,\n safeCrop.width,\n safeCrop.height\n );\n\n return canvas.toDataURL('image/jpeg');\n}\n\nexport function ProfileImageEditor(props: {\n user: NonNullable<ComponentProps<typeof UserAvatar>['user']>,\n onProfileImageUrlChange: (profileImageUrl: string | null) => void | Promise<void>,\n}) {\n const { t } = useTranslation();\n const [rawUrl, setRawUrl] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [crop, setCrop] = useState({ x: 0, y: 0 });\n const [zoom, setZoom] = useState(1);\n const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);\n\n function reset() {\n setRawUrl(null);\n setError(null);\n setCrop({ x: 0, y: 0 });\n setZoom(1);\n setCroppedAreaPixels(null);\n }\n\n const onCropChange = useCallback((crop: { x: number, y: number }) => {\n setCrop(crop);\n }, []);\n\n const onCropComplete = useCallback((croppedArea: Area, croppedAreaPixels: Area) => {\n setCroppedAreaPixels(croppedAreaPixels);\n }, []);\n\n const onZoomChange = useCallback((zoom: number) => {\n setZoom(zoom);\n }, []);\n\n\n function upload() {\n const input = document.createElement('input');\n input.type = 'file';\n input.onchange = (e) => {\n const file = (e.target as HTMLInputElement).files?.[0];\n if (!file) return;\n runAsynchronouslyWithAlert(async () => {\n const rawUrl = await fileToBase64(file);\n if (await checkImageUrl(rawUrl)) {\n setRawUrl(rawUrl);\n setError(null);\n } else {\n setError(t('Invalid image'));\n }\n input.remove();\n });\n };\n input.click();\n }\n\n if (!rawUrl) {\n return <div className='flex flex-col'>\n <div className='cursor-pointer relative' onClick={upload}>\n <UserAvatar\n size={60}\n user={props.user}\n border\n />\n <div className='absolute top-0 left-0 h-[60px] w-[60px] bg-gray-500/20 backdrop-blur-sm items-center justify-center rounded-full flex opacity-0 hover:opacity-100 transition-opacity'>\n <div className='bg-background p-2 rounded-full'>\n <Upload className='h-5 w-5' />\n </div>\n </div>\n </div>\n {error && <Typography variant='destructive' type='label'>{error}</Typography>}\n </div>;\n }\n\n return (\n <div className='flex flex-col items-center gap-4'>\n <div className='relative w-64 h-64'>\n <Cropper\n image={rawUrl || props.user.profileImageUrl || \"\"}\n crop={crop}\n zoom={zoom}\n aspect={1}\n cropShape=\"round\"\n showGrid={false}\n onCropChange={onCropChange}\n onCropComplete={onCropComplete}\n onZoomChange={onZoomChange}\n />\n </div>\n <Slider\n min={1}\n max={3}\n step={0.1}\n value={[zoom]}\n onValueChange={(v) => onZoomChange(v[0])}\n />\n\n <div className='flex flex-row gap-2'>\n <Button\n onClick={async () => {\n if (rawUrl && croppedAreaPixels) {\n const croppedImageUrl = await getCroppedImg(rawUrl, croppedAreaPixels);\n if (croppedImageUrl) {\n const compressedFile = await imageCompression(\n await imageCompression.getFilefromDataUrl(croppedImageUrl, 'profile-image'),\n {\n maxSizeMB: 0.1,\n fileType: \"image/jpeg\",\n }\n );\n const compressedUrl = await imageCompression.getDataUrlFromFile(compressedFile);\n await props.onProfileImageUrlChange(compressedUrl);\n reset();\n } else {\n setError(t('Could not crop image.'));\n }\n }\n }}\n >\n {t('Save')}\n </Button>\n <Button\n variant=\"secondary\"\n onClick={reset}\n >\n {t('Cancel')}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["crop","croppedAreaPixels","zoom","rawUrl"]}
@@ -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 app = useStackApp();
34
- const user = useUser();
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(Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: () => navigate(`${app.urls.accountSettings}#team-${user.selectedTeam?.id}`), children: /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }) })
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: () => navigate(`${app.urls.accountSettings}#team-creation`),
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: [