Initial commit of eigent-main

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

View file

@ -0,0 +1,157 @@
"use client";
"use client";
// src/components/api-key-dialogs.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { ActionDialog, Button, CopyField, Input, Label, Typography } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useUser } from "..";
import { FormWarningText } from "../components/elements/form-warning";
import { useTranslation } from "../lib/translations";
import { jsx, jsxs } from "react/jsx-runtime";
var neverInMs = 1e3 * 60 * 60 * 24 * 365 * 200;
var expiresInOptions = {
[1e3 * 60 * 60 * 24 * 1]: "1 day",
[1e3 * 60 * 60 * 24 * 7]: "7 days",
[1e3 * 60 * 60 * 24 * 30]: "30 days",
[1e3 * 60 * 60 * 24 * 90]: "90 days",
[1e3 * 60 * 60 * 24 * 365]: "1 year",
[neverInMs]: "Never"
};
function CreateApiKeyDialog(props) {
const { t } = useTranslation();
const user = useUser({ or: "redirect" });
const [loading, setLoading] = useState(false);
const apiKeySchema = yupObject({
description: yupString().defined().nonEmpty(t("Description is required")),
expiresIn: yupString().defined()
});
const { register, handleSubmit, formState: { errors }, reset } = useForm({
resolver: yupResolver(apiKeySchema),
defaultValues: {
description: "",
expiresIn: Object.keys(expiresInOptions)[2]
// Default to 30 days
}
});
const onSubmit = async (data) => {
setLoading(true);
try {
const expiresAt = new Date(Date.now() + parseInt(data.expiresIn));
const apiKey = await props.createApiKey({
description: data.description,
expiresAt
});
if (props.onKeyCreated) {
props.onKeyCreated(apiKey);
}
reset();
props.onOpenChange(false);
} catch (error) {
captureError("Failed to create API key", { error });
} finally {
setLoading(false);
}
};
return /* @__PURE__ */ jsx(
ActionDialog,
{
open: props.open,
onOpenChange: props.onOpenChange,
title: t("Create API Key"),
description: t("API keys grant programmatic access to your account."),
children: /* @__PURE__ */ jsxs(
"form",
{
onSubmit: (e) => {
e.preventDefault();
runAsynchronously(handleSubmit(onSubmit));
},
className: "space-y-4",
children: [
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "description", children: t("Description") }),
/* @__PURE__ */ jsx(
Input,
{
id: "description",
placeholder: t("e.g. Development, Production, CI/CD"),
...register("description")
}
),
errors.description && /* @__PURE__ */ jsx(FormWarningText, { text: errors.description.message })
] }),
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "expiresIn", children: t("Expires In") }),
/* @__PURE__ */ jsx(
"select",
{
id: "expiresIn",
className: "w-full p-2 border border-input rounded-md bg-background",
...register("expiresIn"),
children: Object.entries(expiresInOptions).map(([value, label]) => /* @__PURE__ */ jsx("option", { value, children: t(label) }, value))
}
),
errors.expiresIn && /* @__PURE__ */ jsx(FormWarningText, { text: errors.expiresIn.message })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-4", children: [
/* @__PURE__ */ jsx(
Button,
{
type: "button",
variant: "secondary",
onClick: () => {
reset();
props.onOpenChange(false);
},
children: t("Cancel")
}
),
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Create") })
] })
]
}
)
}
);
}
function ShowApiKeyDialog(props) {
const { t } = useTranslation();
return /* @__PURE__ */ jsx(
ActionDialog,
{
open: !!props.apiKey,
title: t("API Key"),
okButton: { label: t("Close") },
onClose: props.onClose,
preventClose: true,
confirmText: t("I understand that I will not be able to view this key again."),
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
/* @__PURE__ */ jsxs(Typography, { children: [
t("Here is your API key."),
" ",
/* @__PURE__ */ jsx("span", { className: "font-bold", children: t("Copy it to a safe place. You will not be able to view it again.") })
] }),
/* @__PURE__ */ jsx(
CopyField,
{
monospace: true,
value: props.apiKey?.value ?? "",
label: t("Secret API Key")
}
)
] })
}
);
}
export {
CreateApiKeyDialog,
ShowApiKeyDialog,
expiresInOptions,
neverInMs
};
//# sourceMappingURL=api-key-dialogs.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,125 @@
"use client";
"use client";
// src/components/api-key-table.tsx
import { ActionCell, ActionDialog, BadgeCell, DataTable, DataTableColumnHeader, DataTableFacetedFilter, DateCell, SearchToolbarItem, TextCell, standardFilterFn } from "@stackframe/stack-ui";
import { useMemo, useState } from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function toolbarRender(table) {
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(SearchToolbarItem, { table, placeholder: "Search table" }),
/* @__PURE__ */ jsx(
DataTableFacetedFilter,
{
column: table.getColumn("status"),
title: "Status",
options: ["valid", "expired", "revoked"].map((provider) => ({
value: provider,
label: provider
}))
}
)
] });
}
function RevokeDialog(props) {
return /* @__PURE__ */ jsx(
ActionDialog,
{
open: props.open,
onOpenChange: props.onOpenChange,
title: "Revoke API Key",
danger: true,
cancelButton: true,
okButton: { label: "Revoke Key", onClick: async () => {
await props.apiKey.revoke();
} },
confirmText: "I understand this will unlink all the apps using this API key",
children: `Are you sure you want to revoke API key *****${props.apiKey.value.lastFour}?`
}
);
}
function Actions({ row }) {
const [isRevokeModalOpen, setIsRevokeModalOpen] = useState(false);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(RevokeDialog, { apiKey: row.original, open: isRevokeModalOpen, onOpenChange: setIsRevokeModalOpen }),
/* @__PURE__ */ jsx(
ActionCell,
{
invisible: row.original.status !== "valid",
items: [{
item: "Revoke",
danger: true,
onClick: () => setIsRevokeModalOpen(true)
}]
}
)
] });
}
var columns = [
{
accessorKey: "description",
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Description" }),
cell: ({ row }) => /* @__PURE__ */ jsx(TextCell, { size: 100, children: row.original.description })
},
{
accessorKey: "status",
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Status" }),
cell: ({ row }) => /* @__PURE__ */ jsx(BadgeCell, { badges: [row.original.status] }),
filterFn: standardFilterFn
},
{
id: "value",
accessorFn: (row) => row.value.lastFour,
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Client Key" }),
cell: ({ row }) => /* @__PURE__ */ jsxs(TextCell, { children: [
"*******",
row.original.value.lastFour
] }),
enableSorting: false
},
{
accessorKey: "expiresAt",
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Expires At" }),
cell: ({ row }) => {
if (row.original.status === "revoked") return /* @__PURE__ */ jsx(TextCell, { children: "-" });
return row.original.expiresAt ? /* @__PURE__ */ jsx(DateCell, { date: row.original.expiresAt, ignoreAfterYears: 50 }) : /* @__PURE__ */ jsx(TextCell, { children: "Never" });
}
},
{
accessorKey: "createdAt",
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Created At" }),
cell: ({ row }) => /* @__PURE__ */ jsx(DateCell, { date: row.original.createdAt, ignoreAfterYears: 50 })
},
{
id: "actions",
cell: ({ row }) => /* @__PURE__ */ jsx(Actions, { row })
}
];
function ApiKeyTable(props) {
const extendedApiKeys = useMemo(() => {
const keys = props.apiKeys.map((apiKey) => ({
...apiKey,
status: { "valid": "valid", "manually-revoked": "revoked", "expired": "expired" }[apiKey.whyInvalid() || "valid"]
}));
return keys.sort((a, b) => {
if (a.status === b.status) {
return a.createdAt < b.createdAt ? 1 : -1;
}
return a.status === "valid" ? -1 : 1;
});
}, [props.apiKeys]);
return /* @__PURE__ */ jsx(
DataTable,
{
data: extendedApiKeys,
columns,
toolbarRender,
defaultColumnFilters: [],
defaultSorting: []
}
);
}
export {
ApiKeyTable
};
//# sourceMappingURL=api-key-table.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,79 @@
"use client";
"use client";
// src/components/credential-sign-in.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { passwordSchema, strictEmailSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, Label, PasswordInput } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useStackApp } from "..";
import { useTranslation } from "../lib/translations";
import { FormWarningText } from "./elements/form-warning";
import { StyledLink } from "./link";
import { jsx, jsxs } from "react/jsx-runtime";
function CredentialSignIn() {
const { t } = useTranslation();
const schema = yupObject({
email: strictEmailSchema(t("Please enter a valid email")).defined().nonEmpty(t("Please enter your email")),
password: passwordSchema.defined().nonEmpty(t("Please enter your password"))
});
const { register, handleSubmit, setError, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
const app = useStackApp();
const [loading, setLoading] = useState(false);
const onSubmit = async (data) => {
setLoading(true);
try {
const { email, password } = data;
const result = await app.signInWithCredential({
email,
password
});
if (result.status === "error") {
setError("email", { type: "manual", message: result.error.message });
}
} finally {
setLoading(false);
}
};
return /* @__PURE__ */ jsxs(
"form",
{
className: "flex flex-col items-stretch stack-scope",
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "email", className: "mb-1", children: t("Email") }),
/* @__PURE__ */ jsx(
Input,
{
id: "email",
type: "email",
autoComplete: "email",
...register("email")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
/* @__PURE__ */ jsx(Label, { htmlFor: "password", className: "mt-4 mb-1", children: t("Password") }),
/* @__PURE__ */ jsx(
PasswordInput,
{
id: "password",
autoComplete: "current-password",
...register("password")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.password?.message?.toString() }),
/* @__PURE__ */ jsx(StyledLink, { href: app.urls.forgotPassword, className: "mt-1 text-sm", children: t("Forgot password?") }),
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: t("Sign In") })
]
}
);
}
export {
CredentialSignIn
};
//# sourceMappingURL=credential-sign-in.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components/credential-sign-in.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 { passwordSchema, strictEmailSchema, yupObject } from \"@stackframe/stack-shared/dist/schema-fields\";\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport { Button, Input, Label, PasswordInput } from \"@stackframe/stack-ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { FormWarningText } from \"./elements/form-warning\";\nimport { StyledLink } from \"./link\";\n\nexport function CredentialSignIn() {\n const { t } = useTranslation();\n\n const schema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email')).defined().nonEmpty(t('Please enter your email')),\n password: passwordSchema.defined().nonEmpty(t('Please enter your password'))\n });\n\n const { register, handleSubmit, setError, formState: { errors } } = useForm({\n resolver: yupResolver(schema)\n });\n const app = useStackApp();\n const [loading, setLoading] = useState(false);\n\n const onSubmit = async (data: yup.InferType<typeof schema>) => {\n setLoading(true);\n\n try {\n const { email, password } = data;\n const result = await app.signInWithCredential({\n email,\n password,\n });\n if (result.status === 'error') {\n setError('email', { type: 'manual', message: result.error.message });\n }\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <form\n className=\"flex flex-col items-stretch stack-scope\"\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n >\n <Label htmlFor=\"email\" className=\"mb-1\">{t('Email')}</Label>\n <Input\n id=\"email\"\n type=\"email\"\n autoComplete=\"email\"\n {...register('email')}\n />\n <FormWarningText text={errors.email?.message?.toString()} />\n\n <Label htmlFor=\"password\" className=\"mt-4 mb-1\">{t('Password')}</Label>\n <PasswordInput\n id=\"password\"\n autoComplete=\"current-password\"\n {...register('password')}\n />\n <FormWarningText text={errors.password?.message?.toString()} />\n\n <StyledLink href={app.urls.forgotPassword} className=\"mt-1 text-sm\">\n {t('Forgot password?')}\n </StyledLink>\n\n <Button type=\"submit\" className=\"mt-6\" loading={loading}>\n {t('Sign In')}\n </Button>\n </form>\n );\n}\n"],"mappings":";;;AAOA,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB,mBAAmB,iBAAiB;AAC7D,SAAS,kCAAkC;AAC3C,SAAS,QAAQ,OAAO,OAAO,qBAAqB;AACpD,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAExB,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAkCvB,SAKE,KALF;AAhCG,SAAS,mBAAmB;AACjC,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,QAAM,SAAS,UAAU;AAAA,IACvB,OAAO,kBAAkB,EAAE,4BAA4B,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,yBAAyB,CAAC;AAAA,IACzG,UAAU,eAAe,QAAQ,EAAE,SAAS,EAAE,4BAA4B,CAAC;AAAA,EAC7E,CAAC;AAED,QAAM,EAAE,UAAU,cAAc,UAAU,WAAW,EAAE,OAAO,EAAE,IAAI,QAAQ;AAAA,IAC1E,UAAU,YAAY,MAAM;AAAA,EAC9B,CAAC;AACD,QAAM,MAAM,YAAY;AACxB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,QAAM,WAAW,OAAO,SAAuC;AAC7D,eAAW,IAAI;AAEf,QAAI;AACF,YAAM,EAAE,OAAO,SAAS,IAAI;AAC5B,YAAM,SAAS,MAAM,IAAI,qBAAqB;AAAA,QAC5C;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,OAAO,WAAW,SAAS;AAC7B,iBAAS,SAAS,EAAE,MAAM,UAAU,SAAS,OAAO,MAAM,QAAQ,CAAC;AAAA,MACrE;AAAA,IACF,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,UAAU,OAAK,2BAA2B,aAAa,QAAQ,EAAE,CAAC,CAAC;AAAA,MACnE,YAAU;AAAA,MAEV;AAAA,4BAAC,SAAM,SAAQ,SAAQ,WAAU,QAAQ,YAAE,OAAO,GAAE;AAAA,QACpD;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,cAAa;AAAA,YACZ,GAAG,SAAS,OAAO;AAAA;AAAA,QACtB;AAAA,QACA,oBAAC,mBAAgB,MAAM,OAAO,OAAO,SAAS,SAAS,GAAG;AAAA,QAE1D,oBAAC,SAAM,SAAQ,YAAW,WAAU,aAAa,YAAE,UAAU,GAAE;AAAA,QAC/D;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,cAAa;AAAA,YACZ,GAAG,SAAS,UAAU;AAAA;AAAA,QACzB;AAAA,QACA,oBAAC,mBAAgB,MAAM,OAAO,UAAU,SAAS,SAAS,GAAG;AAAA,QAE7D,oBAAC,cAAW,MAAM,IAAI,KAAK,gBAAgB,WAAU,gBAClD,YAAE,kBAAkB,GACvB;AAAA,QAEA,oBAAC,UAAO,MAAK,UAAS,WAAU,QAAO,SACpC,YAAE,SAAS,GACd;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}

