From 44f8ba407dacaad895882715cd576a427dc0d625 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 30 May 2023 09:44:35 -0400 Subject: [PATCH] Authentication Workflow fixes. Redirection fixes (#832) * auth integration fixes * auth integration fixes * auth integration fixes * auth integration fixes * dev: update user api to return fallback workspace and improve the structure of the response * dev: fix the issue keyerror and move onboarding logic to serializer method field * dev: use-user-auth hook imlemented for route access validation and build issues resolved effected by user payload * fix: global theme color fix * style: new onboarding ui , fix: use-user-auth hook implemented * fix: command palette, project invite modal and issue detail page mutation type fix * fix: onboarding redirection fix * dev: build isuue resolved * fix: use user auth hook fix * fix: sign in toast alert fix, sign out redirection fix and user theme error fix * fix: user response fix * fix: unAuthorizedStatus logic updated --------- Co-authored-by: pablohashescobar Co-authored-by: gurusainath Co-authored-by: anmolsinghbhatia --- apiserver/plane/api/serializers/user.py | 4 + apiserver/plane/api/views/people.py | 51 +++-- .../components/account/email-code-form.tsx | 8 +- .../components/account/email-signin-form.tsx | 24 --- .../account/github-login-button.tsx | 2 +- apps/app/components/account/index.ts | 1 - .../auth-screens/not-authorized-view.tsx | 4 +- apps/app/components/breadcrumbs/index.tsx | 8 +- .../command-palette/change-issue-assignee.tsx | 15 +- .../command-palette/change-issue-priority.tsx | 14 +- .../command-palette/change-issue-state.tsx | 13 +- .../command-palette/command-pallette.tsx | 15 +- .../components/onboarding/invite-members.tsx | 3 +- .../components/onboarding/onboarding-card.tsx | 2 +- .../components/onboarding/user-details.tsx | 19 +- apps/app/components/onboarding/workspace.tsx | 167 +++++++++++----- .../project/send-project-invitation-modal.tsx | 7 +- apps/app/components/ui/icon.tsx | 12 ++ apps/app/components/ui/index.ts | 1 + .../workspace/create-workspace-form.tsx | 187 +++++++++--------- .../components/workspace/sidebar-dropdown.tsx | 26 +-- .../workspace/single-invitation.tsx | 34 ++-- apps/app/helpers/string.helper.ts | 9 + apps/app/hooks/use-user-auth.tsx | 107 ++++++++++ apps/app/hooks/use-user.tsx | 60 +++++- .../user-authorization-wrapper.tsx | 2 +- apps/app/lib/auth.ts | 2 +- apps/app/package.json | 2 +- .../[workspaceSlug]/me/profile/index.tsx | 4 +- .../me/profile/preferences.tsx | 4 +- .../projects/[projectId]/issues/[issueId].tsx | 13 +- apps/app/pages/_app.tsx | 33 +--- apps/app/pages/_error.tsx | 2 +- apps/app/pages/create-workspace.tsx | 40 +++- apps/app/pages/index.tsx | 152 +++++++++----- apps/app/pages/invitations.tsx | 158 ++++++++------- apps/app/pages/onboarding.tsx | 46 ++++- apps/app/pages/signin.tsx | 135 ------------- .../[invitationId].tsx | 4 +- apps/app/services/api.service.ts | 3 +- apps/app/styles/globals.css | 4 +- apps/app/types/users.d.ts | 5 +- yarn.lock | 12 +- 43 files changed, 821 insertions(+), 593 deletions(-) delete mode 100644 apps/app/components/account/email-signin-form.tsx create mode 100644 apps/app/components/ui/icon.tsx create mode 100644 apps/app/hooks/use-user-auth.tsx delete mode 100644 apps/app/pages/signin.tsx diff --git a/apiserver/plane/api/serializers/user.py b/apiserver/plane/api/serializers/user.py index 14a33d9c3..d8978479e 100644 --- a/apiserver/plane/api/serializers/user.py +++ b/apiserver/plane/api/serializers/user.py @@ -25,6 +25,10 @@ class UserSerializer(BaseSerializer): ] extra_kwargs = {"password": {"write_only": True}} + # If the user has already filled first name or last name then he is onboarded + def get_is_onboarded(self, obj): + return bool(obj.first_name) or bool(obj.last_name) + class UserLiteSerializer(BaseSerializer): class Meta: diff --git a/apiserver/plane/api/views/people.py b/apiserver/plane/api/views/people.py index 78ae5b2fc..fcf95ff64 100644 --- a/apiserver/plane/api/views/people.py +++ b/apiserver/plane/api/views/people.py @@ -31,36 +31,61 @@ class UserEndpoint(BaseViewSet): def retrieve(self, request): try: - workspace = Workspace.objects.get(pk=request.user.last_workspace_id) + workspace = Workspace.objects.get( + pk=request.user.last_workspace_id, workspace_member__member=request.user + ) workspace_invites = WorkspaceMemberInvite.objects.filter( email=request.user.email ).count() assigned_issues = Issue.objects.filter(assignees__in=[request.user]).count() + serialized_data = UserSerializer(request.user).data + serialized_data["workspace"] = { + "last_workspace_id": request.user.last_workspace_id, + "last_workspace_slug": workspace.slug, + "fallback_workspace_id": request.user.last_workspace_id, + "fallback_workspace_slug": workspace.slug, + "invites": workspace_invites, + } + serialized_data.setdefault("issues", {})["assigned_issues"] = assigned_issues + return Response( - { - "user": UserSerializer(request.user).data, - "slug": workspace.slug, - "workspace_invites": workspace_invites, - "assigned_issues": assigned_issues, - }, + serialized_data, status=status.HTTP_200_OK, ) except Workspace.DoesNotExist: + # This exception will be hit even when the `last_workspace_id` is None + workspace_invites = WorkspaceMemberInvite.objects.filter( email=request.user.email ).count() assigned_issues = Issue.objects.filter(assignees__in=[request.user]).count() + + fallback_workspace = Workspace.objects.filter( + workspace_member__member=request.user + ).order_by("created_at").first() + + serialized_data = UserSerializer(request.user).data + + serialized_data["workspace"] = { + "last_workspace_id": None, + "last_workspace_slug": None, + "fallback_workspace_id": fallback_workspace.id + if fallback_workspace is not None + else None, + "fallback_workspace_slug": fallback_workspace.slug + if fallback_workspace is not None + else None, + "invites": workspace_invites, + } + serialized_data.setdefault("issues", {})["assigned_issues"] = assigned_issues + return Response( - { - "user": UserSerializer(request.user).data, - "slug": None, - "workspace_invites": workspace_invites, - "assigned_issues": assigned_issues, - }, + serialized_data, status=status.HTTP_200_OK, ) except Exception as e: + capture_exception(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, diff --git a/apps/app/components/account/email-code-form.tsx b/apps/app/components/account/email-code-form.tsx index b4a3ef31e..dc1fc725e 100644 --- a/apps/app/components/account/email-code-form.tsx +++ b/apps/app/components/account/email-code-form.tsx @@ -66,8 +66,12 @@ export const EmailCodeForm = ({ onSuccess }: any) => { const handleSignin = async (formData: EmailCodeFormValues) => { await authenticationService .magicSignIn(formData) - .then((response) => { - onSuccess(response); + .then(() => { + setToastAlert({ + title: "Success", + type: "success", + message: "Successfully logged in!", + }); }) .catch((error) => { setToastAlert({ diff --git a/apps/app/components/account/email-signin-form.tsx b/apps/app/components/account/email-signin-form.tsx deleted file mode 100644 index e2f81d50c..000000000 --- a/apps/app/components/account/email-signin-form.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useState, FC } from "react"; -import { KeyIcon } from "@heroicons/react/24/outline"; -// components -import { EmailCodeForm, EmailPasswordForm } from "components/account"; - -export interface EmailSignInFormProps { - handleSuccess: () => void; -} - -export const EmailSignInForm: FC = (props) => { - const { handleSuccess } = props; - // states - const [useCode, setUseCode] = useState(true); - - return ( - <> - {useCode ? ( - - ) : ( - - )} - - ); -}; diff --git a/apps/app/components/account/github-login-button.tsx b/apps/app/components/account/github-login-button.tsx index 16b9743d5..764680d30 100644 --- a/apps/app/components/account/github-login-button.tsx +++ b/apps/app/components/account/github-login-button.tsx @@ -29,7 +29,7 @@ export const GithubLoginButton: FC = (props) => { useEffect(() => { const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - setLoginCallBackURL(`${origin}/signin` as any); + setLoginCallBackURL(`${origin}/` as any); }, []); return ( diff --git a/apps/app/components/account/index.ts b/apps/app/components/account/index.ts index b78e2e921..d0e5e4dfa 100644 --- a/apps/app/components/account/index.ts +++ b/apps/app/components/account/index.ts @@ -2,4 +2,3 @@ export * from "./google-login"; export * from "./email-code-form"; export * from "./email-password-form"; export * from "./github-login-button"; -export * from "./email-signin-form"; diff --git a/apps/app/components/auth-screens/not-authorized-view.tsx b/apps/app/components/auth-screens/not-authorized-view.tsx index 0ebc85fb4..d76e56b61 100644 --- a/apps/app/components/auth-screens/not-authorized-view.tsx +++ b/apps/app/components/auth-screens/not-authorized-view.tsx @@ -39,7 +39,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { {user ? (

You have signed in as {user.email}.
- + Sign in {" "} with different account that has access to this page. @@ -47,7 +47,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { ) : (

You need to{" "} - + Sign in {" "} with an account that has access to this page. diff --git a/apps/app/components/breadcrumbs/index.tsx b/apps/app/components/breadcrumbs/index.tsx index 98944fe37..240faefa2 100644 --- a/apps/app/components/breadcrumbs/index.tsx +++ b/apps/app/components/breadcrumbs/index.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import Link from "next/link"; // icons import { ArrowLeftIcon } from "@heroicons/react/24/outline"; +import { Icon } from "components/ui"; type BreadcrumbsProps = { children: any; @@ -16,10 +17,13 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => {

{children}
diff --git a/apps/app/components/command-palette/change-issue-assignee.tsx b/apps/app/components/command-palette/change-issue-assignee.tsx index 09f597e2e..fd853ef0f 100644 --- a/apps/app/components/command-palette/change-issue-assignee.tsx +++ b/apps/app/components/command-palette/change-issue-assignee.tsx @@ -57,12 +57,15 @@ export const ChangeIssueAssignee: React.FC = ({ setIsPaletteOpen, issue } async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueId) return; - mutate( + mutate( ISSUE_DETAILS(issueId as string), - (prevData: IIssue) => ({ - ...prevData, - ...formData, - }), + async (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...formData, + }; + }, false ); @@ -80,7 +83,7 @@ export const ChangeIssueAssignee: React.FC = ({ setIsPaletteOpen, issue } ); const handleIssueAssignees = (assignee: string) => { - const updatedAssignees = issue.assignees ?? []; + const updatedAssignees = issue.assignees_list ?? []; if (updatedAssignees.includes(assignee)) { updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1); diff --git a/apps/app/components/command-palette/change-issue-priority.tsx b/apps/app/components/command-palette/change-issue-priority.tsx index b6eca1df8..d0e6258c5 100644 --- a/apps/app/components/command-palette/change-issue-priority.tsx +++ b/apps/app/components/command-palette/change-issue-priority.tsx @@ -27,12 +27,16 @@ export const ChangeIssuePriority: React.FC = ({ setIsPaletteOpen, issue } async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueId) return; - mutate( + mutate( ISSUE_DETAILS(issueId as string), - (prevData: IIssue) => ({ - ...prevData, - ...formData, - }), + async (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + ...formData, + }; + }, false ); diff --git a/apps/app/components/command-palette/change-issue-state.tsx b/apps/app/components/command-palette/change-issue-state.tsx index 30ab68b63..8e3255b0a 100644 --- a/apps/app/components/command-palette/change-issue-state.tsx +++ b/apps/app/components/command-palette/change-issue-state.tsx @@ -39,12 +39,15 @@ export const ChangeIssueState: React.FC = ({ setIsPaletteOpen, issue }) = async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueId) return; - mutate( + mutate( ISSUE_DETAILS(issueId as string), - (prevData: IIssue) => ({ - ...prevData, - ...formData, - }), + async (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...formData, + }; + }, false ); diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index bed84f5ad..06f4f3c2b 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -120,12 +120,17 @@ export const CommandPalette: React.FC = () => { async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueId) return; - mutate( + mutate( ISSUE_DETAILS(issueId as string), - (prevData: IIssue) => ({ - ...prevData, - ...formData, - }), + + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + ...formData, + }; + }, false ); diff --git a/apps/app/components/onboarding/invite-members.tsx b/apps/app/components/onboarding/invite-members.tsx index 4fa58db2e..e1ebdf117 100644 --- a/apps/app/components/onboarding/invite-members.tsx +++ b/apps/app/components/onboarding/invite-members.tsx @@ -45,8 +45,9 @@ export const InviteMembers: React.FC = ({ setStep, workspace }) => { >
-

Invite your team to your workspace.

+

Invite co-workers to your team

+ Email
= ({ data, gradient = false }) => (
diff --git a/apps/app/components/onboarding/user-details.tsx b/apps/app/components/onboarding/user-details.tsx index e8704df59..f4f94117d 100644 --- a/apps/app/components/onboarding/user-details.tsx +++ b/apps/app/components/onboarding/user-details.tsx @@ -66,10 +66,17 @@ export const UserDetails: React.FC = ({ user, setStep, setUserRole }) => return (
-
+
-
-
+
+

User Details

+

+ Enter your details as a first step to open your Plane account. +

+
+ +
+
First name = ({ user, setStep, setUserRole }) => error={errors.first_name} />
-
+
Last name = ({ user, setStep, setUserRole }) => />
-
+ +
What is your role?
= ({ user, setStep, setUserRole }) =>
+
>; @@ -30,6 +31,7 @@ export const Workspace: React.FC = ({ setStep, setWorkspace }) => { slug: "", company_size: null, }); + const [currentTab, setCurrentTab] = useState("create"); const { data: invitations, mutate } = useSWR(USER_WORKSPACE_INVITATIONS, () => workspaceService.userWorkspaceInvitations() @@ -64,53 +66,72 @@ export const Workspace: React.FC = ({ setStep, setWorkspace }) => { }); }; + const currentTabValue = (tab: string | null) => { + switch (tab) { + case "join": + return 0; + case "create": + return 1; + default: + return 1; + } + }; + + console.log("invitations:", invitations); + return ( -
+
{ + switch (i) { + case 0: + return setCurrentTab("join"); + case 1: + return setCurrentTab("create"); + default: + return setCurrentTab("create"); + } + }} > - - - `rounded-3xl border px-4 py-2 outline-none ${ - selected - ? "border-brand-accent bg-brand-accent text-white" - : "border-brand-base bg-brand-surface-2 hover:bg-brand-surface-1" - }` - } - > - New Workspace - - - `rounded-3xl border px-5 py-2 outline-none ${ - selected - ? "border-brand-accent bg-brand-accent text-white" - : "border-brand-base bg-brand-surface-2 hover:bg-brand-surface-1" - }` - } - > - Invited Workspace - + +
+

