Skip to content

Commit

Permalink
feat(store): use Zustand to store UserProfile
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrBulanek authored Nov 4, 2024
1 parent 8430630 commit eca500e
Show file tree
Hide file tree
Showing 26 changed files with 343 additions and 165 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@carbon/motion": "^11.17.0",
"@carbon/react": "^1.64.0",
"@carbon/styles": "^1.63.0",
"@dhmk/zustand-lens": "^5.0.0",
"@floating-ui/react": "^0.26.22",
"@hookform/resolvers": "^3.9.0",
"@opentelemetry/api-logs": "^0.53.0",
Expand Down Expand Up @@ -81,7 +82,8 @@
"use-resize-observer": "^9.1.0",
"usehooks-ts": "^3.1.0",
"uuid": "^9.0.1",
"zod": "^3.22.4"
"zod": "^3.22.4",
"zustand": "^5.0.1"
},
"devDependencies": {
"@commitlint/cli": "^19.2.1",
Expand Down
51 changes: 48 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 1 addition & 15 deletions src/app/api/users/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,10 @@
* limitations under the License.
*/

import { UserMetadata } from '@/store/user-profile/types';
import { paths } from '../schema';
import { EntityWithDecodedMetadata } from '../types';

export type UserMetadata = {
email?: string;
tou_accepted_at?: number;
};

export interface UserProfile {
id: string;
name: string;
firstName: string;
lastName: string;
email: string;
metadata?: UserMetadata;
isDummy?: boolean;
}

export type UserResult =
paths['/v1/users']['get']['responses']['200']['content']['application/json'];

Expand Down
2 changes: 1 addition & 1 deletion src/app/auth/accept-tou/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
'use server';

import { updateUser } from '@/app/api/rsc';
import { UserMetadata } from '@/app/api/users/types';
import { encodeMetadata } from '@/app/api/utils';
import { UserMetadata } from '@/store/user-profile/types';
import { updateSession } from '..';
import { ensureSession } from '../rsc';

Expand Down
5 changes: 3 additions & 2 deletions src/app/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
* limitations under the License.
*/

import { UserMetadata, UserProfileState } from '@/store/user-profile/types';
import { checkErrorCode } from '@/utils/handleApiError';
import NextAuth from 'next-auth';
import type { OAuthConfig } from 'next-auth/providers';
import * as z from 'zod';
import { ApiError, HttpError } from '../api/errors';
import { createUser, readUser } from '../api/users';
import { UserEntity, UserMetadata, UserProfile } from '../api/users/types';
import { UserEntity } from '../api/users/types';
import {
decodeEntityWithMetadata,
encodeMetadata,
Expand Down Expand Up @@ -275,7 +276,7 @@ const refreshAccessTokenSchema = z.object({

declare module 'next-auth' {
interface Session {
userProfile: UserProfile;
userProfile: UserProfileState;
}
interface User {
firstName?: string;
Expand Down
11 changes: 2 additions & 9 deletions src/app/auth/rsc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import 'server-only';
import { defaultUserProfileState } from '@/store/user-profile';
import { JWT } from 'next-auth/jwt';
import { redirect } from 'next/navigation';
import { cache } from 'react';
Expand All @@ -32,15 +33,7 @@ export const ensureSession = async () => {
user: {
access_token: DUMMY_JWT_TOKEN,
},
userProfile: {
id: '',
name: 'Test User',
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
metadata: {},
isDummy: true,
},
userProfile: defaultUserProfileState,
};

const session = await getSession();
Expand Down
30 changes: 18 additions & 12 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import { NavigationControlProvider } from '@/layout/providers/NavigationControlP
import { ProgressBarProvider } from '@/layout/providers/ProgressBarProvider';
import { ThemeProvider } from '@/layout/providers/ThemeProvider';
import { ToastProvider } from '@/layout/providers/ToastProvider';
import { StoreProvider } from '@/store/StoreProvider';
import type { Metadata } from 'next';
import { PropsWithChildren, ReactNode } from 'react';
import { IncludeGlobalStyles } from './IncludeGlobalStyles';
import { ensureSession } from './auth/rsc';

const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME!;

Expand All @@ -29,10 +31,12 @@ export const metadata: Metadata = {
icons: { icon: '//www.ibm.com/favicon.ico' },
};

export default function RootLayout({
export default async function RootLayout({
children,
modal,
}: PropsWithChildren<{ modal: ReactNode }>) {
const session = await ensureSession();

return (
// suppressHydrationWarning is added because of ThemeProvider
<html lang="en" suppressHydrationWarning>
Expand All @@ -43,18 +47,20 @@ export default function RootLayout({
</script>
</head>
<body>
<ThemeProvider>
<ToastProvider>
<ProgressBarProvider>
<NavigationControlProvider>
<IncludeGlobalStyles />
<StoreProvider userProfile={session.userProfile}>
<ThemeProvider>
<ToastProvider>
<ProgressBarProvider>
<NavigationControlProvider>
<IncludeGlobalStyles />

{children}
{modal}
</NavigationControlProvider>
</ProgressBarProvider>
</ToastProvider>
</ThemeProvider>
{children}
{modal}
</NavigationControlProvider>
</ProgressBarProvider>
</ToastProvider>
</ThemeProvider>
</StoreProvider>
</body>
</html>
);
Expand Down
5 changes: 3 additions & 2 deletions src/components/UserAvatar/UserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { useUserProfile } from '@/modules/chat/providers/UserProfileProvider';
import { useUserProfile } from '@/store/user-profile';
import clsx from 'clsx';
import { HTMLAttributes } from 'react';
import classes from './UserAvatar.module.scss';
Expand All @@ -39,6 +39,7 @@ const getUserInitials = (name: string) => {
};

export function CurrentUserAvatar(props: HTMLAttributes<HTMLSpanElement>) {
const { name } = useUserProfile();
const name = useUserProfile((state) => state.name);

return <UserAvatar {...props} name={name} />;
}
4 changes: 2 additions & 2 deletions src/layout/providers/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import { Project } from '@/app/api/projects/types';
import { encodeEntityWithMetadata } from '@/app/api/utils';
import { readAssistantQuery } from '@/modules/assistants/queries';
import { Assistant } from '@/modules/assistants/types';
import { useUserProfile } from '@/modules/chat/providers/UserProfileProvider';
import { readProjectQuery } from '@/modules/projects/queries';
import { readProjectUserQuery } from '@/modules/projects/users/queries';
import { useUserProfile } from '@/store/user-profile';
import { useQuery } from '@tanstack/react-query';
import {
createContext,
Expand Down Expand Up @@ -67,7 +67,7 @@ export function AppProvider({
const [project, setProject] = useState<Project>(initialProject);
const [assistant, setAssistant] = useState<Assistant | null>(null);
const onPageLeaveRef = useRef(() => null);
const { id } = useUserProfile();
const id = useUserProfile((state) => state.id);

const { data: projectData } = useQuery({
...readProjectQuery(project.id),
Expand Down
4 changes: 2 additions & 2 deletions src/layout/shell/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
'use client';
import { prefetchThreads } from '@/modules/chat/history/queries';
import { ThreadsHistory } from '@/modules/chat/history/ThreadsHistory';
import { ProjectSelector } from '@/modules/projects/ProjectSelector';
import { useQueryClient } from '@tanstack/react-query';
import clsx from 'clsx';
import { useEffect, useId, useRef, useState } from 'react';
import { useUserSetting } from '../hooks/useUserSetting';
import { useAppContext } from '../providers/AppProvider';
import { ActionButton } from './ActionButton';
import classes from './AppHeader.module.scss';
import { CollapsibleGroup } from './CollapsibleGroup';
Expand All @@ -29,8 +31,6 @@ import Pinned from './Pinned.svg';
import { RecentAssistantsList } from './RecentAssistantsList';
import Unpinned from './Unpinned.svg';
import { UserNav } from './UserNav';
import { useAppContext } from '../providers/AppProvider';
import { ProjectSelector } from '@/modules/projects/ProjectSelector';

export function AppHeader() {
const id = useId();
Expand Down
29 changes: 12 additions & 17 deletions src/layout/shell/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@
* limitations under the License.
*/

import { ensureSession } from '@/app/auth/rsc';
import { readProject } from '@/app/api/rsc';
import { ErrorPage } from '@/components/ErrorPage/ErrorPage';
import { UserProfileProvider } from '@/modules/chat/providers/UserProfileProvider';
import { handleApiError } from '@/utils/handleApiError';
import { notFound } from 'next/navigation';
import { PropsWithChildren } from 'react';
import { AppProvider } from '../providers/AppProvider';
import { AppHeader } from './AppHeader';
import classes from './AppShell.module.scss';
import { handleApiError } from '@/utils/handleApiError';
import { notFound } from 'next/navigation';
import { readProject } from '@/app/api/rsc';

interface Props {
projectId: string;
Expand All @@ -33,9 +31,8 @@ export async function AppShell({
projectId,
children,
}: PropsWithChildren<Props>) {
const session = await ensureSession();

let project;

try {
project = await readProject(projectId);
} catch (e) {
Expand All @@ -54,16 +51,14 @@ export async function AppShell({
if (!project) notFound();

return (
<UserProfileProvider value={session.userProfile}>
<AppProvider project={project}>
<div className={classes.root}>
<AppHeader />
<AppProvider project={project}>
<div className={classes.root}>
<AppHeader />

<main id="main-content" className={classes.content}>
{children}
</main>
</div>
</AppProvider>
</UserProfileProvider>
<main id="main-content" className={classes.content}>
{children}
</main>
</div>
</AppProvider>
);
}
Loading

0 comments on commit eca500e

Please sign in to comment.