View file

@ -0,0 +1,104 @@
"use client";
"use client";
// src/components/credential-sign-up.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
import { passwordSchema, strictEmailSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, Label, PasswordInput } from "@stackframe/stack-ui";
import { useState } from "react";
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { useStackApp } from "..";
import { useTranslation } from "../lib/translations";
import { FormWarningText } from "./elements/form-warning";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function CredentialSignUp(props) {
const { t } = useTranslation();
const schema = yupObject({
email: strictEmailSchema(t("Please enter a valid email")).defined().nonEmpty(t("Please enter your email")),
password: passwordSchema.defined().nonEmpty(t("Please enter your password")).test({
name: "is-valid-password",
test: (value, ctx) => {
const error = getPasswordError(value);
if (error) {
return ctx.createError({ message: error.message });
} else {
return true;
}
}
}),
...!props.noPasswordRepeat && {
passwordRepeat: passwordSchema.nullable().oneOf([yup.ref("password"), "", null], t("Passwords do not match")).nonEmpty(t("Please repeat your password"))
}
});
const { register, handleSubmit, setError, formState: { errors }, clearErrors } = useForm({
resolver: yupResolver(schema)
});
const app = useStackApp();
const [loading, setLoading] = useState(false);
const onSubmit = async (data) => {
setLoading(true);
try {
const { email, password } = data;
const result = await app.signUpWithCredential({ email, password });
if (result.status === "error") {
setError("email", { type: "manual", message: result.error.message });
}
} finally {
setLoading(false);
}
};
const registerPassword = register("password");
const registerPasswordRepeat = register("passwordRepeat");
return /* @__PURE__ */ jsxs(
"form",
{
className: "flex flex-col items-stretch stack-scope",
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "email", className: "mb-1", children: t("Email") }),
/* @__PURE__ */ jsx(Input, { id: "email", type: "email", autoComplete: "email", ...register("email") }),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
/* @__PURE__ */ jsx(Label, { htmlFor: "password", className: "mt-4 mb-1", children: t("Password") }),
/* @__PURE__ */ jsx(
PasswordInput,
{
id: "password",
autoComplete: "new-password",
...registerPassword,
onChange: (e) => {
clearErrors("password");
clearErrors("passwordRepeat");
runAsynchronously(registerPassword.onChange(e));
}
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.password?.message?.toString() }),
!props.noPasswordRepeat && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: t("Repeat Password") }),
/* @__PURE__ */ jsx(
PasswordInput,
{
id: "repeat-password",
...registerPasswordRepeat,
onChange: (e) => {
clearErrors("password");
clearErrors("passwordRepeat");
runAsynchronously(registerPasswordRepeat.onChange(e));
}
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.passwordRepeat?.message?.toString() })
] }),
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: t("Sign Up") })
]
}
);
}
export {
CredentialSignUp
};
//# sourceMappingURL=credential-sign-up.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,15 @@
"use client";
"use client";
// src/components/elements/form-warning.tsx
import { jsx } from "react/jsx-runtime";
function FormWarningText({ text }) {
if (!text) {
return null;
}
return /* @__PURE__ */ jsx("div", { className: "text-red-500 text-sm mt-1", children: text });
}
export {
FormWarningText
};
//# sourceMappingURL=form-warning.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/elements/form-warning.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nexport function FormWarningText({ text }: { text?: string }) {\n if (!text) {\n return null;\n }\n return (\n <div className=\"text-red-500 text-sm mt-1\">\n {text}\n </div>\n );\n}\n"],"mappings":";;;AAYI;AALG,SAAS,gBAAgB,EAAE,KAAK,GAAsB;AAC3D,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AACA,SACE,oBAAC,SAAI,WAAU,6BACZ,gBACH;AAEJ;","names":[]}

View file

