@stackframe/stack 2.3.3 → 2.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/components/user-avatar.d.ts +3 -1
- package/dist/components/user-avatar.js +4 -6
- package/dist/components/user-button.d.ts +4 -2
- package/dist/components/user-button.js +14 -8
- package/dist/components-core/index.d.ts +3 -0
- package/dist/components-core/index.js +1 -0
- package/dist/components-core/skeleton.d.ts +5 -0
- package/dist/components-core/skeleton.js +32 -0
- package/dist/components-page/account-settings.js +1 -1
- package/dist/lib/stack-app.d.ts +2 -3
- package/dist/lib/stack-app.js +16 -28
- package/dist/providers/component-provider.d.ts +6 -0
- package/dist/providers/component-provider.js +2 -0
- package/dist/providers/design-provider.d.ts +1 -1
- package/dist/providers/design-provider.js +0 -6
- package/dist/providers/stack-provider-client.d.ts +5 -1
- package/dist/providers/stack-provider-client.js +11 -2
- package/dist/providers/stack-provider.js +8 -3
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Stack
|
|
2
|
+
|
|
3
|
+
Stack is an open-source, self-hostable, and highly customizable authentication and user management system.
|
|
4
|
+
|
|
5
|
+
We provide frontend and backend libraries for Next.js, React, and JavaScript. You can set it up in one minute and scale with the project as it grows.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Composable React components & hooks
|
|
10
|
+
- OAuth (Google, Facebook, GitHub, etc.)
|
|
11
|
+
- Email and password authentication (with email verification and password reset)
|
|
12
|
+
- Easy to set up with proxied providers (no need to sign up and create OAuth endpoints yourself on all the providers)
|
|
13
|
+
- User management & analytics
|
|
14
|
+
- User-associated metadata with client-/server-specific permissions
|
|
15
|
+
- Out-of-the-box Dark/Light mode support
|
|
16
|
+
- suports switching out the underlying UI library, support MUI Joy out of the box
|
|
17
|
+
- **100% open-source!**
|
|
18
|
+
|
|
19
|
+
Currently, only Next.js is supported, but we are working on adding other frameworks.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
To get started with Stack, you need to [create a Next.js project](https://nextjs.org/docs/getting-started/installation) using the App router. Then, you can install Stack by running the following command:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @stackframe/stack
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For setup, refer to [our documentation](https://docs.stack-auth.com).
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Avatar, AvatarFallback, AvatarImage, Text
|
|
2
|
+
import { Avatar, AvatarFallback, AvatarImage, Text } from "..";
|
|
3
|
+
const anonUserDataUrl = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' viewBox='-100 -1060 1160 1160' width='24'%3E%3Crect x='-100' y='-1060' width='100%25' height='100%25' fill='%23d8d8d8' /%3E%3Cpath d='M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z'/%3E%3C/svg%3E";
|
|
3
4
|
export default function UserAvatar(props) {
|
|
4
|
-
const user =
|
|
5
|
-
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
return (_jsxs(Avatar, { style: { height: props.size, width: props.size }, children: [_jsx(AvatarImage, { src: user.profileImageUrl || '' }), _jsx(AvatarFallback, { children: _jsx(Text, { style: { fontWeight: 500 }, children: (user.displayName || user.primaryEmail)?.slice(0, 2).toUpperCase() }) })] }));
|
|
5
|
+
const user = props.user;
|
|
6
|
+
return (_jsxs(Avatar, { style: { height: props.size, width: props.size }, children: [_jsx(AvatarImage, { src: user ? user.profileImageUrl || '' : anonUserDataUrl }), _jsx(AvatarFallback, { children: _jsx(Text, { style: { fontWeight: 500 }, children: (user?.displayName || user?.primaryEmail)?.slice(0, 2).toUpperCase() ?? "?" }) })] }));
|
|
9
7
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
type UserButtonProps = {
|
|
2
2
|
showUserInfo?: boolean;
|
|
3
3
|
showColorMode?: boolean;
|
|
4
|
-
}
|
|
4
|
+
};
|
|
5
|
+
export default function UserButton({ showUserInfo, showColorMode, }: UserButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import { useUser, Text, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuLabel, DropdownMenuSeparator, useStackApp, useDesign, Skeleton } from "..";
|
|
5
|
+
import { RxPerson, RxEnter, RxSun, RxShadow, RxFilePlus } from "react-icons/rx";
|
|
5
6
|
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
|
6
7
|
import { SECONDARY_FONT_COLORS } from "../utils/constants";
|
|
7
8
|
import UserAvatar from "./user-avatar";
|
|
@@ -9,9 +10,16 @@ import { useRouter } from "next/navigation";
|
|
|
9
10
|
function Item(props) {
|
|
10
11
|
return (_jsxs(DropdownMenuItem, { onClick: () => runAsynchronously(props.onClick), style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [props.icon, _jsx(Text, { children: props.text })] }));
|
|
11
12
|
}
|
|
12
|
-
export default function UserButton({ showUserInfo = false, showColorMode =
|
|
13
|
-
|
|
13
|
+
export default function UserButton({ showUserInfo = false, showColorMode = false, }) {
|
|
14
|
+
return (_jsx(Suspense, { fallback: _jsx(Skeleton, { children: _jsx(UserButtonInnerInner, { showUserInfo: showUserInfo, showColorMode: showColorMode, user: null }) }), children: _jsx(UserButtonInner, { showUserInfo: showUserInfo, showColorMode: showColorMode }) }));
|
|
15
|
+
}
|
|
16
|
+
function UserButtonInner(props) {
|
|
14
17
|
const user = useUser();
|
|
18
|
+
return _jsx(UserButtonInnerInner, { showUserInfo: props.showUserInfo, showColorMode: props.showColorMode, user: user });
|
|
19
|
+
}
|
|
20
|
+
function UserButtonInnerInner(props) {
|
|
21
|
+
const { colorMode, setColorMode } = useDesign();
|
|
22
|
+
const user = props.user;
|
|
15
23
|
const app = useStackApp();
|
|
16
24
|
const router = useRouter();
|
|
17
25
|
const textStyles = {
|
|
@@ -19,9 +27,7 @@ export default function UserButton({ showUserInfo = false, showColorMode = true,
|
|
|
19
27
|
whiteSpace: 'nowrap',
|
|
20
28
|
overflow: 'hidden'
|
|
21
29
|
};
|
|
22
|
-
|
|
23
|
-
return null;
|
|
24
|
-
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [_jsx(UserAvatar, {}), showUserInfo && _jsxs("div", { style: { display: 'flex', flexDirection: 'column', justifyContent: 'center' }, children: [_jsx(Text, { style: textStyles, children: user.displayName }), _jsx(Text, { style: { ...textStyles, fontWeight: 400 }, variant: "secondary", size: "sm", children: user.primaryEmail })] })] }) }), _jsxs(DropdownMenuContent, { style: { zIndex: 1500 }, children: [_jsx(DropdownMenuLabel, { children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [_jsx(UserAvatar, {}), _jsxs("div", { children: [_jsx(Text, { children: user.displayName }), _jsx(Text, { variant: "secondary", size: "sm", style: { fontWeight: 400 }, children: user.primaryEmail })] })] }) }), _jsx(DropdownMenuSeparator, {}), _jsx(Item, { text: "Account settings", onClick: () => runAsynchronously(router.push(app.urls.accountSettings)), icon: _jsx(RxPerson, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) }), showColorMode && _jsx(Item, { text: colorMode === 'dark' ? 'Light theme' : 'Dark theme', onClick: () => setColorMode(colorMode === 'dark' ? 'light' : 'dark'), icon: colorMode === 'dark' ?
|
|
30
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [_jsx(UserAvatar, { user: user }), user && props.showUserInfo && _jsxs("div", { style: { display: 'flex', flexDirection: 'column', justifyContent: 'center' }, children: [_jsx(Text, { style: textStyles, children: user.displayName }), _jsx(Text, { style: { ...textStyles, fontWeight: 400 }, variant: "secondary", size: "sm", children: user.primaryEmail })] })] }) }), _jsxs(DropdownMenuContent, { style: { zIndex: 1500 }, children: [_jsx(DropdownMenuLabel, { children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [_jsx(UserAvatar, { user: user }), _jsxs("div", { children: [user && _jsx(Text, { children: user.displayName }), user && _jsx(Text, { variant: "secondary", size: "sm", style: { fontWeight: 400 }, children: user.primaryEmail }), !user && _jsx(Text, { variant: "secondary", children: "Not logged in" })] })] }) }), _jsx(DropdownMenuSeparator, {}), user && _jsx(Item, { text: "Account settings", onClick: () => runAsynchronously(router.push(app.urls.accountSettings)), icon: _jsx(RxPerson, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) }), !user && _jsx(Item, { text: "Sign in", onClick: () => runAsynchronously(router.push(app.urls.signIn)), icon: _jsx(RxEnter, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) }), !user && _jsx(Item, { text: "Sign up", onClick: () => runAsynchronously(router.push(app.urls.signUp)), icon: _jsx(RxFilePlus, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) }), props.showColorMode && _jsx(Item, { text: colorMode === 'dark' ? 'Light theme' : 'Dark theme', onClick: () => setColorMode(colorMode === 'dark' ? 'light' : 'dark'), icon: colorMode === 'dark' ?
|
|
25
31
|
_jsx(RxSun, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) :
|
|
26
|
-
_jsx(RxShadow, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) }), _jsx(Item, { text: "Sign out", onClick: () => user.signOut(), icon: _jsx(RxEnter, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) })] })] }));
|
|
32
|
+
_jsx(RxShadow, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) }), user && _jsx(Item, { text: "Sign out", onClick: () => user.signOut(), icon: _jsx(RxEnter, { size: 22, color: SECONDARY_FONT_COLORS[colorMode] }) })] })] }));
|
|
27
33
|
}
|
|
@@ -43,3 +43,6 @@ export declare const Tabs: React.ForwardRefExoticComponent<Omit<import("@radix-u
|
|
|
43
43
|
export declare const TabsList: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-tabs").TabsListProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
44
44
|
export declare const TabsContent: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-tabs").TabsContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
45
45
|
export declare const TabsTrigger: React.ForwardRefExoticComponent<Omit<Omit<import("@radix-ui/react-tabs").TabsTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
46
|
+
export declare const Skeleton: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
47
|
+
deactivated?: boolean | undefined;
|
|
48
|
+
} & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -46,3 +46,4 @@ export const Tabs = createDynamicComponent('Tabs');
|
|
|
46
46
|
export const TabsList = createDynamicComponent('TabsList');
|
|
47
47
|
export const TabsContent = createDynamicComponent('TabsContent');
|
|
48
48
|
export const TabsTrigger = createDynamicComponent('TabsTrigger');
|
|
49
|
+
export const Skeleton = createDynamicComponent('Skeleton');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
declare const Skeleton: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
3
|
+
deactivated?: boolean | undefined;
|
|
4
|
+
} & React.RefAttributes<HTMLSpanElement>>;
|
|
5
|
+
export { Skeleton };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import styled, { keyframes } from 'styled-components';
|
|
5
|
+
import { SECONDARY_FONT_COLORS } from "../utils/constants";
|
|
6
|
+
import { useDesign } from "../providers/design-provider";
|
|
7
|
+
const animation = keyframes `
|
|
8
|
+
0% {
|
|
9
|
+
filter: grayscale(1) contrast(0) brightness(0) invert(1) brightness(0.8);
|
|
10
|
+
}
|
|
11
|
+
100% {
|
|
12
|
+
filter: grayscale(1) contrast(0) brightness(0) invert(1) brightness(0.7);
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
const Primitive = styled("span") `
|
|
16
|
+
&[data-stack-state="activated"] {
|
|
17
|
+
animation: ${animation} 1s infinite alternate-reverse !important;
|
|
18
|
+
}
|
|
19
|
+
&[data-stack-state="activated"], &[data-stack-state="activated"] * {
|
|
20
|
+
pointer-events: none !important;
|
|
21
|
+
-webkit-user-select: none !important;
|
|
22
|
+
-moz-user-select: none !important;
|
|
23
|
+
user-select: none !important;
|
|
24
|
+
cursor: default !important;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
const Skeleton = React.forwardRef((props, ref) => {
|
|
28
|
+
const { colorMode } = useDesign();
|
|
29
|
+
return _jsx(Primitive, { "$color": colorMode === 'dark' ? SECONDARY_FONT_COLORS.dark : SECONDARY_FONT_COLORS.light, ref: ref, "data-stack-state": props.deactivated ? "deactivated" : "activated", ...props });
|
|
30
|
+
});
|
|
31
|
+
Skeleton.displayName = "Skeleton";
|
|
32
|
+
export { Skeleton };
|
|
@@ -17,7 +17,7 @@ function ProfileSection() {
|
|
|
17
17
|
return (_jsxs(SettingSection, { title: 'Profile', desc: 'Your profile information', buttonDisabled: !changed, buttonText: 'Save', onButtonClick: async () => {
|
|
18
18
|
await user?.update(userInfo);
|
|
19
19
|
setChanged(false);
|
|
20
|
-
}, children: [_jsxs("div", { style: { display: 'flex', gap: '1rem', alignItems: 'center' }, children: [_jsx(UserAvatar, { size: 60 }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Text, { size: 'lg', children: user?.displayName }), _jsx(Text, { variant: 'secondary', size: 'sm', children: user?.primaryEmail })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Label, { htmlFor: 'display-name', children: "Display Name" }), _jsx(Input, { id: 'display-name', value: userInfo.displayName, onChange: (e) => {
|
|
20
|
+
}, children: [_jsxs("div", { style: { display: 'flex', gap: '1rem', alignItems: 'center' }, children: [_jsx(UserAvatar, { user: user, size: 60 }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Text, { size: 'lg', children: user?.displayName }), _jsx(Text, { variant: 'secondary', size: 'sm', children: user?.primaryEmail })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Label, { htmlFor: 'display-name', children: "Display Name" }), _jsx(Input, { id: 'display-name', value: userInfo.displayName, onChange: (e) => {
|
|
21
21
|
setUserInfo((i) => ({ ...i, displayName: e.target.value }));
|
|
22
22
|
setChanged(true);
|
|
23
23
|
} })] })] }));
|
package/dist/lib/stack-app.d.ts
CHANGED
|
@@ -37,8 +37,6 @@ export type StackAdminAppConstructorOptions<HasTokenStore extends boolean, Proje
|
|
|
37
37
|
}));
|
|
38
38
|
export type StackClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId> & {
|
|
39
39
|
uniqueIdentifier: string;
|
|
40
|
-
currentClientUserJson: UserJson | null;
|
|
41
|
-
currentProjectJson: ClientProjectJson;
|
|
42
40
|
};
|
|
43
41
|
export declare const stackAppInternalsSymbol: unique symbol;
|
|
44
42
|
type Auth<T, C> = {
|
|
@@ -179,7 +177,8 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
|
|
|
179
177
|
verifyEmail(code: string): Promise<KnownErrors["EmailVerificationError"] | undefined>;
|
|
180
178
|
signInWithMagicLink(code: string): Promise<KnownErrors["MagicLinkError"] | undefined>;
|
|
181
179
|
[stackAppInternalsSymbol]: {
|
|
182
|
-
toClientJson():
|
|
180
|
+
toClientJson(): StackClientAppJson<HasTokenStore, ProjectId>;
|
|
181
|
+
setCurrentUser(userJsonPromise: Promise<UserJson | null>): void;
|
|
183
182
|
};
|
|
184
183
|
} & AsyncStoreProperty<"project", ClientProjectJson, false> & {
|
|
185
184
|
[K in `redirectTo${Capitalize<keyof Omit<HandlerUrls, 'handler' | 'oauthCallback'>>}`]: () => Promise<never>;
|
package/dist/lib/stack-app.js
CHANGED
|
@@ -9,10 +9,10 @@ import { AsyncStore } from "@stackframe/stack-shared/dist/utils/stores";
|
|
|
9
9
|
import { getProductionModeErrors } from "@stackframe/stack-shared/dist/interface/clientInterface";
|
|
10
10
|
import { isClient } from "../utils/next";
|
|
11
11
|
import { callOAuthCallback, signInWithOAuth } from "./auth";
|
|
12
|
-
import
|
|
12
|
+
import * as NextNavigation from "next/navigation"; // import the entire module to get around some static compiler warnings emitted by Next.js in some cases
|
|
13
13
|
import { constructRedirectUrl } from "../utils/url";
|
|
14
14
|
import { filterUndefined, omit } from "@stackframe/stack-shared/dist/utils/objects";
|
|
15
|
-
import { neverResolve, resolved, runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
|
15
|
+
import { neverResolve, resolved, runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises";
|
|
16
16
|
import { AsyncCache } from "@stackframe/stack-shared/dist/utils/caches";
|
|
17
17
|
import { suspend } from "@stackframe/stack-shared/dist/utils/react";
|
|
18
18
|
function getUrls(partial) {
|
|
@@ -36,10 +36,10 @@ function getUrls(partial) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
function getDefaultProjectId() {
|
|
39
|
-
return process.env.NEXT_PUBLIC_STACK_PROJECT_ID || throwErr("
|
|
39
|
+
return process.env.NEXT_PUBLIC_STACK_PROJECT_ID || throwErr("Welcome to Stack! It seems that you haven't provided a project ID. Please create a project on the Stack dashboard at https://app.stack-auth.com and put it in the NEXT_PUBLIC_STACK_PROJECT_ID environment variable.");
|
|
40
40
|
}
|
|
41
41
|
function getDefaultPublishableClientKey() {
|
|
42
|
-
return process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY || throwErr("
|
|
42
|
+
return process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY || throwErr("Welcome to Stack! It seems that you haven't provided a publishable client key. Please create an API key for your project on the Stack dashboard at https://app.stack-auth.com and copy your publishable client key into the NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY environment variable.");
|
|
43
43
|
}
|
|
44
44
|
function getDefaultSecretServerKey() {
|
|
45
45
|
return process.env.STACK_SECRET_SERVER_KEY || throwErr("No secret server key provided. Please copy your publishable client key from the Stack dashboard and put your it in the STACK_SECRET_SERVER_KEY environment variable.");
|
|
@@ -172,7 +172,11 @@ class _StackClientAppImpl {
|
|
|
172
172
|
_interface;
|
|
173
173
|
_tokenStoreOptions;
|
|
174
174
|
_urlOptions;
|
|
175
|
+
__DEMO_ENABLE_SLIGHT_FETCH_DELAY = false;
|
|
175
176
|
_currentUserCache = createCacheByTokenStore(async (tokenStore) => {
|
|
177
|
+
if (this.__DEMO_ENABLE_SLIGHT_FETCH_DELAY) {
|
|
178
|
+
await wait(2000);
|
|
179
|
+
}
|
|
176
180
|
const user = await this._interface.getClientUserByToken(tokenStore);
|
|
177
181
|
return Result.or(user, null);
|
|
178
182
|
});
|
|
@@ -200,19 +204,6 @@ class _StackClientAppImpl {
|
|
|
200
204
|
throw new StackAssertionError("A Stack client app with the same unique identifier already exists");
|
|
201
205
|
}
|
|
202
206
|
allClientApps.set(this._uniqueIdentifier, [options.checkString ?? "default check string", this]);
|
|
203
|
-
// For some important calls, either use the provided cached values or start fetching them now
|
|
204
|
-
if (options.currentClientUserJson !== undefined) {
|
|
205
|
-
this._currentUserCache.forceSetCachedValue([getTokenStore(this._tokenStoreOptions)], options.currentClientUserJson);
|
|
206
|
-
}
|
|
207
|
-
else if (this.hasPersistentTokenStore()) {
|
|
208
|
-
runAsynchronously(this.getUser(), { ignoreErrors: true });
|
|
209
|
-
}
|
|
210
|
-
if (options.currentProjectJson !== undefined) {
|
|
211
|
-
this._currentProjectCache.forceSetCachedValue([], options.currentProjectJson);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
runAsynchronously(this.getProject(), { ignoreErrors: true });
|
|
215
|
-
}
|
|
216
207
|
}
|
|
217
208
|
hasPersistentTokenStore() {
|
|
218
209
|
return this._tokenStoreOptions !== null;
|
|
@@ -401,7 +392,7 @@ class _StackClientAppImpl {
|
|
|
401
392
|
if (userJson === null) {
|
|
402
393
|
switch (options?.or) {
|
|
403
394
|
case 'redirect': {
|
|
404
|
-
redirect(this.urls.signIn, RedirectType.replace);
|
|
395
|
+
NextNavigation.redirect(this.urls.signIn, NextNavigation.RedirectType.replace);
|
|
405
396
|
throw new Error("redirect should never return!");
|
|
406
397
|
}
|
|
407
398
|
case 'throw': {
|
|
@@ -416,7 +407,7 @@ class _StackClientAppImpl {
|
|
|
416
407
|
}
|
|
417
408
|
useUser(options) {
|
|
418
409
|
this._ensurePersistentTokenStore();
|
|
419
|
-
const router = useRouter();
|
|
410
|
+
const router = NextNavigation.useRouter();
|
|
420
411
|
const tokenStore = getTokenStore(this._tokenStoreOptions);
|
|
421
412
|
const userJson = useCache(this._currentUserCache, [tokenStore], "useUser()");
|
|
422
413
|
if (userJson === null) {
|
|
@@ -569,7 +560,7 @@ class _StackClientAppImpl {
|
|
|
569
560
|
static get [stackAppInternalsSymbol]() {
|
|
570
561
|
return {
|
|
571
562
|
fromClientJson: (json) => {
|
|
572
|
-
const providedCheckString = JSON.stringify(omit(json, [
|
|
563
|
+
const providedCheckString = JSON.stringify(omit(json, [ /* none currently */]));
|
|
573
564
|
const existing = allClientApps.get(json.uniqueIdentifier);
|
|
574
565
|
if (existing) {
|
|
575
566
|
const [existingCheckString, clientApp] = existing;
|
|
@@ -587,15 +578,11 @@ class _StackClientAppImpl {
|
|
|
587
578
|
}
|
|
588
579
|
get [stackAppInternalsSymbol]() {
|
|
589
580
|
return {
|
|
590
|
-
toClientJson:
|
|
581
|
+
toClientJson: () => {
|
|
591
582
|
if (!("publishableClientKey" in this._interface.options)) {
|
|
592
583
|
// TODO find a way to do this
|
|
593
584
|
throw Error("Cannot serialize to JSON from an application without a publishable client key");
|
|
594
585
|
}
|
|
595
|
-
const [user, project] = await Promise.all([
|
|
596
|
-
this.getUser(),
|
|
597
|
-
this.getProject(),
|
|
598
|
-
]);
|
|
599
586
|
return {
|
|
600
587
|
baseUrl: this._interface.options.baseUrl,
|
|
601
588
|
projectId: this.projectId,
|
|
@@ -603,10 +590,11 @@ class _StackClientAppImpl {
|
|
|
603
590
|
tokenStore: this._tokenStoreOptions,
|
|
604
591
|
urls: this._urlOptions,
|
|
605
592
|
uniqueIdentifier: this._uniqueIdentifier,
|
|
606
|
-
currentClientUserJson: user?.toJson() ?? null,
|
|
607
|
-
currentProjectJson: project,
|
|
608
593
|
};
|
|
609
|
-
}
|
|
594
|
+
},
|
|
595
|
+
setCurrentUser: (userJsonPromise) => {
|
|
596
|
+
runAsynchronously(this._currentUserCache.forceSetCachedValueAsync([getTokenStore(this._tokenStoreOptions)], userJsonPromise));
|
|
597
|
+
},
|
|
610
598
|
};
|
|
611
599
|
}
|
|
612
600
|
;
|
|
@@ -40,6 +40,9 @@ export declare const Components: {
|
|
|
40
40
|
readonly TabsList: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-tabs").TabsListProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
41
41
|
readonly TabsContent: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-tabs").TabsContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
42
42
|
readonly TabsTrigger: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-tabs").TabsTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
43
|
+
readonly Skeleton: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
44
|
+
deactivated?: boolean | undefined;
|
|
45
|
+
} & React.RefAttributes<HTMLSpanElement>>;
|
|
43
46
|
};
|
|
44
47
|
export type ComponentConfig = {
|
|
45
48
|
components?: Partial<typeof Components>;
|
|
@@ -83,6 +86,9 @@ export declare function useComponents(): {
|
|
|
83
86
|
readonly TabsList: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-tabs").TabsListProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
84
87
|
readonly TabsContent: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-tabs").TabsContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
85
88
|
readonly TabsTrigger: React.ForwardRefExoticComponent<Omit<import("@radix-ui/react-tabs").TabsTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
89
|
+
readonly Skeleton: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
90
|
+
deactivated?: boolean | undefined;
|
|
91
|
+
} & React.RefAttributes<HTMLSpanElement>>;
|
|
86
92
|
};
|
|
87
93
|
export declare function StackComponentProvider(props: {
|
|
88
94
|
children?: React.ReactNode;
|
|
@@ -14,6 +14,7 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuIte
|
|
|
14
14
|
import { Avatar, AvatarFallback, AvatarImage } from '../components-core/avatar';
|
|
15
15
|
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '../components-core/collapsible';
|
|
16
16
|
import { Tabs, TabsList, TabsContent, TabsTrigger } from '../components-core/tabs';
|
|
17
|
+
import { Skeleton } from '../components-core/skeleton';
|
|
17
18
|
export const Components = {
|
|
18
19
|
Input,
|
|
19
20
|
Button,
|
|
@@ -45,6 +46,7 @@ export const Components = {
|
|
|
45
46
|
TabsList,
|
|
46
47
|
TabsContent,
|
|
47
48
|
TabsTrigger,
|
|
49
|
+
Skeleton,
|
|
48
50
|
};
|
|
49
51
|
const ComponentContext = createContext(undefined);
|
|
50
52
|
export function useComponents() {
|
|
@@ -34,6 +34,6 @@ export declare function hasCustomColorMode(config: DesignConfig): config is Desi
|
|
|
34
34
|
};
|
|
35
35
|
export declare function StackDesignProvider(props: {
|
|
36
36
|
children?: React.ReactNode;
|
|
37
|
-
} & DesignConfig): import("react/jsx-runtime").JSX.Element
|
|
37
|
+
} & DesignConfig): import("react/jsx-runtime").JSX.Element;
|
|
38
38
|
export declare function useDesign(): DesignContextValue;
|
|
39
39
|
export {};
|
|
@@ -37,7 +37,6 @@ const useColorMode = (props) => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
export function StackDesignProvider(props) {
|
|
40
|
-
const [mounted, setMounted] = useState(false);
|
|
41
40
|
const [colorMode, setColorMode] = useColorMode(props);
|
|
42
41
|
const [designValue, setDesignValue] = useState({
|
|
43
42
|
colors: getColors(colorMode, props.colors),
|
|
@@ -52,11 +51,6 @@ export function StackDesignProvider(props) {
|
|
|
52
51
|
colorMode,
|
|
53
52
|
}));
|
|
54
53
|
}, [colorMode]);
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
setMounted(true);
|
|
57
|
-
}, []);
|
|
58
|
-
if (!mounted)
|
|
59
|
-
return null;
|
|
60
54
|
return (_jsx(DesignContext.Provider, { value: designValue, children: props.children }));
|
|
61
55
|
}
|
|
62
56
|
export function useDesign() {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { StackClientApp, StackClientAppJson } from "../lib/stack-app";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import { UserJson } from "@stackframe/stack-shared";
|
|
3
4
|
export declare const StackContext: React.Context<{
|
|
4
5
|
app: StackClientApp<true>;
|
|
5
6
|
} | null>;
|
|
6
7
|
export declare function StackProviderClient(props: {
|
|
7
|
-
|
|
8
|
+
appJson: StackClientAppJson<true, string>;
|
|
8
9
|
children?: React.ReactNode;
|
|
9
10
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function UserSetter(props: {
|
|
12
|
+
userJsonPromise: Promise<UserJson | null>;
|
|
13
|
+
}): null;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect } from "react";
|
|
4
4
|
import { StackClientApp, stackAppInternalsSymbol } from "../lib/stack-app";
|
|
5
5
|
import React from "react";
|
|
6
|
+
import { useStackApp } from "..";
|
|
6
7
|
export const StackContext = React.createContext(null);
|
|
7
8
|
export function StackProviderClient(props) {
|
|
8
|
-
const appJson =
|
|
9
|
+
const appJson = props.appJson;
|
|
9
10
|
const app = StackClientApp[stackAppInternalsSymbol].fromClientJson(appJson);
|
|
10
11
|
if (process.env.NODE_ENV === "development") {
|
|
11
12
|
globalThis.stackApp = app;
|
|
12
13
|
}
|
|
13
14
|
return (_jsx(StackContext.Provider, { value: { app }, children: props.children }));
|
|
14
15
|
}
|
|
16
|
+
export function UserSetter(props) {
|
|
17
|
+
const app = useStackApp();
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const promise = (async () => await props.userJsonPromise)(); // there is a Next.js bug where Promises passed by server components return `undefined` as their `then` value, so wrap it in a normal promise
|
|
20
|
+
app[stackAppInternalsSymbol].setCurrentUser(promise);
|
|
21
|
+
}, []);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Suspense } from 'react';
|
|
2
3
|
import { stackAppInternalsSymbol } from '../lib/stack-app';
|
|
3
|
-
import { StackProviderClient } from './stack-provider-client';
|
|
4
|
+
import { StackProviderClient, UserSetter } from './stack-provider-client';
|
|
4
5
|
export default function StackProvider({ children, app, }) {
|
|
5
|
-
return (
|
|
6
|
+
return (_jsxs(StackProviderClient, { appJson: app[stackAppInternalsSymbol].toClientJson(), children: [_jsx(Suspense, { fallback: null, children: _jsx(UserFetcher, { app: app }) }), children] }));
|
|
7
|
+
}
|
|
8
|
+
function UserFetcher(props) {
|
|
9
|
+
const userPromise = props.app.getUser().then((user) => user?.toJson() ?? null);
|
|
10
|
+
return _jsx(UserSetter, { userJsonPromise: userPromise });
|
|
6
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackframe/stack",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"default": "./dist/joy.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"homepage": "https://stack-auth.com",
|
|
15
16
|
"files": [
|
|
16
17
|
"README.md",
|
|
17
18
|
"dist"
|
|
@@ -34,11 +35,11 @@
|
|
|
34
35
|
"tailwindcss-scoped-preflight": "^2.1.0",
|
|
35
36
|
"yup": "^1.4.0",
|
|
36
37
|
"@stackframe/stack-sc": "1.4.1",
|
|
37
|
-
"@stackframe/stack-shared": "2.3.
|
|
38
|
+
"@stackframe/stack-shared": "2.3.3"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"@mui/joy": "^5.0.0-beta.30",
|
|
41
|
-
"next": "
|
|
42
|
+
"next": ">=14.1",
|
|
42
43
|
"react": "^18.2"
|
|
43
44
|
},
|
|
44
45
|
"peerDependenciesMeta": {
|