From f8797b5be6809c5aec967932da8578e3b8231b21 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 7 Oct 2024 12:41:26 +0530 Subject: [PATCH 1/4] [WEB-2431] chore: timezone and language management. --- packages/types/src/project/projects.d.ts | 1 + .../ui/src/dropdowns/custom-search-select.tsx | 6 +- web/app/profile/page.tsx | 390 +++++++++--------- web/core/components/project/form.tsx | 42 +- 4 files changed, 234 insertions(+), 205 deletions(-) diff --git a/packages/types/src/project/projects.d.ts b/packages/types/src/project/projects.d.ts index a46f490f16f..68ce4cd67b3 100644 --- a/packages/types/src/project/projects.d.ts +++ b/packages/types/src/project/projects.d.ts @@ -54,6 +54,7 @@ export interface IProject { updated_by: string; workspace: IWorkspace | string; workspace_detail: IWorkspaceLite; + timezone: string; } export interface IProjectLite { diff --git a/packages/ui/src/dropdowns/custom-search-select.tsx b/packages/ui/src/dropdowns/custom-search-select.tsx index 7842f1531de..2a6d3957192 100644 --- a/packages/ui/src/dropdowns/custom-search-select.tsx +++ b/packages/ui/src/dropdowns/custom-search-select.tsx @@ -135,14 +135,14 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
-
+
{ />
{ /> setDeactivateAccountModal(false)} />
-
+
{currentUser?.first_name -
+
-
{ />
- -
+
-
+
{`${watch("first_name")} ${watch("last_name")}`}
- {watch("email")} + {watch("email")}
- - {/* - - - Activity Overview - - */}
- -
-
-

- First name* -

- ( - - )} - /> - {errors.first_name && Please enter first name} -
- -
-

Last name

- - ( - - )} - /> -
- -
-

- Email* -

- ( - - )} - /> -
- -
-

- Role* -

- ( - - {USER_ROLES.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.role && Please select a role} -
- -
-

- Display name* -

- { - if (value.trim().length < 1) return "Display name can't be empty."; - - if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; - - if (value.replace(/\s/g, "").length < 1) - return "Display name must be at least 1 characters long."; - - if (value.replace(/\s/g, "").length > 20) - return "Display name must be less than 20 characters long."; - - return true; - }, - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - {errors?.display_name && {errors?.display_name?.message}} +
+

Personal information

+
+
+

+ First name* +

+ ( + + )} + /> + {errors.first_name && Please enter first name} +
+
+

Last name

+ ( + + )} + /> +
+
+

+ Display name* +

+ { + if (value.trim().length < 1) return "Display name can't be empty."; + if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; + if (value.replace(/\s/g, "").length < 1) + return "Display name must be at least 1 characters long."; + if (value.replace(/\s/g, "").length > 20) + return "Display name must be less than 20 characters long."; + return true; + }, + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> + {errors?.display_name && {errors?.display_name?.message}} +
+
+

+ Email* +

+ ( + + )} + /> +
+
+

+ Role* +

+ ( + + {USER_ROLES.map((item) => ( + + {item.label} + + ))} + + )} + /> + {errors.role && Please select a role} +
- -
-

- Timezone* -

- - ( - t.value === value)?.label ?? value) : "Select a timezone"} - options={timeZoneOptions} - onChange={onChange} - buttonClassName={errors.user_timezone ? "border-red-500" : "border-none"} - className="rounded-md border-[0.5px] !border-custom-border-200" - input - /> - )} - /> - {errors.role && Please select a time zone} +
+
+

Timezone/Language

+
+
+

+ Timezone* +

+ ( + t.value === value)?.label ?? value) : "Select a timezone"} + options={timeZoneOptions} + onChange={onChange} + buttonClassName={errors.user_timezone ? "border-red-500" : "border-none"} + className="rounded-md border-[0.5px] !border-custom-border-200" + input + /> + )} + /> + {errors.user_timezone && {errors.user_timezone.message}} +
+ {/* TODO: Add Coming soon tooltip */} +
+

+ Language* +