@ -0,0 +1,51 @@
"use client";
"use client";
// src/components/elements/maybe-full-page.tsx
import { useId } from "react";
import { SsrScript } from "./ssr-layout-effect";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function MaybeFullPage({
children,
fullPage
}) {
const uniqueId = useId();
const id = `stack-full-page-container-${uniqueId}`;
const scriptString = `(([id]) => {
const el = document.getElementById(id);
if (!el) {
// component is not full page
return;
}
const offset = el.getBoundingClientRect().top + document.documentElement.scrollTop;
el.style.minHeight = \`calc(100vh - \${offset}px)\`;
})(${JSON.stringify([id])})`;
if (fullPage) {
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
"div",
{
suppressHydrationWarning: true,
id,
style: {
minHeight: "100vh",
alignSelf: "stretch",
flexGrow: 1,
display: "flex",
justifyContent: "center",
alignItems: "center"
},
className: "stack-scope",
children
}
),
/* @__PURE__ */ jsx(SsrScript, { script: scriptString })
] });
} else {
return /* @__PURE__ */ jsx(Fragment, { children });
}
}
export {
MaybeFullPage
};
//# sourceMappingURL=maybe-full-page.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/elements/maybe-full-page.tsx"],"sourcesContent":["\"use client\";\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport React, { useId } from \"react\";\nimport { SsrScript } from \"./ssr-layout-effect\";\n\nexport function MaybeFullPage({\n children,\n fullPage,\n}: {\n children: React.ReactNode,\n fullPage: boolean,\n size?: number,\n containerClassName?: string,\n}) {\n const uniqueId = useId();\n const id = `stack-full-page-container-${uniqueId}`;\n\n const scriptString = `(([id]) => {\n const el = document.getElementById(id);\n if (!el) {\n // component is not full page\n return;\n }\n const offset = el.getBoundingClientRect().top + document.documentElement.scrollTop;\n el.style.minHeight = \\`calc(100vh - \\${offset}px)\\`;\n })(${JSON.stringify([id])})`;\n\n if (fullPage) {\n return (\n <>\n <div\n suppressHydrationWarning\n id={id}\n style={{\n minHeight: '100vh',\n alignSelf: 'stretch',\n flexGrow: 1,\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n }}\n className=\"stack-scope\"\n >\n {children}\n </div>\n <SsrScript script={scriptString} />\n </>\n );\n } else {\n return <>\n {children}\n </>;\n }\n\n}\n"],"mappings":";;;AAOA,SAAgB,aAAa;AAC7B,SAAS,iBAAiB;AA0BpB,mBACE,KADF;AAxBC,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAKG;AACD,QAAM,WAAW,MAAM;AACvB,QAAM,KAAK,6BAA6B,QAAQ;AAEhD,QAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAQhB,KAAK,UAAU,CAAC,EAAE,CAAC,CAAC;AAEzB,MAAI,UAAU;AACZ,WACE,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,0BAAwB;AAAA,UACxB;AAAA,UACA,OAAO;AAAA,YACL,WAAW;AAAA,YACX,WAAW;AAAA,YACX,UAAU;AAAA,YACV,SAAS;AAAA,YACT,gBAAgB;AAAA,YAChB,YAAY;AAAA,UACd;AAAA,UACA,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,MACA,oBAAC,aAAU,QAAQ,cAAc;AAAA,OACnC;AAAA,EAEJ,OAAO;AACL,WAAO,gCACJ,UACH;AAAA,EACF;AAEF;","names":[]}

View file

@ -0,0 +1,17 @@
"use client";
"use client";
// src/components/elements/separator-with-text.tsx
import { Separator } from "@stackframe/stack-ui";
import { jsx, jsxs } from "react/jsx-runtime";
function SeparatorWithText({ text }) {
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center my-6 stack-scope", children: [
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(Separator, {}) }),
/* @__PURE__ */ jsx("div", { className: "mx-2 text-sm text-zinc-500", children: text }),
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(Separator, {}) })
] });
}
export {
SeparatorWithText
};
//# sourceMappingURL=separator-with-text.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/elements/separator-with-text.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { Separator } from \"@stackframe/stack-ui\";\n\nexport function SeparatorWithText({ text }: { text: string }) {\n return (\n <div className=\"flex items-center justify-center my-6 stack-scope\">\n <div className=\"flex-1\">\n <Separator />\n </div>\n <div className=\"mx-2 text-sm text-zinc-500\">{text}</div>\n <div className=\"flex-1\">\n <Separator />\n </div>\n </div>\n );\n}\n"],"mappings":";;;AAOA,SAAS,iBAAiB;AAItB,SAEI,KAFJ;AAFG,SAAS,kBAAkB,EAAE,KAAK,GAAqB;AAC5D,SACE,qBAAC,SAAI,WAAU,qDACb;AAAA,wBAAC,SAAI,WAAU,UACb,8BAAC,aAAU,GACb;AAAA,IACA,oBAAC,SAAI,WAAU,8BAA8B,gBAAK;AAAA,IAClD,oBAAC,SAAI,WAAU,UACb,8BAAC,aAAU,GACb;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,95 @@
"use client";
"use client";
// src/components/elements/sidebar-layout.tsx
import { useHash } from "@stackframe/stack-shared/dist/hooks/use-hash";
import { Button, Typography, cn } from "@stackframe/stack-ui";
import { XIcon } from "lucide-react";
import { useStackApp } from "../..";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function SidebarLayout(props) {
const hash = useHash();
const selectedIndex = props.items.findIndex((item) => item.id && item.id === hash);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { className: cn("hidden sm:flex stack-scope h-full", props.className), children: /* @__PURE__ */ jsx(DesktopLayout, { items: props.items, title: props.title, selectedIndex }) }),
/* @__PURE__ */ jsx("div", { className: cn("sm:hidden stack-scope h-full", props.className), children: /* @__PURE__ */ jsx(MobileLayout, { items: props.items, title: props.title, selectedIndex }) })
] });
}
function Items(props) {
const app = useStackApp();
const navigate = app.useNavigate();
const activeItemIndex = props.selectedIndex === -1 ? 0 : props.selectedIndex;
return props.items.map((item, index) => item.type === "item" ? /* @__PURE__ */ jsxs(
Button,
{
variant: "ghost",
size: "sm",
className: cn(
activeItemIndex === index && "sm:bg-muted",
"justify-start text-md text-zinc-800 dark:text-zinc-300 px-2 text-left"
),
onClick: () => {
if (item.id) {
navigate("#" + item.id);
}
},
children: [
item.icon,
item.title
]
},
index
) : /* @__PURE__ */ jsx(Typography, { children: item.title }, index));
}
function DesktopLayout(props) {
const selectedItem = props.items[props.selectedIndex === -1 ? 0 : props.selectedIndex];
return /* @__PURE__ */ jsxs("div", { className: "stack-scope flex w-full h-full max-w-full relative", children: [
/* @__PURE__ */ jsxs("div", { className: "flex max-w-[200px] min-w-[200px] border-r flex-col items-stretch gap-2 p-2 overflow-y-auto", children: [
props.title && /* @__PURE__ */ jsx("div", { className: "mb-2 ml-2", children: /* @__PURE__ */ jsx(Typography, { type: "h2", className: "text-lg font-semibold text-zinc-800 dark:text-zinc-300", children: props.title }) }),
/* @__PURE__ */ jsx(Items, { items: props.items, selectedIndex: props.selectedIndex })
] }),
/* @__PURE__ */ jsx("div", { className: "flex-1 w-0 flex justify-center gap-4 py-2 px-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col max-w-[800px] w-[800px]", children: [
/* @__PURE__ */ jsxs("div", { className: "mt-4 mb-6", children: [
/* @__PURE__ */ jsx(Typography, { type: "h4", className: "font-semibold", children: selectedItem.title }),
selectedItem.description && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: selectedItem.description })
] }),
/* @__PURE__ */ jsx("div", { className: "flex-1", children: selectedItem.content })
] }) })
] });
}
function MobileLayout(props) {
const selectedItem = props.items[props.selectedIndex];
const app = useStackApp();
const navigate = app.useNavigate();
if (props.selectedIndex === -1) {
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-2", children: [
props.title && /* @__PURE__ */ jsx("div", { className: "mb-2 ml-2", children: /* @__PURE__ */ jsx(Typography, { type: "h2", className: "text-lg font-semibold text-zinc-800 dark:text-zinc-300", children: props.title }) }),
/* @__PURE__ */ jsx(Items, { items: props.items, selectedIndex: props.selectedIndex })
] });
} else {
return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-4 py-2 px-4", children: [
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
/* @__PURE__ */ jsx(Typography, { type: "h4", className: "font-semibold", children: selectedItem.title }),
/* @__PURE__ */ jsx(
Button,
{
variant: "ghost",
size: "icon",
onClick: () => {
navigate("#");
},
children: /* @__PURE__ */ jsx(XIcon, { className: "h-5 w-5" })
}
)
] }),
selectedItem.description && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: selectedItem.description })
] }),
/* @__PURE__ */ jsx("div", { className: "flex-1", children: selectedItem.content })
] });
}
}
export {
SidebarLayout
};
//# sourceMappingURL=sidebar-layout.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
"use client";
"use client";
// src/components/elements/ssr-layout-effect.tsx
import { useLayoutEffect } from "react";
import { jsx } from "react/jsx-runtime";
function SsrScript(props) {
useLayoutEffect(() => {
(0, eval)(props.script);
}, []);
return /* @__PURE__ */ jsx(
"script",
{
suppressHydrationWarning: true,
nonce: props.nonce,
dangerouslySetInnerHTML: { __html: props.script }
}
);
}
export {
SsrScript
};
//# sourceMappingURL=ssr-layout-effect.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/elements/ssr-layout-effect.tsx"],"sourcesContent":["\"use client\";\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { useLayoutEffect } from \"react\";\n\nexport function SsrScript(props: { script: string, nonce?: string }) {\n useLayoutEffect(() => {\n // TODO fix workaround: React has a bug where it doesn't run the script on the first CSR render if SSR has been skipped due to suspense\n // As a workaround, we run the script in the <script> tag again after the first render\n // Note that we do an indirect eval as described here: https://esbuild.github.io/content-types/#direct-eval\n (0, eval)(props.script);\n }, []);\n\n return (\n <script\n suppressHydrationWarning // the transpiler is setup differently for client/server targets, so if `script` was generated with Function.toString they will differ\n nonce={props.nonce}\n dangerouslySetInnerHTML={{ __html: props.script }}\n />\n );\n}\n"],"mappings":";;;AAMA,SAAS,uBAAuB;AAW5B;AATG,SAAS,UAAU,OAA2C;AACnE,kBAAgB,MAAM;AAIpB,KAAC,GAAG,MAAM,MAAM,MAAM;AAAA,EACxB,GAAG,CAAC,CAAC;AAEL,SACE;AAAA,IAAC;AAAA;AAAA,MACC,0BAAwB;AAAA,MACxB,OAAO,MAAM;AAAA,MACb,yBAAyB,EAAE,QAAQ,MAAM,OAAO;AAAA;AAAA,EAClD;AAEJ;","names":[]}

