feat: add timezone selection to workspace settings (#8248)
* feat: add timezone selection to workspace onboarding, creation and settings * refactor: remove timezone selection from workspace creation and onboarding forms
This commit is contained in:
parent
0bfb74d4c0
commit
079a624006
24 changed files with 187 additions and 131 deletions
|
|
@ -18,7 +18,7 @@ import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
|||
// plane web helpers
|
||||
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
|
||||
|
||||
function CreateWorkspacePage() {
|
||||
const CreateWorkspacePage = observer(function CreateWorkspacePage() {
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
|
|
@ -104,6 +104,6 @@ function CreateWorkspacePage() {
|
|||
</div>
|
||||
</AuthenticationWrapper>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default observer(CreateWorkspacePage);
|
||||
export default CreateWorkspacePage;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { CircleCheck } from "lucide-react";
|
||||
|
|
@ -71,13 +71,12 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
|
|||
const handleCreateWorkspace = async (formData: IWorkspace) => {
|
||||
if (isSubmitting) return;
|
||||
|
||||
await workspaceService
|
||||
.workspaceSlugCheck(formData.slug)
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const res = (await workspaceService.workspaceSlugCheck(formData.slug)) as { status: boolean };
|
||||
if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) {
|
||||
setSlugError(false);
|
||||
await createWorkspace(formData)
|
||||
.then(async (workspaceResponse) => {
|
||||
try {
|
||||
const workspaceResponse = await createWorkspace(formData);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("workspace_creation.toast.success.title"),
|
||||
|
|
@ -90,8 +89,7 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
|
|||
await fetchWorkspaces();
|
||||
await completeStep(workspaceResponse.id);
|
||||
onComplete(formData.organization_size === "Just myself");
|
||||
})
|
||||
.catch(() => {
|
||||
} catch {
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: { slug: formData.slug },
|
||||
|
|
@ -102,16 +100,17 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
|
|||
title: t("workspace_creation.toast.error.title"),
|
||||
message: t("workspace_creation.toast.error.message"),
|
||||
});
|
||||
});
|
||||
} else setSlugError(true);
|
||||
})
|
||||
.catch(() =>
|
||||
}
|
||||
} else {
|
||||
setSlugError(true);
|
||||
}
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("workspace_creation.toast.error.title"),
|
||||
message: t("workspace_creation.toast.error.message"),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const completeStep = async (workspaceId: string) => {
|
||||
|
|
@ -136,7 +135,12 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
|
|||
);
|
||||
}
|
||||
return (
|
||||
<form className="flex flex-col gap-10" onSubmit={handleSubmit(handleCreateWorkspace)}>
|
||||
<form
|
||||
className="flex flex-col gap-10"
|
||||
onSubmit={(e) => {
|
||||
void handleSubmit(handleCreateWorkspace)(e);
|
||||
}}
|
||||
>
|
||||
<CommonOnboardingHeader title="Create your workspace" description="All your work — unified." />
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
|
|
@ -181,6 +185,7 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
|
|||
"border-red-500": errors.name,
|
||||
}
|
||||
)}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,14 +67,13 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
|
|||
} = useForm<IWorkspace>({ defaultValues, mode: "onChange" });
|
||||
|
||||
const handleCreateWorkspace = async (formData: IWorkspace) => {
|
||||
await workspaceService
|
||||
.workspaceSlugCheck(formData.slug)
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const res = (await workspaceService.workspaceSlugCheck(formData.slug)) as { status: boolean };
|
||||
if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) {
|
||||
setSlugError(false);
|
||||
|
||||
await createWorkspace(formData)
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const workspaceResponse = await createWorkspace(formData);
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: { slug: formData.slug },
|
||||
|
|
@ -85,9 +84,8 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
|
|||
message: t("workspace_creation.toast.success.message"),
|
||||
});
|
||||
|
||||
if (onSubmit) await onSubmit(res);
|
||||
})
|
||||
.catch(() => {
|
||||
if (onSubmit) await onSubmit(workspaceResponse);
|
||||
} catch {
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: { slug: formData.slug },
|
||||
|
|
@ -98,16 +96,17 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
|
|||
title: t("workspace_creation.toast.error.title"),
|
||||
message: t("workspace_creation.toast.error.message"),
|
||||
});
|
||||
});
|
||||
} else setSlugError(true);
|
||||
})
|
||||
.catch(() => {
|
||||
}
|
||||
} else {
|
||||
setSlugError(true);
|
||||
}
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("workspace_creation.toast.error.title"),
|
||||
message: t("workspace_creation.toast.error.message"),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(
|
||||
|
|
@ -119,7 +118,12 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
|
|||
);
|
||||
|
||||
return (
|
||||
<form className="space-y-6 sm:space-y-9" onSubmit={handleSubmit(handleCreateWorkspace)}>
|
||||
<form
|
||||
className="space-y-6 sm:space-y-9"
|
||||
onSubmit={(e) => {
|
||||
void handleSubmit(handleCreateWorkspace)(e);
|
||||
}}
|
||||
>
|
||||
<div className="space-y-6 sm:space-y-7">
|
||||
<div className="space-y-1 text-sm">
|
||||
<label htmlFor="workspaceName">
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { copyUrlToClipboard, getFileURL } from "@plane/utils";
|
|||
// components
|
||||
import { WorkspaceImageUploadModal } from "@/components/core/modals/workspace-image-upload-modal";
|
||||
// helpers
|
||||
import { TimezoneSelect } from "@/components/global/timezone-select";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
|
@ -30,6 +31,7 @@ const defaultValues: Partial<IWorkspace> = {
|
|||
url: "",
|
||||
organization_size: "2-10",
|
||||
logo_url: null,
|
||||
timezone: "UTC",
|
||||
};
|
||||
|
||||
export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
||||
|
|
@ -62,10 +64,11 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
const payload: Partial<IWorkspace> = {
|
||||
name: formData.name,
|
||||
organization_size: formData.organization_size,
|
||||
timezone: formData.timezone,
|
||||
};
|
||||
|
||||
await updateWorkspace(currentWorkspace.slug, payload)
|
||||
.then(() => {
|
||||
try {
|
||||
await updateWorkspace(currentWorkspace.slug, payload);
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.update,
|
||||
payload: { slug: currentWorkspace.slug },
|
||||
|
|
@ -75,50 +78,54 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Workspace updated successfully",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
} catch (err: unknown) {
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.update,
|
||||
payload: { slug: currentWorkspace.slug },
|
||||
error: err,
|
||||
error: err instanceof Error ? err : new Error(String(err)),
|
||||
});
|
||||
console.error(err);
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveLogo = async () => {
|
||||
if (!currentWorkspace) return;
|
||||
|
||||
try {
|
||||
await updateWorkspace(currentWorkspace.slug, {
|
||||
logo_url: "",
|
||||
})
|
||||
.then(() => {
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Workspace picture removed successfully.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "There was some error in deleting your profile picture. Please try again.",
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyUrl = () => {
|
||||
if (!currentWorkspace) return;
|
||||
|
||||
copyUrlToClipboard(`${currentWorkspace.slug}`).then(() => {
|
||||
void copyUrlToClipboard(`${currentWorkspace.slug}`)
|
||||
.then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Workspace URL copied to the clipboard.",
|
||||
});
|
||||
return undefined;
|
||||
})
|
||||
.catch(() => {
|
||||
// Silently handle clipboard errors
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -264,12 +271,30 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.url)}
|
||||
className="w-full"
|
||||
className="w-full cursor-not-allowed rounded-md !bg-custom-background-90"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1 ">
|
||||
<h4 className="text-sm">{t("workspace_settings.settings.general.workspace_timezone")}</h4>
|
||||
<Controller
|
||||
name="timezone"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<TimezoneSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
buttonClassName="border-none"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isAdmin && (
|
||||
|
|
@ -277,7 +302,9 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
|
|||
<Button
|
||||
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.UPDATE_WORKSPACE_BUTTON}
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
onClick={(e) => {
|
||||
void handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? t("updating") : t("workspace_settings.settings.general.update_workspace")}
|
||||
|
|
|
|||
|
|
@ -1578,6 +1578,7 @@ export default {
|
|||
name: "Název pracovního prostoru",
|
||||
company_size: "Velikost společnosti",
|
||||
url: "URL pracovního prostoru",
|
||||
workspace_timezone: "Časové pásmo pracovního prostoru",
|
||||
update_workspace: "Aktualizovat prostor",
|
||||
delete_workspace: "Smazat tento prostor",
|
||||
delete_workspace_description: "Smazáním prostoru odstraníte všechna data a zdroje. Akce je nevratná.",
|
||||
|
|
|
|||
|
|
@ -1596,6 +1596,7 @@ export default {
|
|||
name: "Name des Arbeitsbereichs",
|
||||
company_size: "Unternehmensgröße",
|
||||
url: "URL des Arbeitsbereichs",
|
||||
workspace_timezone: "Zeitzone des Arbeitsbereichs",
|
||||
update_workspace: "Arbeitsbereich aktualisieren",
|
||||
delete_workspace: "Diesen Arbeitsbereich löschen",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1431,6 +1431,7 @@ export default {
|
|||
name: "Workspace name",
|
||||
company_size: "Company size",
|
||||
url: "Workspace URL",
|
||||
workspace_timezone: "Workspace Timezone",
|
||||
update_workspace: "Update workspace",
|
||||
delete_workspace: "Delete this workspace",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1600,6 +1600,7 @@ export default {
|
|||
name: "Nombre del espacio de trabajo",
|
||||
company_size: "Tamaño de la empresa",
|
||||
url: "URL del espacio de trabajo",
|
||||
workspace_timezone: "Zona horaria del espacio de trabajo",
|
||||
update_workspace: "Actualizar espacio de trabajo",
|
||||
delete_workspace: "Eliminar este espacio de trabajo",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1598,6 +1598,7 @@ export default {
|
|||
name: "Nom de l’espace de travail",
|
||||
company_size: "Taille de l’entreprise",
|
||||
url: "URL de l’espace de travail",
|
||||
workspace_timezone: "Fuseau horaire de l’espace de travail",
|
||||
update_workspace: "Mettre à jour l’espace de travail",
|
||||
delete_workspace: "Supprimer cet espace de travail",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1586,6 +1586,7 @@ export default {
|
|||
name: "Nama ruang kerja",
|
||||
company_size: "Ukuran perusahaan",
|
||||
url: "URL ruang kerja",
|
||||
workspace_timezone: "Zona waktu ruang kerja",
|
||||
update_workspace: "Perbarui ruang kerja",
|
||||
delete_workspace: "Hapus ruang kerja ini",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1590,6 +1590,7 @@ export default {
|
|||
name: "Nome dello spazio di lavoro",
|
||||
company_size: "Dimensione aziendale",
|
||||
url: "URL dello spazio di lavoro",
|
||||
workspace_timezone: "Fuso orario dello spazio di lavoro",
|
||||
update_workspace: "Aggiorna spazio di lavoro",
|
||||
delete_workspace: "Elimina questo spazio di lavoro",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1577,6 +1577,7 @@ export default {
|
|||
name: "ワークスペース名",
|
||||
company_size: "会社の規模",
|
||||
url: "ワークスペースURL",
|
||||
workspace_timezone: "ワークスペースのタイムゾーン",
|
||||
update_workspace: "ワークスペースを更新",
|
||||
delete_workspace: "このワークスペースを削除",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1570,6 +1570,7 @@ export default {
|
|||
name: "작업 공간 이름",
|
||||
company_size: "회사 규모",
|
||||
url: "작업 공간 URL",
|
||||
workspace_timezone: "작업 공간 시간대",
|
||||
update_workspace: "작업 공간 업데이트",
|
||||
delete_workspace: "이 작업 공간 삭제",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1581,6 +1581,7 @@ export default {
|
|||
name: "Nazwa przestrzeni roboczej",
|
||||
company_size: "Rozmiar firmy",
|
||||
url: "URL przestrzeni roboczej",
|
||||
workspace_timezone: "Strefa czasowa przestrzeni roboczej",
|
||||
update_workspace: "Zaktualizuj przestrzeń",
|
||||
delete_workspace: "Usuń tę przestrzeń",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1598,6 +1598,7 @@ export default {
|
|||
name: "Nome do espaço de trabalho",
|
||||
company_size: "Tamanho da empresa",
|
||||
url: "URL do espaço de trabalho",
|
||||
workspace_timezone: "Fuso horário do espaço de trabalho",
|
||||
update_workspace: "Atualizar espaço de trabalho",
|
||||
delete_workspace: "Excluir este espaço de trabalho",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1590,6 +1590,7 @@ export default {
|
|||
name: "Numele spațiului de lucru",
|
||||
company_size: "Dimensiunea companiei",
|
||||
url: "URL-ul spațiului de lucru",
|
||||
workspace_timezone: "Fusul orar al spațiului de lucru",
|
||||
update_workspace: "Actualizează spațiul de lucru",
|
||||
delete_workspace: "Șterge acest spațiu de lucru",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1583,6 +1583,7 @@ export default {
|
|||
name: "Название пространства",
|
||||
company_size: "Размер компании",
|
||||
url: "URL пространства",
|
||||
workspace_timezone: "Часовой пояс рабочего пространства",
|
||||
update_workspace: "Обновить пространство",
|
||||
delete_workspace: "Удалить пространство",
|
||||
delete_workspace_description: "Все данные будут безвозвратно удалены.",
|
||||
|
|
|
|||
|
|
@ -1581,6 +1581,7 @@ export default {
|
|||
name: "Názov pracovného priestoru",
|
||||
company_size: "Veľkosť spoločnosti",
|
||||
url: "URL pracovného priestoru",
|
||||
workspace_timezone: "Časové pásmo pracovného priestoru",
|
||||
update_workspace: "Aktualizovať priestor",
|
||||
delete_workspace: "Zmazať tento priestor",
|
||||
delete_workspace_description: "Zmazaním priestoru odstránite všetky dáta a zdroje. Akcia je nevratná.",
|
||||
|
|
|
|||
|
|
@ -1586,6 +1586,7 @@ export default {
|
|||
name: "Çalışma Alanı Adı",
|
||||
company_size: "Şirket Büyüklüğü",
|
||||
url: "Çalışma Alanı URL'si",
|
||||
workspace_timezone: "Çalışma Alanı Saat Dilimi",
|
||||
update_workspace: "Çalışma Alanını Güncelle",
|
||||
delete_workspace: "Bu çalışma alanını sil",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1585,6 +1585,7 @@ export default {
|
|||
name: "Назва робочого простору",
|
||||
company_size: "Розмір компанії",
|
||||
url: "URL робочого простору",
|
||||
workspace_timezone: "Часовий пояс робочого простору",
|
||||
update_workspace: "Оновити простір",
|
||||
delete_workspace: "Видалити цей простір",
|
||||
delete_workspace_description: "Видалення простору призведе до втрати всіх даних і ресурсів. Дія незворотна.",
|
||||
|
|
|
|||
|
|
@ -1587,6 +1587,7 @@ export default {
|
|||
name: "Tên không gian làm việc",
|
||||
company_size: "Quy mô công ty",
|
||||
url: "URL không gian làm việc",
|
||||
workspace_timezone: "Múi giờ không gian làm việc",
|
||||
update_workspace: "Cập nhật không gian làm việc",
|
||||
delete_workspace: "Xóa không gian làm việc này",
|
||||
delete_workspace_description:
|
||||
|
|
|
|||
|
|
@ -1560,6 +1560,7 @@ export default {
|
|||
name: "工作区名称",
|
||||
company_size: "公司规模",
|
||||
url: "工作区网址",
|
||||
workspace_timezone: "工作区时区",
|
||||
update_workspace: "更新工作区",
|
||||
delete_workspace: "删除此工作区",
|
||||
delete_workspace_description: "删除工作区时,该工作区内的所有数据和资源将被永久删除,且无法恢复。",
|
||||
|
|
|
|||
|
|
@ -1561,6 +1561,7 @@ export default {
|
|||
name: "工作區名稱",
|
||||
company_size: "公司規模",
|
||||
url: "工作區網址",
|
||||
workspace_timezone: "工作區時區",
|
||||
update_workspace: "更新工作區",
|
||||
delete_workspace: "刪除此工作區",
|
||||
delete_workspace_description: "刪除工作區時,該工作區內的所有資料和資源都將被永久移除且無法復原。",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export interface IWorkspace {
|
|||
organization_size: string;
|
||||
total_projects?: number;
|
||||
role: number;
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceLite {
|
||||
|
|
@ -228,7 +229,7 @@ export interface IWorkspaceProgressResponse {
|
|||
unstarted_issues: number;
|
||||
}
|
||||
export interface IWorkspaceAnalyticsResponse {
|
||||
completion_chart: any;
|
||||
completion_chart: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type TWorkspacePaginationInfo = TPaginationInfo & {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue