From 991a6858ecea76aef5387f65abd1be79ef3c6d36 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Wed, 11 Oct 2023 13:57:47 +0530 Subject: [PATCH] fix: Auth fixes and Layout fixes (#2408) * fix: auth fixes and layout improvements * fix: layout fixes --- ...orm.tsx => email-forgot-password-form.tsx} | 56 +---- .../account/email-password-form.tsx | 19 +- web/components/account/index.ts | 2 +- web/components/common/index.ts | 1 + .../{ui => common}/product-updates-modal.tsx | 0 .../headers/workspace-dashboard.tsx | 52 +++++ web/components/ui/index.ts | 1 - web/components/user/index.ts | 1 + web/components/user/user-greetings.tsx | 49 +++++ web/components/views/index.ts | 1 + web/components/views/signin.tsx | 44 ++-- web/components/views/workspace-dashboard.tsx | 90 ++++++++ web/layouts/app-layout/layout.tsx | 21 +- web/layouts/auth-layout/workspace-wrapper.tsx | 33 ++- web/pages/[workspaceSlug]/index.tsx | 206 +----------------- web/pages/accounts/forget-password.tsx | 0 web/pages/accounts/forgot-password.tsx | 71 ++++++ .../index.tsx} | 0 .../{onboarding.tsx => onboarding/index.tsx} | 0 .../index.tsx} | 0 web/services/user.service.ts | 16 +- web/store/user.ts | 87 +++++++- web/types/users.d.ts | 125 +++++------ 23 files changed, 505 insertions(+), 370 deletions(-) rename web/components/account/{email-reset-password-form.tsx => email-forgot-password-form.tsx} (59%) create mode 100644 web/components/common/index.ts rename web/components/{ui => common}/product-updates-modal.tsx (100%) create mode 100644 web/components/headers/workspace-dashboard.tsx create mode 100644 web/components/user/index.ts create mode 100644 web/components/user/user-greetings.tsx create mode 100644 web/components/views/workspace-dashboard.tsx delete mode 100644 web/pages/accounts/forget-password.tsx create mode 100644 web/pages/accounts/forgot-password.tsx rename web/pages/{create-workspace.tsx => create-workspace/index.tsx} (100%) rename web/pages/{onboarding.tsx => onboarding/index.tsx} (100%) rename web/pages/{workspace-member-invitation.tsx => workspace-invitations/index.tsx} (100%) diff --git a/web/components/account/email-reset-password-form.tsx b/web/components/account/email-forgot-password-form.tsx similarity index 59% rename from web/components/account/email-reset-password-form.tsx rename to web/components/account/email-forgot-password-form.tsx index a4610f293..f811572e6 100644 --- a/web/components/account/email-reset-password-form.tsx +++ b/web/components/account/email-forgot-password-form.tsx @@ -1,21 +1,19 @@ -import React from "react"; +import { FC } from "react"; import { useRouter } from "next/router"; import { useForm } from "react-hook-form"; -// services -import userService from "services/user.service"; -// hooks -import useToast from "hooks/use-toast"; // ui import { Input, PrimaryButton, SecondaryButton } from "components/ui"; -// types -type Props = { - onSubmit: (formValues: any) => void; -}; -export const EmailResetPasswordForm: React.FC = (props) => { +export interface EmailForgotPasswordFormValues { + email: string; +} + +export interface IEmailForgotPasswordForm { + onSubmit: (formValues: any) => Promise; +} + +export const EmailForgotPasswordForm: FC = (props) => { const { onSubmit } = props; - // toast - const { setToastAlert } = useToast(); // router const router = useRouter(); // form data @@ -23,7 +21,7 @@ export const EmailResetPasswordForm: React.FC = (props) => { register, handleSubmit, formState: { errors, isSubmitting }, - } = useForm({ + } = useForm({ defaultValues: { email: "", }, @@ -31,38 +29,8 @@ export const EmailResetPasswordForm: React.FC = (props) => { reValidateMode: "onChange", }); - const forgotPassword = async (formData: any) => { - const payload = { - email: formData.email, - }; - - await userService - .forgotPassword(payload) - .then(() => - setToastAlert({ - type: "success", - title: "Success!", - message: "Password reset link has been sent to your email address.", - }) - ) - .catch((err) => { - if (err.status === 400) - setToastAlert({ - type: "error", - title: "Error!", - message: "Please check the Email ID entered.", - }); - else - setToastAlert({ - type: "error", - title: "Error!", - message: "Something went wrong. Please try again.", - }); - }); - }; - return ( -
+
= (props) => { const { onSubmit } = props; + // router + const router = useRouter(); // form info const { register, @@ -66,7 +70,11 @@ export const EmailPasswordForm: React.FC = (props) => { />
-
@@ -80,6 +88,15 @@ export const EmailPasswordForm: React.FC = (props) => { {isSubmitting ? "Signing in..." : "Sign in"} +
+ +
); diff --git a/web/components/account/index.ts b/web/components/account/index.ts index e3af244b6..36a204c62 100644 --- a/web/components/account/index.ts +++ b/web/components/account/index.ts @@ -1,6 +1,6 @@ export * from "./email-code-form"; export * from "./email-password-form"; -export * from "./email-reset-password-form"; +export * from "./email-forgot-password-form"; export * from "./github-login-button"; export * from "./google-login"; export * from "./email-signup-form"; diff --git a/web/components/common/index.ts b/web/components/common/index.ts new file mode 100644 index 000000000..355d549ef --- /dev/null +++ b/web/components/common/index.ts @@ -0,0 +1 @@ +export * from "./product-updates-modal"; diff --git a/web/components/ui/product-updates-modal.tsx b/web/components/common/product-updates-modal.tsx similarity index 100% rename from web/components/ui/product-updates-modal.tsx rename to web/components/common/product-updates-modal.tsx diff --git a/web/components/headers/workspace-dashboard.tsx b/web/components/headers/workspace-dashboard.tsx new file mode 100644 index 000000000..7b9e5a1bf --- /dev/null +++ b/web/components/headers/workspace-dashboard.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import { LayoutGrid, Zap } from "lucide-react"; +import Image from "next/image"; +import { useTheme } from "next-themes"; +// images +import githubBlackImage from "/public/logos/github-black.png"; +import githubWhiteImage from "/public/logos/github-white.png"; +// components +import { ProductUpdatesModal } from "components/common"; + +export const WorkspaceDashboardHeader = () => { + const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false); + // theme + const { resolvedTheme } = useTheme(); + + return ( + <> + +
+
+ + Dashboard +
+
+ + + GitHub Logo + Star us on GitHub + +
+
+ + ); +}; diff --git a/web/components/ui/index.ts b/web/components/ui/index.ts index 3f9815533..b77ee2615 100644 --- a/web/components/ui/index.ts +++ b/web/components/ui/index.ts @@ -19,7 +19,6 @@ export * from "./spinner"; export * from "./tooltip"; export * from "./toggle-switch"; export * from "./markdown-to-component"; -export * from "./product-updates-modal"; export * from "./integration-and-import-export-banner"; export * from "./range-datepicker"; export * from "./circular-progress"; diff --git a/web/components/user/index.ts b/web/components/user/index.ts new file mode 100644 index 000000000..9c2663b18 --- /dev/null +++ b/web/components/user/index.ts @@ -0,0 +1 @@ +export * from "./user-greetings"; diff --git a/web/components/user/user-greetings.tsx b/web/components/user/user-greetings.tsx new file mode 100644 index 000000000..f7d00a489 --- /dev/null +++ b/web/components/user/user-greetings.tsx @@ -0,0 +1,49 @@ +import { FC } from "react"; +import { IUser } from "types"; + +export interface IUserGreetingsView { + user: IUser; +} + +export const UserGreetingsView: FC = (props) => { + const { user } = props; + + const currentTime = new Date(); + + const hour = new Intl.DateTimeFormat("en-US", { + hour12: false, + hour: "numeric", + }).format(currentTime); + + const date = new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + }).format(currentTime); + + const weekDay = new Intl.DateTimeFormat("en-US", { + weekday: "long", + }).format(currentTime); + + const timeString = new Intl.DateTimeFormat("en-US", { + timeZone: user?.user_timezone, + hour12: false, // Use 24-hour format + hour: "2-digit", + minute: "2-digit", + }).format(currentTime); + + const greeting = parseInt(hour, 10) < 12 ? "morning" : parseInt(hour, 10) < 18 ? "afternoon" : "evening"; + + return ( +
+

+ Good {greeting}, {user?.first_name} {user?.last_name} +

+
+
{greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"}
+
+ {weekDay}, {date} {timeString} +
+
+
+ ); +}; diff --git a/web/components/views/index.ts b/web/components/views/index.ts index 9de594e16..b5c07cf4a 100644 --- a/web/components/views/index.ts +++ b/web/components/views/index.ts @@ -5,3 +5,4 @@ export * from "./modal"; export * from "./select-filters"; export * from "./single-view-item"; export * from "./signin"; +export * from "./workspace-dashboard"; diff --git a/web/components/views/signin.tsx b/web/components/views/signin.tsx index c03f02cfb..ed7ed08c8 100644 --- a/web/components/views/signin.tsx +++ b/web/components/views/signin.tsx @@ -1,6 +1,7 @@ import useSWR from "swr"; import { observer } from "mobx-react-lite"; import Image from "next/image"; +import { useRouter } from "next/router"; // hooks import useToast from "hooks/use-toast"; import { useMobxStore } from "lib/mobx/store-provider"; @@ -13,25 +14,37 @@ import { GithubLoginButton, EmailCodeForm, EmailPasswordForm, - EmailResetPasswordForm, EmailPasswordFormValues, } from "components/account"; // ui import { Spinner } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; +import { IUserSettings } from "types"; const appConfigService = new AppConfigService(); const authService = new AuthService(); export const SignInView = observer(() => { const { user: userStore } = useMobxStore(); + // router + const router = useRouter(); // toast const { setToastAlert } = useToast(); // fetch app config const { data } = useSWR("APP_CONFIG", () => appConfigService.envConfig()); // fetch user info const { data: user, error } = useSWR("USER_INFO", () => userStore.fetchCurrentUser()); + // computed + const enableEmailPassword = + data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github); + + const handleLoginRedirection = () => + userStore.fetchCurrentUserSettings().then((userSettings: IUserSettings) => { + const workspaceSlug = + userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; + router.push(`/${workspaceSlug}`); + }); const handleGoogleSignIn = async ({ clientId, credential }: any) => { try { @@ -42,9 +55,8 @@ export const SignInView = observer(() => { clientId, }; const response = await authService.socialAuth(socialAuthPayload); - if (response && response?.user) { - mutateUser(); - handleTheme(response?.user); + if (response) { + handleLoginRedirection(); } } else { throw Error("Cant find credentials"); @@ -67,9 +79,8 @@ export const SignInView = observer(() => { clientId: data.github, }; const response = await authService.socialAuth(socialAuthPayload); - if (response && response?.user) { - mutateUser(); - handleTheme(response?.user); + if (response) { + handleLoginRedirection(); } } else { throw Error("Cant find credentials"); @@ -87,17 +98,8 @@ export const SignInView = observer(() => { await authService .emailLogin(formData) .then((response) => { - try { - if (response) { - mutateUser(); - handleTheme(response?.user); - } - } catch (err: any) { - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error || "Something went wrong. Please try again later or contact the support team.", - }); + if (response) { + handleLoginRedirection(); } }) .catch((err) => @@ -112,8 +114,7 @@ export const SignInView = observer(() => { const handleEmailCodeSignIn = async (response: any) => { try { if (response) { - mutateUser(); - handleTheme(response?.user); + handleLoginRedirection(); } } catch (err: any) { setToastAlert({ @@ -147,9 +148,8 @@ export const SignInView = observer(() => {

Sign in to Plane

- <> - {data?.email_password_login && } + {enableEmailPassword && } {data?.magic_login && (
diff --git a/web/components/views/workspace-dashboard.tsx b/web/components/views/workspace-dashboard.tsx new file mode 100644 index 000000000..ac887c0fe --- /dev/null +++ b/web/components/views/workspace-dashboard.tsx @@ -0,0 +1,90 @@ +import { useState } from "react"; +import Image from "next/image"; +import { useRouter } from "next/router"; +import useSWR from "swr"; +import { observer } from "mobx-react-lite"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { TourRoot } from "components/onboarding"; +import { UserGreetingsView } from "components/user"; +import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "components/workspace"; +import { PrimaryButton } from "components/ui"; +// images +import emptyDashboard from "public/empty-state/dashboard.svg"; + +export const WorkspaceDashboardView = observer(() => { + // router + const router = useRouter(); + const { workspaceSlug } = router.query; + // store + const { user: userStore, project: projectStore } = useMobxStore(); + const user = userStore.currentUser; + const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null; + const workspaceDashboardInfo = userStore.dashboardInfo; + // states + const [month, setMonth] = useState(new Date().getMonth() + 1); + // fetch user dashboard info + useSWR( + workspaceSlug ? `USER_WORKSPACE_DASHBOARD_${workspaceSlug}_${month}` : null, + workspaceSlug ? () => userStore.fetchUserDashboardInfo(workspaceSlug.toString(), month) : null + ); + + const handleTourCompleted = () => { + userStore.updateTourCompleted(); + }; + + return ( + <> + {/* {isProductUpdatesModalOpen && ( + + )} */} + {user && !user.is_tour_completed && ( +
+ +
+ )} +
+ {user && } + + {projects ? ( + projects.length > 0 ? ( +
+ +
+ + + + +
+
+ ) : ( +
+
+
Create a project
+

Manage your projects by creating issues, cycles, modules, views and pages.

+ { + const e = new KeyboardEvent("keydown", { + key: "p", + }); + document.dispatchEvent(e); + }} + > + Create Project + +
+
+ Empty Dashboard +
+
+ ) + ) : null} +
+ + ); +}); diff --git a/web/layouts/app-layout/layout.tsx b/web/layouts/app-layout/layout.tsx index 6e9f10264..394d35d6b 100644 --- a/web/layouts/app-layout/layout.tsx +++ b/web/layouts/app-layout/layout.tsx @@ -6,29 +6,22 @@ import { CommandPalette } from "components/command-palette"; import { AppSidebar } from "./sidebar"; export interface IAppLayout { - bg: string; children: ReactNode; + header: ReactNode; } export const AppLayout: FC = (props) => { - const { bg = "primary", children } = props; + const { children, header } = props; return (
- + {/* */} -
+
-
-
+
+
{header}
+
{children}
diff --git a/web/layouts/auth-layout/workspace-wrapper.tsx b/web/layouts/auth-layout/workspace-wrapper.tsx index 99233abc9..db7792fcd 100644 --- a/web/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/layouts/auth-layout/workspace-wrapper.tsx @@ -2,12 +2,11 @@ import { FC, ReactNode } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; import useSWR from "swr"; -// services -import workspaceServices from "services/workspace.service"; +import { observer } from "mobx-react-lite"; // icons import { Spinner, PrimaryButton, SecondaryButton } from "components/ui"; -// fetch-keys -import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; export interface IWorkspaceAuthWrapper { children: ReactNode; @@ -18,18 +17,30 @@ export interface IWorkspaceAuthWrapper { right?: JSX.Element; } -export const WorkspaceAuthWrapper: FC = (props) => { +export const WorkspaceAuthWrapper: FC = observer((props) => { const { children } = props; + // store + const { user: userStore, project: projectStore } = useMobxStore(); // router const router = useRouter(); const { workspaceSlug } = router.query; // fetching user workspace information - const { data: workspaceMemberMe, error } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null, - workspaceSlug ? () => workspaceServices.workspaceMemberMe(workspaceSlug.toString()) : null + useSWR( + workspaceSlug ? `WORKSPACE_MEMBERS_ME_${workspaceSlug}` : null, + workspaceSlug ? () => userStore.fetchUserWorkspaceInfo(workspaceSlug.toString()) : null ); + // fetching workspace projects + useSWR( + workspaceSlug ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null, + workspaceSlug ? () => projectStore.fetchProjects(workspaceSlug.toString()) : null + ); + + console.log("workspaceSlug", workspaceSlug); + + console.log("userStore.memberInfo", userStore.memberInfo); + // while data is being loaded - if (!workspaceMemberMe && !error) { + if (!userStore.memberInfo && userStore.hasPermissionToWorkspace === null) { return (
@@ -39,7 +50,7 @@ export const WorkspaceAuthWrapper: FC = (props) => { ); } // while user does not have access to view that workspace - if (error?.status === 401 || error?.status === 403) { + if (userStore.hasPermissionToWorkspace !== null && userStore.hasPermissionToWorkspace === false) { return (
@@ -70,4 +81,4 @@ export const WorkspaceAuthWrapper: FC = (props) => { } return <>{children}; -}; +}); diff --git a/web/pages/[workspaceSlug]/index.tsx b/web/pages/[workspaceSlug]/index.tsx index 3968e9be7..362cd5499 100644 --- a/web/pages/[workspaceSlug]/index.tsx +++ b/web/pages/[workspaceSlug]/index.tsx @@ -1,200 +1,14 @@ -import React, { useEffect, useState } from "react"; - -import { useRouter } from "next/router"; -import Image from "next/image"; -import Link from "next/link"; - -import useSWR, { mutate } from "swr"; - -// next-themes -import { useTheme } from "next-themes"; -// layouts -import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy"; -// services -import userService from "services/user.service"; -// hooks -import useUser from "hooks/use-user"; -import useProjects from "hooks/use-projects"; -// components -import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "components/workspace"; -import { TourRoot } from "components/onboarding"; -// ui -import { PrimaryButton, ProductUpdatesModal } from "components/ui"; -// icons -import { BoltOutlined, GridViewOutlined } from "@mui/icons-material"; -// images -import emptyDashboard from "public/empty-state/dashboard.svg"; -import githubBlackImage from "/public/logos/github-black.png"; -import githubWhiteImage from "/public/logos/github-white.png"; -// types -import { ICurrentUserResponse } from "types"; import type { NextPage } from "next"; -// fetch-keys -import { CURRENT_USER, USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys"; +// layouts +import { AppLayout } from "layouts/app-layout"; +// components +import { WorkspaceDashboardView } from "components/views"; +import { WorkspaceDashboardHeader } from "components/headers/workspace-dashboard"; -const Greeting = ({ user }: { user: ICurrentUserResponse | undefined }) => { - const currentTime = new Date(); - - const hour = new Intl.DateTimeFormat("en-US", { - hour12: false, - hour: "numeric", - }).format(currentTime); - - const date = new Intl.DateTimeFormat("en-US", { - month: "short", - day: "numeric", - }).format(currentTime); - - const weekDay = new Intl.DateTimeFormat("en-US", { - weekday: "long", - }).format(currentTime); - - const timeString = new Intl.DateTimeFormat("en-US", { - timeZone: user?.user_timezone, - hour12: false, // Use 24-hour format - hour: "2-digit", - minute: "2-digit", - }).format(currentTime); - - const greeting = parseInt(hour, 10) < 12 ? "morning" : parseInt(hour, 10) < 18 ? "afternoon" : "evening"; - - return ( -
-

