chore: resolved merge conflicts

This commit is contained in:
gurusainath 2024-05-01 12:13:36 +05:30
commit 86196c38bc
15 changed files with 297 additions and 316 deletions

View File

@ -59,7 +59,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

@ -69,10 +69,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));
};
@ -83,10 +83,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
}, []);
@ -146,7 +143,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
</p>
<button
type="button"
onClick={handleRequestNewCode}
onClick={() => handleRequestNewCode(uniqueCodeFormData.email)}
className={`${
isRequestNewCodeDisabled
? "text-onboarding-text-400"
@ -162,7 +159,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

@ -5,7 +5,7 @@ import { mutate } from "swr";
import { Trash2 } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
import { TOAST_TYPE, setToast } from "@plane/ui";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { useUser } from "@/hooks/store";
// ui
@ -24,7 +24,7 @@ export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
// store hooks
const { signOut, deactivateAccount } = useUser();
const { resolvedTheme, setTheme } = useTheme();
const { setTheme } = useTheme();
const handleClose = () => {
setSwitchingAccount(false);
@ -102,17 +102,11 @@ export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel
className={`relative transform overflow-hidden rounded-lg bg-onboarding-background-200 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]`}
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div>
<div className="flex items-center gap-x-4">
<div
className={`grid place-items-center rounded-full ${
resolvedTheme === "dark" ? "bg-[#2F3135]" : "bg-red-500/20"
} p-4`}
>
<div className="grid place-items-center rounded-full bg-red-500/20 p-4">
<Trash2 className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<Dialog.Title as="h3" className="text-2xl font-medium leading-6 text-onboarding-text-100">
@ -129,22 +123,12 @@ export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
</div>
</div>
<div className="mb-2 flex items-center justify-end gap-3 p-4 sm:px-6">
<button
onClick={handleSwitchAccount}
disabled={switchingAccount}
className={`${resolvedTheme === "dark" ? "bg-[#2F3135]" : ""} rounded-sm px-4 py-1.5 text-sm`}
>
<Button variant="neutral-primary" onClick={handleSwitchAccount} disabled={switchingAccount}>
{switchingAccount ? "Switching..." : "Switch account"}
</button>
<button
disabled={isDeactivating}
onClick={handleDeactivateAccount}
className={`${
resolvedTheme === "dark" ? "bg-[#2F3135]" : ""
} rounded-sm border border-red-500 px-4 py-1.5 text-sm text-red-500`}
>
</Button>
<Button variant="outline-danger" onClick={handleDeactivateAccount} loading={isDeactivating}>
{isDeactivating ? "Deleting..." : "Delete account"}
</button>
</Button>
</div>
</Dialog.Panel>
</Transition.Child>

View File

@ -4,6 +4,8 @@ import { ChevronDown } from "lucide-react";
import { Menu, Transition } from "@headlessui/react";
// ui
import { Avatar } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useUser } from "@/hooks/store";
// components
@ -20,19 +22,19 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
// store hooks
const { data: user } = useUser();
const displayName = user?.first_name
? `${user?.first_name} ${user?.last_name ?? ""}`
: fullName && fullName.trim().length > 0
? fullName
: user?.email;
return (
<div className="flex w-full shrink-0 justify-end">
<SwitchOrDeleteAccountModal isOpen={showDeleteAccountModal} onClose={() => setShowDeleteAccountModal(false)} />
<div className="flex items-center gap-x-2 pr-4 z-10">
{user?.avatar && (
<Avatar
name={
user?.first_name
? `${user?.first_name} ${user?.last_name ?? ""}`
: fullName && fullName.trim().length > 0
? fullName
: user?.email
}
name={displayName}
src={user?.avatar}
size={24}
shape="square"
@ -40,43 +42,35 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
className="!text-base capitalize"
/>
)}
<div>
<Menu>
<Menu.Button className={"flex items-center gap-x-1 z-10"}>
<span className="text-sm font-medium">
<p className="text-sm font-medium text-custom-text-200">
{user?.first_name
? `${user?.first_name} ${user?.last_name ?? ""}`
: fullName && fullName.trim().length > 0
? fullName
: user?.email}
</p>
</span>
<ChevronDown className="h-4 w-4 text-custom-text-300" />
</Menu.Button>
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Menu.Items className={"absolute min-w-fit"}>
<Menu.Item as="div">
<div
className="mr-auto mt-2 rounded-md border border-red-400 bg-onboarding-background-200 p-3 text-base font-normal text-red-400 shadow-sm hover:cursor-pointer"
onClick={() => {
setShowDeleteAccountModal(true);
}}
>
Wrong e-mail address?
</div>
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</div>
<Menu as="div" className="relative">
<Menu.Button className="flex items-center gap-x-1 z-10">
<span className="text-sm font-medium text-custom-text-200">{displayName}</span>
<ChevronDown className="h-4 w-4 text-custom-text-300" />
</Menu.Button>
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Menu.Items className="absolute z-10 right-0 rounded-md border-[0.5px] border-custom-border-300 mt-2 bg-custom-background-100 px-2 py-2.5 text-sm min-w-[12rem] shadow-custom-shadow-rg">
<Menu.Item
as="button"
type="button"
className={({ active }) =>
cn("text-red-500 px-1 py-1.5 whitespace-nowrap text-left rounded w-full", {
"bg-custom-background-80": active,
})
}
onClick={() => setShowDeleteAccountModal(true)}
>
Wrong e-mail address?
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
);

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

@ -1,4 +1,6 @@
import { FC, ReactNode } from "react";
// hooks
import { useUser } from "@/hooks/store";
type TPageType = "public" | "onboarding" | "private";
@ -9,6 +11,10 @@ type TAuthenticationWrapper = {
export const AuthenticationWrapper: FC<TAuthenticationWrapper> = (props) => {
const { children, pageType } = props;
// hooks
const { data: currentUser } = useUser();
console.log("currentUser", currentUser);
return <div key={pageType}>{children}</div>;
};

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