diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 5aef561e9..905e8e3a8 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -176,6 +176,24 @@ module.exports = { }, backdrop: "rgba(0, 0, 0, 0.25)", }, + onboarding: { + background: { + 100: convertToRGB("--color-onboarding-background-100"), + 200: convertToRGB("--color-onboarding-background-200"), + 300: convertToRGB("--color-onboarding-background-300"), + 400: convertToRGB("--color-onboarding-background-400"), + }, + text:{ + 100: convertToRGB("--color-onboarding-text-100"), + 200: convertToRGB("--color-onboarding-text-200"), + 300: convertToRGB("--color-onboarding-text-300"), + 400: convertToRGB("--color-onboarding-text-400"), + }, + border: { + 100: convertToRGB("--color-onboarding-border-100"), + 200: convertToRGB("--color-onboarding-border-200"), + }, + }, }, keyframes: { leftToaster: { diff --git a/web/components/account/delete-account-modal.tsx b/web/components/account/delete-account-modal.tsx index ac9c9c375..4663a1004 100644 --- a/web/components/account/delete-account-modal.tsx +++ b/web/components/account/delete-account-modal.tsx @@ -16,13 +16,12 @@ import { AlertTriangle } from "lucide-react"; type Props = { isOpen: boolean; onClose: () => void; - heading: string; }; const authService = new AuthService(); const DeleteAccountModal: React.FC = (props) => { - const { isOpen, onClose, heading } = props; + const { isOpen, onClose } = props; const [isDeleteLoading, setIsDeleteLoading] = useState(false); const router = useRouter(); const { setToastAlert } = useToast(); @@ -72,20 +71,20 @@ const DeleteAccountModal: React.FC = (props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
- - {heading} + + Not the right workspace?
-

+

  • Delete this account if you have another and won’t use this account.
  • Switch to another account if you’d like to come back to this account another time.
  • @@ -96,9 +95,9 @@ const DeleteAccountModal: React.FC = (props) => { Switch account - +
    diff --git a/web/components/account/email-code-form.tsx b/web/components/account/email-code-form.tsx index a5250741e..560306c3b 100644 --- a/web/components/account/email-code-form.tsx +++ b/web/components/account/email-code-form.tsx @@ -9,6 +9,7 @@ import useToast from "hooks/use-toast"; import useTimer from "hooks/use-timer"; // icons import { XCircle } from "lucide-react"; +import { useTheme } from "next-themes"; // types type EmailCodeFormValues = { @@ -93,10 +94,6 @@ export const EmailCodeForm = ({ handleSignIn }: any) => { const emailOld = getValues("email"); - useEffect(() => { - setErrorResendingCode(false); - }, [emailOld]); - useEffect(() => { const submitForm = (e: KeyboardEvent) => { if (!codeSent && e.key === "Enter") { @@ -133,21 +130,21 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {

    Moving to the runway

    -
    +

    Paste the code you got at

    {sentEmail} - below. + below.
    ) : ( <> -

    +

    Let’s get you prepped!

    -

    +

    This whole thing will take less than two minutes.

    -

    Promise!

    +

    Promise!

    )} @@ -164,7 +161,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => { ) || "Email address is not valid", }} render={({ field: { value, onChange, ref } }) => ( -
    +
    { ref={ref} hasError={Boolean(errors.email)} placeholder="orville.wright@firstflight.com" - className="w-full h-[46px] placeholder:text-custom-text-400/50 border-custom-border-200 pr-12" - > + className={`w-full h-[46px] placeholder:text-onboarding-text-400 border border-onboarding-border-100 pr-12`} + /> {value.length > 0 && ( { <>
    {codeResent && sentEmail === getValues("email") ? ( -
    +
    You got a new code at {sentEmail}.
    ) : sentEmail != getValues("email") && getValues("email").length > 0 ? ( -
    +
    Hit enter or Tab to get a new code
    @@ -203,27 +200,28 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
    )}
    - - ( - - )} - /> +
    + ( + + )} + /> +
    )} {codeSent ? ( @@ -241,7 +239,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => { {isLoading ? "Signing in..." : "Next step"}
    -

    +

    When you click the button above, you agree with our{" "} = (props) => { -

    diff --git a/web/components/account/sidebar.tsx b/web/components/account/sidebar.tsx index 990ae4d10..284719176 100644 --- a/web/components/account/sidebar.tsx +++ b/web/components/account/sidebar.tsx @@ -1,9 +1,9 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // react-hook-form -import { Control, Controller, UseFormSetValue } from "react-hook-form"; +import { Control, Controller, UseFormSetValue, UseFormWatch } from "react-hook-form"; // types import { IWorkspace } from "types"; // icons @@ -80,13 +80,13 @@ type Props = { showProject: boolean; control?: Control; setValue?: UseFormSetValue; + watch?: UseFormWatch; }; var timer: number = 0; var lastWorkspaceName: string = ""; const DummySidebar: React.FC = (props) => { - const { workspaceName, showProject, control, setValue } = props; + const { workspaceName, showProject, control, setValue, watch } = props; const { workspace: workspaceStore, user: userStore } = useMobxStore(); - const workspace = workspaceStore.workspaces ? workspaceStore.workspaces[0] : null; const handleZoomWorkspace = (value: string) => { @@ -104,14 +104,20 @@ const DummySidebar: React.FC = (props) => { setValue!("name", lastWorkspaceName); clearInterval(interval); } + console.log("timer", timer); timer--; }, 1000); } }; - console.log("CALLED"); + useEffect(() => { + if (watch) { + watch(); + } + }); + return ( -
    +
    {control && setValue ? ( = (props) => { handleZoomWorkspace(value); } return timer > 0 ? ( - )} diff --git a/web/components/account/step-indicator.tsx b/web/components/account/step-indicator.tsx index 5180d67a6..75957b145 100644 --- a/web/components/account/step-indicator.tsx +++ b/web/components/account/step-indicator.tsx @@ -1,23 +1,21 @@ import React from "react"; -const OnboardingStepIndicator = ({ step }: { step: number }) => { - return ( -
    -
    -
    = 2 ? "bg-custom-primary-100" : "bg-custom-primary-20"}`} /> -
    = 2 ? "bg-custom-primary-100 h-4 w-4" : " h-3 w-3 bg-custom-primary-20" - }`} - /> -
    = 3 ? "bg-custom-primary-100" : "bg-custom-primary-20"}`} /> -
    = 3 ? "bg-custom-primary-100 h-4 w-4" : "h-3 w-3 bg-custom-primary-20" - }`} - /> -
    - ); -}; +const OnboardingStepIndicator = ({ step }: { step: number }) => ( +
    +
    +
    = 2 ? "bg-custom-primary-100" : "bg-onboarding-background-100"}`} /> +
    = 2 ? "bg-custom-primary-100 h-4 w-4" : " h-3 w-3 bg-onboarding-background-100" + }`} + /> +
    = 3 ? "bg-custom-primary-100" : "bg-onboarding-background-100"}`} /> +
    = 3 ? "bg-custom-primary-100 h-4 w-4" : "h-3 w-3 bg-onboarding-background-100" + }`} + /> +
    +); export default OnboardingStepIndicator; diff --git a/web/components/onboarding/invitations.tsx b/web/components/onboarding/invitations.tsx index a2b817c1c..2919645d8 100644 --- a/web/components/onboarding/invitations.tsx +++ b/web/components/onboarding/invitations.tsx @@ -1,7 +1,7 @@ // react import React, { useState } from "react"; // components -import { Button } from "@plane/ui"; +import { Button, Loader } from "@plane/ui"; // helpers import { truncateText } from "helpers/string.helper"; @@ -17,24 +17,29 @@ import { ROLE } from "constants/workspace"; // types import { IWorkspaceMemberInvitation } from "types"; // icons -import { CheckCircle2 } from "lucide-react"; +import { CheckCircle2, Search } from "lucide-react"; type Props = { handleNextStep: () => void; - updateLastWorkspace: () => void; + setTryDiffAccount: () => void; }; const workspaceService = new WorkspaceService(); const Invitations: React.FC = (props) => { - const { handleNextStep, updateLastWorkspace } = props; + const { handleNextStep, setTryDiffAccount } = props; const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); const [invitationsRespond, setInvitationsRespond] = useState([]); - const { workspace: workspaceStore } = useMobxStore(); + const { + workspace: workspaceStore, + user: { updateCurrentUser }, + } = useMobxStore(); - const { data: invitations, mutate: mutateInvitations } = useSWR(USER_WORKSPACE_INVITATIONS, () => - workspaceService.userWorkspaceInvitations() - ); + const { + data: invitations, + mutate: mutateInvitations, + isLoading, + } = useSWR(USER_WORKSPACE_INVITATIONS, () => workspaceService.userWorkspaceInvitations()); const handleInvitation = (workspace_invitation: IWorkspaceMemberInvitation, action: "accepted" | "withdraw") => { if (action === "accepted") { @@ -44,6 +49,13 @@ const Invitations: React.FC = (props) => { } }; + const updateLastWorkspace = async () => { + if (!workspaceStore.workspaces) return; + await updateCurrentUser({ + last_workspace_id: workspaceStore.workspaces[0].id, + }); + }; + const submitInvitations = async () => { if (invitationsRespond.length <= 0) return; @@ -61,55 +73,98 @@ const Invitations: React.FC = (props) => { .finally(() => setIsJoiningWorkspaces(false)); }; - return ( -
    -

    Choose a workspace to join

    -
    - {invitations && - invitations.map((invitation) => { - const isSelected = invitationsRespond.includes(invitation.id); - return ( -
    handleInvitation(invitation, isSelected ? "withdraw" : "accepted")} - > -
    -
    - {invitation.workspace.logo && invitation.workspace.logo !== "" ? ( - {invitation.workspace.name} - ) : ( - - {invitation.workspace.name[0]} - - )} + return invitations && invitations.length > 0 ? ( +
    +
    +

    Choose a workspace to join

    +
    + {invitations && + invitations.length > 0 && + invitations.map((invitation) => { + const isSelected = invitationsRespond.includes(invitation.id); + return ( +
    handleInvitation(invitation, isSelected ? "withdraw" : "accepted")} + > +
    +
    + {invitation.workspace.logo && invitation.workspace.logo !== "" ? ( + {invitation.workspace.name} + ) : ( + + {invitation.workspace.name[0]} + + )} +
    +
    +
    {truncateText(invitation.workspace.name, 30)}
    +

    {ROLE[invitation.role]}

    +
    + + +
    -
    -
    {truncateText(invitation.workspace.name, 30)}
    -

    {ROLE[invitation.role]}

    -
    - - - -
    - ); - })} -
    + ); + })} +
    - + +
    +
    +
    + + Don't see your workspace? +
    + +
    +
    + Try a different email address +
    +

    + Your right e-mail address could be from a Google or GitHub login. +

    +
    +
    + ) : ( + ); }; +const EmptyInvitation = () => ( +
    +

    Is your Team already on Plane?

    +

    + We couldn’t find any existing workspaces for the email address bhavesh@caravel.ai +

    +
    {}} + > + Try a different email address +
    +

    + Your right e-mail address could be from a Google or GitHub login. +

    +
    +); + export default Invitations; diff --git a/web/components/onboarding/invite-members.tsx b/web/components/onboarding/invite-members.tsx index af0342a44..c05d92664 100644 --- a/web/components/onboarding/invite-members.tsx +++ b/web/components/onboarding/invite-members.tsx @@ -12,12 +12,16 @@ import { Button, Input } from "@plane/ui"; // hooks import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; // icons -import { Check, ChevronDown, Plus, X, XCircle } from "lucide-react"; +import { Check, ChevronDown, Plus, User2, X, XCircle } from "lucide-react"; // types import { IUser, IWorkspace, TOnboardingSteps, TUserWorkspaceRole } from "types"; // constants import { ROLE } from "constants/workspace"; import OnboardingStepIndicator from "components/account/step-indicator"; +import { useTheme } from "next-themes"; +import user1 from "public/users/user-1.png"; +import user2 from "public/users/user-2.png"; +import Image from "next/image"; type Props = { finishOnboarding: () => Promise; @@ -59,7 +63,7 @@ const InviteMemberForm: React.FC = (props) => { return (
    -
    +
    = (props) => { ref={ref} hasError={Boolean(errors.emails?.[index]?.email)} placeholder="Enter their email..." - className="text-xs sm:text-sm w-full h-11 placeholder:text-custom-text-400/50" + className="text-xs sm:text-sm w-full h-12 placeholder:text-onboarding-text-400 border-onboarding-border-100" /> )} />
    -
    +
    = (props) => { type="button" ref={buttonRef} onClick={() => setIsDropdownOpen((prev) => !prev)} - className="flex items-center px-2.5 h-11 py-2 text-xs justify-between gap-1 w-full rounded-md border border-custom-border-200 duration-300" + className="flex items-center px-2.5 h-11 py-2 text-xs justify-between gap-1 w-full rounded-md duration-300" > - {ROLE[value]} + {ROLE[value]} - + = (props) => { >
    {Object.entries(ROLE).map(([key, value]) => ( @@ -132,8 +136,8 @@ const InviteMemberForm: React.FC = (props) => { value={parseInt(key)} className={({ active, selected }) => `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || selected ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` + active || selected ? "bg-onboarding-background-400/40" : "" + } ${selected ? "text-onboarding-text-100" : "text-custom-text-200"}` } > {({ selected }) => ( @@ -168,6 +172,7 @@ export const InviteMembers: React.FC = (props) => { const { finishOnboarding, stepChange, user, workspace } = props; const { setToastAlert } = useToast(); + const { resolvedTheme } = useTheme(); const { control, @@ -224,33 +229,43 @@ export const InviteMembers: React.FC = (props) => { return (
    -
    -

    Members

    +
    +

    Members

    {Array.from({ length: 4 }).map(() => (
    -
    +
    + +
    -
    -
    +
    +
    ))}
    -
    -
    +
    +
    + user +

    Murphy cooper

    -

    murphy@plane.so

    +

    murphy@plane.so

    -
    -
    +
    +
    + user +

    Else Thompson

    -

    Elsa@plane.so

    +

    Elsa@plane.so

    @@ -298,7 +313,7 @@ export const InviteMembers: React.FC = (props) => { Copy invite link */} - + Do this later
    diff --git a/web/components/onboarding/join-workspaces.tsx b/web/components/onboarding/join-workspaces.tsx index a6474737e..9cde07883 100644 --- a/web/components/onboarding/join-workspaces.tsx +++ b/web/components/onboarding/join-workspaces.tsx @@ -1,43 +1,26 @@ import React, { useState } from "react"; -// services -import { WorkspaceService } from "services/workspace.service"; // hooks import useUser from "hooks/use-user"; // components import Invitations from "./invitations"; import DummySidebar from "components/account/sidebar"; import OnboardingStepIndicator from "components/account/step-indicator"; -import { Button, Input } from "@plane/ui"; -// hooks -import useToast from "hooks/use-toast"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; // types import { IWorkspace, TOnboardingSteps } from "types"; -// constants -import { RESTRICTED_URLS, ROLE } from "constants/workspace"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // icons -import { Search } from "lucide-react"; +import { Workspace } from "./workspace"; type Props = { finishOnboarding: () => Promise; stepChange: (steps: Partial) => Promise; - updateLastWorkspace: () => Promise; setTryDiffAccount: () => void; }; -// services -const workspaceService = new WorkspaceService(); - -export const JoinWorkspaces: React.FC = ({ stepChange, updateLastWorkspace, setTryDiffAccount }) => { - const [slugError, setSlugError] = useState(false); - const [invalidSlug, setInvalidSlug] = useState(false); +export const JoinWorkspaces: React.FC = ({ stepChange, setTryDiffAccount }) => { const { user } = useUser(); - const { workspace: workspaceStore } = useMobxStore(); - const { setToastAlert } = useToast(); const { handleSubmit, control, @@ -57,60 +40,15 @@ export const JoinWorkspaces: React.FC = ({ stepChange, updateLastWorkspac await stepChange({ workspace_join: true, workspace_create: true }); }; - const handleCreateWorkspace = async (formData: IWorkspace) => { - const slug = formData.slug.split("/"); - formData.slug = slug[slug.length - 1]; - // TODO: remove this after adding organization size in backend - formData.organization_size = "Just myself"; - console.log(formData.slug); - await workspaceService - .workspaceSlugCheck(formData.slug) - .then(async (res) => { - if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { - setSlugError(false); - - await workspaceStore - .createWorkspace(formData) - .then(async (res) => { - setToastAlert({ - type: "success", - title: "Success!", - message: "Workspace created successfully.", - }); - - const payload: Partial = { - workspace_create: true, - workspace_join: true, - }; - await updateLastWorkspace(); - await stepChange(payload); - }) - .catch(() => - setToastAlert({ - type: "error", - title: "Error!", - message: "Workspace could not be created. Please try again.", - }) - ); - } else setSlugError(true); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Some error occurred while creating workspace. Please try again.", - }); - }); - }; - return ( -
    +
    ( + render={({ field: { value } }) => ( = ({ stepChange, updateLastWorkspac />
    -
    +
    -

    What will your workspace

    +

    What will your workspace

    -
    -
    -

    Name it.

    - - /^[\w\s-]*$/.test(value) || `Name can only contain (" "), ( - ), ( _ ) & alphanumeric characters.`, - maxLength: { - value: 80, - message: "Workspace name should not exceed 80 characters", - }, - }} - render={({ field: { value, ref, onChange } }) => ( -
    - { - onChange(event.target.value); - setValue("name", event.target.value); - if (window && window.location.host) { - const host = window.location.host; - const slug = event.currentTarget.value.split("/"); - setValue( - "slug", - `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}` - ); - } - }} - placeholder="Enter workspace name..." - ref={ref} - hasError={Boolean(errors.name)} - className="w-full h-[46px] text-base placeholder:text-custom-text-400/50 placeholder:text-base border-custom-border-200" - > -
    - )} - /> - {errors.name && {errors.name.message}} -

    You can edit the slug.

    - ( -
    - { - const host = window.location.host; - const slug = e.currentTarget.value.split("/"); - /^[a-zA-Z0-9_-]+$/.test(slug[slug.length - 1]) ? setInvalidSlug(false) : setInvalidSlug(true); - setValue( - "slug", - `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}` - ); - }} - ref={ref} - hasError={Boolean(errors.slug)} - className="w-full h-[46px] border-custom-border-300" - > -
    - )} - /> - {slugError && Workspace URL is already taken!} - {invalidSlug && ( - {`URL can only contain ( - ), ( _ ) & alphanumeric characters.`} - )} -
    - -
    - -
    -
    + +
    +

    Or

    -
    +
    -
    - -
    - -
    -
    - - Don't see your workspace? -
    - -
    -
    - Try a different email address -
    -

    - Your right e-mail address could be from a Google or GitHub login. -

    -
    +
    +
    diff --git a/web/components/onboarding/user-details.tsx b/web/components/onboarding/user-details.tsx index 982fc8a7b..ffc494330 100644 --- a/web/components/onboarding/user-details.tsx +++ b/web/components/onboarding/user-details.tsx @@ -106,11 +106,11 @@ export const UserDetails: React.FC = observer((props) => {
    -
    +
    = observer((props) => { ref={ref} hasError={Boolean(errors.first_name)} placeholder="Enter your full name..." - className="w-full focus:border-custom-primary-100" + className="w-full focus:border-custom-primary-100 border-onboarding-border-100" /> )} /> @@ -157,17 +157,19 @@ export const UserDetails: React.FC = observer((props) => { control={control} name="first_name" render={({ field: { value } }) => ( -

    And how will you use Plane, {value} ?

    +

    + And how will you use Plane, {value} ? +

    )} /> -

    Choose just one

    +

    Choose just one

    {useCases.map((useCase, index) => (
    setSelectedUsecase(index)} > diff --git a/web/components/onboarding/workspace.tsx b/web/components/onboarding/workspace.tsx index 916c91f5d..d76de588c 100644 --- a/web/components/onboarding/workspace.tsx +++ b/web/components/onboarding/workspace.tsx @@ -1,67 +1,168 @@ import { useState } from "react"; // ui -import { Button } from "@plane/ui"; +import { Button, Input } from "@plane/ui"; // types import { IUser, IWorkspace, TOnboardingSteps } from "types"; // constants -import { CreateWorkspaceForm } from "components/workspace"; +import { RESTRICTED_URLS } from "constants/workspace"; +import { Control, Controller, FieldErrors, UseFormHandleSubmit, UseFormSetValue } from "react-hook-form"; +import { WorkspaceService } from "services/workspace.service"; +import useToast from "hooks/use-toast"; +import { useMobxStore } from "lib/mobx/store-provider"; type Props = { - finishOnboarding: () => Promise; stepChange: (steps: Partial) => Promise; - updateLastWorkspace: () => Promise; user: IUser | undefined; - workspaces: IWorkspace[] | undefined; + control: Control; + handleSubmit: UseFormHandleSubmit; + errors: FieldErrors; + setValue: UseFormSetValue; + isSubmitting: boolean; }; -export const Workspace: React.FC = (props) => { - const { finishOnboarding, stepChange, updateLastWorkspace, user, workspaces } = props; +// services +const workspaceService = new WorkspaceService(); - const [defaultValues, setDefaultValues] = useState({ - name: "", - slug: "", - organization_size: "", - }); +export const Workspace: React.FC = (props) => { + const { stepChange, user, control, handleSubmit, setValue, errors, isSubmitting } = props; + const [slugError, setSlugError] = useState(false); + const [invalidSlug, setInvalidSlug] = useState(false); + + const { + workspace: workspaceStore, + user: { updateCurrentUser }, + } = useMobxStore(); + + const { setToastAlert } = useToast(); + + const handleCreateWorkspace = async (formData: IWorkspace) => { + if (isSubmitting) return; + const slug = formData.slug.split("/"); + formData.slug = slug[slug.length - 1]; + + await workspaceService + .workspaceSlugCheck(formData.slug) + .then(async (res) => { + if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { + setSlugError(false); + + await workspaceStore + .createWorkspace(formData) + .then(async (res) => { + setToastAlert({ + type: "success", + title: "Success!", + message: "Workspace created successfully.", + }); + await workspaceStore.fetchWorkspaces(); + await completeStep(); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Workspace could not be created. Please try again.", + }) + ); + } else setSlugError(true); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Some error occurred while creating workspace. Please try again.", + }); + }); + }; const completeStep = async () => { - if (!user) return; + if (!user || !workspaceStore.workspaces) return; const payload: Partial = { workspace_create: true, + workspace_join: true, }; await stepChange(payload); - await updateLastWorkspace(); - }; - - const secondaryButtonAction = async () => { - if (workspaces && workspaces.length > 0) { - await stepChange({ workspace_create: true, workspace_invite: true, workspace_join: true }); - await finishOnboarding(); - } else await stepChange({ profile_complete: false, workspace_join: false }); + await updateCurrentUser({ + last_workspace_id: workspaceStore.workspaces[0]?.id, + }); }; return ( -
    -

    Create your workspace

    -
    - +
    +

    Name it.

    + + /^[\w\s-]*$/.test(value) || `Name can only contain (" "), ( - ), ( _ ) & alphanumeric characters.`, + maxLength: { + value: 80, + message: "Workspace name should not exceed 80 characters", + }, }} - secondaryButton={ - workspaces ? ( - - ) : undefined - } + render={({ field: { value, ref, onChange } }) => ( +
    + { + onChange(event.target.value); + setValue("name", event.target.value); + if (window && window.location.host) { + const host = window.location.host; + const slug = event.currentTarget.value.split("/"); + setValue("slug", `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}`); + } + }} + placeholder="Enter workspace name..." + ref={ref} + hasError={Boolean(errors.name)} + className="w-full h-[46px] text-base placeholder:text-custom-text-400/50 placeholder:text-base border-onboarding-border-100" + /> +
    + )} /> + {errors.name && {errors.name.message}} +

    You can edit the slug.

    + ( +
    + { + const host = window.location.host; + const slug = e.currentTarget.value.split("/"); + /^[a-zA-Z0-9_-]+$/.test(slug[slug.length - 1]) ? setInvalidSlug(false) : setInvalidSlug(true); + setValue("slug", `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}`); + }} + ref={ref} + hasError={Boolean(errors.slug)} + className="w-full h-[46px] border-onboarding-border-100" + /> +
    + )} + /> + {slugError && Workspace URL is already taken!} + {invalidSlug && ( + {`URL can only contain ( - ), ( _ ) & alphanumeric characters.`} + )}
    -
    + + ); }; diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx index 9dec8abec..050bd72ef 100644 --- a/web/components/page-views/signin.tsx +++ b/web/components/page-views/signin.tsx @@ -19,11 +19,12 @@ import { import { Loader, Spinner } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; -import signInIssues from "public/onboarding/sign-in.svg"; +import signInIssues from "public/onboarding/onboarding-issues.svg"; // types import { IUser, IUserSettings } from "types"; // icons import { Lightbulb } from "lucide-react"; +import { useTheme } from "next-themes"; const authService = new AuthService(); @@ -39,7 +40,9 @@ export const SignInView = observer(() => { const [isLoading, setLoading] = useState(false); // toast const { setToastAlert } = useToast(); - // computed + const { resolvedTheme } = useTheme(); + + // computed. const enableEmailPassword = envConfig && (envConfig?.email_password_login || @@ -69,7 +72,7 @@ export const SignInView = observer(() => { const workspaceSlug = userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; if (workspaceSlug) router.push(`/${workspaceSlug}`); - else if (userSettings.workspace.invites > 0) router.push("/invitations"); + // else if (userSettings.workspace.invites > 0) router.push("/invitations"); else router.push("/create-workspace"); }) .catch(() => { @@ -183,7 +186,13 @@ export const SignInView = observer(() => {
    ) : ( -
    +
    @@ -194,19 +203,29 @@ export const SignInView = observer(() => {
    -
    -
    +
    +
    {!envConfig ? ( -
    - - - - +
    +
    + + + + - - - - + + + + +
    ) : ( <> @@ -219,10 +238,12 @@ export const SignInView = observer(() => {
    )} -
    -
    -

    Or continue with

    -
    +
    +
    +

    + Or continue with +

    +
    {envConfig?.google_client_id && ( @@ -233,17 +254,21 @@ export const SignInView = observer(() => { )}
    -
    +
    -

    +

    Try the latest features, like Tiptap editor, to write compelling responses.{" "} {}}> See new features

    -
    - Plane Logo +
    + Plane Logo
    )} diff --git a/web/pages/onboarding/index.tsx b/web/pages/onboarding/index.tsx index 8cdc6d5a1..7814384f1 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/pages/onboarding/index.tsx @@ -15,17 +15,16 @@ import { UserAuthWrapper } from "layouts/auth-layout"; // components import { InviteMembers, JoinWorkspaces, UserDetails, Workspace } from "components/onboarding"; // ui -import { Avatar, CustomMenu, Spinner } from "@plane/ui"; +import { Avatar, Spinner } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; -import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg"; -import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg"; // types import { IUser, TOnboardingSteps } from "types"; import { NextPageWithLayout } from "types/app"; import { ChevronDown } from "lucide-react"; import { Menu, Popover, Transition } from "@headlessui/react"; import DeleteAccountModal from "components/account/delete-account-modal"; +import { useRouter } from "next/router"; // services const workspaceService = new WorkspaceService(); @@ -33,17 +32,17 @@ const workspaceService = new WorkspaceService(); const OnboardingPage: NextPageWithLayout = observer(() => { const [step, setStep] = useState(null); const [showDeleteModal, setShowDeleteModal] = useState(false); - const [tryDiffAccount, setTryDiffAccount] = useState(false); const { user: { currentUser, updateCurrentUser, updateUserOnBoard }, workspace: workspaceStore, } = useMobxStore(); + const router = useRouter(); const user = currentUser ?? undefined; const workspaces = workspaceStore.workspaces; - const { setTheme } = useTheme(); + const { setTheme, resolvedTheme } = useTheme(); const {} = useUserAuth("onboarding"); @@ -51,16 +50,6 @@ const OnboardingPage: NextPageWithLayout = observer(() => { workspaceService.userWorkspaceInvitations() ); - // update last active workspace details - const updateLastWorkspace = async () => { - console.log("Workspaces", workspaces); - if (!workspaces) return; - - await updateCurrentUser({ - last_workspace_id: workspaces[0]?.id, - }); - }; - // handle step change const stepChange = async (steps: Partial) => { if (!user) return; @@ -76,9 +65,11 @@ const OnboardingPage: NextPageWithLayout = observer(() => { }; // complete onboarding const finishOnboarding = async () => { - if (!user) return; + if (!user || !workspaces) return; await updateUserOnBoard(); + + router.replace(`/${workspaces[0].slug}`); }; useEffect(() => { @@ -94,7 +85,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => { if (!onboardingStep.workspace_join && !onboardingStep.workspace_create && step !== 1) setStep(1); if (onboardingStep.workspace_join || onboardingStep.workspace_create) { - if (!onboardingStep.profile_complete && step!==2) setStep(2); + if (!onboardingStep.profile_complete && step !== 2) setStep(2); } if ( onboardingStep.profile_complete && @@ -111,15 +102,19 @@ const OnboardingPage: NextPageWithLayout = observer(() => { return ( <> { setShowDeleteModal(false); - setTryDiffAccount(false); }} /> {user && step !== null ? ( -
    +
    @@ -138,7 +133,11 @@ const OnboardingPage: NextPageWithLayout = observer(() => { /> )}
    - {step != 1 &&

    {currentUser?.first_name}

    } + {step != 1 && ( +

    + {currentUser?.first_name} {currentUser?.last_name} +

    + )} @@ -153,10 +152,10 @@ const OnboardingPage: NextPageWithLayout = observer(() => { leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > - +
    { setShowDeleteModal(true); }} @@ -172,16 +171,23 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
    -
    -
    +
    +
    {step === 1 ? ( { - setTryDiffAccount(true); + setShowDeleteModal(true); }} finishOnboarding={finishOnboarding} stepChange={stepChange} - updateLastWorkspace={updateLastWorkspace} /> ) : step === 2 ? ( diff --git a/web/public/users/user-1.png b/web/public/users/user-1.png new file mode 100644 index 000000000..b69f539cc Binary files /dev/null and b/web/public/users/user-1.png differ diff --git a/web/public/users/user-2.png b/web/public/users/user-2.png new file mode 100644 index 000000000..c5b9bc519 Binary files /dev/null and b/web/public/users/user-2.png differ diff --git a/web/styles/globals.css b/web/styles/globals.css index 8f7ebe624..633270a7e 100644 --- a/web/styles/globals.css +++ b/web/styles/globals.css @@ -125,6 +125,21 @@ --color-border-200: 229, 229, 229; /* subtle border- 2 */ --color-border-300: 212, 212, 212; /* strong border- 1 */ --color-border-400: 185, 185, 185; /* strong border- 2 */ + + /* onboarding colors */ + --color-onboarding-text-100: 23, 23, 23; + --color-onboarding-text-200: 58, 58, 58; + --color-onboarding-text-300: 82, 82, 82; + --color-onboarding-text-400: 163, 163, 163; + + --color-onboarding-background-100: 236, 241, 255; + --color-onboarding-background-200: 255, 255, 255; + --color-onboarding-background-300: 236, 241, 255; + --color-onboarding-background-400: 177, 206, 250; + + --color-onboarding-border-100: 229, 229, 229; + --color-onboarding-border-200: 217, 228, 255; + } [data-theme="light-contrast"] { @@ -172,6 +187,23 @@ --color-border-200: 38, 38, 38; /* subtle border- 2 */ --color-border-300: 46, 46, 46; /* strong border- 1 */ --color-border-400: 58, 58, 58; /* strong border- 2 */ + + + + /* onboarding colors */ + + --color-onboarding-text-100: 237, 238, 240; + --color-onboarding-text-200: 176, 180, 187; + --color-onboarding-text-300: 118, 123, 132; + --color-onboarding-text-400: 105, 110, 119; + + --color-onboarding-background-100: 54, 58, 64; + --color-onboarding-background-200: 40, 42, 45; + --color-onboarding-background-300: 40, 42, 45; + --color-onboarding-background-400: 67, 72, 79; + + --color-onboarding-border-100: 54, 58, 64; + --color-onboarding-border-200: 54, 58, 64; } [data-theme="dark-contrast"] {