diff --git a/web/components/onboarding/user-details.tsx b/web/components/onboarding/user-details.tsx index e487f48dc..3e59668c6 100644 --- a/web/components/onboarding/user-details.tsx +++ b/web/components/onboarding/user-details.tsx @@ -1,16 +1,12 @@ import { useEffect } from "react"; -import { mutate } from "swr"; import { Controller, useForm } from "react-hook-form"; -// hooks -import useToast from "hooks/use-toast"; -// services -import { UserService } from "services/user.service"; +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // ui import { Button, CustomSelect, CustomSearchSelect, Input } from "@plane/ui"; // types import { IUser } from "types"; -// fetch-keys -import { CURRENT_USER } from "constants/fetch-keys"; // helpers import { getUserTimeZoneFromWindow } from "helpers/date-time.helper"; // constants @@ -33,10 +29,10 @@ const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ content: timeZone.label, })); -const userService = new UserService(); +export const UserDetails: React.FC = observer((props) => { + const { user } = props; -export const UserDetails: React.FC = ({ user }) => { - const { setToastAlert } = useToast(); + const { user: userStore } = useMobxStore(); const { handleSubmit, @@ -58,31 +54,7 @@ export const UserDetails: React.FC = ({ user }) => { }, }; - await userService - .updateUser(payload) - .then(() => { - mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - ...payload, - }; - }, - false - ); - - setToastAlert({ - type: "success", - title: "Success!", - message: "Details updated successfully.", - }); - }) - .catch(() => { - mutate(CURRENT_USER); - }); + await userStore.updateCurrentUser(payload); }; useEffect(() => { @@ -217,4 +189,4 @@ export const UserDetails: React.FC = ({ user }) => { ); -}; +}); diff --git a/web/components/onboarding/workspace.tsx b/web/components/onboarding/workspace.tsx index 8cafdde43..916c91f5d 100644 --- a/web/components/onboarding/workspace.tsx +++ b/web/components/onboarding/workspace.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; - // ui import { Button } from "@plane/ui"; // types @@ -15,7 +14,9 @@ type Props = { workspaces: IWorkspace[] | undefined; }; -export const Workspace: React.FC = ({ finishOnboarding, stepChange, updateLastWorkspace, user, workspaces }) => { +export const Workspace: React.FC = (props) => { + const { finishOnboarding, stepChange, updateLastWorkspace, user, workspaces } = props; + const [defaultValues, setDefaultValues] = useState({ name: "", slug: "", diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx index e0be15e31..f6d8ac25c 100644 --- a/web/components/page-views/signin.tsx +++ b/web/components/page-views/signin.tsx @@ -29,7 +29,6 @@ const authService = new AuthService(); export const SignInView = observer(() => { const { user: userStore } = useMobxStore(); - const { fetchCurrentUserSettings } = userStore; // router const router = useRouter(); const { next: next_url } = router.query as { next: string }; @@ -46,7 +45,7 @@ export const SignInView = observer(() => { (data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github)); useEffect(() => { - fetchCurrentUserSettings().then((settings) => { + userStore.fetchCurrentUserSettings().then((settings) => { setLoading(true); if (next_url) router.push(next_url); else @@ -58,12 +57,13 @@ export const SignInView = observer(() => { }` ); }); - }, [fetchCurrentUserSettings, router, next_url]); + }, [userStore, router, next_url]); const handleLoginRedirection = () => { userStore.fetchCurrentUser().then((user) => { - const isOnboard = user.onboarding_step.profile_complete; - if (isOnboard) { + const isOnboarded = user.is_onboarded; + + if (isOnboarded) { userStore .fetchCurrentUserSettings() .then((userSettings: IUserSettings) => { diff --git a/web/components/workspace/create-workspace-form.tsx b/web/components/workspace/create-workspace-form.tsx index 584292422..b127a077b 100644 --- a/web/components/workspace/create-workspace-form.tsx +++ b/web/components/workspace/create-workspace-form.tsx @@ -2,7 +2,6 @@ import { Dispatch, SetStateAction, useEffect, useState, FC } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; -import { mutate } from "swr"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // services @@ -13,8 +12,6 @@ import useToast from "hooks/use-toast"; import { Button, CustomSelect, Input } from "@plane/ui"; // types import { IWorkspace } from "types"; -// fetch-keys -import { USER_WORKSPACES } from "constants/fetch-keys"; // constants import { ORGANIZATION_SIZE } from "constants/workspace"; @@ -96,7 +93,6 @@ export const CreateWorkspaceForm: FC = observer((props) => { message: "Workspace created successfully.", }); - mutate(USER_WORKSPACES, (prevData) => [res, ...(prevData ?? [])], false); if (onSubmit) await onSubmit(res); }) .catch(() => diff --git a/web/layouts/auth-layout/user-wrapper.tsx b/web/layouts/auth-layout/user-wrapper.tsx index e9dcb1f74..7e4f51fb7 100644 --- a/web/layouts/auth-layout/user-wrapper.tsx +++ b/web/layouts/auth-layout/user-wrapper.tsx @@ -13,13 +13,15 @@ export interface IUserAuthWrapper { export const UserAuthWrapper: FC = (props) => { const { children } = props; // store - const { user: userStore } = useMobxStore(); + const { user: userStore, workspace: workspaceStore } = useMobxStore(); // router const router = useRouter(); // fetching user information const { data: currentUser, error } = useSWR("CURRENT_USER_DETAILS", () => userStore.fetchCurrentUser()); // fetching user settings useSWR("CURRENT_USER_SETTINGS", () => userStore.fetchCurrentUserSettings()); + // fetching all workspaces + useSWR(`USER_WORKSPACES_LIST`, () => workspaceStore.fetchWorkspaces()); if (!currentUser && !error) { return ( diff --git a/web/layouts/auth-layout/workspace-wrapper.tsx b/web/layouts/auth-layout/workspace-wrapper.tsx index bd8465995..4950e4191 100644 --- a/web/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/layouts/auth-layout/workspace-wrapper.tsx @@ -20,8 +20,6 @@ export const WorkspaceAuthWrapper: FC = observer((props) // router const router = useRouter(); const { workspaceSlug } = router.query; - // fetching all workspaces - useSWR(`USER_WORKSPACES_LIST`, () => workspaceStore.fetchWorkspaces()); // fetching user workspace information useSWR( workspaceSlug ? `WORKSPACE_MEMBERS_ME_${workspaceSlug}` : null, diff --git a/web/pages/onboarding/index.tsx b/web/pages/onboarding/index.tsx index ffd46470b..811517f5c 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/pages/onboarding/index.tsx @@ -1,17 +1,17 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import { observer } from "mobx-react-lite"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; import { useTheme } from "next-themes"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // services -import { UserService } from "services/user.service"; import { WorkspaceService } from "services/workspace.service"; // hooks import useUserAuth from "hooks/use-user-auth"; // layouts import DefaultLayout from "layouts/default-layout"; +import { UserAuthWrapper } from "layouts/auth-layout"; // components import { InviteMembers, JoinWorkspaces, UserDetails, Workspace } from "components/onboarding"; // ui @@ -23,52 +23,34 @@ import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-l // types import { IUser, TOnboardingSteps } from "types"; import type { NextPage } from "next"; -// fetch-keys -import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys"; // services -const userService = new UserService(); const workspaceService = new WorkspaceService(); const Onboarding: NextPage = observer(() => { const [step, setStep] = useState(null); - const { workspace: workspaceStore } = useMobxStore(); + const { user: userStore, workspace: workspaceStore } = useMobxStore(); + + const user = userStore.currentUser ?? undefined; + const workspaces = workspaceStore.workspaces; + const userWorkspaces = workspaceStore.workspacesCreateByCurrentUser; const { theme, setTheme } = useTheme(); - const { user, isLoading: userLoading } = useUserAuth("onboarding"); + const {} = useUserAuth("onboarding"); - const { workspaces } = workspaceStore; - const userWorkspaces = workspaces?.filter((w) => w.created_by === user?.id); - - const { data: invitations } = useSWR(USER_WORKSPACE_INVITATIONS, () => workspaceService.userWorkspaceInvitations()); + const { data: invitations } = useSWR("USER_WORKSPACE_INVITATIONS_LIST", () => + workspaceService.userWorkspaceInvitations() + ); // update last active workspace details const updateLastWorkspace = async () => { if (!workspaces) return; - await mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - last_workspace_id: workspaces[0]?.id, - // workspace: { - // ...prevData.workspace, - // fallback_workspace_id: workspaces[0]?.id, - // fallback_workspace_slug: workspaces[0]?.slug, - // last_workspace_id: workspaces[0]?.id, - // last_workspace_slug: workspaces[0]?.slug, - // }, - }; - }, - false - ); - - await userService.updateUser({ last_workspace_id: workspaces?.[0]?.id }); + await userStore.updateCurrentUser({ + last_workspace_id: workspaces[0]?.id, + }); }; // handle step change @@ -82,40 +64,14 @@ const Onboarding: NextPage = observer(() => { }, }; - mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - ...payload, - }; - }, - false - ); - - await userService.updateUser(payload); + await userStore.updateCurrentUser(payload); }; // complete onboarding const finishOnboarding = async () => { if (!user) return; - mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - is_onboarded: true, - }; - }, - false - ); - - await userService.updateUserOnBoard({ userRole: user.role }, user); + await userStore.updateUserOnBoard(); }; useEffect(() => { @@ -148,84 +104,85 @@ const Onboarding: NextPage = observer(() => { handleStepChange(); }, [user, invitations, step]); - if (userLoading || step === null) - return ( -
- -
- ); - return ( - -
-
-
- {step === 1 ? ( -
-
- Plane logo + + {user && step !== null ? ( + +
+
+
+ {step === 1 ? ( +
+
+ Plane logo +
+
+ ) : ( +
+
+ {theme === "light" ? ( + Plane black logo + ) : ( + Plane white logo + )} +
+
+ )} +
+ {user?.email}
- ) : ( -
-
- {theme === "light" ? ( - Plane black logo - ) : ( - Plane white logo - )} -
-
- )} -
- {user?.email} -
-
-
- {step === 1 ? ( - - ) : step === 2 ? ( - - ) : step === 3 ? ( - - ) : ( - step === 4 && ( - - ) - )} -
- {step !== 4 && ( -
-
-

{step} of 3 steps

-
-
+ {step === 1 ? ( + + ) : step === 2 ? ( + -
+ ) : step === 3 ? ( + + ) : ( + step === 4 && ( + + ) + )}
+ {step !== 4 && ( +
+
+

{step} of 3 steps

+
+
+
+
+
+ )}
- )} -
- + + ) : ( +
+ +
+ )} + ); }); diff --git a/web/store/user.store.ts b/web/store/user.store.ts index 89eafb2e9..80cafdf44 100644 --- a/web/store/user.store.ts +++ b/web/store/user.store.ts @@ -47,6 +47,7 @@ export interface IUserStore { fetchUserProjectInfo: (workspaceSlug: string, projectId: string) => Promise; fetchUserDashboardInfo: (workspaceSlug: string, month: number) => Promise; + updateUserOnBoard: () => Promise; updateTourCompleted: () => Promise; updateCurrentUser: (data: Partial) => Promise; updateCurrentUserTheme: (theme: string) => Promise; @@ -98,6 +99,7 @@ class UserStore implements IUserStore { fetchUserDashboardInfo: action, fetchUserWorkspaceInfo: action, fetchUserProjectInfo: action, + updateUserOnBoard: action, updateTourCompleted: action, updateCurrentUser: action, updateCurrentUserTheme: action, @@ -242,6 +244,27 @@ class UserStore implements IUserStore { } }; + updateUserOnBoard = async () => { + try { + runInAction(() => { + this.currentUser = { + ...this.currentUser, + is_onboarded: true, + } as IUser; + }); + + const user = this.currentUser ?? undefined; + + if (!user) return; + + await this.userService.updateUserOnBoard({ userRole: user.role }, user); + } catch (error) { + this.fetchCurrentUser(); + + throw error; + } + }; + updateTourCompleted = async () => { try { if (this.currentUser) { @@ -251,7 +274,9 @@ class UserStore implements IUserStore { is_tour_completed: true, } as IUser; }); + const response = await this.userService.updateUserTourCompleted(this.currentUser); + return response; } } catch (error) { @@ -275,6 +300,8 @@ class UserStore implements IUserStore { }); return response; } catch (error) { + this.fetchCurrentUser(); + throw error; } }; diff --git a/web/store/workspace/workspace.store.ts b/web/store/workspace/workspace.store.ts index 7c6551ec0..6891da72c 100644 --- a/web/store/workspace/workspace.store.ts +++ b/web/store/workspace/workspace.store.ts @@ -14,7 +14,7 @@ export interface IWorkspaceStore { // observables workspaceSlug: string | null; - workspaces: IWorkspace[]; + workspaces: IWorkspace[] | undefined; labels: { [workspaceSlug: string]: IIssueLabels[] }; // workspaceSlug: labels[] members: { [workspaceSlug: string]: IWorkspaceMember[] }; // workspaceSlug: members[] @@ -22,7 +22,7 @@ export interface IWorkspaceStore { setWorkspaceSlug: (workspaceSlug: string) => void; getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null; getWorkspaceLabelById: (workspaceSlug: string, labelId: string) => IIssueLabels | null; - fetchWorkspaces: () => Promise; + fetchWorkspaces: () => Promise; fetchWorkspaceLabels: (workspaceSlug: string) => Promise; fetchWorkspaceMembers: (workspaceSlug: string) => Promise; @@ -37,6 +37,7 @@ export interface IWorkspaceStore { // computed currentWorkspace: IWorkspace | null; + workspacesCreateByCurrentUser: IWorkspace[] | null; workspaceLabels: IIssueLabels[] | null; workspaceMembers: IWorkspaceMember[] | null; } @@ -48,7 +49,7 @@ export class WorkspaceStore implements IWorkspaceStore { // observables workspaceSlug: string | null = null; - workspaces: IWorkspace[] = []; + workspaces: IWorkspace[] | undefined = []; projects: { [workspaceSlug: string]: IProject[] } = {}; // workspaceSlug: project[] labels: { [workspaceSlug: string]: IIssueLabels[] } = {}; members: { [workspaceSlug: string]: IWorkspaceMember[] } = {}; @@ -112,6 +113,19 @@ export class WorkspaceStore implements IWorkspaceStore { return this.workspaces?.find((workspace) => workspace.slug === this.workspaceSlug) || null; } + /** + * computed value of all the workspaces created by the current logged in user + */ + get workspacesCreateByCurrentUser() { + if (!this.workspaces) return null; + + const user = this.rootStore.user.currentUser; + + if (!user) return null; + + return this.workspaces.filter((w) => w.created_by === user?.id); + } + /** * computed value of workspace labels using the workspace slug from the store */ @@ -141,7 +155,7 @@ export class WorkspaceStore implements IWorkspaceStore { * fetch workspace info from the array of workspaces in the store. * @param workspaceSlug */ - getWorkspaceBySlug = (workspaceSlug: string) => this.workspaces.find((w) => w.slug == workspaceSlug) || null; + getWorkspaceBySlug = (workspaceSlug: string) => this.workspaces?.find((w) => w.slug == workspaceSlug) || null; /** * get workspace label information from the workspace labels @@ -166,11 +180,18 @@ export class WorkspaceStore implements IWorkspaceStore { this.loader = false; this.error = null; }); + + return workspaceResponse; } catch (error) { console.log("Failed to fetch user workspaces in workspace store", error); - this.loader = false; - this.error = error; - this.workspaces = []; + + runInAction(() => { + this.loader = false; + this.error = error; + this.workspaces = []; + }); + + throw error; } }; @@ -250,7 +271,7 @@ export class WorkspaceStore implements IWorkspaceStore { runInAction(() => { this.loader = false; this.error = null; - this.workspaces = [...this.workspaces, response]; + this.workspaces = [...(this.workspaces ?? []), response]; }); return response;