chore: onboarding ui updates and accept invitation workflow updates.

This commit is contained in:
Prateek Shourya 2024-05-01 12:06:51 +05:30
parent 43ce850ae9
commit 4330f0f0c9
13 changed files with 251 additions and 254 deletions

View File

@ -58,7 +58,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
}, [csrfToken]);
const redirectToUniqueCodeLogin = async () => {
const redirectToUniqueCodeSignIn = async () => {
handleStepChange(EAuthSteps.UNIQUE_CODE);
};
@ -194,7 +194,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
{instance && isSmtpConfigured && (
<Button
type="button"
onClick={redirectToUniqueCodeLogin}
onClick={redirectToUniqueCodeSignIn}
variant="outline-primary"
className="w-full"
size="lg"

View File

@ -39,19 +39,19 @@ type Props = {
mode: EAuthModes;
};
const Titles = {
const titles = {
[EAuthModes.SIGN_IN]: {
[EAuthSteps.EMAIL]: {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
subHeader: "Get back to your projects and make progress.",
},
[EAuthSteps.PASSWORD]: {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
subHeader: "Get back to your projects and make progress.",
},
[EAuthSteps.UNIQUE_CODE]: {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
subHeader: "Get back to your projects and make progress.",
},
[EAuthSteps.OPTIONAL_SET_PASSWORD]: {
header: "",
@ -61,7 +61,7 @@ const Titles = {
[EAuthModes.SIGN_UP]: {
[EAuthSteps.EMAIL]: {
header: "Create your account",
subHeader: "Start tracking your projects with Plane",
subHeader: "Start tracking your projects with Plane.",
},
[EAuthSteps.PASSWORD]: {
header: "Create your account",
@ -98,7 +98,7 @@ const getHeaderSubHeader = (
};
}
return Titles[mode][step];
return titles[mode][step];
};
export const AuthRoot = observer((props: Props) => {

View File

@ -72,10 +72,10 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
);
};
const handleRequestNewCode = async () => {
const handleRequestNewCode = async (email: string) => {
setIsRequestingNewCode(true);
await handleSendNewCode(uniqueCodeFormData.email)
await handleSendNewCode(email)
.then(() => setResendCodeTimer(30))
.finally(() => setIsRequestingNewCode(false));
};
@ -86,10 +86,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
}, [csrfToken]);
useEffect(() => {
setIsRequestingNewCode(true);
handleSendNewCode(email)
.then(() => setResendCodeTimer(30))
.finally(() => setIsRequestingNewCode(false));
handleRequestNewCode(email);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -149,7 +146,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
</p>
<button
type="button"
onClick={handleRequestNewCode}
onClick={() => handleRequestNewCode(uniqueCodeFormData.email)}
className={`${
isRequestNewCodeDisabled
? "text-onboarding-text-400"
@ -165,7 +162,14 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
</button>
</div>
</div>
<Button type="submit" variant="primary" className="w-full" size="lg" loading={isRequestingNewCode}>
<Button
type="submit"
variant="primary"
className="w-full"
size="lg"
loading={isRequestingNewCode}
disabled={isRequestingNewCode || !uniqueCodeFormData.code}
>
{isRequestingNewCode ? "Sending code" : submitButtonText}
</Button>
</form>

View File

@ -24,10 +24,11 @@ type Props = {
invitations: IWorkspaceMemberInvitation[];
totalSteps: number;
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
finishOnboarding: () => Promise<void>;
};
export const CreateOrJoinWorkspaces: React.FC<Props> = observer((props) => {
const { invitations, totalSteps, stepChange } = props;
const { invitations, totalSteps, stepChange, finishOnboarding } = props;
// states
const [currentView, setCurrentView] = useState<ECreateOrJoinWorkspaceViews | null>(null);
// store hooks
@ -45,14 +46,15 @@ export const CreateOrJoinWorkspaces: React.FC<Props> = observer((props) => {
const handleNextStep = async () => {
if (!user) return;
await stepChange({ workspace_join: true, workspace_create: true });
await finishOnboarding();
};
return (
<div className="flex h-full w-full">
<div className="w-full h-full overflow-auto px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
<div className="flex items-center justify-between">
<OnboardingHeader currentStep={2} totalSteps={totalSteps} />
<OnboardingHeader currentStep={totalSteps - 1} totalSteps={totalSteps} />
<div className="shrink-0 lg:hidden">
<SwitchOrDeleteAccountDropdown />
</div>

View File

@ -1,25 +1,23 @@
import React, { useState } from "react";
import useSWR, { mutate } from "swr";
// icons
import { CheckCircle2 } from "lucide-react";
import useSWR from "swr";;
// types
import { IWorkspaceMemberInvitation } from "@plane/types";
// ui
import { Button } from "@plane/ui";
import { Button, Checkbox } from "@plane/ui";
// constants
import { MEMBER_ACCEPTED } from "@/constants/event-tracker";
import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "@/constants/fetch-keys";
import { USER_WORKSPACE_INVITATIONS } from "@/constants/fetch-keys";
import { ROLE } from "@/constants/workspace";
// helpers
import { truncateText } from "@/helpers/string.helper";
import { getUserRole } from "@/helpers/user.helper";
// hooks
import { useEventTracker, useUser, useWorkspace } from "@/hooks/store";
import { useEventTracker, useWorkspace } from "@/hooks/store";
// services
import { WorkspaceService } from "@/services/workspace.service";
type Props = {
handleNextStep: () => void;
handleNextStep: () => Promise<void>;
handleCurrentViewChange: () => void;
};
const workspaceService = new WorkspaceService();
@ -31,14 +29,9 @@ export const Invitations: React.FC<Props> = (props) => {
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
// store hooks
const { captureEvent } = useEventTracker();
const { updateCurrentUser } = useUser();
const { workspaces, fetchWorkspaces } = useWorkspace();
const { fetchWorkspaces } = useWorkspace();
const workspacesList = Object.values(workspaces);
const { data: invitations, mutate: mutateInvitations } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
workspaceService.userWorkspaceInvitations()
);
const { data: invitations } = useSWR(USER_WORKSPACE_INVITATIONS, () => workspaceService.userWorkspaceInvitations());
const handleInvitation = (workspace_invitation: IWorkspaceMemberInvitation, action: "accepted" | "withdraw") => {
if (action === "accepted") {
@ -48,13 +41,6 @@ export const Invitations: React.FC<Props> = (props) => {
}
};
const updateLastWorkspace = async () => {
if (!workspacesList) return;
await updateCurrentUser({
last_workspace_id: workspacesList[0]?.id,
});
};
const submitInvitations = async () => {
const invitation = invitations?.find((invitation) => invitation.id === invitationsRespond[0]);
@ -62,42 +48,37 @@ export const Invitations: React.FC<Props> = (props) => {
setIsJoiningWorkspaces(true);
await workspaceService
.joinWorkspaces({ invitations: invitationsRespond })
.then(async () => {
captureEvent(MEMBER_ACCEPTED, {
member_id: invitation?.id,
role: getUserRole(invitation?.role as any),
project_id: undefined,
accepted_from: "App",
state: "SUCCESS",
element: "Workspace invitations page",
});
await fetchWorkspaces();
await mutate(USER_WORKSPACES);
await updateLastWorkspace();
await handleNextStep();
await mutateInvitations();
})
.catch((error) => {
console.error(error);
captureEvent(MEMBER_ACCEPTED, {
member_id: invitation?.id,
role: getUserRole(invitation?.role as any),
project_id: undefined,
accepted_from: "App",
state: "FAILED",
element: "Workspace invitations page",
});
})
.finally(() => setIsJoiningWorkspaces(false));
try {
await workspaceService.joinWorkspaces({ invitations: invitationsRespond });
captureEvent(MEMBER_ACCEPTED, {
member_id: invitation?.id,
role: getUserRole(invitation?.role as any),
project_id: undefined,
accepted_from: "App",
state: "SUCCESS",
element: "Workspace invitations page",
});
await fetchWorkspaces();
await handleNextStep();
} catch (error) {
console.error(error);
captureEvent(MEMBER_ACCEPTED, {
member_id: invitation?.id,
role: getUserRole(invitation?.role as any),
project_id: undefined,
accepted_from: "App",
state: "FAILED",
element: "Workspace invitations page",
});
setIsJoiningWorkspaces(false);
}
};
return invitations && invitations.length > 0 ? (
<div className="space-y-4">
<div className="text-center space-y-1 py-4 mx-auto">
<h3 className="text-3xl font-bold text-onboarding-text-100">You are invited!</h3>
<p className="font-medium text-onboarding-text-400">Accept the invites to collaborate with your team!</p>
<p className="font-medium text-onboarding-text-400">Accept the invites to collaborate with your team.</p>
</div>
<div>
{invitations &&
@ -108,11 +89,7 @@ export const Invitations: React.FC<Props> = (props) => {
return (
<div
key={invitation.id}
className={`flex cursor-pointer items-center gap-2 rounded border p-3.5 ${
isSelected
? "border-custom-primary-100"
: "border-onboarding-border-200 hover:bg-onboarding-background-300/30"
}`}
className={`flex cursor-pointer items-center gap-2 rounded border p-3.5 border-custom-border-200 hover:bg-onboarding-background-300/30`}
onClick={() => handleInvitation(invitation, isSelected ? "withdraw" : "accepted")}
>
<div className="flex-shrink-0">
@ -136,23 +113,35 @@ export const Invitations: React.FC<Props> = (props) => {
<div className="text-sm font-medium">{truncateText(invitedWorkspace?.name, 30)}</div>
<p className="text-xs text-custom-text-200">{ROLE[invitation.role]}</p>
</div>
<span className={`flex-shrink-0 ${isSelected ? "text-custom-primary-100" : "text-custom-text-200"}`}>
<CheckCircle2 className="h-5 w-5" />
<span className={`flex-shrink-0`}>
<Checkbox checked={isSelected} />
</span>
</div>
);
})}
</div>
<Button variant="primary" size="lg" className="w-full" onClick={submitInvitations}>
{isJoiningWorkspaces ? "Joining..." : "Continue"}
<Button
variant="primary"
size="lg"
className="w-full"
onClick={submitInvitations}
disabled={isJoiningWorkspaces || !invitationsRespond.length}
>
Continue to workspace
</Button>
<div className="mx-auto mt-4 flex items-center sm:w-96">
<hr className="w-full border-onboarding-border-100" />
<p className="mx-3 flex-shrink-0 text-center text-sm text-onboarding-text-400">or</p>
<hr className="w-full border-onboarding-border-100" />
</div>
<Button variant="link-neutral" size="lg" className="w-full text-base bg-custom-background-90" onClick={handleCurrentViewChange}>
Create my own workspace
<Button
variant="link-neutral"
size="lg"
className="w-full text-base bg-custom-background-90"
onClick={handleCurrentViewChange}
disabled={isJoiningWorkspaces}
>
Create your own workspace
</Button>
</div>
) : (

View File

@ -16,12 +16,12 @@ import {
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
import { Listbox, Transition } from "@headlessui/react";
// types
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
import { IUser, IWorkspace } from "@plane/types";
// ui
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// constants
import { MEMBER_INVITED } from "@/constants/event-tracker";
import { EUserWorkspaceRoles, ROLE } from "@/constants/workspace";
import { EUserWorkspaceRoles, ROLE, ROLE_DETAILS } from "@/constants/workspace";
// helpers
import { getUserRole } from "@/helpers/user.helper";
// hooks
@ -39,7 +39,6 @@ import { SwitchOrDeleteAccountDropdown } from "./switch-or-delete-account-dropdo
type Props = {
finishOnboarding: () => Promise<void>;
totalSteps: number;
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
user: IUser | undefined;
workspace: IWorkspace | undefined;
};
@ -215,10 +214,10 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
>
<Listbox.Options
ref={dropdownRef}
className="fixed z-10 mt-1 max-h-48 w-48 overflow-y-auto rounded-md border border-onboarding-border-100 bg-onboarding-background-200 text-xs shadow-lg focus:outline-none"
className="fixed z-10 mt-1 h-fit w-48 sm:w-60 overflow-y-auto rounded-md border border-onboarding-border-100 bg-onboarding-background-200 shadow-sm focus:outline-none"
>
<div className="space-y-1 p-2">
{Object.entries(ROLE).map(([key, value]) => (
{Object.entries(ROLE_DETAILS).map(([key, value]) => (
<Listbox.Option
key={key}
value={parseInt(key)}
@ -229,9 +228,12 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
}
>
{({ selected }) => (
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">{value}</div>
{selected && <Check className="h-4 w-4 flex-shrink-0" />}
<div className="flex items-center text-wrap gap-2 p-1">
<div className="flex flex-col">
<div className="text-sm font-medium">{value.title}</div>
<div className="flex text-xs text-custom-text-300">{value.description}</div>
</div>
{selected && <Check className="h-4 w-4 shrink-0" />}
</div>
)}
</Listbox.Option>
@ -264,7 +266,7 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
};
export const InviteMembers: React.FC<Props> = (props) => {
const { finishOnboarding, totalSteps, stepChange, workspace } = props;
const { finishOnboarding, totalSteps, workspace } = props;
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
@ -287,11 +289,6 @@ export const InviteMembers: React.FC<Props> = (props) => {
});
const nextStep = async () => {
const payload: Partial<TOnboardingSteps> = {
workspace_invite: true,
};
await stepChange(payload);
await finishOnboarding();
};
@ -371,11 +368,11 @@ export const InviteMembers: React.FC<Props> = (props) => {
<SwitchOrDeleteAccountDropdown />
</div>
</div>
<div className="flex flex-col w-full items-center justify-center p-8 mt-6">
<div className="flex flex-col w-full items-center justify-center p-8 mt-6 md:w-4/5 mx-auto">
<div className="text-center space-y-1 py-4 mx-auto w-4/5">
<h3 className="text-3xl font-bold text-onboarding-text-100">Invite your teammates</h3>
<p className="font-medium text-onboarding-text-400">
Work in plane happens best with your team. Invite them now to use Plane to its potential.
Work in plane happens best with your team. Invite them now to use Plane to its potential.
</p>
</div>
<form
@ -410,14 +407,14 @@ export const InviteMembers: React.FC<Props> = (props) => {
</div>
<button
type="button"
className="flex items-center mx-8 gap-1.5 bg-transparent text-sm font-semibold text-custom-primary-100 outline-custom-primary-100"
className="flex items-center mx-8 gap-1.5 bg-transparent text-sm font-medium text-custom-primary-100 outline-custom-primary-100"
onClick={appendField}
>
<Plus className="h-4 w-4 mb-0.5" strokeWidth={2.5} />
<Plus className="h-4 w-4" strokeWidth={2} />
Add another
</button>
</div>
<div className="flex flex-col mx-auto items-center justify-center gap-4 sm:w-96">
<div className="flex flex-col mx-auto px-8 sm:px-2 items-center justify-center gap-4 w-96">
<Button
variant="primary"
type="submit"

View File

@ -131,22 +131,21 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
await Promise.all([
updateCurrentUser(userDetailsPayload),
updateUserProfile(profileUpdatePayload),
stepChange({ profile_complete: true }),
]).then(() => {
captureEvent(USER_DETAILS, {
state: "SUCCESS",
element: "Onboarding",
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
message: "Profile setup completed!",
});
// For Invited Users, they will skip all other steps and finish onboarding.
if (totalSteps <= 2) {
finishOnboarding();
}
totalSteps > 2 && stepChange({ profile_complete: true }),
]);
captureEvent(USER_DETAILS, {
state: "SUCCESS",
element: "Onboarding",
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
message: "Profile setup completed!",
});
// For Invited Users, they will skip all other steps and finish onboarding.
if (totalSteps <= 2) {
finishOnboarding();
}
} catch {
captureEvent(USER_DETAILS, {
state: "FAILED",
@ -169,7 +168,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
try {
await Promise.all([
updateCurrentUser(userDetailsPayload),
formData.password ? handleSetPassword(formData.password) : Promise.resolve(),
formData.password && handleSetPassword(formData.password),
]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION));
} catch {
captureEvent(USER_DETAILS, {
@ -190,21 +189,23 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
role: formData.role,
};
try {
await Promise.all([updateUserProfile(profileUpdatePayload), stepChange({ profile_complete: true })]).then(() => {
captureEvent(USER_DETAILS, {
state: "SUCCESS",
element: "Onboarding",
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
message: "Profile setup completed!",
});
// For Invited Users, they will skip all other steps and finish onboarding.
if (totalSteps <= 2) {
finishOnboarding();
}
await Promise.all([
updateUserProfile(profileUpdatePayload),
totalSteps > 2 && stepChange({ profile_complete: true }),
]);
captureEvent(USER_DETAILS, {
state: "SUCCESS",
element: "Onboarding",
});
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
message: "Profile setup completed!",
});
// For Invited Users, they will skip all other steps and finish onboarding.
if (totalSteps <= 2) {
finishOnboarding();
}
} catch {
captureEvent(USER_DETAILS, {
state: "FAILED",

View File

@ -1,5 +1,5 @@
import { objToQueryParams } from "@/helpers/string.helper";
import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "@plane/types";
import { objToQueryParams } from "@/helpers/string.helper";
const paramsToKey = (params: any) => {
const {
@ -76,7 +76,7 @@ const myIssuesParamsToKey = (params: any) => {
export const CURRENT_USER = "CURRENT_USER";
export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS";
export const USER_WORKSPACES = "USER_WORKSPACES";
export const USER_WORKSPACES_LIST = "USER_WORKSPACES_LIST";
export const WORKSPACE_DETAILS = (workspaceSlug: string) => `WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`;

View File

@ -1,14 +1,14 @@
// services images
// types
import { TStaticViewTypes } from "@plane/types";
// icons
import { SettingIcon } from "@/components/icons/attachment";
import { Props } from "@/components/icons/types";
// services images
import CSVLogo from "public/services/csv.svg";
import ExcelLogo from "public/services/excel.svg";
import GithubLogo from "public/services/github.png";
import JiraLogo from "public/services/jira.svg";
import JSONLogo from "public/services/json.svg";
// types
import { TStaticViewTypes } from "@plane/types";
// icons
export enum EUserWorkspaceRoles {
GUEST = 5,
@ -24,6 +24,25 @@ export const ROLE = {
20: "Admin",
};
export const ROLE_DETAILS = {
5: {
title: "Guest",
description: "External members of organizations can be invited as guests.",
},
10: {
title: "Viewer",
description: "External members of organizations can be invited as guests.",
},
15: {
title: "Member",
description: "Ability to read, write, edit, and delete entities inside projects, cycles, and modules",
},
20: {
title: "Admin",
description: "All permissions set to true within the workspace.",
},
};
export const ORGANIZATION_SIZE = ["Just myself", "2-10", "11-50", "51-200", "201-500", "500+"];
export const USER_ROLES = [

View File

@ -6,6 +6,7 @@ import useSWR from "swr";
// ui
import { Spinner } from "@plane/ui";
// hooks
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
import { useUser, useUserProfile, useWorkspace } from "@/hooks/store";
import { useCurrentUserSettings } from "@/hooks/store/use-current-user-settings";
@ -34,7 +35,7 @@ export const UserAuthWrapper: FC<IUserAuthWrapper> = observer((props) => {
shouldRetryOnError: false,
});
// fetching all workspaces
const { isLoading: workspaceLoader } = useSWR("USER_WORKSPACES_LIST", () => fetchWorkspaces(), {
const { isLoading: workspaceLoader } = useSWR(USER_WORKSPACES_LIST, () => fetchWorkspaces(), {
shouldRetryOnError: false,
});

View File

@ -16,6 +16,7 @@ import { EmptyState } from "@/components/common";
import { PageHead } from "@/components/core";
// constants
import { MEMBER_ACCEPTED } from "@/constants/event-tracker";
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
import { ROLE } from "@/constants/workspace";
// helpers
import { truncateText } from "@/helpers/string.helper";
@ -79,7 +80,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
workspaceService
.joinWorkspaces({ invitations: invitationsRespond })
.then(() => {
mutate("USER_WORKSPACES");
mutate(USER_WORKSPACES_LIST);
const firstInviteId = invitationsRespond[0];
const invitation = invitations?.find((i) => i.id === firstInviteId);
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;

View File

@ -9,8 +9,10 @@ import { Spinner } from "@plane/ui";
// components
import { PageHead } from "@/components/core";
import { InviteMembers, CreateOrJoinWorkspaces, ProfileSetup } from "@/components/onboarding";
// hooks
// constants
import { USER_ONBOARDING_COMPLETED } from "@/constants/event-tracker";
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
// hooks
import { useUser, useWorkspace, useUserProfile, useEventTracker } from "@/hooks/store";
import useUserAuth from "@/hooks/use-user-auth";
// layouts
@ -24,7 +26,7 @@ import { WorkspaceService } from "@/services/workspace.service";
export enum EOnboardingSteps {
PROFILE_SETUP = "PROFILE_SETUP",
WORKSPACE_CREATE_OR_JOIN = "WORKSPACE_CREATE_OR_JOIN",
WORKSPACE_INVITE = "WORKSPACE_INVITE",
INVITE_MEMBERS = "INVITE_MEMBERS",
}
const workspaceService = new WorkspaceService();
@ -50,7 +52,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
// computed values
const workspacesList = Object.values(workspaces ?? {});
// fetching workspaces list
useSWR(`USER_WORKSPACES_LIST`, () => fetchWorkspaces(), {
useSWR(USER_WORKSPACES_LIST, () => fetchWorkspaces(), {
shouldRetryOnError: false,
});
// fetching user workspace invitations
@ -70,11 +72,25 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
await updateUserProfile(payload);
};
// complete onboarding
const finishOnboarding = async () => {
if (!user || !workspacesList) return;
if (!user || !workspaces) return;
await updateUserOnBoard()
const firstWorkspace = Object.values(workspaces ?? {})?.[0];
await Promise.all([
updateUserProfile({
onboarding_step: {
profile_complete: true,
workspace_join: true,
workspace_create: true,
workspace_invite: true,
},
last_workspace_id: firstWorkspace?.id,
}),
updateUserOnBoard(),
])
.then(() => {
captureEvent(USER_ONBOARDING_COMPLETED, {
// user_role: user.role,
@ -87,7 +103,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
console.log("Failed to update onboarding status");
});
router.replace(`/${workspacesList[0]?.slug}`);
router.replace(`/${firstWorkspace?.slug}`);
};
useEffect(() => {
@ -112,23 +128,6 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
if (!onboardingStep.profile_complete) setStep(EOnboardingSteps.PROFILE_SETUP);
if (
!onboardingStep.workspace_join &&
!onboardingStep.workspace_create &&
workspacesList &&
workspacesList?.length > 0
) {
await updateUserProfile({
onboarding_step: {
...profile.onboarding_step,
workspace_join: true,
workspace_create: true,
},
last_workspace_id: workspacesList[0]?.id,
});
return;
}
// For Invited Users, they will skip all other steps.
if (totalSteps && totalSteps <= 2) return;
@ -141,7 +140,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
(onboardingStep.workspace_join || onboardingStep.workspace_create) &&
!onboardingStep.workspace_invite
)
setStep(EOnboardingSteps.WORKSPACE_INVITE);
setStep(EOnboardingSteps.INVITE_MEMBERS);
};
handleStepChange();
@ -161,12 +160,16 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
finishOnboarding={finishOnboarding}
/>
) : step === EOnboardingSteps.WORKSPACE_CREATE_OR_JOIN ? (
<CreateOrJoinWorkspaces invitations={invitations} totalSteps={totalSteps} stepChange={stepChange} />
) : step === EOnboardingSteps.WORKSPACE_INVITE ? (
<CreateOrJoinWorkspaces
invitations={invitations}
totalSteps={totalSteps}
stepChange={stepChange}
finishOnboarding={finishOnboarding}
/>
) : step === EOnboardingSteps.INVITE_MEMBERS ? (
<InviteMembers
finishOnboarding={finishOnboarding}
totalSteps={totalSteps}
stepChange={stepChange}
user={user}
workspace={workspacesList?.[0]}
/>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 167 KiB