View file

@ -0,0 +1,16 @@
// src/components/elements/user-avatar.tsx
import { Avatar, AvatarFallback, AvatarImage } from "@stackframe/stack-ui";
import { UserRound } from "lucide-react";
import { jsx, jsxs } from "react/jsx-runtime";
var defaultSize = 34;
function UserAvatar(props) {
const user = props.user;
return /* @__PURE__ */ jsxs(Avatar, { style: { height: props.size || defaultSize, width: props.size || defaultSize }, className: props.border ? "border" : "", children: [
/* @__PURE__ */ jsx(AvatarImage, { src: user?.profileImageUrl || "" }),
/* @__PURE__ */ jsx(AvatarFallback, { children: user ? /* @__PURE__ */ jsx("div", { className: "font-medium", style: { fontSize: (props.size || defaultSize) * 0.4 }, children: (user.displayName || user.primaryEmail)?.slice(0, 2).toUpperCase() }) : /* @__PURE__ */ jsx(UserRound, { className: "text-zinc-500", size: (props.size || defaultSize) * 0.6 }) })
] });
}
export {
UserAvatar
};
//# sourceMappingURL=user-avatar.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/elements/user-avatar.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Avatar, AvatarFallback, AvatarImage } from \"@stackframe/stack-ui\";\nimport { UserRound } from \"lucide-react\";\n\nconst defaultSize = 34;\n\nexport function UserAvatar(props: {\n size?: number,\n user?: {\n profileImageUrl?: string | null,\n displayName?: string | null,\n primaryEmail?: string | null,\n } | null,\n border?: boolean,\n}) {\n const user = props.user;\n return (\n <Avatar style={{ height: props.size || defaultSize, width: props.size || defaultSize }} className={props.border ? 'border' : ''}>\n <AvatarImage src={user?.profileImageUrl || ''} />\n <AvatarFallback>\n {user ?\n <div className='font-medium' style={{ fontSize: (props.size || defaultSize) * 0.4 }}>\n {(user.displayName || user.primaryEmail)?.slice(0, 2).toUpperCase()}\n </div> :\n <UserRound className=\"text-zinc-500\" size={(props.size || defaultSize) * 0.6} />}\n </AvatarFallback>\n </Avatar>\n );\n}\n"],"mappings":";AAIA,SAAS,QAAQ,gBAAgB,mBAAmB;AACpD,SAAS,iBAAiB;AAetB,SACE,KADF;AAbJ,IAAM,cAAc;AAEb,SAAS,WAAW,OAQxB;AACD,QAAM,OAAO,MAAM;AACnB,SACE,qBAAC,UAAO,OAAO,EAAE,QAAQ,MAAM,QAAQ,aAAa,OAAO,MAAM,QAAQ,YAAY,GAAG,WAAW,MAAM,SAAS,WAAW,IAC3H;AAAA,wBAAC,eAAY,KAAK,MAAM,mBAAmB,IAAI;AAAA,IAC/C,oBAAC,kBACE,iBACC,oBAAC,SAAI,WAAU,eAAc,OAAO,EAAE,WAAW,MAAM,QAAQ,eAAe,IAAI,GAC9E,gBAAK,eAAe,KAAK,eAAe,MAAM,GAAG,CAAC,EAAE,YAAY,GACpE,IACA,oBAAC,aAAU,WAAU,iBAAgB,OAAO,MAAM,QAAQ,eAAe,KAAK,GAClF;AAAA,KACF;AAEJ;","names":[]}

View file

@ -0,0 +1,28 @@
"use client";
"use client";
// src/components/iframe-preventer.tsx
import { useEffect, useState } from "react";
import { jsxs } from "react/jsx-runtime";
function IframePreventer({ children }) {
const [isIframe, setIsIframe] = useState(false);
useEffect(() => {
if (window.self !== window.top) {
setIsIframe(true);
}
}, []);
if (isIframe) {
return /* @__PURE__ */ jsxs("div", { children: [
"Stack Auth components may not run in an ",
"<",
"iframe",
">",
"."
] });
}
return children;
}
export {
IframePreventer
};
//# sourceMappingURL=iframe-preventer.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components/iframe-preventer.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { useEffect, useState } from \"react\";\n\nexport function IframePreventer({ children }: {\n children: React.ReactNode,\n}) {\n const [isIframe, setIsIframe] = useState(false);\n useEffect(() => {\n if (window.self !== window.top) {\n setIsIframe(true);\n }\n }, []);\n\n if (isIframe) {\n return <div>Stack Auth components may not run in an {'<'}iframe{'>'}.</div>;\n }\n\n return children;\n}\n"],"mappings":";;;AAMA,SAAS,WAAW,gBAAgB;AAazB;AAXJ,SAAS,gBAAgB,EAAE,SAAS,GAExC;AACD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,YAAU,MAAM;AACd,QAAI,OAAO,SAAS,OAAO,KAAK;AAC9B,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,MAAI,UAAU;AACZ,WAAO,qBAAC,SAAI;AAAA;AAAA,MAAyC;AAAA,MAAI;AAAA,MAAO;AAAA,MAAI;AAAA,OAAC;AAAA,EACvE;AAEA,SAAO;AACT;","names":[]}

View file

@ -0,0 +1,26 @@
"use client";
"use client";
// src/components/link.tsx
import { cn } from "@stackframe/stack-ui";
import { jsx } from "react/jsx-runtime";
function Link(props) {
return /* @__PURE__ */ jsx(
"a",
{
href: props.href,
target: props.target,
className: props.className,
onClick: props.onClick,
children: props.children
}
);
}
function StyledLink(props) {
return /* @__PURE__ */ jsx(Link, { ...props, className: cn("underline font-medium", props.className), children: props.children });
}
export {
Link,
StyledLink
};
//# sourceMappingURL=link.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components/link.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { cn } from \"@stackframe/stack-ui\";\n\ntype LinkProps = {\n href: string,\n children: React.ReactNode,\n className?: string,\n target?: string,\n onClick?: React.MouseEventHandler<HTMLAnchorElement>,\n prefetch?: boolean,\n};\n\nfunction Link(props: LinkProps) {\n return <a\n href={props.href}\n target={props.target}\n className={props.className}\n onClick={props.onClick}\n >\n {props.children}\n </a>;\n}\n\nfunction StyledLink(props: LinkProps) {\n return (\n <Link {...props} className={cn(\"underline font-medium\", props.className)}>\n {props.children}\n </Link>\n );\n}\n\nexport { Link, StyledLink };\n"],"mappings":";;;AAOA,SAAS,UAAU;AAYV;AADT,SAAS,KAAK,OAAkB;AAC9B,SAAO;AAAA,IAAC;AAAA;AAAA,MACN,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MAEd,gBAAM;AAAA;AAAA,EACT;AACF;AAEA,SAAS,WAAW,OAAkB;AACpC,SACE,oBAAC,QAAM,GAAG,OAAO,WAAW,GAAG,yBAAyB,MAAM,SAAS,GACpE,gBAAM,UACT;AAEJ;","names":[]}

View file

@ -0,0 +1,127 @@
"use client";
"use client";
// src/components/magic-link-sign-in.tsx
import { yupResolver } from "@hookform/resolvers/yup";
import { KnownErrors } from "@stackframe/stack-shared";
import { strictEmailSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, Typography } from "@stackframe/stack-ui";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useStackApp } from "..";
import { useTranslation } from "../lib/translations";
import { FormWarningText } from "./elements/form-warning";
import { jsx, jsxs } from "react/jsx-runtime";
function OTP(props) {
const { t } = useTranslation();
const [otp, setOtp] = useState("");
const [submitting, setSubmitting] = useState(false);
const stackApp = useStackApp();
const [error, setError] = useState(null);
useEffect(() => {
if (otp.length === 6 && !submitting) {
setSubmitting(true);
stackApp.signInWithMagicLink(otp + props.nonce).then((result) => {
if (result.status === "error") {
if (KnownErrors.VerificationCodeError.isInstance(result.error)) {
setError(t("Invalid code"));
} else if (KnownErrors.InvalidTotpCode.isInstance(result.error)) {
setError(t("Invalid TOTP code"));
} else {
throw result.error;
}
}
}).catch((e) => console.error(e)).finally(() => {
setSubmitting(false);
setOtp("");
});
}
if (otp.length !== 0 && otp.length !== 6) {
setError(null);
}
}, [otp, submitting]);
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-stretch stack-scope", children: [
/* @__PURE__ */ jsxs("form", { className: "w-full flex flex-col items-center mb-2", children: [
/* @__PURE__ */ jsx(Typography, { className: "mb-2", children: t("Enter the code from your email") }),
/* @__PURE__ */ jsx(
InputOTP,
{
maxLength: 6,
type: "text",
inputMode: "text",
pattern: "^[a-zA-Z0-9]+$",
value: otp,
onChange: (value) => setOtp(value.toUpperCase()),
disabled: submitting,
children: /* @__PURE__ */ jsx(InputOTPGroup, { children: [0, 1, 2, 3, 4, 5].map((index) => /* @__PURE__ */ jsx(InputOTPSlot, { index, size: "lg" }, index)) })
}
),
error && /* @__PURE__ */ jsx(FormWarningText, { text: error })
] }),
/* @__PURE__ */ jsx(Button, { variant: "link", onClick: props.onBack, className: "underline", children: t("Cancel") })
] });
}
function MagicLinkSignIn() {
const { t } = useTranslation();
const app = useStackApp();
const [loading, setLoading] = useState(false);
const [nonce, setNonce] = useState(null);
const schema = yupObject({
email: strictEmailSchema(t("Please enter a valid email")).defined().nonEmpty(t("Please enter your email"))
});
const { register, handleSubmit, setError, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
const onSubmit = async (data) => {
setLoading(true);
try {
const { email } = data;
const result = await app.sendMagicLinkEmail(email);
if (result.status === "error") {
setError("email", { type: "manual", message: result.error.message });
return;
} else {
setNonce(result.data.nonce);
}
} catch (e) {
if (KnownErrors.SignUpNotEnabled.isInstance(e)) {
setError("email", { type: "manual", message: t("New account registration is not allowed") });
} else {
throw e;
}
} finally {
setLoading(false);
}
};
if (nonce) {
return /* @__PURE__ */ jsx(OTP, { nonce, onBack: () => setNonce(null) });
} else {
return /* @__PURE__ */ jsxs(
"form",
{
className: "flex flex-col items-stretch stack-scope",
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
noValidate: true,
children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "email", className: "mb-1", children: t("Email") }),
/* @__PURE__ */ jsx(
Input,
{
id: "email",
type: "email",
autoComplete: "email",
...register("email")
}
),
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: t("Send email") })
]
}
);
}
}
export {
MagicLinkSignIn
};
//# sourceMappingURL=magic-link-sign-in.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,37 @@
"use client";
"use client";
// src/components/message-cards/known-error-message-card.tsx
import { Typography } from "@stackframe/stack-ui";
import { useStackApp } from "../..";
import { MessageCard } from "./message-card";
import { jsxs } from "react/jsx-runtime";
function KnownErrorMessageCard({
error,
fullPage = false
}) {
const stackApp = useStackApp();
return /* @__PURE__ */ jsxs(
MessageCard,
{
title: "An error occurred",
fullPage,
primaryButtonText: "Go Home",
primaryAction: () => stackApp.redirectToHome(),
children: [
/* @__PURE__ */ jsxs(Typography, { children: [
"Error Code: ",
error.errorCode
] }),
/* @__PURE__ */ jsxs(Typography, { children: [
"Error Message: ",
error.message
] })
]
}
);
}
export {
KnownErrorMessageCard
};
//# sourceMappingURL=known-error-message-card.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/message-cards/known-error-message-card.tsx"],"sourcesContent":["\"use client\";\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { KnownError } from \"@stackframe/stack-shared\";\nimport { Typography } from \"@stackframe/stack-ui\";\nimport { useStackApp } from \"../..\";\nimport { MessageCard } from \"./message-card\";\n\nexport function KnownErrorMessageCard({\n error,\n fullPage=false,\n}: {\n error: KnownError,\n fullPage?: boolean,\n}) {\n const stackApp = useStackApp();\n\n return (\n <MessageCard\n title={\"An error occurred\"}\n fullPage={fullPage}\n primaryButtonText={\"Go Home\"}\n primaryAction={() => stackApp.redirectToHome()}\n >\n {<Typography>Error Code: {error.errorCode}</Typography>}\n {<Typography>Error Message: {error.message}</Typography>}\n </MessageCard>\n );\n}\n"],"mappings":";;;AAQA,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAkBrB;AAhBA,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA,WAAS;AACX,GAGG;AACD,QAAM,WAAW,YAAY;AAE7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,MACP;AAAA,MACA,mBAAmB;AAAA,MACnB,eAAe,MAAM,SAAS,eAAe;AAAA,MAE5C;AAAA,6BAAC,cAAW;AAAA;AAAA,UAAa,MAAM;AAAA,WAAU;AAAA,QACzC,qBAAC,cAAW;AAAA;AAAA,UAAgB,MAAM;AAAA,WAAQ;AAAA;AAAA;AAAA,EAC7C;AAEJ;","names":[]}

View file

@ -0,0 +1,21 @@
"use client";
"use client";
// src/components/message-cards/message-card.tsx
import { MaybeFullPage } from "../elements/maybe-full-page";
import { Button, Typography } from "@stackframe/stack-ui";
import { jsx, jsxs } from "react/jsx-runtime";
function MessageCard({ fullPage = false, ...props }) {
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage, children: /* @__PURE__ */ jsxs("div", { className: "text-center stack-scope flex flex-col gap-4", style: { maxWidth: "380px", flexBasis: "380px", padding: fullPage ? "1rem" : 0 }, children: [
/* @__PURE__ */ jsx(Typography, { type: "h3", children: props.title }),
props.children,
(props.primaryButtonText || props.secondaryButtonText) && /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-4 my-5", children: [
props.secondaryButtonText && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: props.secondaryAction, children: props.secondaryButtonText }),
props.primaryButtonText && /* @__PURE__ */ jsx(Button, { onClick: props.primaryAction, children: props.primaryButtonText })
] })
] }) });
}
export {
MessageCard
};
//# sourceMappingURL=message-card.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/message-cards/message-card.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport React from \"react\";\nimport { MaybeFullPage } from \"../elements/maybe-full-page\";\nimport { Button, Typography } from \"@stackframe/stack-ui\";\n\nexport function MessageCard(\n { fullPage=false, ...props }:\n {\n children?: React.ReactNode,\n title: string,\n fullPage?: boolean,\n primaryButtonText?: string,\n primaryAction?: () => Promise<void> | void,\n secondaryButtonText?: string,\n secondaryAction?: () => Promise<void> | void,\n }\n) {\n return (\n <MaybeFullPage fullPage={fullPage}>\n <div className=\"text-center stack-scope flex flex-col gap-4\" style={{ maxWidth: '380px', flexBasis: '380px', padding: fullPage ? '1rem' : 0 }}>\n <Typography type='h3'>{props.title}</Typography>\n {props.children}\n {(props.primaryButtonText || props.secondaryButtonText) && (\n <div className=\"flex justify-center gap-4 my-5\">\n {props.secondaryButtonText && (\n <Button variant=\"secondary\" onClick={props.secondaryAction}>\n {props.secondaryButtonText}\n </Button>\n )}\n {props.primaryButtonText && (\n <Button onClick={props.primaryAction}>\n {props.primaryButtonText}\n </Button>\n )}\n </div>\n )}\n </div>\n </MaybeFullPage>\n );\n}\n"],"mappings":";;;AAQA,SAAS,qBAAqB;AAC9B,SAAS,QAAQ,kBAAkB;AAiB3B,cAGE,YAHF;AAfD,SAAS,YACd,EAAE,WAAS,OAAO,GAAG,MAAM,GAU3B;AACA,SACE,oBAAC,iBAAc,UACb,+BAAC,SAAI,WAAU,+CAA8C,OAAO,EAAE,UAAU,SAAS,WAAW,SAAS,SAAS,WAAW,SAAS,EAAE,GAC1I;AAAA,wBAAC,cAAW,MAAK,MAAM,gBAAM,OAAM;AAAA,IAClC,MAAM;AAAA,KACL,MAAM,qBAAqB,MAAM,wBACjC,qBAAC,SAAI,WAAU,kCACZ;AAAA,YAAM,uBACL,oBAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,iBACxC,gBAAM,qBACT;AAAA,MAED,MAAM,qBACL,oBAAC,UAAO,SAAS,MAAM,eACpB,gBAAM,mBACT;AAAA,OAEJ;AAAA,KAEJ,GACF;AAEJ;","names":[]}

View file