- Good {greeting}, {user?.first_name} {user?.last_name} -

-
-
{greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"}
-
- {weekDay}, {date} {timeString} -
-
-
- ); -}; - -const WorkspacePage: NextPage = () => { - const [month, setMonth] = useState(new Date().getMonth() + 1); - const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false); - - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { theme } = useTheme(); - - const { user } = useUser(); - const { projects } = useProjects(); - - const { data: workspaceDashboardData } = useSWR( - workspaceSlug ? USER_WORKSPACE_DASHBOARD(workspaceSlug as string) : null, - workspaceSlug ? () => userService.userWorkspaceDashboard(workspaceSlug as string, month) : null - ); - - useEffect(() => { - if (!workspaceSlug) return; - - mutate(USER_WORKSPACE_DASHBOARD(workspaceSlug as string)); - }, [month, workspaceSlug]); - - return ( - - - Dashboard -
- } - right={ -
- - - - GitHub Logo - Star us on GitHub - - -
- } - > - {isProductUpdatesModalOpen && ( - - )} - {user && !user.is_tour_completed && ( -
- { - mutate( - CURRENT_USER, - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - is_tour_completed: true, - }; - }, - false - ); - - userService.updateUserTourCompleted(user).catch(() => mutate(CURRENT_USER)); - }} - /> -
- )} -
- - - {projects ? ( - projects.length > 0 ? ( -
- -
- - - - -
-
- ) : ( -
-
-
Create a project
-

Manage your projects by creating issues, cycles, modules, views and pages.

- { - const e = new KeyboardEvent("keydown", { - key: "p", - }); - document.dispatchEvent(e); - }} - > - Create Project - -
-
- Empty Dashboard -
-
- ) - ) : null} -
- - ); -}; +const WorkspacePage: NextPage = () => ( + }> + + +); export default WorkspacePage; diff --git a/web/pages/accounts/forget-password.tsx b/web/pages/accounts/forget-password.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/pages/accounts/forgot-password.tsx b/web/pages/accounts/forgot-password.tsx new file mode 100644 index 000000000..6b313a796 --- /dev/null +++ b/web/pages/accounts/forgot-password.tsx @@ -0,0 +1,71 @@ +import { NextPage } from "next"; +import Image from "next/image"; +// components +import { EmailForgotPasswordForm, EmailForgotPasswordFormValues } from "components/account"; +// layouts +import DefaultLayout from "layouts/default-layout"; +// services +import { UserService } from "services/user.service"; +// hooks +import useToast from "hooks/use-toast"; +// images +import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; + +const userService = new UserService(); + +const ForgotPasswordPage: NextPage = () => { + // toast + const { setToastAlert } = useToast(); + + const handleForgotPassword = (formData: EmailForgotPasswordFormValues) => { + const payload = { + email: formData.email, + }; + + return userService + .forgotPassword(payload) + .then(() => + setToastAlert({ + type: "success", + title: "Success!", + message: "Password reset link has been sent to your email address.", + }) + ) + .catch((err) => { + if (err.status === 400) + setToastAlert({ + type: "error", + title: "Error!", + message: "Please check the Email ID entered.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again.", + }); + }); + }; + return ( + + <> +
+
+
+
+ Plane Logo +
+
+
+ +
+
+