Workspaces

+

+ Create or join the workspace to get started with Plane. +

+
+
+ + `rounded-3xl border px-4 py-2 outline-none ${ + selected + ? "border-brand-accent bg-brand-accent text-white font-medium" + : "border-brand-base bg-brand-base hover:bg-brand-surface-2" + }` + } + > + Invited Workspace + + + `rounded-3xl border px-4 py-2 outline-none ${ + selected + ? "border-brand-accent bg-brand-accent text-white font-medium" + : "border-brand-base bg-brand-base hover:bg-brand-surface-2" + }` + } + > + New Workspace + +
- - { - setWorkspace(res); - setStep(3); - }} - defaultValues={defaultValues} - setDefaultValues={setDefaultValues} - /> - -
-
+
+
{invitations && invitations.length > 0 ? ( invitations.map((invitation) => (
@@ -129,34 +150,62 @@ export const Workspace: React.FC = ({ setStep, setWorkspace }) => { alt={invitation.workspace.name} /> ) : ( - - {invitation.workspace.name.charAt(0)} + + {getFirstCharacters(invitation.workspace.name)} )}
-
{invitation.workspace.name}
+
+ {truncateText(invitation.workspace.name, 30)} +

Invited by {invitation.workspace.owner.first_name}

- { + + {/* { + handleInvitation( + invitation, + invitationsRespond.includes(invitation.id) ? "withdraw" : "accepted" + ); + }} + type="button" + className={`${ + invitationsRespond.includes(invitation.id) + ? "bg-brand-surface-2 text-brand-secondary" + : "bg-brand-accent text-white" + } text-sm px-4 py-2 border border-brand-base rounded-3xl`} + + // className="h-4 w-4 rounded border-brand-base text-brand-accent focus:ring-brand-accent" + /> */}
@@ -167,7 +216,7 @@ export const Workspace: React.FC = ({ setStep, setWorkspace }) => {
)}
-
+
= ({ setStep, setWorkspace }) => {
+ + { + setWorkspace(res); + setStep(3); + }} + defaultValues={defaultValues} + setDefaultValues={setDefaultValues} + /> +
diff --git a/apps/app/components/project/send-project-invitation-modal.tsx b/apps/app/components/project/send-project-invitation-modal.tsx index cfc8cb99c..d21cf07f3 100644 --- a/apps/app/components/project/send-project-invitation-modal.tsx +++ b/apps/app/components/project/send-project-invitation-modal.tsx @@ -73,9 +73,12 @@ const SendProjectInvitationModal: React.FC = ({ isOpen, setIsOpen, member .inviteProject(workspaceSlug as string, projectId as string, formData) .then((response) => { setIsOpen(false); - mutate( + mutate( PROJECT_INVITATIONS, - (prevData: any[]) => [{ ...formData, ...response }, ...(prevData ?? [])], + (prevData) => { + if (!prevData) return prevData; + return [{ ...formData, ...response }, ...(prevData ?? [])]; + }, false ); setToastAlert({ diff --git a/apps/app/components/ui/icon.tsx b/apps/app/components/ui/icon.tsx new file mode 100644 index 000000000..aefc24dff --- /dev/null +++ b/apps/app/components/ui/icon.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +type Props = { + iconName: string; + className?: string; +}; + +export const Icon: React.FC = ({ iconName, className = "" }) => ( + + {iconName} + +); diff --git a/apps/app/components/ui/index.ts b/apps/app/components/ui/index.ts index 476f0af3e..6eb273c4a 100644 --- a/apps/app/components/ui/index.ts +++ b/apps/app/components/ui/index.ts @@ -25,3 +25,4 @@ export * from "./markdown-to-component"; export * from "./product-updates-modal"; export * from "./integration-and-import-export-banner"; export * from "./range-datepicker"; +export * from "./icon"; diff --git a/apps/app/components/workspace/create-workspace-form.tsx b/apps/app/components/workspace/create-workspace-form.tsx index 0b2b7d984..b3415b2c8 100644 --- a/apps/app/components/workspace/create-workspace-form.tsx +++ b/apps/app/components/workspace/create-workspace-form.tsx @@ -99,110 +99,105 @@ export const CreateWorkspaceForm: React.FC = ({ ); return ( - -
-
-
-
- Workspace name + +
+
+
+ Workspace name + + setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-")) + } + validations={{ + required: "Workspace name is required", + validate: (value) => + /^[\w\s-]*$/.test(value) || + `Name can only contain (" "), ( - ), ( _ ) & Alphanumeric characters.`, + }} + placeholder="e.g. My Workspace" + className="placeholder:text-brand-secondary" + error={errors.name} + /> +
+
+ Workspace URL +
+ + {typeof window !== "undefined" && window.location.origin}/ + - setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-")) - } + name="slug" + register={register} + className="block w-full rounded-md bg-transparent py-2 !px-0 text-sm" validations={{ - required: "Workspace name is required", - validate: (value) => - /^[\w\s-]*$/.test(value) || - `Name can only contain (" "), ( - ), ( _ ) & Alphanumeric characters.`, + required: "Workspace URL is required", }} - placeholder="e.g. My Workspace" - className="placeholder:text-brand-secondary" - error={errors.name} + onChange={(e) => + /^[a-zA-Z0-9_-]+$/.test(e.target.value) + ? setInvalidSlug(false) + : setInvalidSlug(true) + } />
-
- Workspace URL -
- - {typeof window !== "undefined" && window.location.origin}/ - - - /^[a-zA-Z0-9_-]+$/.test(e.target.value) - ? setInvalidSlug(false) - : setInvalidSlug(true) - } - /> -
- {slugError && ( - Workspace URL is already taken! - )} - {invalidSlug && ( - {`URL can only contain ( - ), ( _ ) & Alphanumeric characters.`} - )} -
-
- -
- How large is your company? -
- ( - Select company size - ) - } - input - width="w-full" - > - {COMPANY_SIZE?.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.company_size && ( - {errors.company_size.message} - )} -
-
- -
- - {isSubmitting ? "Creating..." : "Create Workspace"} - + {slugError && ( + Workspace URL is already taken! + )} + {invalidSlug && ( + {`URL can only contain ( - ), ( _ ) & Alphanumeric characters.`} + )}
+ +
+ How large is your company? +
+ ( + Select company size + ) + } + input + width="w-full" + > + {COMPANY_SIZE?.map((item) => ( + + {item.label} + + ))} + + )} + /> + {errors.company_size && ( + {errors.company_size.message} + )} +
+
+
+ +
+ + {isSubmitting ? "Creating..." : "Create Workspace"} +
); diff --git a/apps/app/components/workspace/sidebar-dropdown.tsx b/apps/app/components/workspace/sidebar-dropdown.tsx index e4d3db7c5..680b688ed 100644 --- a/apps/app/components/workspace/sidebar-dropdown.tsx +++ b/apps/app/components/workspace/sidebar-dropdown.tsx @@ -67,17 +67,19 @@ export const WorkspaceSidebarDropdown = () => { }; const handleSignOut = async () => { - router.push("/signin").then(() => { - mutateUser(); - }); - - await authenticationService.signOut().catch(() => - setToastAlert({ - type: "error", - title: "Error!", - message: "Failed to sign out. Please try again.", + await authenticationService + .signOut() + .then(() => { + mutateUser(undefined); + router.push("/"); }) - ); + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Failed to sign out. Please try again.", + }) + ); }; return ( @@ -137,8 +139,8 @@ export const WorkspaceSidebarDropdown = () => { border border-brand-base bg-brand-surface-2 shadow-lg focus:outline-none" >
-
{user?.email}
- Workspace +
{user?.email}
+ Workspace {workspaces ? (
{workspaces.length > 0 ? ( diff --git a/apps/app/components/workspace/single-invitation.tsx b/apps/app/components/workspace/single-invitation.tsx index e9092c11d..9dbdc62ce 100644 --- a/apps/app/components/workspace/single-invitation.tsx +++ b/apps/app/components/workspace/single-invitation.tsx @@ -1,4 +1,5 @@ // next +import { getFirstCharacters, truncateText } from "helpers/string.helper"; import Image from "next/image"; // react import { useState } from "react"; @@ -22,9 +23,7 @@ const SingleInvitation: React.FC = ({ <>
  • diff --git a/apps/app/helpers/string.helper.ts b/apps/app/helpers/string.helper.ts index a13f149fc..4dfc9855b 100644 --- a/apps/app/helpers/string.helper.ts +++ b/apps/app/helpers/string.helper.ts @@ -109,3 +109,12 @@ export const generateRandomColor = (string: string): string => { return randomColor; }; + +export const getFirstCharacters = (str: string) => { + const words = str.trim().split(" "); + if (words.length === 1) { + return words[0].charAt(0); + } else { + return words[0].charAt(0) + words[1].charAt(0); + } +}; diff --git a/apps/app/hooks/use-user-auth.tsx b/apps/app/hooks/use-user-auth.tsx new file mode 100644 index 000000000..9dba5ce61 --- /dev/null +++ b/apps/app/hooks/use-user-auth.tsx @@ -0,0 +1,107 @@ +import { useEffect, useState } from "react"; +// next imports +import { useRouter } from "next/router"; +// swr +import useSWR from "swr"; +// keys +import { CURRENT_USER } from "constants/fetch-keys"; +// services +import userService from "services/user.service"; +import workspaceService from "services/workspace.service"; +// types +import type { IWorkspace, ICurrentUserResponse } from "types"; + +const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "admin") => { + const router = useRouter(); + const [isRouteAccess, setIsRouteAccess] = useState(true); + + const { + data: user, + isLoading, + error, + mutate, + } = useSWR(CURRENT_USER, () => userService.currentUser()); + + useEffect(() => { + const handleWorkSpaceRedirection = async () => { + workspaceService.userWorkspaces().then(async (userWorkspaces) => { + const lastActiveWorkspace = userWorkspaces.find( + (workspace: IWorkspace) => workspace.id === user?.last_workspace_id + ); + if (lastActiveWorkspace) { + router.push(`/${lastActiveWorkspace.slug}`); + return; + } else if (userWorkspaces.length > 0) { + router.push(`/${userWorkspaces[0].slug}`); + return; + } else { + const invitations = await workspaceService.userWorkspaceInvitations(); + if (invitations.length > 0) { + router.push(`/invitations`); + return; + } else { + router.push(`/create-workspace`); + return; + } + } + }); + }; + + const handleUserRouteAuthentication = async () => { + console.log("user", user); + + if (user && user.is_active) { + if (routeAuth === "sign-in") { + if (user.is_onboarded) handleWorkSpaceRedirection(); + else { + router.push("/onboarding"); + return; + } + } else if (routeAuth === "onboarding") { + if (user.is_onboarded) handleWorkSpaceRedirection(); + else { + setIsRouteAccess(() => false); + return; + } + } else { + if (!user.is_onboarded) { + router.push("/onboarding"); + return; + } else { + setIsRouteAccess(() => false); + return; + } + } + } else { + // user is not active and we can redirect to no access page + // router.push("/no-access"); + // remove token + return; + } + }; + + if (!isLoading) { + setIsRouteAccess(() => true); + if (user) handleUserRouteAuthentication(); + else { + if (routeAuth === "sign-in") { + setIsRouteAccess(() => false); + return; + } else { + router.push("/"); + return; + } + } + } + }, [user, isLoading, routeAuth, router]); + + return { + isLoading: isRouteAccess, + user: error ? undefined : user, + mutateUser: mutate, + assignedIssuesLength: user?.assigned_issues ?? 0, + workspaceInvitesLength: user?.workspace_invites ?? 0, + }; +}; + +export default useUserAuth; diff --git a/apps/app/hooks/use-user.tsx b/apps/app/hooks/use-user.tsx index e59cb32e5..f5186a273 100644 --- a/apps/app/hooks/use-user.tsx +++ b/apps/app/hooks/use-user.tsx @@ -1,13 +1,55 @@ -import { useContext } from "react"; +import { useEffect } from "react"; +import { useRouter } from "next/router"; +import useSWR from "swr"; +// services +import userService from "services/user.service"; +// constants +import { CURRENT_USER } from "constants/fetch-keys"; +// types +import type { ICurrentUserResponse, IUser } from "types"; -// context -import { UserContext } from "contexts/user.context"; +export default function useUser({ redirectTo = "", redirectIfFound = false, options = {} } = {}) { + const router = useRouter(); + // API to fetch user information + const { data, isLoading, error, mutate } = useSWR( + CURRENT_USER, + () => userService.currentUser(), + options + ); -const useUser = () => { - // context - const contextData = useContext(UserContext); + const user = error ? undefined : data; + // console.log("useUser", user); - return { ...contextData }; -}; + useEffect(() => { + // if no redirect needed, just return (example: already on /dashboard) + // if user data not yet there (fetch in progress, logged in or not) then don't do anything yet + if (!redirectTo || !user) return; -export default useUser; + if ( + // If redirectTo is set, redirect if the user was not found. + (redirectTo && !redirectIfFound) || + // If redirectIfFound is also set, redirect if the user was found + (redirectIfFound && user) + ) { + router.push(redirectTo); + return; + // const nextLocation = router.asPath.split("?next=")[1]; + // if (nextLocation) { + // router.push(nextLocation as string); + // return; + // } else { + // router.push("/"); + // return; + // } + } + }, [user, redirectIfFound, redirectTo, router]); + + return { + user, + isUserLoading: isLoading, + mutateUser: mutate, + userError: error, + assignedIssuesLength: user?.assigned_issues ?? 0, + workspaceInvitesLength: user?.workspace_invites ?? 0, + }; +} diff --git a/apps/app/layouts/auth-layout/user-authorization-wrapper.tsx b/apps/app/layouts/auth-layout/user-authorization-wrapper.tsx index d11bb2ac1..f48403b0f 100644 --- a/apps/app/layouts/auth-layout/user-authorization-wrapper.tsx +++ b/apps/app/layouts/auth-layout/user-authorization-wrapper.tsx @@ -32,7 +32,7 @@ export const UserAuthorizationLayout: React.FC = ({ children }) => { if (error) { const redirectTo = router.asPath; - router.push(`/signin?next=${redirectTo}`); + router.push(`/?next=${redirectTo}`); return null; } diff --git a/apps/app/lib/auth.ts b/apps/app/lib/auth.ts index e607fd00c..47a52663d 100644 --- a/apps/app/lib/auth.ts +++ b/apps/app/lib/auth.ts @@ -104,7 +104,7 @@ export const homePageRedirect = async (cookie?: string) => { if (!user) return { redirect: { - destination: "/signin", + destination: "/", permanent: false, }, }; diff --git a/apps/app/package.json b/apps/app/package.json index af90cf99a..01287f068 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -48,7 +48,7 @@ "react-markdown": "^8.0.7", "recharts": "^2.3.2", "remirror": "^2.0.23", - "swr": "^1.3.0", + "swr": "^2.1.3", "tlds": "^1.238.0", "uuid": "^9.0.0" }, diff --git a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx index 0d1782563..c4ab6918f 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx @@ -8,7 +8,7 @@ import { Controller, useForm } from "react-hook-form"; import fileService from "services/file.service"; import userService from "services/user.service"; // hooks -import useUser from "hooks/use-user"; +import useUserAuth from "hooks/use-user-auth"; import useToast from "hooks/use-toast"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; @@ -50,7 +50,7 @@ const Profile: NextPage = () => { } = useForm({ defaultValues }); const { setToastAlert } = useToast(); - const { user: myProfile, mutateUser } = useUser(); + const { user: myProfile, mutateUser } = useUserAuth(); useEffect(() => { reset({ ...defaultValues, ...myProfile }); diff --git a/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx b/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx index 6b48d9aa3..f2fe98f80 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { useTheme } from "next-themes"; // hooks -import useUser from "hooks/use-user"; +import useUserAuth from "hooks/use-user-auth"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import SettingsNavbar from "layouts/settings-navbar"; @@ -15,7 +15,7 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { ICustomTheme } from "types"; const ProfilePreferences = () => { - const { user: myProfile } = useUser(); + const { user: myProfile } = useUserAuth(); const { theme } = useTheme(); const [customThemeSelectorOptions, setCustomThemeSelectorOptions] = useState(false); const [preLoadedData, setPreLoadedData] = useState(null); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 15237617e..29eaee83c 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -78,12 +78,15 @@ const IssueDetailsPage: NextPage = () => { async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueId) return; - mutate( + mutate( ISSUE_DETAILS(issueId as string), - (prevData: IIssue) => ({ - ...prevData, - ...formData, - }), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...formData, + }; + }, false ); diff --git a/apps/app/pages/_app.tsx b/apps/app/pages/_app.tsx index 61788592a..f1d16cfd1 100644 --- a/apps/app/pages/_app.tsx +++ b/apps/app/pages/_app.tsx @@ -44,31 +44,14 @@ Router.events.on("routeChangeComplete", NProgress.done); function MyApp({ Component, pageProps }: AppProps) { return ( - - - - - {" "} - - {SITE_TITLE} - - - - - - - - - - - - - - - - - - + // + + + + + + + // ); } diff --git a/apps/app/pages/_error.tsx b/apps/app/pages/_error.tsx index 0d1605484..8482cbc93 100644 --- a/apps/app/pages/_error.tsx +++ b/apps/app/pages/_error.tsx @@ -26,7 +26,7 @@ const CustomErrorComponent = () => { message: "Failed to sign out. Please try again.", }) ) - .finally(() => router.push("/signin")); + .finally(() => router.push("/")); }; return ( diff --git a/apps/app/pages/create-workspace.tsx b/apps/app/pages/create-workspace.tsx index d445e873e..389cdd7ec 100644 --- a/apps/app/pages/create-workspace.tsx +++ b/apps/app/pages/create-workspace.tsx @@ -2,7 +2,10 @@ import React from "react"; import { useRouter } from "next/router"; import Image from "next/image"; - +// hooks +import useUser from "hooks/use-user"; +// components +import { OnboardingLogo } from "components/onboarding"; // layouts import DefaultLayout from "layouts/default-layout"; import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; @@ -21,19 +24,36 @@ const CreateWorkspace: NextPage = () => { company_size: null, }; + const { user } = useUser(); return ( -
    -
    -
    - Plane Logo +
    +
    +
    +
    - {}} - onSubmit={(res) => router.push(`/${res.slug}`)} - /> + +
    +
    +
    +

    Create Workspace

    +

    + Create or join the workspace to get started with Plane. +

    +
    +
    + {}} + onSubmit={(res) => router.push(`/${res.slug}`)} + /> +
    +
    + +
    + Logged in: + {user?.email}
    diff --git a/apps/app/pages/index.tsx b/apps/app/pages/index.tsx index bd21abfc4..b032fb203 100644 --- a/apps/app/pages/index.tsx +++ b/apps/app/pages/index.tsx @@ -1,66 +1,116 @@ -import { useEffect } from "react"; - -import Router from "next/router"; - +import React from "react"; +// next imports +import Image from "next/image"; +// next types +import type { NextPage } from "next"; +// layouts +import DefaultLayout from "layouts/default-layout"; +// hooks +import useUserAuth from "hooks/use-user-auth"; +import useToast from "hooks/use-toast"; // services -import userService from "services/user.service"; -import workspaceService from "services/workspace.service"; +import authenticationService from "services/authentication.service"; +// social auth buttons +import { + GoogleLoginButton, + GithubLoginButton, + EmailCodeForm, + EmailPasswordForm, +} from "components/account"; // ui import { Spinner } from "components/ui"; -// types -import type { NextPage } from "next"; +// icons +import Logo from "public/logo.png"; -const redirectUserTo = async () => { - const user = await userService - .currentUser() - .then((res) => res) - .catch(() => { - Router.push("/signin"); - return; - }); +const HomePage: NextPage = () => { + const { user, isLoading, mutateUser } = useUserAuth("sign-in"); - if (!user) { - Router.push("/signin"); - return; - } else if (!user.user.is_onboarded) { - Router.push("/onboarding"); - return; - } else { - const userWorkspaces = await workspaceService.userWorkspaces(); + const { setToastAlert } = useToast(); - const lastActiveWorkspace = userWorkspaces.find( - (workspace) => workspace.id === user.user.last_workspace_id - ); - - if (lastActiveWorkspace) { - Router.push(`/${lastActiveWorkspace.slug}`); - return; - } else if (userWorkspaces.length > 0) { - Router.push(`/${userWorkspaces[0].slug}`); - return; - } else { - const invitations = await workspaceService.userWorkspaceInvitations(); - if (invitations.length > 0) { - Router.push(`/invitations`); - return; + const handleGoogleSignIn = async ({ clientId, credential }: any) => { + try { + if (clientId && credential) { + mutateUser( + await authenticationService.socialAuth({ + medium: "google", + credential, + clientId, + }) + ); } else { - Router.push(`/create-workspace`); - return; + throw Error("Cant find credentials"); } + } catch (error) { + console.log(error); + setToastAlert({ + title: "Error signing in!", + type: "error", + message: "Something went wrong. Please try again later or contact the support team.", + }); } - } -}; + }; -const Home: NextPage = () => { - useEffect(() => { - redirectUserTo(); - }, []); + const handleGithubSignIn = async (credential: string) => { + try { + if (process.env.NEXT_PUBLIC_GITHUB_ID && credential) { + mutateUser( + await authenticationService.socialAuth({ + medium: "github", + credential, + clientId: process.env.NEXT_PUBLIC_GITHUB_ID, + }) + ); + } else { + throw Error("Cant find credentials"); + } + } catch (error) { + console.log(error); + setToastAlert({ + title: "Error signing in!", + type: "error", + message: "Something went wrong. Please try again later or contact the support team.", + }); + } + }; return ( -
    - -
    + + {isLoading ? ( +
    + +
    + ) : ( +
    +
    +
    +
    + Plane Web Logo +
    + Sign In to your Plane Account +
    +
    + +
    + {parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0") ? ( + <> + +
    + + +
    + + ) : ( + <> + + + )} +
    +
    +
    +
    + )} +
    ); }; -export default Home; +export default HomePage; diff --git a/apps/app/pages/invitations.tsx b/apps/app/pages/invitations.tsx index 21f6e25a7..107af4051 100644 --- a/apps/app/pages/invitations.tsx +++ b/apps/app/pages/invitations.tsx @@ -15,6 +15,7 @@ import DefaultLayout from "layouts/default-layout"; import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; // components import SingleInvitation from "components/workspace/single-invitation"; +import { OnboardingLogo } from "components/onboarding"; // ui import { Spinner, EmptySpace, EmptySpaceItem, SecondaryButton, PrimaryButton } from "components/ui"; // icons @@ -81,86 +82,93 @@ const OnBoard: NextPage = () => { return ( -
    - {user && ( -
    -

    logged in as {user.email}

    +
    +
    +
    +
    - )} -
    - {invitations && workspaces ? ( - invitations.length > 0 ? ( -
    -

    Workspace Invitations

    -

    - Select invites that you want to accept. -

    -
      - {invitations.map((invitation) => ( - - ))} -
    -
    - - - Go Home - - - - Accept and Continue - +
    + {invitations && workspaces ? ( + invitations.length > 0 ? ( +
    +
    +

    + Workspace Invitations +

    +

    + Create or join the workspace to get started with Plane. +

    +
    + +
      + {invitations.map((invitation) => ( + + ))} +
    + +
    + + + Go Home + + + + Accept and Continue + +
    -
    - ) : workspaces && workspaces.length > 0 ? ( -
    -

    Your workspaces

    - {workspaces.map((workspace) => ( - - - + ) : ( + invitations.length === 0 && + workspaces.length === 0 && ( + + { + router.push("/create-workspace"); + }} + /> + + ) ) - ) - ) : ( -
    - -
    - )} + ) : ( +
    + +
    + )} +
    +
    +
    + Logged in: + {user?.email}
    diff --git a/apps/app/pages/onboarding.tsx b/apps/app/pages/onboarding.tsx index 3891dba50..9d24a7664 100644 --- a/apps/app/pages/onboarding.tsx +++ b/apps/app/pages/onboarding.tsx @@ -1,14 +1,15 @@ import { useState } from "react"; import Image from "next/image"; -import { useRouter } from "next/router"; +import Router, { useRouter } from "next/router"; import { mutate } from "swr"; // services import userService from "services/user.service"; +import workspaceService from "services/workspace.service"; // hooks -import useUser from "hooks/use-user"; +import useUserAuth from "hooks/use-user-auth"; // layouts import DefaultLayout from "layouts/default-layout"; import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; @@ -38,15 +39,17 @@ const Onboarding: NextPage = () => { const router = useRouter(); - const { user } = useUser(); + const { user } = useUserAuth("onboarding"); + + console.log("user", user); return ( -
    +
    {step <= 3 ? ( -
    -
    +
    +
    {step === 1 ? ( @@ -59,7 +62,7 @@ const Onboarding: NextPage = () => {
    ) : (
    -
    +
    {step === 4 ? ( ) : step === 5 ? ( @@ -80,7 +83,7 @@ const Onboarding: NextPage = () => { if (step === 8) { userService .updateUserOnBoard({ userRole }) - .then(() => { + .then(async () => { mutate( CURRENT_USER, (prevData) => { @@ -96,7 +99,28 @@ const Onboarding: NextPage = () => { }, false ); - router.push("/"); + const userWorkspaces = await workspaceService.userWorkspaces(); + + const lastActiveWorkspace = userWorkspaces.find( + (workspace) => workspace.id === user?.last_workspace_id + ); + + if (lastActiveWorkspace) { + Router.push(`/${lastActiveWorkspace.slug}`); + return; + } else if (userWorkspaces.length > 0) { + Router.push(`/${userWorkspaces[0].slug}`); + return; + } else { + const invitations = await workspaceService.userWorkspaceInvitations(); + if (invitations.length > 0) { + Router.push(`/invitations`); + return; + } else { + Router.push(`/create-workspace`); + return; + } + } }) .catch((err) => { console.log(err); @@ -110,6 +134,10 @@ const Onboarding: NextPage = () => {
    )} +
    + Logged in: + {user?.email} +
    diff --git a/apps/app/pages/signin.tsx b/apps/app/pages/signin.tsx deleted file mode 100644 index 26d6832f1..000000000 --- a/apps/app/pages/signin.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useCallback, useState } from "react"; -import { useRouter } from "next/router"; -import Image from "next/image"; -// hooks -import useUser from "hooks/use-user"; -import useToast from "hooks/use-toast"; -// services -import authenticationService from "services/authentication.service"; -// layouts -import DefaultLayout from "layouts/default-layout"; -// social button -import { - GoogleLoginButton, - GithubLoginButton, - EmailSignInForm, - EmailPasswordForm, -} from "components/account"; -// ui -import { Spinner } from "components/ui"; -// icons -import Logo from "public/logo.png"; -// types -import type { NextPage } from "next"; - -const SignInPage: NextPage = () => { - // router - const router = useRouter(); - // user hook - const { mutateUser } = useUser(); - // states - const [isLoading, setLoading] = useState(false); - - const { setToastAlert } = useToast(); - - const onSignInSuccess = useCallback(async () => { - setLoading(true); - await mutateUser(); - const nextLocation = router.asPath.split("?next=")[1]; - if (nextLocation) await router.push(nextLocation as string); - else await router.push("/"); - }, [mutateUser, router]); - - const handleGoogleSignIn = ({ clientId, credential }: any) => { - if (clientId && credential) { - setLoading(true); - authenticationService - .socialAuth({ - medium: "google", - credential, - clientId, - }) - .then(async () => { - await onSignInSuccess(); - }) - .catch((err) => { - console.log(err); - setToastAlert({ - title: "Error signing in!", - type: "error", - message: "Something went wrong. Please try again later or contact the support team.", - }); - setLoading(false); - }); - } - }; - - const handleGithubSignIn = useCallback( - (credential: string) => { - setLoading(true); - authenticationService - .socialAuth({ - medium: "github", - credential, - clientId: process.env.NEXT_PUBLIC_GITHUB_ID, - }) - .then(async () => { - await onSignInSuccess(); - }) - .catch((err) => { - console.log(err); - setToastAlert({ - title: "Error signing in!", - type: "error", - message: "Something went wrong. Please try again later or contact the support team.", - }); - setLoading(false); - }); - }, - [onSignInSuccess, setToastAlert] - ); - - return ( - - {isLoading ? ( -
    -

    Signing in. Please wait...

    - -
    - ) : ( -
    -
    -
    -
    - Plane Web Logo -

    - Sign In to your Plane Account -

    -
    - -
    - {parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0") ? ( - <> - - -
    - - - -
    - - ) : ( - <> - - - )} -
    -
    -
    -
    - )} -
    - ); -}; - -export default SignInPage; diff --git a/apps/app/pages/workspace-member-invitation/[invitationId].tsx b/apps/app/pages/workspace-member-invitation/[invitationId].tsx index 4eb45f529..cc4c5ec6d 100644 --- a/apps/app/pages/workspace-member-invitation/[invitationId].tsx +++ b/apps/app/pages/workspace-member-invitation/[invitationId].tsx @@ -49,7 +49,7 @@ const WorkspaceInvitation: NextPage = () => { if (email === user?.email) { router.push("/invitations"); } else { - router.push("/signin"); + router.push("/"); } }) .catch((err) => console.error(err)); @@ -108,7 +108,7 @@ const WorkspaceInvitation: NextPage = () => { Icon={UserIcon} title="Sign in to continue" action={() => { - router.push("/signin"); + router.push("/"); }} /> ) : ( diff --git a/apps/app/services/api.service.ts b/apps/app/services/api.service.ts index 5434742ba..296b693aa 100644 --- a/apps/app/services/api.service.ts +++ b/apps/app/services/api.service.ts @@ -9,7 +9,8 @@ axios.interceptors.response.use( if (unAuthorizedStatus.includes(status)) { Cookies.remove("refreshToken", { path: "/" }); Cookies.remove("accessToken", { path: "/" }); - if (window.location.pathname != "/signin") window.location.href = "/signin"; + if (window.location.pathname != "/") + window.location.href = "/?next_url=window.location.pathname"; } return Promise.reject(error); } diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index 2791b4c42..22d060c86 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -17,9 +17,9 @@ } :root { - --color-bg-base: 243, 244, 246; + --color-bg-base: 255, 255, 255; --color-bg-surface-1: 249, 250, 251; - --color-bg-surface-2: 255, 255, 255; + --color-bg-surface-2: 243, 244, 246; --color-border: 229, 231, 235; --color-bg-sidebar: 255, 255, 255; diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index eb7f5c742..eaa75b75a 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -41,10 +41,11 @@ export interface ICustomTheme { textSecondary: string; } -export interface ICurrentUserResponse { +export interface ICurrentUserResponse extends IUser { assigned_issues: number; - user: IUser; + // user: IUser; workspace_invites: number; + is_onboarded: boolean; } export interface IUserLite { diff --git a/yarn.lock b/yarn.lock index d8a63fe19..5fb2c1578 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8212,10 +8212,12 @@ svgmoji@^3.2.0: "@svgmoji/openmoji" "^3.2.0" "@svgmoji/twemoji" "^3.2.0" -swr@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" - integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== +swr@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.1.3.tgz#ae3608d4dc19f75121e0b51d1982735fbf02f52e" + integrity sha512-g3ApxIM4Fjbd6vvEAlW60hJlKcYxHb+wtehogTygrh6Jsw7wNagv9m4Oj5Gq6zvvZw0tcyhVGL9L0oISvl3sUw== + dependencies: + use-sync-external-store "^1.2.0" table@^6.0.9: version "6.8.1" @@ -8680,7 +8682,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==