@ -0,0 +1,83 @@
"use client";
"use client";
// src/components/message-cards/predefined-message-card.tsx
import { Typography } from "@stackframe/stack-ui";
import { useStackApp } from "../..";
import { useTranslation } from "../../lib/translations";
import { MessageCard } from "./message-card";
import { jsx } from "react/jsx-runtime";
function PredefinedMessageCard({
type,
fullPage = false
}) {
const stackApp = useStackApp();
const { t } = useTranslation();
let title;
let message = null;
let primaryButton = null;
let secondaryButton = null;
let primaryAction = null;
let secondaryAction = null;
switch (type) {
case "signedIn": {
title = t("You are already signed in");
primaryAction = () => stackApp.redirectToHome();
secondaryAction = () => stackApp.redirectToSignOut();
primaryButton = t("Go home");
secondaryButton = t("Sign out");
break;
}
case "signedOut": {
title = t("You are not currently signed in.");
primaryAction = () => stackApp.redirectToSignIn();
primaryButton = t("Sign in");
break;
}
case "signUpDisabled": {
title = t("Sign up for new users is not enabled at the moment.");
primaryAction = () => stackApp.redirectToHome();
secondaryAction = () => stackApp.redirectToSignIn();
primaryButton = t("Go home");
secondaryButton = t("Sign in");
break;
}
case "emailSent": {
title = t("Email sent!");
message = t("If the user with this e-mail address exists, an e-mail was sent to your inbox. Make sure to check your spam folder.");
primaryAction = () => stackApp.redirectToHome();
primaryButton = t("Go home");
break;
}
case "passwordReset": {
title = t("Password reset successfully!");
message = t("Your password has been reset. You can now sign in with your new password.");
primaryAction = () => stackApp.redirectToSignIn({ noRedirectBack: true });
primaryButton = t("Sign in");
break;
}
case "unknownError": {
title = t("An unknown error occurred");
message = t("Please try again and if the problem persists, contact support.");
primaryAction = () => stackApp.redirectToHome();
primaryButton = t("Go home");
break;
}
}
return /* @__PURE__ */ jsx(
MessageCard,
{
title,
fullPage,
primaryButtonText: primaryButton,
primaryAction,
secondaryButtonText: secondaryButton || void 0,
secondaryAction: secondaryAction || void 0,
children: message && /* @__PURE__ */ jsx(Typography, { children: message })
}
);
}
export {
PredefinedMessageCard
};
//# sourceMappingURL=predefined-message-card.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/components/message-cards/predefined-message-card.tsx"],"sourcesContent":["\"use client\";\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { Typography } from \"@stackframe/stack-ui\";\nimport { useStackApp } from \"../..\";\nimport { useTranslation } from \"../../lib/translations\";\nimport { MessageCard } from \"./message-card\";\n\nexport function PredefinedMessageCard({\n type,\n fullPage=false,\n}: {\n type: 'signedIn' | 'signedOut' | 'emailSent' | 'passwordReset' | 'unknownError' | 'signUpDisabled',\n fullPage?: boolean,\n}) {\n const stackApp = useStackApp();\n const { t } = useTranslation();\n\n let title: string;\n let message: string | null = null;\n let primaryButton: string | null = null;\n let secondaryButton: string | null = null;\n let primaryAction: (() => Promise<void> | void) | null = null;\n let secondaryAction: (() => Promise<void> | void) | null = null;\n\n switch (type) {\n case 'signedIn': {\n title = t(\"You are already signed in\");\n primaryAction = () => stackApp.redirectToHome();\n secondaryAction = () => stackApp.redirectToSignOut();\n primaryButton = t(\"Go home\");\n secondaryButton = t(\"Sign out\");\n break;\n }\n case 'signedOut': {\n title = t(\"You are not currently signed in.\");\n primaryAction = () => stackApp.redirectToSignIn();\n primaryButton = t(\"Sign in\");\n break;\n }\n case 'signUpDisabled': {\n title = t(\"Sign up for new users is not enabled at the moment.\");\n primaryAction = () => stackApp.redirectToHome();\n secondaryAction = () => stackApp.redirectToSignIn();\n primaryButton = t(\"Go home\");\n secondaryButton = t(\"Sign in\");\n break;\n }\n case 'emailSent': {\n title = t(\"Email sent!\");\n message = t(\"If the user with this e-mail address exists, an e-mail was sent to your inbox. Make sure to check your spam folder.\");\n primaryAction = () => stackApp.redirectToHome();\n primaryButton = t(\"Go home\");\n break;\n }\n case 'passwordReset': {\n title = t(\"Password reset successfully!\");\n message = t(\"Your password has been reset. You can now sign in with your new password.\");\n primaryAction = () => stackApp.redirectToSignIn({ noRedirectBack: true });\n primaryButton = t(\"Sign in\");\n break;\n }\n case 'unknownError': {\n title = t(\"An unknown error occurred\");\n message = t(\"Please try again and if the problem persists, contact support.\");\n primaryAction = () => stackApp.redirectToHome();\n primaryButton = t(\"Go home\");\n break;\n }\n }\n\n return (\n <MessageCard\n title={title}\n fullPage={fullPage}\n primaryButtonText={primaryButton}\n primaryAction={primaryAction}\n secondaryButtonText={secondaryButton || undefined}\n secondaryAction={secondaryAction || undefined}\n >\n {message && <Typography>{message}</Typography>}\n </MessageCard>\n );\n}\n"],"mappings":";;;AAOA,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AA0EV;AAxEX,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA,WAAS;AACX,GAGG;AACD,QAAM,WAAW,YAAY;AAC7B,QAAM,EAAE,EAAE,IAAI,eAAe;AAE7B,MAAI;AACJ,MAAI,UAAyB;AAC7B,MAAI,gBAA+B;AACnC,MAAI,kBAAiC;AACrC,MAAI,gBAAqD;AACzD,MAAI,kBAAuD;AAE3D,UAAQ,MAAM;AAAA,IACZ,KAAK,YAAY;AACf,cAAQ,EAAE,2BAA2B;AACrC,sBAAgB,MAAM,SAAS,eAAe;AAC9C,wBAAkB,MAAM,SAAS,kBAAkB;AACnD,sBAAgB,EAAE,SAAS;AAC3B,wBAAkB,EAAE,UAAU;AAC9B;AAAA,IACF;AAAA,IACA,KAAK,aAAa;AAChB,cAAQ,EAAE,kCAAkC;AAC5C,sBAAgB,MAAM,SAAS,iBAAiB;AAChD,sBAAgB,EAAE,SAAS;AAC3B;AAAA,IACF;AAAA,IACA,KAAK,kBAAkB;AACrB,cAAQ,EAAE,qDAAqD;AAC/D,sBAAgB,MAAM,SAAS,eAAe;AAC9C,wBAAkB,MAAM,SAAS,iBAAiB;AAClD,sBAAgB,EAAE,SAAS;AAC3B,wBAAkB,EAAE,SAAS;AAC7B;AAAA,IACF;AAAA,IACA,KAAK,aAAa;AAChB,cAAQ,EAAE,aAAa;AACvB,gBAAU,EAAE,qHAAqH;AACjI,sBAAgB,MAAM,SAAS,eAAe;AAC9C,sBAAgB,EAAE,SAAS;AAC3B;AAAA,IACF;AAAA,IACA,KAAK,iBAAiB;AACpB,cAAQ,EAAE,8BAA8B;AACxC,gBAAU,EAAE,2EAA2E;AACvF,sBAAgB,MAAM,SAAS,iBAAiB,EAAE,gBAAgB,KAAK,CAAC;AACxE,sBAAgB,EAAE,SAAS;AAC3B;AAAA,IACF;AAAA,IACA,KAAK,gBAAgB;AACnB,cAAQ,EAAE,2BAA2B;AACrC,gBAAU,EAAE,gEAAgE;AAC5E,sBAAgB,MAAM,SAAS,eAAe;AAC9C,sBAAgB,EAAE,SAAS;AAC3B;AAAA,IACF;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,MACnB;AAAA,MACA,qBAAqB,mBAAmB;AAAA,MACxC,iBAAiB,mBAAmB;AAAA,MAEnC,qBAAW,oBAAC,cAAY,mBAAQ;AAAA;AAAA,EACnC;AAEJ;","names":[]}

View file

@ -0,0 +1,27 @@
"use client";
"use client";
// src/components/oauth-button-group.tsx
import { useStackApp } from "../lib/hooks";
import { OAuthButton } from "./oauth-button";
import { jsx } from "react/jsx-runtime";
function OAuthButtonGroup({
type,
mockProject
}) {
const stackApp = useStackApp();
const project = mockProject || stackApp.useProject();
return /* @__PURE__ */ jsx("div", { className: "gap-4 flex flex-col items-stretch stack-scope", children: project.config.oauthProviders.map((p) => /* @__PURE__ */ jsx(
OAuthButton,
{
provider: p.id,
type,
isMock: !!mockProject
},
p.id
)) });
}
export {
OAuthButtonGroup
};
//# sourceMappingURL=oauth-button-group.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components/oauth-button-group.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { useStackApp } from \"../lib/hooks\";\nimport { OAuthButton } from \"./oauth-button\";\n\nexport function OAuthButtonGroup({\n type,\n mockProject,\n}: {\n type: 'sign-in' | 'sign-up',\n mockProject?: {\n config: {\n oauthProviders: {\n id: string,\n }[],\n },\n },\n}) {\n const stackApp = useStackApp();\n const project = mockProject || stackApp.useProject();\n return (\n <div className='gap-4 flex flex-col items-stretch stack-scope'>\n {project.config.oauthProviders.map(p => (\n <OAuthButton key={p.id} provider={p.id} type={type}\n isMock={!!mockProject}\n />\n ))}\n </div>\n );\n}\n"],"mappings":";;;AAOA,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAoBpB;AAlBD,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GASG;AACD,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,eAAe,SAAS,WAAW;AACnD,SACE,oBAAC,SAAI,WAAU,iDACZ,kBAAQ,OAAO,eAAe,IAAI,OACjC;AAAA,IAAC;AAAA;AAAA,MAAuB,UAAU,EAAE;AAAA,MAAI;AAAA,MACtC,QAAQ,CAAC,CAAC;AAAA;AAAA,IADM,EAAE;AAAA,EAEpB,CACD,GACH;AAEJ;","names":[]}

View file