Forgot Password

+ +
+
+ + ); +}; + +export default ForgotPasswordPage; diff --git a/web/pages/create-workspace.tsx b/web/pages/create-workspace/index.tsx similarity index 100% rename from web/pages/create-workspace.tsx rename to web/pages/create-workspace/index.tsx diff --git a/web/pages/onboarding.tsx b/web/pages/onboarding/index.tsx similarity index 100% rename from web/pages/onboarding.tsx rename to web/pages/onboarding/index.tsx diff --git a/web/pages/workspace-member-invitation.tsx b/web/pages/workspace-invitations/index.tsx similarity index 100% rename from web/pages/workspace-member-invitation.tsx rename to web/pages/workspace-invitations/index.tsx diff --git a/web/services/user.service.ts b/web/services/user.service.ts index 0f354d1c5..083031d41 100644 --- a/web/services/user.service.ts +++ b/web/services/user.service.ts @@ -3,12 +3,12 @@ import APIService from "services/api.service"; import trackEventServices from "services/track_event.service"; import type { - ICurrentUserResponse, IIssue, IUser, IUserActivityResponse, IUserProfileData, IUserProfileProjectSegregation, + IUserSettings, IUserWorkspaceDashboard, } from "types"; @@ -44,7 +44,7 @@ export class UserService extends APIService { }); } - async currentUser(): Promise { + async currentUser(): Promise { return this.get("/api/users/me/") .then((response) => response?.data) .catch((error) => { @@ -52,6 +52,14 @@ export class UserService extends APIService { }); } + async currentUserSettings(): Promise { + return this.get("/api/users/me/settings/") + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + async updateUser(data: Partial): Promise { return this.patch("/api/users/me/", data) .then((response) => response?.data) @@ -60,7 +68,7 @@ export class UserService extends APIService { }); } - async updateUserOnBoard({ userRole }: any, user: ICurrentUserResponse | undefined): Promise { + async updateUserOnBoard({ userRole }: any, user: IUser | undefined): Promise { return this.patch("/api/users/me/onboard/", { is_onboarded: true, }) @@ -78,7 +86,7 @@ export class UserService extends APIService { }); } - async updateUserTourCompleted(user: ICurrentUserResponse): Promise { + async updateUserTourCompleted(user: IUser): Promise { return this.patch("/api/users/me/tour-completed/", { is_tour_completed: true, }) diff --git a/web/store/user.ts b/web/store/user.ts index 9a4bcf006..211ee1a6f 100644 --- a/web/store/user.ts +++ b/web/store/user.ts @@ -2,24 +2,34 @@ import { action, observable, computed, runInAction, makeObservable } from "mobx"; // services import { UserService } from "services/user.service"; +import { WorkspaceService } from "services/workspace.service"; // interfaces -import { ICurrentUser, ICurrentUserSettings } from "types/users"; +import { IUser, IUserSettings } from "types/users"; interface IUserStore { loader: boolean; - currentUser: ICurrentUser | null; - currentUserSettings: ICurrentUserSettings | null; - fetchCurrentUser: () => Promise; + currentUser: IUser | null; + currentUserSettings: IUserSettings | null; + dashboardInfo: any; + memberInfo: any; + hasPermissionToWorkspace: boolean | null; + fetchCurrentUser: () => Promise; + fetchCurrentUserSettings: () => Promise; + updateTourCompleted: () => Promise; } class UserStore implements IUserStore { loader: boolean = false; - currentUser: ICurrentUser | null = null; - currentUserSettings: ICurrentUserSettings | null = null; + currentUser: IUser | null = null; + currentUserSettings: IUserSettings | null = null; + dashboardInfo: any = null; + memberInfo: any = null; + hasPermissionToWorkspace: boolean | null = null; // root store rootStore; // services userService; + workspaceService; constructor(_rootStore: any) { makeObservable(this, { @@ -27,18 +37,22 @@ class UserStore implements IUserStore { loader: observable.ref, currentUser: observable.ref, currentUserSettings: observable.ref, + dashboardInfo: observable.ref, + memberInfo: observable.ref, + hasPermissionToWorkspace: observable.ref, // action fetchCurrentUser: action, + fetchCurrentUserSettings: action, // computed }); this.rootStore = _rootStore; this.userService = new UserService(); + this.workspaceService = new WorkspaceService(); } fetchCurrentUser = async () => { try { const response = await this.userService.currentUser(); - console.log("response", response); if (response) { runInAction(() => { this.currentUser = response; @@ -50,6 +64,65 @@ class UserStore implements IUserStore { } }; + fetchCurrentUserSettings = async () => { + try { + const response = await this.userService.currentUserSettings(); + if (response) { + runInAction(() => { + this.currentUserSettings = response; + }); + } + return response; + } catch (error) { + throw error; + } + }; + + fetchUserDashboardInfo = async (workspaceSlug: string, month: number) => { + try { + const response = await this.userService.userWorkspaceDashboard(workspaceSlug, month); + runInAction(() => { + this.dashboardInfo = response; + }); + return response; + } catch (error) { + throw error; + } + }; + + fetchUserWorkspaceInfo = async (workspaceSlug: string) => { + try { + const response = await this.workspaceService.workspaceMemberMe(workspaceSlug.toString()); + runInAction(() => { + this.memberInfo = response; + this.hasPermissionToWorkspace = true; + }); + return response; + } catch (error) { + runInAction(() => { + this.hasPermissionToWorkspace = false; + }); + throw error; + } + }; + + updateTourCompleted = async () => { + try { + if (this.currentUser) { + runInAction(() => { + this.currentUser = { + ...this.currentUser, + is_tour_completed: true, + } as IUser; + }); + const response = await this.userService.updateUserTourCompleted(this.currentUser); + return response; + } + } catch (error) { + throw error; + } + }; + // setCurrentUser = async () => { // try { // let userResponse: ICurrentUser | null = await UserService.currentUser(); diff --git a/web/types/users.d.ts b/web/types/users.d.ts index 8440922d1..3c38e3c4e 100644 --- a/web/types/users.d.ts +++ b/web/types/users.d.ts @@ -1,44 +1,43 @@ -import { - IIssue, - IIssueActivity, - IIssueLite, - IWorkspace, - IWorkspaceLite, - NestedKeyOf, - Properties, - TStateGroups, -} from "."; +import { IIssueActivity, IIssueLite, TStateGroups } from "."; export interface IUser { + id: string; avatar: string; cover_image: string | null; - created_at: readonly Date; - created_location: readonly string; - date_joined: readonly Date; - email: string; + date_joined: string; display_name: string; + email: string; first_name: string; - id: readonly string; + last_name: string; + is_active: boolean; + is_bot: boolean; is_email_verified: boolean; + is_managed: boolean; is_onboarded: boolean; is_tour_completed: boolean; - last_location: readonly string; - last_login: readonly Date; - last_name: string; - mobile_number: string; - my_issues_prop: { - properties: Properties; - groupBy: NestedKeyOf | null; - } | null; - onboarding_step: TOnboardingSteps; - role: string; - token: string; - theme: ICustomTheme; - updated_at: readonly Date; - username: string; + mobile_number: string | null; + role: string | null; + onboarding_step: { + workspace_join?: boolean; + profile_complete?: boolean; + workspace_create?: boolean; + workspace_invite?: boolean; + }; user_timezone: string; + username: string; + theme: ICustomTheme | {}; +} - [...rest: string]: any; +export interface IUserSettings { + id: string; + email: string; + workspace: { + last_workspace_id: string; + last_workspace_slug: string; + fallback_workspace_id: string; + fallback_workspace_slug: string; + invites: number; + }; } export interface ICustomTheme { @@ -52,18 +51,6 @@ export interface ICustomTheme { theme: string; } -export interface ICurrentUserResponse extends IUser { - assigned_issues: number; - last_workspace_id: string | null; - workspace_invites: number; - workspace: { - fallback_workspace_id: string | null; - fallback_workspace_slug: string | null; - invites: number; - last_workspace_id: string | null; - last_workspace_slug: string | null; - }; -} export interface IUserLite { avatar: string; created_at: Date; @@ -166,32 +153,32 @@ export interface IUserProfileProjectSegregation { }; } -export interface ICurrentUser { - id: readonly string; - avatar: string; - first_name: string; - last_name: string; - username: string; - email: string; - mobile_number: string; - is_email_verified: boolean; - is_tour_completed: boolean; - onboarding_step: TOnboardingSteps; - is_onboarded: boolean; - role: string; -} +// export interface ICurrentUser { +// id: readonly string; +// avatar: string; +// first_name: string; +// last_name: string; +// username: string; +// email: string; +// mobile_number: string; +// is_email_verified: boolean; +// is_tour_completed: boolean; +// onboarding_step: TOnboardingSteps; +// is_onboarded: boolean; +// role: string; +// } -export interface ICustomTheme { - background: string; - text: string; - primary: string; - sidebarBackground: string; - sidebarText: string; - darkPalette: boolean; - palette: string; - theme: string; -} +// export interface ICustomTheme { +// background: string; +// text: string; +// primary: string; +// sidebarBackground: string; +// sidebarText: string; +// darkPalette: boolean; +// palette: string; +// theme: string; +// } -export interface ICurrentUserSettings { - theme: ICustomTheme; -} +// export interface ICurrentUserSettings { +// theme: ICustomTheme; +// }