mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
New docs structures, added docs for OAuth providers and team selections (#116)
* new doc structure * added domain docs * added team selection * added oauth page, improved navigation * improved team-selection * improved code styling, updated permissions docs * improved wording in teams and overview * added team switcher updates * updated production docs * added oauth provider docs * updated docs navigation
This commit is contained in:
parent
b9ebd30d10
commit
436cd95bf2
@ -6,15 +6,15 @@ title: Stack Auth Documentation
|
||||
tabs:
|
||||
documentation:
|
||||
display-name: Documentation
|
||||
icon: 'fa-solid fa-home'
|
||||
icon: fa-solid fa-home
|
||||
slug: docs
|
||||
sdk:
|
||||
display-name: SDK Reference
|
||||
icon: 'fa-solid fa-hammer'
|
||||
icon: fa-solid fa-hammer
|
||||
slug: sdk
|
||||
api:
|
||||
display-name: API Reference
|
||||
icon: 'fa-solid fa-code'
|
||||
icon: fa-solid fa-code
|
||||
slug: rest-api
|
||||
|
||||
navigation:
|
||||
@ -23,22 +23,53 @@ navigation:
|
||||
- section: Get Started
|
||||
contents:
|
||||
- page: Overview
|
||||
icon: fa-regular fa-globe
|
||||
path: ./docs/pages/getting-started/overview.mdx
|
||||
- page: Installation & Setup
|
||||
icon: fa-regular fa-download
|
||||
path: ./docs/pages/getting-started/setup.mdx
|
||||
- page: Users & Protected Pages
|
||||
- page: Users
|
||||
icon: fa-regular fa-address-book
|
||||
path: ./docs/pages/getting-started/users.mdx
|
||||
- page: Teams & Permissions
|
||||
path: ./docs/pages/getting-started/teams.mdx
|
||||
# - page: Protecting Pages
|
||||
# icon: fa-regular fa-shield-check
|
||||
# path: ./docs/pages/getting-started/protecting-pages.mdx
|
||||
- page: Going to Production
|
||||
icon: fa-regular fa-rocket
|
||||
path: ./docs/pages/getting-started/production.mdx
|
||||
- section: Concepts
|
||||
contents:
|
||||
# - page: Client vs. Server
|
||||
# icon: fa-regular fa-code-branch
|
||||
- page: OAuth Providers
|
||||
icon: fa-regular fa-key
|
||||
path: ./docs/pages/concepts/oauth.mdx
|
||||
- page: Teams
|
||||
icon: fa-regular fa-users
|
||||
path: ./docs/pages/concepts/teams.mdx
|
||||
- page: Selecting a Team
|
||||
icon: fa-regular fa-exchange
|
||||
path: ./docs/pages/concepts/team-selection.mdx
|
||||
- page: Permissions
|
||||
icon: fa-regular fa-user-lock
|
||||
path: ./docs/pages/concepts/permissions.mdx
|
||||
# - page: Local Development
|
||||
# icon: fa-regular fa-laptop
|
||||
- section: Customization
|
||||
contents:
|
||||
- page: Dark/Light Mode
|
||||
icon: fa-regular fa-circle-half-stroke
|
||||
path: ./docs/pages/customization/dark-mode.mdx
|
||||
- page: Colors and Styles
|
||||
icon: fa-regular fa-paint-brush
|
||||
path: ./docs/pages/customization/custom-styles.mdx
|
||||
# - page: Customize Emails
|
||||
# icon: fa-regular fa-envelope
|
||||
- page: Custom Layouts and Pages
|
||||
icon: fa-regular fa-table-layout
|
||||
path: ./docs/pages/customization/custom-pages.mdx
|
||||
- section: Custom Page Examples
|
||||
icon: fa-regular fa-files
|
||||
contents:
|
||||
- page: Sign In
|
||||
path: ./docs/pages/customization/page-examples/sign-in.mdx
|
||||
@ -84,11 +115,11 @@ colors:
|
||||
light: '#FFFFFF'
|
||||
dark: '#000000'
|
||||
sidebar-background:
|
||||
light: '#FFFFFF'
|
||||
dark: '#000000'
|
||||
light: '#FCFCFC'
|
||||
dark: '#090909'
|
||||
card-background:
|
||||
light: '#FFFFFF'
|
||||
dark: '#111111'
|
||||
light: '#FCFCFC'
|
||||
dark: '#090909'
|
||||
layout:
|
||||
page-width: full
|
||||
content-width: 40rem
|
||||
|
||||
91
docs/fern/docs/pages/concepts/oauth.mdx
Normal file
91
docs/fern/docs/pages/concepts/oauth.mdx
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
slug: concepts/oauth
|
||||
subtitle: Integrating OAuth Providers for API Access
|
||||
---
|
||||
|
||||
With Stack, users can connect multiple OAuth accounts, enabling access to provider-specific data and services such as Google Drive, Microsoft Calendar, GitHub repositories, and more.
|
||||
|
||||
## Connecting with an OAuth Provider Post Sign-in
|
||||
|
||||
Users can connect with an OAuth provider during sign-in or after signing in using other methods. For the latter, use the `user.useConnectedAccount('<provider_name>', { or: 'redirect' })` method. If the account is already connected, the method returns a Connection object; otherwise, it redirects the user to the provider's authorization page. Here’s how to connect with Google:
|
||||
|
||||
```jsx
|
||||
'use client';
|
||||
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export default function Page() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
// Redirects to Google authorization if not already connected
|
||||
const account = user.useConnectedAccount('google', { or: 'redirect' });
|
||||
// Account is always defined because of the redirect
|
||||
return <div>Google account connected</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Connecting with Extra Scopes
|
||||
|
||||
You can request extra scopes when connecting with an OAuth provider. For instance, to access Google Drive, pass the `https://www.googleapis.com/auth/drive` scope. Here’s an example:
|
||||
|
||||
```jsx
|
||||
'use client';
|
||||
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export default function Page() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
// Redirects to the Google authorization page, requesting access to Google Drive
|
||||
const account = user.useConnectedAccount('google', { or: 'redirect' });
|
||||
// Account is always defined because of the redirect
|
||||
return <div>Google Drive connected</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Retrieving the Access Token
|
||||
|
||||
Once connected with an OAuth provider, obtain the access token using the `account.getAccessToken()` method. Use this token to access the provider’s API endpoints. Here’s an example of using the access token to interact with Google Drive API:
|
||||
|
||||
```jsx
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export default function Page() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const account = user.useConnectedAccount('google', { or: 'redirect' });
|
||||
const tokens = account.getAccessToken();
|
||||
const [response, setResponse] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('https://www.googleapis.com/drive/v3/files', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.access_token}`
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => setResponse(data));
|
||||
}, [tokens]);
|
||||
|
||||
return <div>{response ? JSON.stringify(response) : 'Loading...'}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Requesting Extra Scopes During Sign-in
|
||||
|
||||
To avoid showing the authorization page twice, you can request extra scopes during the sign-in process. This approach is optional and depends on your application's design. Some applications may prefer to request extra permissions only when needed, while others might want to obtain all necessary permissions upfront.
|
||||
|
||||
Configure the `StackServerApp` with the required scopes:
|
||||
|
||||
```jsx title='stack.ts'
|
||||
// imports ...
|
||||
|
||||
export const stackServerApp = new StackServerApp({
|
||||
// your other settings ...
|
||||
oauthScopesOnSignIn: {
|
||||
google: ['https://www.googleapis.com/auth/drive']
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
By setting this up, users will be prompted for all necessary permissions during the initial sign-in, avoiding the need for them to approve additional permissions later. Choose the approach that best fits your application's user experience and workflow.
|
||||
130
docs/fern/docs/pages/concepts/permissions.mdx
Normal file
130
docs/fern/docs/pages/concepts/permissions.mdx
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
slug: concepts/permissions
|
||||
subtitle: Control what each user can do and access with the permission system
|
||||
---
|
||||
|
||||
## Team Permissions
|
||||
|
||||
Team permissions control what a user can do within each team. You can create and assign permissions to team members from the Stack dashboard. These permissions could include actions like `create_post` or `read_secret_info`, or roles like `admin` or `moderator`. Within your app, you can verify if a user has a specific permission within a team.
|
||||
|
||||
Permissions can be nested to create a hierarchical structure. For example, an `admin` permission can include both `moderator` and `user` permissions. We provide tools to help you verify whether a user has a permission directly or indirectly.
|
||||
|
||||
### Creating a Permission
|
||||
|
||||
To create a new permission, navigate to the `Team Permissions` section of the Stack dashboard. You can select the permissions that the new permission will contain. Any permissions included within these selected permissions will also be recursively included.
|
||||
|
||||
### System Permissions
|
||||
|
||||
Stack comes with a few predefined team permissions known as system permissions. These permissions start with a dollar sign (`$`). While you can assign these permissions to members or include them within other permissions, you cannot modify them as they are integral to the Stack backend system.
|
||||
|
||||
### Checking if a User has a Permission
|
||||
|
||||
To check whether a user has a specific permission, use the `getPermission` method or the `usePermission` hook on the `User` object. This returns the `Permission` object if the user has it; otherwise, it returns `null`. Always perform permission checks on the server side for business logic, as client-side checks can be bypassed. Here’s an example:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component">
|
||||
|
||||
```tsx title="Check user permission on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function CheckUserPermission() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const permission = user.usePermission('read');
|
||||
|
||||
// Don't rely on client-side permission checks for business logic.
|
||||
return (
|
||||
<div>
|
||||
{permission ? 'You have the read permission' : 'You shall not pass'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Server Component">
|
||||
|
||||
```tsx title="Check user permission on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function CheckUserPermission() {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const permission = await user.getPermission('read');
|
||||
|
||||
// This is a server-side check, so it's secure.
|
||||
return (
|
||||
<div>
|
||||
{permission ? 'You have the read permission' : 'You shall not pass'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Listing All Permissions of a User
|
||||
|
||||
To get a list of all permissions a user has, use the `listPermissions` method or the `usePermissions` hook on the `User` object. This method retrieves both direct and indirect permissions. Here is an example:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component" default>
|
||||
|
||||
```tsx title="List user permissions on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function DisplayUserPermissions() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const permissions = user.usePermissions();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{permissions.map(permission => (
|
||||
<div key={permission.id}>{permission.id}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Server Component">
|
||||
|
||||
```tsx title="List user permissions on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function DisplayUserPermissions() {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const permissions = await user.listPermissions();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{permissions.map(permission => (
|
||||
<div key={permission.id}>{permission.id}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Granting a Permission to a User
|
||||
|
||||
To grant a permission to a user, use the `grantPermission` method on the `ServerUser`. Here’s an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
const user = await stackServerApp.getUser();
|
||||
await user.grantPermission(team, 'read');
|
||||
```
|
||||
|
||||
### Revoking a Permission from a User
|
||||
|
||||
To revoke a permission from a user, use the `revokePermission` method on the `ServerUser`. Here’s an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
const user = await stackServerApp.getUser();
|
||||
await user.revokePermission(team, 'read');
|
||||
```
|
||||
|
||||
By following these guidelines, you can efficiently manage and verify team permissions within your application.
|
||||
119
docs/fern/docs/pages/concepts/team-selection.mdx
Normal file
119
docs/fern/docs/pages/concepts/team-selection.mdx
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
slug: concepts/team-selection
|
||||
subtitle: Switch between multiple teams of a user
|
||||
---
|
||||
|
||||
A user can be a member of multiple teams, so most websites using teams will need a way to select a "current team" that the user is working on. There are two primary methods to accomplish this:
|
||||
|
||||
- **Deep Link**: Each team has a unique URL, for example, `your-website.com/team/<team-id>`. When a team is selected, it redirects to a page with that team's URL.
|
||||
- **Current Team**: When a user selects a team, the app stores the team as a global "current team" state. In this way, the URL of the current team might be something like `your-website.com/current-team`, and the URL won't change after switching teams.
|
||||
|
||||
## Deep Link Method
|
||||
The deep link method is generally recommended because it avoids some common issues associated with the current team method. If two users share a link while using deep link URLs, the receiving user will always be directed to the correct team's information based on the link.
|
||||
|
||||
## Current Team Method
|
||||
While the current team method can be simpler to implement, it has a downside. If a user shares a link, the recipient might see information about the wrong team (if their "current team" is set differently). This method can also cause problems when a user has multiple browser tabs open with different teams.
|
||||
|
||||
## Selected Team Switcher
|
||||
|
||||
To facilitate team selection, Stack provides a component that looks like this:
|
||||
|
||||

|
||||
|
||||
You can import and use the `SelectedTeamSwitcher` component for the "current team" method. It updates the `selectedTeam` when a user selects a team:
|
||||
|
||||
```jsx
|
||||
import { SelectedTeamSwitcher } from "@stackframe/stack";
|
||||
|
||||
export function MyPage() {
|
||||
return (
|
||||
<div>
|
||||
<SelectedTeamSwitcher/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
To combine the switcher with the deep link method, you can pass in `urlMap` and `selectedTeam`. The `urlMap` is a function to generate a URL based on the team information, and `selectedTeam` is the team that the user is currently working on. This lets you implement "deep link" + "most recent team". The component will update the `user.selectedTeam` with the `selectedTeam` prop:
|
||||
|
||||
```jsx
|
||||
<SelectedTeamSwitcher
|
||||
urlMap={team => `/team/${team.id}`}
|
||||
selectedTeam={team}
|
||||
/>
|
||||
```
|
||||
|
||||
To implement the "deep link" + "default team" method, where you update the `selectedTeam` only when the user clicks "set to default team" or similar, pass `noUpdateSelectedTeam`:
|
||||
|
||||
```jsx
|
||||
<SelectedTeamSwitcher
|
||||
urlMap={team => `/team/${team.id}`}
|
||||
selectedTeam={team}
|
||||
noUpdateSelectedTeam
|
||||
/>
|
||||
```
|
||||
|
||||
## Example: Deep Link + Most Recent Team
|
||||
|
||||
First, create a page at `/app/team/[teamId]/page.tsx` to display information about a specific team:
|
||||
|
||||
```jsx title="/app/team/[teamId]/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { useUser, SelectedTeamSwitcher } from "@stackframe/stack";
|
||||
|
||||
export default function TeamPage({ params }: { params: { teamId: string } }) {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const team = user.useTeam(params.teamId);
|
||||
|
||||
if (!team) {
|
||||
return <div>Team not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SelectedTeamSwitcher
|
||||
urlMap={team => `/team/${team.id}`}
|
||||
selectedTeam={team}
|
||||
/>
|
||||
|
||||
<p>Team Name: {team.displayName}</p>
|
||||
<p>You are a member of this team.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Next, create a page to display all teams at `/app/team/page.tsx`:
|
||||
|
||||
```jsx title="/app/team/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export default function TeamsPage() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const teams = user.useTeams();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{user.selectedTeam &&
|
||||
<button onClick={() => router.push(`/team/${user.selectedTeam.id}`)}>
|
||||
Most recent team
|
||||
</button>
|
||||
}
|
||||
|
||||
<h2>All Teams</h2>
|
||||
{teams.map(team => (
|
||||
<button key={team.id} onClick={() => router.push(`/team/${team.id}`)}>
|
||||
Open {team.displayName}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you navigate to `http://localhost:3000/team`, you should be able to see and interact with the teams.
|
||||
154
docs/fern/docs/pages/concepts/teams.mdx
Normal file
154
docs/fern/docs/pages/concepts/teams.mdx
Normal file
@ -0,0 +1,154 @@
|
||||
---
|
||||
slug: getting-started/teams
|
||||
subtitle: Manage teams and team members
|
||||
---
|
||||
|
||||
Teams provide a structured way to group users and manage their permissions. Users can belong to multiple teams, which can represent departments, B2B customers, or projects.
|
||||
|
||||
To assign users to a default team upon sign-up, activate the corresponding toggle in the Stack dashboard under the team settings tab. This setting automatically assigns each new user to a team.
|
||||
|
||||
## Creating a Team
|
||||
|
||||
To create a team, use the `createTeam` method on the `stackServerApp`. Here’s an example:
|
||||
|
||||
```tsx title="Create a Team"
|
||||
const team = await stackServerApp.createTeam({
|
||||
displayName: 'New Team',
|
||||
});
|
||||
```
|
||||
|
||||
## Adding a User to a Team
|
||||
|
||||
To add a user to a team, use the `addUser` method on the `Team` object. Here’s how:
|
||||
|
||||
```tsx title="Add a User to a Team"
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
await team.addUser(user);
|
||||
```
|
||||
|
||||
## List All the Teams of a User
|
||||
|
||||
You can list all the teams a user belongs to by using the `listTeams` method or the `useTeams` hook on the User object. Here's how to do it:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component">
|
||||
```tsx title="List teams of a user on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function DisplayUserTeams() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const teams = user.useTeams();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{teams.map(team => (
|
||||
<div key={team.id}>{team.displayName}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Component">
|
||||
```tsx title="List teams of a user on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function DisplayUserTeams() {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const teams = await user.listTeams();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{teams.map(team => (
|
||||
<div key={team.id}>{team.displayName}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Get Specific Team of a User
|
||||
|
||||
To obtain details of a specific team a user belongs to, use the `getTeam` method or `useTeam` hook. Note: this might return `null` if the user is not a member of that team. Here’s an example:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component">
|
||||
```tsx title="Get a specific team of a user on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function DisplayUserTeam(props: { teamId: string }) {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const team = user.useTeam(props.teamId);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{team ? team.displayName : 'Not a member of this team'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Component">
|
||||
```tsx title="Get a specific team of a user on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function DisplayUserTeam(props: { teamId: string }) {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const team = await user.getTeam(props.teamId);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{team ? team.displayName : 'Not a member of this team'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## List All the Teams
|
||||
|
||||
To list all teams, use the `listTeams` method on the `stackServerApp`. Here’s an example:
|
||||
|
||||
```tsx
|
||||
const teams = await stackServerApp.listTeams();
|
||||
```
|
||||
|
||||
## Update a Team
|
||||
|
||||
To update a team, use the `update` method on a server-side team object. Here’s how to do it:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
await team.update({
|
||||
displayName: 'New Team Name',
|
||||
});
|
||||
```
|
||||
|
||||
## Remove a User from a Team
|
||||
|
||||
To remove a user from a team, use the `removeUser` method on the `team` object. Here's an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam(teamId);
|
||||
await team?.removeUser(userId);
|
||||
```
|
||||
|
||||
## List All the Members of a Team
|
||||
|
||||
To list all the members of a team, use the `listMembers` method on a server-side team object. Note that the returned result is a list of `TeamMember` objects, from which you can also get the user object. Here’s an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
const members = await team.listMembers();
|
||||
for (const member of members) {
|
||||
const user = member.user;
|
||||
console.log(`${user.displayName} is a member of ${team.displayName}`);
|
||||
}
|
||||
```
|
||||
@ -14,10 +14,12 @@ For example, if you want to create a custom sign-in page with a customized title
|
||||
import { SignIn } from "@stackframe/stack";
|
||||
|
||||
export default function CustomSignInPage() {
|
||||
return <div>
|
||||
<h1>My Custom Sign In page</h1>
|
||||
<SignIn />
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
<h1>My Custom Sign In page</h1>
|
||||
<SignIn />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@ -49,15 +51,17 @@ import { useStackApp } from "@stackframe/stack";
|
||||
export default function CustomOAuthSignIn() {
|
||||
const app = useStackApp();
|
||||
|
||||
return <div>
|
||||
<h1>My Custom Sign In page</h1>
|
||||
<button onClick={async () => {
|
||||
// this will redirect to the OAuth provider's login page
|
||||
await app.signInWithOAuth('google');
|
||||
}}>
|
||||
Sign In with Google
|
||||
</button>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
<h1>My Custom Sign In page</h1>
|
||||
<button onClick={async () => {
|
||||
// this will redirect to the OAuth provider's login page
|
||||
await app.signInWithOAuth('google');
|
||||
}}>
|
||||
Sign In with Google
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -37,15 +37,17 @@ import { useStackApp } from "@stackframe/stack";
|
||||
export default function CustomOAuthSignIn() {
|
||||
const app = useStackApp();
|
||||
|
||||
return <div>
|
||||
<h1>My Custom Sign In page</h1>
|
||||
<button onClick={async () => {
|
||||
// this will redirect to the OAuth provider's login page
|
||||
await app.signInWithOAuth('google');
|
||||
}}>
|
||||
Sign In with Google
|
||||
</button>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
<h1>My Custom Sign In page</h1>
|
||||
<button onClick={async () => {
|
||||
// this will redirect to the OAuth provider's login page
|
||||
await app.signInWithOAuth('google');
|
||||
}}>
|
||||
Sign In with Google
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -4,50 +4,50 @@ subtitle: Welcome to Stack!
|
||||
---
|
||||
|
||||
<CardGroup>
|
||||
<Card
|
||||
title="Setup Guide"
|
||||
icon="fa-regular fa-play"
|
||||
href="/getting-started/setup"
|
||||
>
|
||||
Setup Stack in your project
|
||||
</Card>
|
||||
<Card
|
||||
title="SDK Reference"
|
||||
icon="fa-regular fa-file-lines"
|
||||
href="/sdk"
|
||||
>
|
||||
Learn how to use Stack's SDKs for Next.js
|
||||
</Card>
|
||||
<Card
|
||||
title="REST API Reference"
|
||||
icon="fa-solid fa-code"
|
||||
href="/rest-api"
|
||||
>
|
||||
Explore Stack's REST API for frameworks that aren't natively supported yet
|
||||
</Card>
|
||||
<Card
|
||||
title="Discord"
|
||||
icon="fa-brands fa-discord"
|
||||
href="https://discord.stack-auth.com"
|
||||
>
|
||||
Join our Discord community
|
||||
</Card>
|
||||
<Card
|
||||
title="Setup Guide"
|
||||
icon="fa-regular fa-play"
|
||||
href="/getting-started/setup"
|
||||
>
|
||||
Setup Stack in your project
|
||||
</Card>
|
||||
<Card
|
||||
title="SDK Reference"
|
||||
icon="fa-regular fa-file-lines"
|
||||
href="/sdk"
|
||||
>
|
||||
Learn how to use Stack's SDKs for Next.js
|
||||
</Card>
|
||||
<Card
|
||||
title="REST API Reference"
|
||||
icon="fa-solid fa-code"
|
||||
href="/rest-api"
|
||||
>
|
||||
Explore Stack's REST API for frameworks we don't natively support yet
|
||||
</Card>
|
||||
<Card
|
||||
title="Discord"
|
||||
icon="fa-brands fa-discord"
|
||||
href="https://discord.stack-auth.com"
|
||||
>
|
||||
Join our Discord community
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Why Stack?
|
||||
## Why Choose Stack?
|
||||
|
||||
Authentication is inherently difficult. There are only few things more sensitive than user data, and only few things more difficult than cryptography. It's not surprising that a majority of online businesses struggle to get it right.
|
||||
Authentication is inherently difficult. Few things are more sensitive than user data and more complex than cryptography. Not surprisingly, many online businesses struggle to get it right.
|
||||
|
||||
The optimal authentication solution is secure, yet approachable. If a developer has to worry about JWTs, OAuth flows, or password hashing, then we have failed. If an authentication solution uses closed-source, unauditable code for the most critical parts of your application, then we have failed.
|
||||
The optimal authentication solution should be secure, yet approachable. If a developer has to worry about JWTs, OAuth flows, or password hashing, then we have failed. If an authentication solution uses closed-source, unauditable code for the most critical parts of your application, then we have failed.
|
||||
|
||||
The truth is; as the authentication services industry, we have collectively failed. It is dominated by proprietary giants with predatory "bait-and-switch" pricing who provide no transparency into their codebase, and a terrible developer experience because they have determined that enterprises are willing to pay more if setting up auth systems is painful.
|
||||
In truth, the authentication services industry has collectively failed. It's dominated by proprietary giants with predatory "bait-and-switch" pricing, providing no transparency into their codebase and delivering a subpar developer experience — because they know enterprises will pay more if setting up auth systems is painful.
|
||||
|
||||
That's why we built Stack. Integrating secure authentication into your app should be a matter of **5 minutes**, rather than 5 days.
|
||||
That's why we built Stack. Integrating secure authentication into your app should take **5 minutes**, not 5 days.
|
||||
|
||||
At the core of this are deep integrations into frontend and backend frameworks. We give the best developer experience to anyone using our supported tech stacks; at the moment, this is Next.js with Postgres and TypeScript or Python backends. Instead of giving mediocre support for a lot of frameworks, we chose to make a few integrations as excellent as possible before adding new ones (though we do offer a cross-compatible REST API as a fallback).
|
||||
At the core of Stack are deep integrations into frontend and backend frameworks. We offer the best developer experience to those using our supported tech stacks — currently, Next.js with Postgres and TypeScript or Python backends. Instead of providing mediocre support for numerous frameworks, we focused on making a few integrations excellent before adding new ones. We also offer a cross-compatible REST API as a fallback.
|
||||
|
||||
Here’s an example. To retrieve the current user, simply call:
|
||||
|
||||
Here is an example. To retrieve the current user, simply call:
|
||||
|
||||
```tsx
|
||||
export function MyComponent() {
|
||||
const user = useUser({ or: "redirect" });
|
||||
@ -64,24 +64,27 @@ You can also add a button to change the user's name:
|
||||
Change Name
|
||||
</button>
|
||||
```
|
||||
|
||||
The user data will update in both the frontend and backend automatically. The updated user data will be reflected in all other components on your page as well.
|
||||
|
||||
You also get pages and components for authentication flow out-of-the-box. This is the sign-in page that you get without writing a single line of code:
|
||||
You also get pages and components for the authentication flow out-of-the-box. This is the sign-in page you get without writing a single line of code:
|
||||
|
||||

|
||||
|
||||
Notably, there's no branding on any of our components. We believe that we should grow by building the best product, not by forcing our brand on your users — but this means that we **rely on you to spread the word about Stack**. If you like what you're reading, we'd love if you could take a second to tell one or two of your friends about us.
|
||||
Notice, there's no branding on our components. We believe we should grow by building the best product, not by forcing our brand onto your users. This means we **rely on you to spread the word about Stack**. If you like what you’re reading, please take a moment to tell one or two of your friends about us.
|
||||
|
||||
If you prefer a fully customized UI, you can use our low-level functions like `signInWithOAuth` or `signInWithCredential` to build your own sign-in page:
|
||||
|
||||
```tsx
|
||||
export default function CustomOAuthSignIn() {
|
||||
const app = useStackApp();
|
||||
return <div>
|
||||
<button onClick={async () => await app.signInWithOAuth('google')}>
|
||||
Sign In with Google
|
||||
</button>
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
<button onClick={async () => await app.signInWithOAuth('google')}>
|
||||
Sign In with Google
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@ -89,10 +92,10 @@ To manage everything efficiently, there is a powerful admin dashboard:
|
||||
|
||||

|
||||
|
||||
Best of all, Stack is **100% open-source**. That means client, server, dashboard, and even this very documentation you're reading right now. Feel free to check out our [GitHub](https://github.com/stack-auth/stack) and open an issue or pull request.
|
||||
Best of all, Stack is **100% open-source**. This means the client, server, dashboard, and even this documentation you’re reading right now. Check out our [GitHub](https://github.com/stack-auth/stack) to open an issue or pull request.
|
||||
|
||||
This is just a glimpse of what Stack can do. Stack also handles many other tasks like backend integration, data storage, emails, teams, permissions, and more, which you will learn later in the documentation.
|
||||
This is just a glimpse of what Stack can do. Stack also handles many other tasks like backend integration, data storage, emails, teams, permissions, and more, which you will learn about later in the documentation.
|
||||
|
||||
If this sounds interesting, [get started](../getting-started/setup.mdx) with our interactive setup wizard, or join [our Discord community](https://discord.stack-auth.com) to ask questions and get help from our team.
|
||||
|
||||
We're excited to have you on board! 🚀
|
||||
We're excited to have you on board! 🚀
|
||||
60
docs/fern/docs/pages/getting-started/production.mdx
Normal file
60
docs/fern/docs/pages/getting-started/production.mdx
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
slug: getting-started/production
|
||||
subtitle: Steps to Prepare Stack for Production Use
|
||||
---
|
||||
|
||||
Stack makes development easy with various default settings, but these settings need to be optimized for security and user experience when moving to production. Here's a checklist of things you need to do before switching to production mode:
|
||||
|
||||
### Domains and Handlers
|
||||
|
||||
By default, Stack allows all localhost paths as valid callback URLs. This is convenient for development but poses a security risk in production because attackers could use their own domains as callback URLs to intercept sensitive information. Therefore, in production, Stack must know your domain (e.g., `https://your-website.com`) and only allow callbacks from those domains.
|
||||
|
||||
Follow these steps when you're ready to push your application to production:
|
||||
|
||||
1. **Add Your Domain**: Navigate to the `Domain & Handlers` tab in the Stack dashboard. If you haven't configured your handler, you can leave it as the default. (Learn more about handlers [here](../sdk/app.mdx)).
|
||||
|
||||
2. **Disable Localhost Callbacks**: For enhanced security, disable the `Allow all localhost callbacks for development` option.
|
||||
|
||||
### OAuth Providers
|
||||
|
||||
Stack uses shared OAuth keys for development to simplify setup when using "Sign in with Google/GitHub/etc." However, this isn't secure for production as it displays "Stack Development" on the providers' consent screens, making it unclear to users if the OAuth request is genuinely from your site. Thus, you should configure your own OAuth keys with the providers and connect them to Stack.
|
||||
|
||||
To use your own OAuth provider setups in production, follow these steps for each provider you use:
|
||||
|
||||
1. **Create an OAuth App**: On the provider's website, create an OAuth app and set the callback URL to the corresponding Stack callback URL. Copy the client ID and client secret.
|
||||
<Tabs>
|
||||
<Tab title="Google">
|
||||
[Google OAuth Setup Guide](https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name-.)
|
||||
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/google`
|
||||
</Tab>
|
||||
<Tab title="GitHub">
|
||||
[GitHub OAuth Setup Guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
|
||||
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/github`
|
||||
</Tab>
|
||||
<Tab title="Facebook">
|
||||
[Facebook OAuth Setup Guide](https://developers.facebook.com/docs/development/create-an-app/facebook-login-use-case)
|
||||
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/facebook`
|
||||
</Tab>
|
||||
<Tab title="Microsoft">
|
||||
[Microsoft Azure OAuth Setup Guide](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
|
||||
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/microsoft`
|
||||
</Tab>
|
||||
<Tab title="Spotify">
|
||||
[Spotify OAuth Setup Guide](https://developer.spotify.com/documentation/general/guides/app-settings/)
|
||||
Callback URL: `https://app.stack-auth.com/api/v1/auth/callback/spotify`
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
2. **Enter OAuth Credentials**: Go to the `Auth Methods` section in the Stack dashboard, open the provider's settings, switch from shared keys to custom keys, and enter the client ID and client secret.
|
||||
|
||||
### Email Server
|
||||
|
||||
For development, Stack uses a shared email server, which sends emails from Stack's domain. This is not ideal for production as users may not trust emails from an unfamiliar domain. You should set up an email server connected to your own domain.
|
||||
|
||||
Steps to connect your own email server with Stack:
|
||||
1. **Setup Email Server**: Configure your own email server and connect it to your domain (this step is beyond Stack's documentation scope).
|
||||
2. **Configure Stack's Email Settings**: Navigate to the `Emails` section in the Stack dashboard, click `Edit` in the `Email Server` section, switch from `Shared` to `Custom SMTP server`, enter your SMTP configurations, and save.
|
||||
|
||||
### Enable Production Mode
|
||||
|
||||
After completing the steps above, you can enable production mode on the `Project Settings` tab in the Stack dashboard, ensuring that your website runs securely with Stack in a production environment.
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
slug: getting-started/setup
|
||||
subtitle: How to install stack to your Next.js project
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
@ -1,248 +0,0 @@
|
||||
---
|
||||
slug: getting-started/teams
|
||||
---
|
||||
|
||||
Teams provide a structured way to group users and manage their permissions. Users can belong to multiple teams, which can represent departments, B2B customers, or projects.
|
||||
|
||||
To assign users to a default team upon sign-up, activate the corresponding toggle in the Stack dashboard under the team settings tab. This setting automatically assigns each new user to a team.
|
||||
|
||||
## Teams
|
||||
|
||||
### Creating a Team
|
||||
|
||||
To create a team, you can call the `createTeam` method on the `stackServerApp`. Here is an example:
|
||||
|
||||
```tsx title="Create a Team"
|
||||
const team = await stackServerApp.createTeam({
|
||||
displayName: 'New Team',
|
||||
});
|
||||
```
|
||||
|
||||
### Adding a User to a Team
|
||||
|
||||
To add a user to a team, you can call the `addUser` method on the `Team` object. Here is an example:
|
||||
|
||||
```tsx title="Add a User to a Team"
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
await team.addUser(user);
|
||||
```
|
||||
|
||||
### List All the Teams of a User
|
||||
|
||||
You can list all the teams a user belongs to by using the `listTeams` method or `useTeams` hook on the User object. Here is how you can do it:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component">
|
||||
```tsx title="List teams of a user on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function DisplayUserTeams() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const teams = user.useTeams();
|
||||
|
||||
return <div>
|
||||
{teams.map(team => <div key={team.id}>{team.displayName}</div>)}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Component">
|
||||
```tsx title="List teams of a user on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function DisplayUserTeams() {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const teams = await user.listTeams();
|
||||
|
||||
return <div>
|
||||
{teams.map(team => <div key={team.id}>{team.displayName}</div>)}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
### Get Specific Team of a User
|
||||
|
||||
To obtain details of a specific team that a user belongs to, use the `getTeam` method or `useTeam` hook. Note, this may return `null` if the user is not a member of the team. Here is an example:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component">
|
||||
```tsx title="Get a specific team of a user on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function DisplayUserTeam(props: { teamId: string }) {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const team = user.useTeam(props.teamId);
|
||||
|
||||
return <div>
|
||||
{team ? team.displayName : 'Not a member of this team'}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Component">
|
||||
```tsx title="Get a specific team of a user on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function DisplayUserTeam(props: { teamId: string }) {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const team = await user.getTeam(props.teamId);
|
||||
|
||||
return <div>
|
||||
{team ? team.displayName : 'Not a member of this team'}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### List All the Teams
|
||||
|
||||
To list all the teams, you can call the `listTeams` method on the `stackServerApp`, this is an example:
|
||||
|
||||
```tsx
|
||||
const teams = await stackServerApp.listTeams();
|
||||
```
|
||||
|
||||
### Update a Team
|
||||
|
||||
To update a team, use the `update` method on a server-side team object. Here is an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
await team.update({
|
||||
displayName: 'New Team Name',
|
||||
});
|
||||
```
|
||||
|
||||
### Remove a User from a Team
|
||||
|
||||
To remove a user from a team, you can call the `removeUser` method on the `team`, this is an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam(teamId);
|
||||
await team?.removeUser(userId);
|
||||
```
|
||||
|
||||
### List All the Members of a Team
|
||||
|
||||
To list all the members of a team, you can call the `listMembers` method on a server side team object. Note that the returned result is a list of `TeamMember` objects, from which you can also get the user object. This is an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
const members = await team.listMembers();
|
||||
for (const member of members) {
|
||||
const user = member.user;
|
||||
console.log(user.displayName, 'is a member of', team.displayName);
|
||||
}
|
||||
```
|
||||
|
||||
## Permissions
|
||||
|
||||
Permissions control what each user can do within your application. Create permissions on the Stack dashboard and assign them to users. You can then verify in your app whether a user has a specific permission.
|
||||
|
||||
Permissions can be nested, allowing you to create a hierarchical structure. For instance, an `admin` permission can include `moderator` and `user` permissions. We provide tools to help you verify whether a user has a permission directly or indirectly.
|
||||
|
||||
|
||||
### Check if a User has a Permission
|
||||
|
||||
You can check if a user has a specific permission by using the `getPermission` method or `usePermission` hook on the `User` object. This returns the `Permission` object if the user has it; otherwise, it returns `null`. Be cautious with client-side checks for permissions, as they can be bypassed. Always perform permission checks on the server side for business logic. Here is an example:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component">
|
||||
```tsx title="Check user permission on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function CheckUserPermission() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const permission = user.usePermission('read');
|
||||
|
||||
// Don't rely on client side permission checks for business logic
|
||||
return <div>
|
||||
{permission ? 'You have the read permission' : 'You shall not pass'}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Component">
|
||||
```tsx title="Check user permission on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function CheckUserPermission() {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const permission = await user.getPermission('read');
|
||||
|
||||
// This is a server side check, so it's secure
|
||||
return <div>
|
||||
{permission ? 'You have the read permission' : 'You shall not pass'}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
### List All the Permissions of a User
|
||||
|
||||
To get all permissions a user has, use the `listPermissions` method or `usePermissions` hook on the User object. This method retrieves all direct and indirect permissions. Here is an example:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Client Component" default>
|
||||
```tsx title="List user permissions on the client"
|
||||
"use client";
|
||||
import { useUser } from "@stackframe/stack";
|
||||
|
||||
export function DisplayUserPermissions() {
|
||||
const user = useUser({ or: 'redirect' });
|
||||
const permissions = user.usePermissions();
|
||||
|
||||
return <div>
|
||||
{permissions.map(permission => <div key={permission.id}>{permission.id}</div>)}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Component">
|
||||
```tsx title="List user permissions on the server"
|
||||
import { stackServerApp } from "@/stack";
|
||||
|
||||
export default async function DisplayUserPermissions() {
|
||||
const user = await stackServerApp.getUser({ or: 'redirect' });
|
||||
const permissions = await user.listPermissions();
|
||||
|
||||
return <div>
|
||||
{permissions.map(permission => <div key={permission.id}>{permission.id}</div>)}
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Grant a Permission to a User
|
||||
|
||||
To grant a permission to a user, you can call the `grantPermission` method on the `ServerUser`. Here is an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
const user = await stackServerApp.getUser();
|
||||
await user.grantPermission(team, 'read');
|
||||
```
|
||||
|
||||
### Revoke a Permission from a User
|
||||
|
||||
To revoke a permission from a user, you can call the `revokePermission` method on the `ServerUser`. Here is an example:
|
||||
|
||||
```tsx
|
||||
const team = await stackServerApp.getTeam('teamId');
|
||||
const user = await stackServerApp.getUser();
|
||||
await user.revokePermission(team, 'read');
|
||||
```
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
slug: getting-started/users
|
||||
subtitle: Get and manage user information
|
||||
---
|
||||
|
||||
In [the last guide](./setup.mdx), we initialized Stack. This created new files containing a `StackServerApp` and `StackProvider`. In this section, we will show you how to utilize those for accessing and modifying the current user information on Server Components and Client Components, respectively.
|
||||
|
||||
BIN
docs/fern/docs/pages/imgs/team-switcher.png
Normal file
BIN
docs/fern/docs/pages/imgs/team-switcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@ -1,5 +1,5 @@
|
||||
'use client';
|
||||
import { useUser } from "..";
|
||||
import { Team, useUser } from "..";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
@ -12,15 +12,17 @@ import {
|
||||
SelectValue,
|
||||
Typography,
|
||||
} from "@stackframe/stack-ui";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
type SelectedTeamSwitcherProps = {
|
||||
projectUrlMap?: (projectId: string) => string,
|
||||
urlMap?: (projectId: string) => string,
|
||||
selectedTeam?: Team,
|
||||
noUpdateSelectedTeam?: boolean,
|
||||
};
|
||||
|
||||
function TeamIcon(props: { displayName: string }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '1.5rem', height: '1.5rem', marginRight: '0.5rem', borderRadius: '0.25rem', backgroundColor: 'rgb(228 228 231)' }}>
|
||||
<div className="flex items-center justify-center w-6 h-6 mr-2 rounded bg-gray-200">
|
||||
<Typography>{props.displayName.slice(0, 1).toUpperCase()}</Typography>
|
||||
</div>
|
||||
);
|
||||
@ -29,10 +31,16 @@ function TeamIcon(props: { displayName: string }) {
|
||||
export function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {
|
||||
const user = useUser();
|
||||
const router = useRouter();
|
||||
const selectedTeam = user?.selectedTeam;
|
||||
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 && teams && selectedTeam && !teams.find(team => team.id === selectedTeam.id)) {
|
||||
runAsynchronouslyWithAlert(user?.setSelectedTeam(selectedTeam));
|
||||
}
|
||||
}, [teams, selectedTeam, props.noUpdateSelectedTeam]);
|
||||
|
||||
return (
|
||||
<Select>
|
||||
<SelectTrigger className="stack-scope">
|
||||
@ -45,9 +53,11 @@ export function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {
|
||||
key={team.id}
|
||||
onClick={() => {
|
||||
runAsynchronouslyWithAlert(async () => {
|
||||
await user?.setSelectedTeam(team);
|
||||
if (props.projectUrlMap) {
|
||||
router.push(props.projectUrlMap(team.id));
|
||||
if (!props.noUpdateSelectedTeam) {
|
||||
await user?.setSelectedTeam(team);
|
||||
}
|
||||
if (props.urlMap) {
|
||||
router.push(props.urlMap(team.id));
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user