@ -0,0 +1,177 @@
"use client";
"use client";
// src/components/oauth-button.tsx
import { BrandIcons, Button } from "@stackframe/stack-ui";
import Color from "color";
import { useEffect, useId, useState } from "react";
import { useStackApp } from "..";
import { useTranslation } from "../lib/translations";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var iconSize = 22;
var changeColor = (c, value) => {
if (c.isLight()) {
value = -value;
}
return c.hsl(c.hue(), c.saturationl(), c.lightness() + value).toString();
};
function OAuthButton({
provider,
type,
isMock = false
}) {
const { t } = useTranslation();
const stackApp = useStackApp();
const styleId = useId().replaceAll(":", "-");
const [lastUsed, setLastUsed] = useState(null);
useEffect(() => {
setLastUsed(localStorage.getItem("_STACK_AUTH.lastUsed"));
}, []);
let style;
switch (provider) {
case "google": {
style = {
backgroundColor: "#fff",
textColor: "#000",
name: "Google",
border: "1px solid #ddd",
icon: /* @__PURE__ */ jsx(BrandIcons.Google, { iconSize })
};
break;
}
case "github": {
style = {
backgroundColor: "#111",
textColor: "#fff",
border: "1px solid #333",
name: "GitHub",
icon: /* @__PURE__ */ jsx(BrandIcons.GitHub, { iconSize })
};
break;
}
case "facebook": {
style = {
backgroundColor: "#1877F2",
textColor: "#fff",
name: "Facebook",
icon: /* @__PURE__ */ jsx(BrandIcons.Facebook, { iconSize })
};
break;
}
case "microsoft": {
style = {
backgroundColor: "#2f2f2f",
textColor: "#fff",
name: "Microsoft",
icon: /* @__PURE__ */ jsx(BrandIcons.Microsoft, { iconSize })
};
break;
}
case "spotify": {
style = {
backgroundColor: "#1DB954",
textColor: "#fff",
name: "Spotify",
icon: /* @__PURE__ */ jsx(BrandIcons.Spotify, { iconSize })
};
break;
}
case "discord": {
style = {
backgroundColor: "#5865F2",
textColor: "#fff",
name: "Discord",
icon: /* @__PURE__ */ jsx(BrandIcons.Discord, { iconSize })
};
break;
}
case "gitlab": {
style = {
backgroundColor: "#111",
textColor: "#fff",
border: "1px solid #333",
name: "Gitlab",
icon: /* @__PURE__ */ jsx(BrandIcons.Gitlab, { iconSize })
};
break;
}
case "apple": {
style = {
backgroundColor: "#000",
textColor: "#fff",
border: "1px solid #333",
name: "Apple",
icon: /* @__PURE__ */ jsx(BrandIcons.Apple, { iconSize })
};
break;
}
case "bitbucket": {
style = {
backgroundColor: "#fff",
textColor: "#000",
border: "1px solid #ddd",
name: "Bitbucket",
icon: /* @__PURE__ */ jsx(BrandIcons.Bitbucket, { iconSize })
};
break;
}
case "linkedin": {
style = {
backgroundColor: "#0073b1",
textColor: "#fff",
name: "LinkedIn",
icon: /* @__PURE__ */ jsx(BrandIcons.LinkedIn, { iconSize })
};
break;
}
case "x": {
style = {
backgroundColor: "#000",
textColor: "#fff",
name: "X",
icon: /* @__PURE__ */ jsx(BrandIcons.X, { iconSize })
};
break;
}
default: {
style = {
name: provider,
icon: null
};
}
}
const styleSheet = `
.stack-oauth-button-${styleId} {
background-color: ${style.backgroundColor} !important;
color: ${style.textColor} !important;
border: ${style.border} !important;
}
.stack-oauth-button-${styleId}:hover {
background-color: ${changeColor(Color(style.backgroundColor), 10)} !important;
}
`;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("style", { children: styleSheet }),
/* @__PURE__ */ jsxs(
Button,
{
onClick: async () => {
localStorage.setItem("_STACK_AUTH.lastUsed", provider);
await stackApp.signInWithOAuth(provider);
},
className: `stack-oauth-button-${styleId} stack-scope relative`,
children: [
!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" }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center w-full gap-4", children: [
style.icon,
/* @__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 }) })
] })
]
}
)
] });
}
export {
OAuthButton
};
//# sourceMappingURL=oauth-button.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,34 @@
"use client";
"use client";
// src/components/passkey-button.tsx
import { Button } from "@stackframe/stack-ui";
import { useId } from "react";
import { useStackApp } from "..";
import { useTranslation } from "../lib/translations";
import { KeyRound } from "lucide-react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function PasskeyButton({
type
}) {
const { t } = useTranslation();
const stackApp = useStackApp();
const styleId = useId().replaceAll(":", "-");
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
Button,
{
onClick: async () => {
await stackApp.signInWithPasskey();
},
className: `stack-oauth-button-${styleId} stack-scope`,
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full gap-4", children: [
/* @__PURE__ */ jsx(KeyRound, {}),
/* @__PURE__ */ jsx("span", { className: "flex-1", children: type === "sign-up" ? t("Sign up with Passkey") : t("Sign in with Passkey") })
] })
}
) });
}
export {
PasskeyButton
};
//# sourceMappingURL=passkey-button.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components/passkey-button.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\n\nimport { Button } from '@stackframe/stack-ui';\nimport { useId } from 'react';\nimport { useStackApp } from '..';\nimport { useTranslation } from '../lib/translations';\nimport { KeyRound } from 'lucide-react';\n\n\nexport function PasskeyButton({\n type,\n}: {\n type: 'sign-in' | 'sign-up',\n}) {\n const { t } = useTranslation();\n const stackApp = useStackApp();\n const styleId = useId().replaceAll(':', '-');\n\n\n return (\n <>\n <Button\n onClick={async () => { await stackApp.signInWithPasskey(); }}\n className={`stack-oauth-button-${styleId} stack-scope`}\n >\n <div className='flex items-center w-full gap-4'>\n <KeyRound />\n <span className='flex-1'>\n {type === 'sign-up' ?\n t('Sign up with Passkey') :\n t('Sign in with Passkey')\n }\n </span>\n </div>\n </Button>\n </>\n );\n}\n"],"mappings":";;;AAOA,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB;AAcrB,mBAMM,KADF,YALJ;AAXG,SAAS,cAAc;AAAA,EAC5B;AACF,GAEG;AACD,QAAM,EAAE,EAAE,IAAI,eAAe;AAC7B,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,MAAM,EAAE,WAAW,KAAK,GAAG;AAG3C,SACE,gCACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,YAAY;AAAE,cAAM,SAAS,kBAAkB;AAAA,MAAG;AAAA,MAC3D,WAAW,sBAAsB,OAAO;AAAA,MAExC,+BAAC,SAAI,WAAU,kCACb;AAAA,4BAAC,YAAS;AAAA,QACV,oBAAC,UAAK,WAAU,UACb,mBAAS,YACR,EAAE,sBAAsB,IACxB,EAAE,sBAAsB,GAE5B;AAAA,SACF;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}

View file

