diff --git a/react/src/lib/auth-context.ts b/react/src/lib/auth-context.ts new file mode 100644 index 0000000..865e149 --- /dev/null +++ b/react/src/lib/auth-context.ts @@ -0,0 +1,10 @@ +import * as React from "react"; + +interface AuthContextType { + authToken: string | null; +} + +const AuthContext = React.createContext(null); +AuthContext.displayName = "AuthContext"; + +export { AuthContext, type AuthContextType }; diff --git a/react/src/lib/auth-provider.tsx b/react/src/lib/auth-provider.tsx new file mode 100644 index 0000000..9fd99bf --- /dev/null +++ b/react/src/lib/auth-provider.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; +import { AuthKitProvider } from "@workos-inc/authkit-react"; +import { AuthContext } from "./auth-context"; + +export const AuthProvider: React.FC< + React.ComponentProps +> = ({ children, ...props }) => { + const [authToken, setAuthToken] = React.useState(null); + return ( + { + props.onRefresh?.(payload); + setAuthToken(payload.accessToken); + }} + > + + {children} + + + ); +}; diff --git a/react/src/lib/use-auth.ts b/react/src/lib/use-auth.ts new file mode 100644 index 0000000..ae888ee --- /dev/null +++ b/react/src/lib/use-auth.ts @@ -0,0 +1,83 @@ +import * as React from "react"; +import { useAuth as useAuthKit } from "@workos-inc/authkit-react"; +import { AuthContext } from "./auth-context"; + +type AuthKit = ReturnType; +type User = Exclude; + +type UseAuthReturnType = Omit & State; + +export function useAuth(): UseAuthReturnType { + const authKit = useAuthKit(); + const context = React.useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + + if (authKit.isLoading) { + return { + ...authKit, + ...context, + status: "loading", + isLoading: true, + user: null, + authToken: null, + }; + } + + if (!authKit.user) { + return { + ...authKit, + ...context, + status: "unauthenticated", + isLoading: false, + user: null, + authToken: null, + }; + } + + return { + ...authKit, + ...context, + status: "authenticated", + isLoading: false, + user: authKit.user, + authToken: context.authToken, + }; +} + +type Status = + | "loading" + | "authenticated" + | "authenticated-with-token" + | "unauthenticated"; + +interface SharedState { + status: Status; + isLoading: boolean; + user: User | null; + authToken: string | null; +} + +interface LoadingState extends SharedState { + status: "loading"; + isLoading: true; + user: null; + authToken: null; +} + +interface AuthenticatedState extends SharedState { + status: "authenticated"; + isLoading: false; + user: User; + authToken: string | null; +} + +interface UnauthenticatedState extends SharedState { + status: "unauthenticated"; + isLoading: false; + user: null; + authToken: null; +} + +export type State = LoadingState | AuthenticatedState | UnauthenticatedState; diff --git a/react/src/root.tsx b/react/src/root.tsx index e4b59d8..16c0eab 100644 --- a/react/src/root.tsx +++ b/react/src/root.tsx @@ -1,8 +1,6 @@ import { Flex, Heading, Link, Text, Theme } from "@radix-ui/themes"; import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary"; import { - Links, - Meta, Outlet, Link as RouterLink, ScrollRestoration, @@ -12,7 +10,7 @@ import { import * as React from "react"; import { preconnect } from "react-dom"; import { getApiProps, getDevtools } from "~/lib/env"; -import { AuthKitProvider } from "@workos-inc/authkit-react"; +import { AuthProvider } from "~/lib/auth-provider"; import { WorkOsWidgets } from "@workos-inc/widgets"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; @@ -40,13 +38,13 @@ function Providers({ children }: { children: React.ReactNode }) { )} > - + {children} - + ); } diff --git a/react/src/routes/_auth.signin.tsx b/react/src/routes/_auth.signin.tsx index 623e8d4..a6ccf16 100644 --- a/react/src/routes/_auth.signin.tsx +++ b/react/src/routes/_auth.signin.tsx @@ -1,6 +1,6 @@ import { Button, Flex, Heading } from "@radix-ui/themes"; import { Link } from "react-router"; -import { useAuth } from "@workos-inc/authkit-react"; +import { useAuth } from "~/lib/use-auth"; export default function HomePage() { const auth = useAuth(); diff --git a/react/src/routes/_dashboard.tsx b/react/src/routes/_dashboard.tsx index 8d26a98..0528f12 100644 --- a/react/src/routes/_dashboard.tsx +++ b/react/src/routes/_dashboard.tsx @@ -11,10 +11,10 @@ import { Text, VisuallyHidden, } from "@radix-ui/themes"; -import { useAuth } from "@workos-inc/authkit-react"; +import { useAuth } from "~/lib/use-auth"; import { Outlet, useNavigation } from "react-router"; -import { PrimaryNav, PrimaryNavItem } from "src/ui/dashboard"; -import { SuperAppMark } from "src/ui/svgs"; +import { PrimaryNav, PrimaryNavItem } from "~/ui/dashboard"; +import { SuperAppMark } from "~/ui/svgs"; export default function DashboardLayout() { const navigation = useNavigation(); diff --git a/react/src/routes/_dashboard.users.tsx b/react/src/routes/_dashboard.users.tsx index b0ca459..cbfdc12 100644 --- a/react/src/routes/_dashboard.users.tsx +++ b/react/src/routes/_dashboard.users.tsx @@ -1,16 +1,19 @@ -import { UsersManagement } from "@workos-inc/widgets"; -import { Flex, Heading, Separator, Text } from "@radix-ui/themes"; +import { UsersManagement, UsersManagementLoading } from "@workos-inc/widgets"; +import { Button, Flex, Heading, Separator, Text } from "@radix-ui/themes"; import { Navigate } from "react-router"; -import { useAuth } from "@workos-inc/authkit-react"; +import { useAuth } from "~/lib/use-auth"; export default function Users() { const auth = useAuth(); if (auth.isLoading) { return ( - - Authenticating... - + <> + SuperApp | Users + + + + ); } @@ -22,20 +25,11 @@ export default function Users() { return ( <> SuperApp | No organization - - -
- - User does not belong to an organization - - - - -
-
-
+ + + ); } @@ -43,20 +37,36 @@ export default function Users() { return ( <> SuperApp | Users - - -
- - Users - - Manage and invite users for the SuperApp team -
- -
- -
-
-
+ + {auth.authToken ? ( + + ) : ( + + )} + ); } + +function Layout({ + children, + subtitle, +}: { + children: React.ReactNode; + subtitle: string; +}) { + return ( + + +
+ + Users + + {subtitle} +
+ +
{children}
+
+
+ ); +}