+ { }} + className="rounded-md bg-custom-background-90" + input + disabled + /> +
- -
+
@@ -398,11 +398,11 @@ const ProfileSettingsPage = observer(() => {
- + {({ open }) => ( <> - Deactivate account + Deactivate account { ); }); -// ProfileSettingsPage.getLayout = function getLayout(page: ReactElement) { -// return {page}; -// }; - export default ProfileSettingsPage; diff --git a/web/core/components/project/form.tsx b/web/core/components/project/form.tsx index 230f17fa4bf..b772860676b 100644 --- a/web/core/components/project/form.tsx +++ b/web/core/components/project/form.tsx @@ -16,6 +16,7 @@ import { CustomEmojiIconPicker, EmojiIconPickerTypes, Tooltip, + CustomSearchSelect, } from "@plane/ui"; // components import { Logo } from "@/components/common"; @@ -24,6 +25,7 @@ import { ImagePickerPopover } from "@/components/core"; import { PROJECT_UPDATED } from "@/constants/event-tracker"; import { NETWORK_CHOICES } from "@/constants/project"; // helpers +import { TIME_ZONES } from "@/constants/timezones"; import { renderFormattedDate } from "@/helpers/date-time.helper"; // hooks import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper"; @@ -64,6 +66,13 @@ export const ProjectDetailsForm: FC = (props) => { workspace: (project.workspace as IWorkspace).id, }, }); + // derived values + const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network); + const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ + value: timeZone.value, + query: timeZone.label + " " + timeZone.value, + content: timeZone.label, + })); useEffect(() => { if (project && projectId !== getValues("id")) { @@ -74,12 +83,15 @@ export const ProjectDetailsForm: FC = (props) => { } // eslint-disable-next-line react-hooks/exhaustive-deps }, [project, projectId]); + + // handlers const handleIdentifierChange = (event: React.ChangeEvent) => { const { value } = event.target; const alphanumericValue = value.replace(/[^a-zA-Z0-9]/g, ""); const formattedValue = alphanumericValue.toUpperCase(); setValue("identifier", formattedValue); }; + const handleUpdateChange = async (payload: Partial) => { if (!workspaceSlug || !project) return; return updateProject(workspaceSlug.toString(), project.id, payload) @@ -113,6 +125,7 @@ export const ProjectDetailsForm: FC = (props) => { }); }); }; + const onSubmit = async (formData: IProject) => { if (!workspaceSlug) return; setIsLoading(true); @@ -123,6 +136,7 @@ export const ProjectDetailsForm: FC = (props) => { description: formData.description, cover_image: formData.cover_image, logo_props: formData.logo_props, + timezone: formData.timezone, }; if (project.identifier !== formData.identifier) @@ -137,7 +151,6 @@ export const ProjectDetailsForm: FC = (props) => { setIsLoading(false); }, 300); }; - const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network); return (
@@ -260,8 +273,8 @@ export const ProjectDetailsForm: FC = (props) => { )} />
-
-
+
+

Project ID

= (props) => { <>{errors?.identifier?.message}
-
+

Network

{ const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === value); - return ( = (props) => { }} />
+
+

Project Timezone

+ ( + t.value === value)?.label ?? value) : "Select a timezone"} + options={timeZoneOptions} + onChange={onChange} + buttonClassName={errors.timezone ? "border-red-500" : "border-none"} + className="rounded-md border-[0.5px] !border-custom-border-200" + input + /> + )} + /> + {errors.timezone && {errors.timezone.message}} +
<> From 4b4b8ac8fc3b524792ceb2cf82d46c7c07ff9068 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 15 Oct 2024 20:29:12 +0530 Subject: [PATCH 2/4] chore: remove project level timezone changes. --- web/core/components/project/form.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/web/core/components/project/form.tsx b/web/core/components/project/form.tsx index 4300b446416..416c0440531 100644 --- a/web/core/components/project/form.tsx +++ b/web/core/components/project/form.tsx @@ -16,7 +16,7 @@ import { CustomEmojiIconPicker, EmojiIconPickerTypes, Tooltip, - CustomSearchSelect, + // CustomSearchSelect, } from "@plane/ui"; // components import { Logo } from "@/components/common"; @@ -25,7 +25,7 @@ import { ImagePickerPopover } from "@/components/core"; import { PROJECT_UPDATED } from "@/constants/event-tracker"; import { NETWORK_CHOICES } from "@/constants/project"; // helpers -import { TIME_ZONES } from "@/constants/timezones"; +// import { TIME_ZONES } from "@/constants/timezones"; import { renderFormattedDate } from "@/helpers/date-time.helper"; import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper"; import { getFileURL } from "@/helpers/file.helper"; @@ -68,11 +68,11 @@ export const ProjectDetailsForm: FC = (props) => { }); // derived values const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network); - const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ - value: timeZone.value, - query: timeZone.label + " " + timeZone.value, - content: timeZone.label, - })); + // const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ + // value: timeZone.value, + // query: timeZone.label + " " + timeZone.value, + // content: timeZone.label, + // })); const coverImage = watch("cover_image_url"); useEffect(() => { @@ -137,7 +137,7 @@ export const ProjectDetailsForm: FC = (props) => { description: formData.description, cover_image_url: formData.cover_image_url, logo_props: formData.logo_props, - timezone: formData.timezone, + // timezone: formData.timezone, }; if (project.identifier !== formData.identifier) @@ -279,7 +279,7 @@ export const ProjectDetailsForm: FC = (props) => { )} />
-
+

Project ID

@@ -372,7 +372,7 @@ export const ProjectDetailsForm: FC = (props) => { }} />
-
+ {/*

Project Timezone

= (props) => { )} /> {errors.timezone && {errors.timezone.message}} -
+
*/}
<> From 1987129f2d0b59a6ff59fd0a51bd737b8c930fce Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 15 Oct 2024 21:09:49 +0530 Subject: [PATCH 3/4] chore: minor UI improvement. --- web/app/profile/page.tsx | 82 +- web/core/components/project/form.tsx | 22 +- web/core/constants/timezones.ts | 1799 +++++++++++++++++--------- 3 files changed, 1270 insertions(+), 633 deletions(-) diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index f9ea8e389ee..f181bca45c3 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -6,14 +6,23 @@ import { Controller, useForm } from "react-hook-form"; import { ChevronDown, CircleUserRound } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; import type { IUser } from "@plane/types"; -import { Button, CustomSelect, CustomSearchSelect, Input, TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; +import { + Button, + CustomSelect, + CustomSearchSelect, + Input, + TOAST_TYPE, + setPromiseToast, + setToast, + Tooltip, +} from "@plane/ui"; // components import { DeactivateAccountModal } from "@/components/account"; import { LogoSpinner } from "@/components/common"; import { ImagePickerPopover, UserImageUploadModal, PageHead } from "@/components/core"; import { ProfileSettingContentWrapper } from "@/components/profile"; // constants -import { TIME_ZONES } from "@/constants/timezones"; +import { TIME_ZONES, TTimezone } from "@/constants/timezones"; import { USER_ROLES } from "@/constants/workspace"; // helpers import { getFileURL } from "@/helpers/file.helper"; @@ -107,10 +116,20 @@ const ProfileSettingsPage = observer(() => { }); }; + const getTimeZoneLabel = (timezone: TTimezone | undefined) => { + if (!timezone) return undefined; + return ( +
+ {timezone.gmtOffset} + {timezone.name} +
+ ); + }; + const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ value: timeZone.value, - query: timeZone.label + " " + timeZone.value, - content: timeZone.label, + query: timeZone.name + " " + timeZone.gmtOffset + " " + timeZone.value, + content: getTimeZoneLabel(timeZone), })); if (!currentUser) @@ -198,8 +217,7 @@ const ProfileSettingsPage = observer(() => {
-

Personal information

-
+

First name* @@ -284,7 +302,9 @@ const ProfileSettingsPage = observer(() => { /> )} /> - {errors?.display_name && {errors?.display_name?.message}} + {errors?.display_name && ( + {errors?.display_name?.message} + )}

@@ -305,8 +325,9 @@ const ProfileSettingsPage = observer(() => { ref={ref} hasError={Boolean(errors.email)} placeholder="Enter your email" - className={`w-full cursor-not-allowed rounded-md !bg-custom-background-90 ${errors.email ? "border-red-500" : "" - }`} + className={`w-full cursor-not-allowed rounded-md !bg-custom-background-90 ${ + errors.email ? "border-red-500" : "" + }`} autoComplete="on" disabled /> @@ -343,9 +364,8 @@ const ProfileSettingsPage = observer(() => {

-
-

Timezone/Language

-
+
+

Timezone* @@ -357,32 +377,38 @@ const ProfileSettingsPage = observer(() => { render={({ field: { value, onChange } }) => ( t.value === value)?.label ?? value) : "Select a timezone"} + label={ + value + ? (getTimeZoneLabel(TIME_ZONES.find((t) => t.value === value)) ?? value) + : "Select a timezone" + } options={timeZoneOptions} onChange={onChange} buttonClassName={errors.user_timezone ? "border-red-500" : "border-none"} className="rounded-md border-[0.5px] !border-custom-border-200" + optionsClassName="w-72" input /> )} /> {errors.user_timezone && {errors.user_timezone.message}}

- {/* TODO: Add Coming soon tooltip */} -
-

- Language* -

- { }} - className="rounded-md bg-custom-background-90" - input - disabled - /> -
+ +
+

+ Language* +

+ {}} + className="rounded-md bg-custom-background-90" + input + disabled + /> +
+