@ -0,0 +1,129 @@
// src/components/profile-image-editor.tsx
import { fileToBase64 } from "@stackframe/stack-shared/dist/utils/base64";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { Button, Slider, Typography } from "@stackframe/stack-ui";
import imageCompression from "browser-image-compression";
import { Upload } from "lucide-react";
import { useRef, useState } from "react";
import AvatarEditor from "react-avatar-editor";
import { useTranslation } from "../lib/translations";
import { UserAvatar } from "./elements/user-avatar";
import { jsx, jsxs } from "react/jsx-runtime";
async function checkImageUrl(url) {
try {
const res = await fetch(url, { method: "HEAD" });
const buff = await res.blob();
return buff.type.startsWith("image/");
} catch (e) {
return false;
}
}
function ProfileImageEditor(props) {
const { t } = useTranslation();
const cropRef = useRef(null);
const [slideValue, setSlideValue] = useState(1);
const [rawUrl, setRawUrl] = useState(null);
const [error, setError] = useState(null);
function reset() {
setSlideValue(1);
setRawUrl(null);
setError(null);
}
function upload() {
const input = document.createElement("input");
input.type = "file";
input.onchange = (e) => {
const file = e.target.files?.[0];
if (!file) return;
runAsynchronouslyWithAlert(async () => {
const rawUrl2 = await fileToBase64(file);
if (await checkImageUrl(rawUrl2)) {
setRawUrl(rawUrl2);
setError(null);
} else {
setError(t("Invalid image"));
}
input.remove();
});
};
input.click();
}
if (!rawUrl) {
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
/* @__PURE__ */ jsxs("div", { className: "cursor-pointer relative", onClick: upload, children: [
/* @__PURE__ */ jsx(
UserAvatar,
{
size: 60,
user: props.user,
border: true
}
),
/* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsx("div", { className: "bg-background p-2 rounded-full", children: /* @__PURE__ */ jsx(Upload, { className: "h-5 w-5" }) }) })
] }),
error && /* @__PURE__ */ jsx(Typography, { variant: "destructive", type: "label", children: error })
] });
}
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
/* @__PURE__ */ jsx(
AvatarEditor,
{
ref: cropRef,
image: rawUrl || props.user.profileImageUrl || "",
borderRadius: 1e3,
color: [0, 0, 0, 0.72],
scale: slideValue,
rotate: 0,
border: 20,
className: "border"
}
),
/* @__PURE__ */ jsx(
Slider,
{
min: 1,
max: 5,
step: 0.1,
defaultValue: [slideValue],
value: [slideValue],
onValueChange: (v) => setSlideValue(v[0])
}
),
/* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2", children: [
/* @__PURE__ */ jsx(
Button,
{
onClick: async () => {
if (cropRef.current && rawUrl) {
const croppedUrl = cropRef.current.getImage().toDataURL("image/jpeg");
const compressedFile = await imageCompression(
await imageCompression.getFilefromDataUrl(croppedUrl, "profile-image"),
{
maxSizeMB: 0.1,
fileType: "image/jpeg"
}
);
const compressedUrl = await imageCompression.getDataUrlFromFile(compressedFile);
await props.onProfileImageUrlChange(compressedUrl);
reset();
}
},
children: t("Save")
}
),
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: reset,
children: t("Cancel")
}
)
] })
] });
}
export {
ProfileImageEditor,
checkImageUrl
};
//# sourceMappingURL=profile-image-editor.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,107 @@
"use client";
"use client";
// src/components/selected-team-switcher.tsx
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import {
Button,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
Skeleton,
Typography
} from "@stackframe/stack-ui";
import { PlusCircle, Settings } from "lucide-react";
import { Suspense, useEffect, useMemo } from "react";
import { useStackApp, useUser } from "..";
import { useTranslation } from "../lib/translations";
import { TeamIcon } from "./team-icon";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function SelectedTeamSwitcher(props) {
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Fallback, {}), children: /* @__PURE__ */ jsx(Inner, { ...props }) });
}
function Fallback() {
return /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full max-w-64 stack-scope" });
}
function Inner(props) {
const { t } = useTranslation();
const app = useStackApp();
const user = useUser();
const project = app.useProject();
const navigate = app.useNavigate();
const selectedTeam = user?.selectedTeam || props.selectedTeam;
const rawTeams = user?.useTeams();
const teams = useMemo(() => rawTeams?.sort((a, b) => b.id === selectedTeam?.id ? 1 : -1), [rawTeams, selectedTeam]);
useEffect(() => {
if (!props.noUpdateSelectedTeam && props.selectedTeam) {
runAsynchronouslyWithAlert(user?.setSelectedTeam(props.selectedTeam));
}
}, [props.noUpdateSelectedTeam, props.selectedTeam]);
return /* @__PURE__ */ jsxs(
Select,
{
value: selectedTeam?.id,
onValueChange: (value) => {
runAsynchronouslyWithAlert(async () => {
const team = teams?.find((team2) => team2.id === value);
if (!team) {
throw new Error("Team not found, this should not happen");
}
if (!props.noUpdateSelectedTeam) {
await user?.setSelectedTeam(team);
}
if (props.urlMap) {
navigate(props.urlMap(team));
}
});
},
children: [
/* @__PURE__ */ jsx(SelectTrigger, { className: "stack-scope max-w-64", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select team" }) }),
/* @__PURE__ */ jsxs(SelectContent, { className: "stack-scope", children: [
user?.selectedTeam ? /* @__PURE__ */ jsxs(SelectGroup, { children: [
/* @__PURE__ */ jsx(SelectLabel, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ jsx("span", { children: t("Current team") }),
/* @__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" }) })
] }) }),
/* @__PURE__ */ jsx(SelectItem, { value: user.selectedTeam.id, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx(TeamIcon, { team: user.selectedTeam }),
/* @__PURE__ */ jsx(Typography, { className: "max-w-40 truncate", children: user.selectedTeam.displayName })
] }) })
] }) : void 0,
teams?.length ? /* @__PURE__ */ jsxs(SelectGroup, { children: [
/* @__PURE__ */ jsx(SelectLabel, { children: t("Other teams") }),
teams.filter((team) => team.id !== user?.selectedTeam?.id).map((team) => /* @__PURE__ */ jsx(SelectItem, { value: team.id, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx(TeamIcon, { team }),
/* @__PURE__ */ jsx(Typography, { className: "max-w-64 truncate", children: team.displayName })
] }) }, team.id))
] }) : /* @__PURE__ */ jsx(SelectGroup, { children: /* @__PURE__ */ jsx(SelectLabel, { children: t("No teams yet") }) }),
project.config.clientTeamCreationEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(SelectSeparator, {}),
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
Button,
{
onClick: () => navigate(`${app.urls.accountSettings}#team-creation`),
className: "w-full",
variant: "ghost",
children: [
/* @__PURE__ */ jsx(PlusCircle, { className: "mr-2 h-4 w-4" }),
" ",
t("Create a team")
]
}
) })
] })
] })
]
}
);
}
export {
SelectedTeamSwitcher
};
//# sourceMappingURL=selected-team-switcher.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
// src/components/team-icon.tsx
import { Avatar, AvatarImage, Typography } from "@stackframe/stack-ui";
import { jsx } from "react/jsx-runtime";
function TeamIcon(props) {
if (props.team.profileImageUrl) {
return /* @__PURE__ */ jsx(Avatar, { className: "min-w-6 min-h-6 max-w-6 max-h-6 rounded", children: /* @__PURE__ */ jsx(AvatarImage, { src: props.team.profileImageUrl, alt: props.team.displayName }) });
} else {
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-w-6 min-h-6 max-w-6 max-h-6 rounded bg-zinc-200", children: /* @__PURE__ */ jsx(Typography, { className: "text-zinc-800 dark:text-zinc-800", children: props.team.displayName.slice(0, 1).toUpperCase() }) });
}
}
export {
TeamIcon
};
//# sourceMappingURL=team-icon.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/components/team-icon.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { Avatar, AvatarImage, Typography } from \"@stackframe/stack-ui\";\nimport { Team } from \"..\";\n\nexport function TeamIcon(props: { team: Team }) {\n if (props.team.profileImageUrl) {\n return (\n <Avatar className=\"min-w-6 min-h-6 max-w-6 max-h-6 rounded\">\n <AvatarImage src={props.team.profileImageUrl} alt={props.team.displayName} />\n </Avatar>\n );\n } else {\n return (\n <div className=\"flex items-center justify-center min-w-6 min-h-6 max-w-6 max-h-6 rounded bg-zinc-200\">\n <Typography className=\"text-zinc-800 dark:text-zinc-800\">{props.team.displayName.slice(0, 1).toUpperCase()}</Typography>\n </div>\n );\n }\n}\n"],"mappings":";AAIA,SAAS,QAAQ,aAAa,kBAAkB;AAOxC;AAJD,SAAS,SAAS,OAAuB;AAC9C,MAAI,MAAM,KAAK,iBAAiB;AAC9B,WACE,oBAAC,UAAO,WAAU,2CAChB,8BAAC,eAAY,KAAK,MAAM,KAAK,iBAAiB,KAAK,MAAM,KAAK,aAAa,GAC7E;AAAA,EAEJ,OAAO;AACL,WACE,oBAAC,SAAI,WAAU,wFACb,8BAAC,cAAW,WAAU,oCAAoC,gBAAM,KAAK,YAAY,MAAM,GAAG,CAAC,EAAE,YAAY,GAAE,GAC7G;AAAA,EAEJ;AACF;","names":[]}

View file

@ -0,0 +1,96 @@
"use client";
"use client";
// src/components/user-button.tsx
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Skeleton, Typography } from "@stackframe/stack-ui";
import { CircleUser, LogIn, LogOut, SunMoon, UserPlus } from "lucide-react";
import { Suspense } from "react";
import { useStackApp, useUser } from "..";
import { useTranslation } from "../lib/translations";
import { UserAvatar } from "./elements/user-avatar";
import { jsx, jsxs } from "react/jsx-runtime";
function Item(props) {
return /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => runAsynchronouslyWithAlert(props.onClick), children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
props.icon,
/* @__PURE__ */ jsx(Typography, { children: props.text })
] }) });
}
function UserButton(props) {
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Skeleton, { className: "h-[34px] w-[34px] rounded-full stack-scope" }), children: /* @__PURE__ */ jsx(UserButtonInner, { ...props }) });
}
function UserButtonInner(props) {
const user = useUser();
return /* @__PURE__ */ jsx(UserButtonInnerInner, { ...props, user });
}
function UserButtonInnerInner(props) {
const { t } = useTranslation();
const user = props.user;
const app = useStackApp();
const iconProps = { size: 20, className: "h-4 w-4" };
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
/* @__PURE__ */ jsx(DropdownMenuTrigger, { className: "outline-none stack-scope", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
/* @__PURE__ */ jsx(UserAvatar, { user }),
user && props.showUserInfo && /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center text-left", children: [
/* @__PURE__ */ jsx(Typography, { className: "max-w-40 truncate", children: user.displayName }),
/* @__PURE__ */ jsx(Typography, { className: "max-w-40 truncate", variant: "secondary", type: "label", children: user.primaryEmail })
] })
] }) }),
/* @__PURE__ */ jsxs(DropdownMenuContent, { className: "stack-scope", children: [
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
/* @__PURE__ */ jsx(UserAvatar, { user }),
/* @__PURE__ */ jsxs("div", { children: [
user && /* @__PURE__ */ jsx(Typography, { className: "max-w-40 truncate", children: user.displayName }),
user && /* @__PURE__ */ jsx(Typography, { className: "max-w-40 truncate", variant: "secondary", type: "label", children: user.primaryEmail }),
!user && /* @__PURE__ */ jsx(Typography, { children: t("Not signed in") })
] })
] }) }),
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
user && /* @__PURE__ */ jsx(
Item,
{
text: t("Account settings"),
onClick: async () => await app.redirectToAccountSettings(),
icon: /* @__PURE__ */ jsx(CircleUser, { ...iconProps })
}
),
!user && /* @__PURE__ */ jsx(
Item,
{
text: t("Sign in"),
onClick: async () => await app.redirectToSignIn(),
icon: /* @__PURE__ */ jsx(LogIn, { ...iconProps })
}
),
!user && /* @__PURE__ */ jsx(
Item,
{
text: t("Sign up"),
onClick: async () => await app.redirectToSignUp(),
icon: /* @__PURE__ */ jsx(UserPlus, { ...iconProps })
}
),
user && props.extraItems && props.extraItems.map((item, index) => /* @__PURE__ */ jsx(Item, { ...item }, index)),
props.colorModeToggle && /* @__PURE__ */ jsx(
Item,
{
text: t("Toggle theme"),
onClick: props.colorModeToggle,
icon: /* @__PURE__ */ jsx(SunMoon, { ...iconProps })
}
),
user && /* @__PURE__ */ jsx(
Item,
{
text: t("Sign out"),
onClick: () => user.signOut(),
icon: /* @__PURE__ */ jsx(LogOut, { ...iconProps })
}
)
] })
] });
}
export {
UserButton
};
//# sourceMappingURL=user-button.js.map

File diff suppressed because one or more lines are too long