0
0
mirror of https://github.com/makeplane/plane synced 2024-06-14 14:31:34 +00:00

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)); authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
}, [csrfToken]); }, [csrfToken]);
const redirectToUniqueCodeLogin = async () => { const redirectToUniqueCodeSignIn = async () => {
handleStepChange(EAuthSteps.UNIQUE_CODE); handleStepChange(EAuthSteps.UNIQUE_CODE);
}; };
@ -194,7 +194,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
{instance && isSmtpConfigured && ( {instance && isSmtpConfigured && (
<Button <Button
type="button" type="button"
onClick={redirectToUniqueCodeLogin} onClick={redirectToUniqueCodeSignIn}
variant="outline-primary" variant="outline-primary"
className="w-full" className="w-full"
size="lg" 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); setIsRequestingNewCode(true);
await handleSendNewCode(uniqueCodeFormData.email) await handleSendNewCode(email)
.then(() => setResendCodeTimer(30)) .then(() => setResendCodeTimer(30))
.finally(() => setIsRequestingNewCode(false)); .finally(() => setIsRequestingNewCode(false));
}; };
@ -83,10 +83,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
}, [csrfToken]); }, [csrfToken]);
useEffect(() => { useEffect(() => {
setIsRequestingNewCode(true); handleRequestNewCode(email);
handleSendNewCode(email)
.then(() => setResendCodeTimer(30))
.finally(() => setIsRequestingNewCode(false));
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
@ -146,7 +143,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
</p> </p>
<button <button
type="button" type="button"
onClick={handleRequestNewCode} onClick={() => handleRequestNewCode(uniqueCodeFormData.email)}
className={`${ className={`${
isRequestNewCodeDisabled isRequestNewCodeDisabled
? "text-onboarding-text-400" ? "text-onboarding-text-400"
@ -162,7 +159,14 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
</button> </button>
</div> </div>
</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} {isRequestingNewCode ? "Sending code" : submitButtonText}
</Button> </Button>
</form> </form>

View File

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

View File

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

View File

@ -16,12 +16,12 @@ import {
import { Check, ChevronDown, Plus, XCircle } from "lucide-react"; import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
// types // types
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types"; import { IUser, IWorkspace } from "@plane/types";
// ui // ui
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// constants // constants
import { MEMBER_INVITED } from "@/constants/event-tracker"; import { MEMBER_INVITED } from "@/constants/event-tracker";
import { EUserWorkspaceRoles, ROLE } from "@/constants/workspace"; import { EUserWorkspaceRoles, ROLE, ROLE_DETAILS } from "@/constants/workspace";
// helpers // helpers
import { getUserRole } from "@/helpers/user.helper"; import { getUserRole } from "@/helpers/user.helper";
// hooks // hooks
@ -39,7 +39,6 @@ import { SwitchOrDeleteAccountDropdown } from "./switch-or-delete-account-dropdo
type Props = { type Props = {
finishOnboarding: () => Promise<void>; finishOnboarding: () => Promise<void>;
totalSteps: number; totalSteps: number;
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
user: IUser | undefined; user: IUser | undefined;
workspace: IWorkspace | undefined; workspace: IWorkspace | undefined;
}; };
@ -215,10 +214,10 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
> >
<Listbox.Options <Listbox.Options
ref={dropdownRef} 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"> <div className="space-y-1 p-2">
{Object.entries(ROLE).map(([key, value]) => ( {Object.entries(ROLE_DETAILS).map(([key, value]) => (
<Listbox.Option <Listbox.Option
key={key} key={key}
value={parseInt(key)} value={parseInt(key)}
@ -229,9 +228,12 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
} }
> >
{({ selected }) => ( {({ selected }) => (
<div className="flex items-center justify-between gap-2"> <div className="flex items-center text-wrap gap-2 p-1">
<div className="flex items-center gap-2">{value}</div> <div className="flex flex-col">
{selected && <Check className="h-4 w-4 flex-shrink-0" />} <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> </div>
)} )}
</Listbox.Option> </Listbox.Option>
@ -264,7 +266,7 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
}; };
export const InviteMembers: React.FC<Props> = (props) => { export const InviteMembers: React.FC<Props> = (props) => {
const { finishOnboarding, totalSteps, stepChange, workspace } = props; const { finishOnboarding, totalSteps, workspace } = props;
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true); const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
@ -287,11 +289,6 @@ export const InviteMembers: React.FC<Props> = (props) => {
}); });
const nextStep = async () => { const nextStep = async () => {
const payload: Partial<TOnboardingSteps> = {
workspace_invite: true,
};
await stepChange(payload);
await finishOnboarding(); await finishOnboarding();
}; };
@ -371,11 +368,11 @@ export const InviteMembers: React.FC<Props> = (props) => {
<SwitchOrDeleteAccountDropdown /> <SwitchOrDeleteAccountDropdown />
</div> </div>
</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"> <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> <h3 className="text-3xl font-bold text-onboarding-text-100">Invite your teammates</h3>
<p className="font-medium text-onboarding-text-400"> <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> </p>
</div> </div>
<form <form
@ -410,14 +407,14 @@ export const InviteMembers: React.FC<Props> = (props) => {
</div> </div>
<button <button
type="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} onClick={appendField}
> >
<Plus className="h-4 w-4 mb-0.5" strokeWidth={2.5} /> <Plus className="h-4 w-4" strokeWidth={2} />
Add another Add another
</button> </button>
</div> </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 <Button
variant="primary" variant="primary"
type="submit" type="submit"

View File

@ -131,8 +131,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
await Promise.all([ await Promise.all([
updateCurrentUser(userDetailsPayload), updateCurrentUser(userDetailsPayload),
updateUserProfile(profileUpdatePayload), updateUserProfile(profileUpdatePayload),
stepChange({ profile_complete: true }), totalSteps > 2 && stepChange({ profile_complete: true }),
]).then(() => { ]);
captureEvent(USER_DETAILS, { captureEvent(USER_DETAILS, {
state: "SUCCESS", state: "SUCCESS",
element: "Onboarding", element: "Onboarding",
@ -146,7 +146,6 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
if (totalSteps <= 2) { if (totalSteps <= 2) {
finishOnboarding(); finishOnboarding();
} }
});
} catch { } catch {
captureEvent(USER_DETAILS, { captureEvent(USER_DETAILS, {
state: "FAILED", state: "FAILED",
@ -169,7 +168,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
try { try {
await Promise.all([ await Promise.all([
updateCurrentUser(userDetailsPayload), updateCurrentUser(userDetailsPayload),
formData.password ? handleSetPassword(formData.password) : Promise.resolve(), formData.password && handleSetPassword(formData.password),
]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION)); ]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION));
} catch { } catch {
captureEvent(USER_DETAILS, { captureEvent(USER_DETAILS, {
@ -190,7 +189,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
role: formData.role, role: formData.role,
}; };
try { try {
await Promise.all([updateUserProfile(profileUpdatePayload), stepChange({ profile_complete: true })]).then(() => { await Promise.all([
updateUserProfile(profileUpdatePayload),
totalSteps > 2 && stepChange({ profile_complete: true }),
]);
captureEvent(USER_DETAILS, { captureEvent(USER_DETAILS, {
state: "SUCCESS", state: "SUCCESS",
element: "Onboarding", element: "Onboarding",
@ -204,7 +206,6 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
if (totalSteps <= 2) { if (totalSteps <= 2) {
finishOnboarding(); finishOnboarding();
} }
});
} catch { } catch {
captureEvent(USER_DETAILS, { captureEvent(USER_DETAILS, {
state: "FAILED", state: "FAILED",

View File

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

View File

@ -4,6 +4,8 @@ import { ChevronDown } from "lucide-react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
// ui // ui
import { Avatar } from "@plane/ui"; import { Avatar } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks // hooks
import { useUser } from "@/hooks/store"; import { useUser } from "@/hooks/store";
// components // components
@ -20,19 +22,19 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
// store hooks // store hooks
const { data: user } = useUser(); const { data: user } = useUser();
const displayName = user?.first_name
? `${user?.first_name} ${user?.last_name ?? ""}`
: fullName && fullName.trim().length > 0
? fullName
: user?.email;
return ( return (
<div className="flex w-full shrink-0 justify-end"> <div className="flex w-full shrink-0 justify-end">
<SwitchOrDeleteAccountModal isOpen={showDeleteAccountModal} onClose={() => setShowDeleteAccountModal(false)} /> <SwitchOrDeleteAccountModal isOpen={showDeleteAccountModal} onClose={() => setShowDeleteAccountModal(false)} />
<div className="flex items-center gap-x-2 pr-4 z-10"> <div className="flex items-center gap-x-2 pr-4 z-10">
{user?.avatar && ( {user?.avatar && (
<Avatar <Avatar
name={ name={displayName}
user?.first_name
? `${user?.first_name} ${user?.last_name ?? ""}`
: fullName && fullName.trim().length > 0
? fullName
: user?.email
}
src={user?.avatar} src={user?.avatar}
size={24} size={24}
shape="square" shape="square"
@ -40,18 +42,9 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
className="!text-base capitalize" className="!text-base capitalize"
/> />
)} )}
<div> <Menu as="div" className="relative">
<Menu> <Menu.Button className="flex items-center gap-x-1 z-10">
<Menu.Button className={"flex items-center gap-x-1 z-10"}> <span className="text-sm font-medium text-custom-text-200">{displayName}</span>
<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" /> <ChevronDown className="h-4 w-4 text-custom-text-300" />
</Menu.Button> </Menu.Button>
<Transition <Transition
@ -62,22 +55,23 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
leaveFrom="transform scale-100 opacity-100" leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0" leaveTo="transform scale-95 opacity-0"
> >
<Menu.Items className={"absolute min-w-fit"}> <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="div"> <Menu.Item
<div as="button"
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" type="button"
onClick={() => { className={({ active }) =>
setShowDeleteAccountModal(true); 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? Wrong e-mail address?
</div>
</Menu.Item> </Menu.Item>
</Menu.Items> </Menu.Items>
</Transition> </Transition>
</Menu> </Menu>
</div> </div>
</div> </div>
</div>
); );
}); });

View File

@ -1,5 +1,5 @@
import { objToQueryParams } from "@/helpers/string.helper";
import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "@plane/types"; import { IAnalyticsParams, IJiraMetadata, INotificationParams } from "@plane/types";
import { objToQueryParams } from "@/helpers/string.helper";
const paramsToKey = (params: any) => { const paramsToKey = (params: any) => {
const { const {
@ -76,7 +76,7 @@ const myIssuesParamsToKey = (params: any) => {
export const CURRENT_USER = "CURRENT_USER"; export const CURRENT_USER = "CURRENT_USER";
export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS"; 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()}`; 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 { SettingIcon } from "@/components/icons/attachment";
import { Props } from "@/components/icons/types"; import { Props } from "@/components/icons/types";
// services images
import CSVLogo from "public/services/csv.svg"; import CSVLogo from "public/services/csv.svg";
import ExcelLogo from "public/services/excel.svg"; import ExcelLogo from "public/services/excel.svg";
import GithubLogo from "public/services/github.png"; import GithubLogo from "public/services/github.png";
import JiraLogo from "public/services/jira.svg"; import JiraLogo from "public/services/jira.svg";
import JSONLogo from "public/services/json.svg"; import JSONLogo from "public/services/json.svg";
// types
import { TStaticViewTypes } from "@plane/types";
// icons
export enum EUserWorkspaceRoles { export enum EUserWorkspaceRoles {
GUEST = 5, GUEST = 5,
@ -24,6 +24,25 @@ export const ROLE = {
20: "Admin", 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 ORGANIZATION_SIZE = ["Just myself", "2-10", "11-50", "51-200", "201-500", "500+"];
export const USER_ROLES = [ export const USER_ROLES = [

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

Before

(image error) Size: 169 KiB

After

(image error) Size: 167 KiB