fix: project layout added and theming fixes (#2455)

* fix: project layout added and theme fixes

* feat: input color picker component added to ui package

* fix: layout fixes

* fix: conflicts and build issues resolved

* fix: layout headers fixes
This commit is contained in:
sriram veeraghanta 2023-10-17 12:46:38 +05:30 committed by GitHub
parent e496cec49f
commit 98b1a078de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 891 additions and 699 deletions

View File

@ -22,8 +22,8 @@
"classnames": "^2.3.2", "classnames": "^2.3.2",
"eslint-config-custom": "*", "eslint-config-custom": "*",
"react": "^18.2.0", "react": "^18.2.0",
"tsconfig": "*",
"tailwind-config-custom": "*", "tailwind-config-custom": "*",
"tsconfig": "*",
"tsup": "^5.10.1", "tsup": "^5.10.1",
"typescript": "4.7.4" "typescript": "4.7.4"
}, },
@ -33,6 +33,7 @@
"dependencies": { "dependencies": {
"@blueprintjs/core": "^4.16.3", "@blueprintjs/core": "^4.16.3",
"@blueprintjs/popover2": "^1.13.3", "@blueprintjs/popover2": "^1.13.3",
"@headlessui/react": "^1.7.17" "@headlessui/react": "^1.7.17",
"react-color": "^2.19.3"
} }
} }

View File

@ -1,2 +1,3 @@
export * from "./input"; export * from "./input";
export * from "./textarea"; export * from "./textarea";
export * from "./input-color-picker"

View File

@ -0,0 +1,91 @@
import * as React from "react";
import { Popover, Transition } from "@headlessui/react";
import { ColorResult, SketchPicker } from "react-color";
// components
import { Input } from "./input";
export interface InputColorPickerProps {
hasError: boolean;
value: string | undefined;
onChange: (value: string) => void;
name: string;
className: string;
placeholder: string;
}
export const InputColorPicker: React.FC<InputColorPickerProps> = (props) => {
const { value, hasError, onChange, name, className, placeholder } = props;
const handleColorChange = (newColor: ColorResult) => {
const { hex } = newColor;
onChange(hex);
};
const handleInputChange = (value: any) => {
onChange(value);
};
return (
<div className="relative">
<Input
id={name}
name={name}
type="text"
value={value}
onChange={handleInputChange}
hasError={hasError}
placeholder={placeholder}
className={className}
/>
<div className="absolute right-4 top-2.5">
<Popover className="relative grid place-items-center">
{({ open }) => (
<>
<Popover.Button
type="button"
className={`group inline-flex items-center outline-none ${
open ? "text-custom-text-100" : "text-custom-text-200"
}`}
>
{value && value !== "" ? (
<span
className="h-4 w-4 rounded border border-custom-border-200"
style={{
backgroundColor: `${value}`,
}}
/>
) : (
<svg
width={14}
height={14}
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0.8125 13.7508C0.65 13.7508 0.515625 13.6977 0.409375 13.5914C0.303125 13.4852 0.25 13.3508 0.25 13.1883V10.8258C0.25 10.7508 0.2625 10.682 0.2875 10.6195C0.3125 10.557 0.35625 10.4945 0.41875 10.432L7.31875 3.53203L6.34375 2.55703C6.24375 2.45703 6.19688 2.32891 6.20312 2.17266C6.20938 2.01641 6.2625 1.88828 6.3625 1.78828C6.4625 1.68828 6.59063 1.63828 6.74688 1.63828C6.90313 1.63828 7.03125 1.68828 7.13125 1.78828L8.4625 3.13828L11.125 0.475781C11.2625 0.338281 11.4094 0.269531 11.5656 0.269531C11.7219 0.269531 11.8688 0.338281 12.0063 0.475781L13.525 1.99453C13.6625 2.13203 13.7313 2.27891 13.7313 2.43516C13.7313 2.59141 13.6625 2.73828 13.525 2.87578L10.8625 5.53828L12.2125 6.88828C12.3125 6.98828 12.3625 7.11328 12.3625 7.26328C12.3625 7.41328 12.3125 7.53828 12.2125 7.63828C12.1125 7.73828 11.9844 7.78828 11.8281 7.78828C11.6719 7.78828 11.5438 7.73828 11.4438 7.63828L10.4688 6.68203L3.56875 13.582C3.50625 13.6445 3.44375 13.6883 3.38125 13.7133C3.31875 13.7383 3.25 13.7508 3.175 13.7508H0.8125ZM1.375 12.6258H3.00625L9.6625 5.96953L8.03125 4.33828L1.375 10.9945V12.6258ZM10.0563 4.75078L12.3813 2.42578L11.575 1.61953L9.25 3.94453L10.0563 4.75078Z" />
</svg>
)}
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel
className={`absolute bottom-8 z-20 mt-1 max-w-xs px-2 sm:px-0 right-0`}
>
<SketchPicker color={value} onChange={handleColorChange} />
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</div>
</div>
);
};

View File

@ -1,14 +1,9 @@
import React, { Dispatch, SetStateAction, useEffect, useState } from "react"; import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
// cmdk
import { Command } from "cmdk"; import { Command } from "cmdk";
import { THEMES_OBJ } from "constants/themes"; import { THEME_OPTIONS } from "constants/themes";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
import { Settings } from "lucide-react"; import { Settings } from "lucide-react";
// helper
import { unsetCustomCssVariables } from "helpers/theme.helper";
// mobx react lite
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
@ -44,7 +39,7 @@ export const ChangeInterfaceTheme: React.FC<Props> = observer(({ setIsPaletteOpe
return ( return (
<> <>
{THEMES_OBJ.filter((t) => t.value !== "custom").map((theme) => ( {THEME_OPTIONS.filter((t) => t.value !== "custom").map((theme) => (
<Command.Item <Command.Item
key={theme.value} key={theme.value}
onSelect={() => { onSelect={() => {

View File

@ -18,14 +18,14 @@ import { Input } from "@plane/ui";
// icons // icons
import { Palette } from "lucide-react"; import { Palette } from "lucide-react";
// types // types
import { ICustomTheme } from "types"; import { IUserTheme } from "types";
type Props = { type Props = {
name: keyof ICustomTheme; name: keyof IUserTheme;
position?: "left" | "right"; position?: "left" | "right";
watch: UseFormWatch<any>; watch: UseFormWatch<any>;
setValue: UseFormSetValue<any>; setValue: UseFormSetValue<any>;
control: Control<ICustomTheme, any>; control: Control<IUserTheme, any>;
error: FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined; error: FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined;
register: UseFormRegister<any>; register: UseFormRegister<any>;
}; };
@ -38,7 +38,7 @@ export const ColorPickerInput: FC<Props> = (props) => {
setValue(name, hex); setValue(name, hex);
}; };
const getColorText = (colorName: keyof ICustomTheme) => { const getColorText = (colorName: keyof IUserTheme) => {
switch (colorName) { switch (colorName) {
case "background": case "background":
return "Background"; return "Background";

View File

@ -1,77 +1,54 @@
import React, { useEffect, useState } from "react"; import { FC } from "react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { Controller, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
// ui // ui
import { ColorPickerInput } from "components/core"; import { Button, InputColorPicker } from "@plane/ui";
import { Button } from "@plane/ui";
// types // types
import { ICustomTheme } from "types"; import { IUserTheme } from "types";
// mobx react lite // mobx react lite
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {};
preLoadedData?: Partial<ICustomTheme> | null;
};
const defaultValues: ICustomTheme = { export const CustomThemeSelector: FC<Props> = observer(() => {
background: "#0d101b", const { user: userStore } = useMobxStore();
text: "#c5c5c5", const userTheme = userStore?.currentUser?.theme;
primary: "#3f76ff", // hooks
sidebarBackground: "#0d101b",
sidebarText: "#c5c5c5",
darkPalette: false,
palette: "",
theme: "custom",
};
export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData }) => {
const store: any = useMobxStore();
const { setTheme } = useTheme(); const { setTheme } = useTheme();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [darkPalette, setDarkPalette] = useState(false);
const { const {
register,
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
handleSubmit, handleSubmit,
control, control,
watch, } = useForm<IUserTheme>({
setValue, defaultValues: {
reset, background: userTheme?.background !== "" ? userTheme?.background : "#0d101b",
} = useForm<ICustomTheme>({ text: userTheme?.text !== "" ? userTheme?.text : "#c5c5c5",
defaultValues, primary: userTheme?.primary !== "" ? userTheme?.primary : "#3f76ff",
sidebarBackground: userTheme?.sidebarBackground !== "" ? userTheme?.sidebarBackground : "#0d101b",
sidebarText: userTheme?.sidebarText !== "" ? userTheme?.sidebarText : "#c5c5c5",
darkPalette: userTheme?.darkPalette || false,
palette: userTheme?.palette !== "" ? userTheme?.palette : "",
},
}); });
useEffect(() => {
reset({
...defaultValues,
...preLoadedData,
});
}, [preLoadedData, reset]);
const handleUpdateTheme = async (formData: any) => { const handleUpdateTheme = async (formData: any) => {
const payload: ICustomTheme = { const payload: IUserTheme = {
background: formData.background, background: formData.background,
text: formData.text, text: formData.text,
primary: formData.primary, primary: formData.primary,
sidebarBackground: formData.sidebarBackground, sidebarBackground: formData.sidebarBackground,
sidebarText: formData.sidebarText, sidebarText: formData.sidebarText,
darkPalette: darkPalette, darkPalette: false,
palette: `${formData.background},${formData.text},${formData.primary},${formData.sidebarBackground},${formData.sidebarText}`, palette: `${formData.background},${formData.text},${formData.primary},${formData.sidebarBackground},${formData.sidebarText}`,
theme: "custom", theme: "custom",
}; };
setTheme("custom"); setTheme("custom");
return store.user return userStore.updateCurrentUser({ theme: payload });
.updateCurrentUserSettings({ theme: payload })
.then((response: any) => response)
.catch((error: any) => error);
}; };
return ( return (
@ -82,63 +59,91 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
<div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2 md:grid-cols-3"> <div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2 md:grid-cols-3">
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Background color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Background color</h3>
<ColorPickerInput <Controller
name="background"
position="right"
control={control} control={control}
error={errors.background} name="background"
watch={watch} render={({ field: { value, onChange } }) => (
setValue={setValue} <InputColorPicker
register={register} name="background"
value={value}
onChange={onChange}
className=""
placeholder="#ffffff"
hasError={Boolean(errors?.background)}
/>
)}
/> />
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Text color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Text color</h3>
<ColorPickerInput <Controller
name="text"
control={control} control={control}
error={errors.text} name="text"
watch={watch} render={({ field: { value, onChange } }) => (
setValue={setValue} <InputColorPicker
register={register} name="text"
value={value}
onChange={onChange}
className=""
placeholder="#ffffff"
hasError={Boolean(errors?.text)}
/>
)}
/> />
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Primary(Theme) color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Primary(Theme) color</h3>
<ColorPickerInput <Controller
name="primary"
error={errors.primary}
control={control} control={control}
watch={watch} name="primary"
setValue={setValue} render={({ field: { value, onChange } }) => (
register={register} <InputColorPicker
name="primary"
value={value}
onChange={onChange}
className=""
placeholder="#ffffff"
hasError={Boolean(errors?.primary)}
/>
)}
/> />
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar background color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar background color</h3>
<ColorPickerInput <Controller
name="sidebarBackground"
position="right"
control={control} control={control}
error={errors.sidebarBackground} name="sidebarBackground"
watch={watch} render={({ field: { value, onChange } }) => (
setValue={setValue} <InputColorPicker
register={register} name="sidebarBackground"
value={value}
onChange={onChange}
className=""
placeholder="#ffffff"
hasError={Boolean(errors?.sidebarBackground)}
/>
)}
/> />
</div> </div>
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar text color</h3> <h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar text color</h3>
<ColorPickerInput <Controller
name="sidebarText"
control={control} control={control}
error={errors.sidebarText} name="sidebarText"
watch={watch} render={({ field: { value, onChange } }) => (
setValue={setValue} <InputColorPicker
register={register} name="sidebarText"
value={value}
onChange={onChange}
className=""
placeholder="#ffffff"
hasError={Boolean(errors?.sidebarText)}
/>
)}
/> />
</div> </div>
</div> </div>

View File

@ -1,130 +1,80 @@
// next-themes import { FC } from "react";
import { useTheme } from "next-themes";
// hooks
import useUser from "hooks/use-user";
// constants // constants
import { THEMES_OBJ } from "constants/themes"; import { THEME_OPTIONS, I_THEME_OPTION } from "constants/themes";
// ui // ui
import { CustomSelect } from "components/ui"; import { CustomSelect } from "components/ui";
// types
import { ICustomTheme } from "types";
import { unsetCustomCssVariables } from "helpers/theme.helper";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {
setPreLoadedData: React.Dispatch<React.SetStateAction<ICustomTheme | null>>; value: I_THEME_OPTION | null;
customThemeSelectorOptions: boolean; onChange: (value: I_THEME_OPTION) => void;
setCustomThemeSelectorOptions: React.Dispatch<React.SetStateAction<boolean>>;
}; };
export const ThemeSwitch: React.FC<Props> = observer( export const ThemeSwitch: FC<Props> = (props) => {
({ setPreLoadedData, customThemeSelectorOptions, setCustomThemeSelectorOptions }) => { const { value, onChange } = props;
const store: any = useMobxStore();
const { user } = useUser();
const { theme, setTheme } = useTheme();
const updateUserTheme = (newTheme: string) => {
if (!user) return;
setTheme(newTheme);
return store.user
.updateCurrentUserSettings({ theme: { ...user.theme, theme: newTheme } })
.then((response: any) => response)
.catch((error: any) => error);
};
const currentThemeObj = THEMES_OBJ.find((t) => t.value === theme);
return ( return (
<CustomSelect <CustomSelect
value={theme} value={value}
label={ label={
currentThemeObj ? ( value ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="border-1 relative flex h-4 w-4 rotate-45 transform items-center justify-center rounded-full border" className="border-1 relative flex h-4 w-4 rotate-45 transform items-center justify-center rounded-full border"
style={{ style={{
borderColor: currentThemeObj.icon.border, borderColor: value.icon.border,
}} }}
> >
<div <div
className="h-full w-1/2 rounded-l-full" className="h-full w-1/2 rounded-l-full"
style={{ style={{
background: currentThemeObj.icon.color1, background: value.icon.color1,
}} }}
/> />
<div <div
className="h-full w-1/2 rounded-r-full border-l" className="h-full w-1/2 rounded-r-full border-l"
style={{ style={{
borderLeftColor: currentThemeObj.icon.border, borderLeftColor: value.icon.border,
background: currentThemeObj.icon.color2, background: value.icon.color2,
}} }}
/> />
</div> </div>
{currentThemeObj.label} {value.label}
</div> </div>
) : ( ) : (
"Select your theme" "Select your theme"
) )
} }
onChange={({ value, type }: { value: string; type: string }) => { onChange={onChange}
if (value === "custom") {
if (user?.theme?.palette) {
setPreLoadedData({
background: user.theme?.background !== "" ? user.theme.background : "#0d101b",
text: user.theme.text !== "" ? user.theme.text : "#c5c5c5",
primary: user.theme.primary !== "" ? user.theme.primary : "#3f76ff",
sidebarBackground: user.theme.sidebarBackground !== "" ? user.theme.sidebarBackground : "#0d101b",
sidebarText: user.theme.sidebarText !== "" ? user.theme.sidebarText : "#c5c5c5",
darkPalette: false,
palette: user.theme.palette !== ",,,," ? user.theme.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
theme: "custom",
});
}
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
} else {
if (customThemeSelectorOptions) setCustomThemeSelectorOptions(false);
unsetCustomCssVariables();
}
updateUserTheme(value);
document.documentElement.style.setProperty("--color-scheme", type);
}}
input input
width="w-full" width="w-full"
> >
{THEMES_OBJ.map(({ value, label, type, icon }) => ( {THEME_OPTIONS.map((themeOption) => (
<CustomSelect.Option key={value} value={{ value, type }}> <CustomSelect.Option key={themeOption.value} value={themeOption}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="border-1 relative flex h-4 w-4 rotate-45 transform items-center justify-center rounded-full border" className="border-1 relative flex h-4 w-4 rotate-45 transform items-center justify-center rounded-full border"
style={{ style={{
borderColor: icon.border, borderColor: themeOption.icon.border,
}} }}
> >
<div <div
className="h-full w-1/2 rounded-l-full" className="h-full w-1/2 rounded-l-full"
style={{ style={{
background: icon.color1, background: themeOption.icon.color1,
}} }}
/> />
<div <div
className="h-full w-1/2 rounded-r-full border-l" className="h-full w-1/2 rounded-r-full border-l"
style={{ style={{
borderLeftColor: icon.border, borderLeftColor: themeOption.icon.border,
background: icon.color2, background: themeOption.icon.color2,
}} }}
/> />
</div> </div>
{label} {themeOption.label}
</div> </div>
</CustomSelect.Option> </CustomSelect.Option>
))} ))}
</CustomSelect> </CustomSelect>
); );
} };
);

View File

@ -0,0 +1,56 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { ArrowLeft, Plus } from "lucide-react";
// components
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
// ui
import { Button } from "@plane/ui";
// helpers
import { truncateText } from "helpers/string.helper";
export interface ICyclesHeader {
name: string | undefined;
}
export const CyclesHeader: FC<ICyclesHeader> = (props) => {
const { name } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(name ?? "Project", 32)} Cycles`} />
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-3">
<Button
variant="primary"
prependIcon={<Plus />}
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "q" });
document.dispatchEvent(e);
}}
>
Add Cycle
</Button>
</div>
</div>
);
};

View File

@ -8,3 +8,6 @@ export * from "./project-views";
export * from "./workspace-analytics"; export * from "./workspace-analytics";
export * from "./workspace-dashboard"; export * from "./workspace-dashboard";
export * from "./projects"; export * from "./projects";
export * from "./profile-preferences";
export * from "./cycles";
export * from "./modules";

View File

@ -0,0 +1,87 @@
import { Dispatch, FC, SetStateAction } from "react";
import { useRouter } from "next/router";
import { ArrowLeft, Plus } from "lucide-react";
// components
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
// ui
import { Button, Tooltip } from "@plane/ui";
import { Icon } from "components/ui";
// helper
import { replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
export interface IModulesHeader {
name: string | undefined;
modulesView: string;
setModulesView: Dispatch<SetStateAction<"grid" | "gantt_chart">>;
}
const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
{
type: "gantt_chart",
icon: "view_timeline",
},
{
type: "grid",
icon: "table_rows",
},
];
export const ModulesHeader: FC<IModulesHeader> = (props) => {
const { name, modulesView, setModulesView } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(name ?? "Project", 32)} Cycles`} />
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-2">
{moduleViewOptions.map((option) => (
<Tooltip
key={option.type}
tooltipContent={<span className="capitalize">{replaceUnderscoreIfSnakeCase(option.type)} Layout</span>}
position="bottom"
>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
modulesView === option.type ? "bg-custom-sidebar-background-80" : "text-custom-sidebar-text-200"
}`}
onClick={() => setModulesView(option.type)}
>
<Icon iconName={option.icon} className={`!text-base ${option.type === "grid" ? "rotate-90" : ""}`} />
</button>
</Tooltip>
))}
<Button
variant="primary"
prependIcon={<Plus />}
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "m" });
document.dispatchEvent(e);
}}
>
Add Module
</Button>
</div>
</div>
);
};

View File

@ -0,0 +1,31 @@
import { useRouter } from "next/router";
import { ArrowLeft } from "lucide-react";
// components
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
export const ProfilePreferencesHeader = () => {
const router = useRouter();
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs>
<BreadcrumbItem title="My Profile Preferences" />
</Breadcrumbs>
</div>
</div>
</div>
);
};

View File

@ -1,23 +1,24 @@
import { useCallback, useState } from "react"; import { useCallback, useState, FC } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { ArrowLeft, Plus } from "lucide-react";
// mobx store // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics"; import { ProjectAnalyticsModal } from "components/analytics";
// ui // ui
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
// icons import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
import { Plus } from "lucide-react";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
// helper
import { truncateText } from "helpers/string.helper";
export const ProjectIssuesHeader: React.FC = observer(() => { export const ProjectIssuesHeader: FC = observer(() => {
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
const router = useRouter(); const router = useRouter();
@ -100,7 +101,27 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
onClose={() => setAnalyticsModal(false)} onClose={() => setAnalyticsModal(false)}
projectDetails={projectDetails ?? undefined} projectDetails={projectDetails ?? undefined}
/> />
<div className="flex items-center gap-2"> <div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Issues`} />
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-3">
<LayoutSelection <LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]} layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)} onChange={(layout) => handleLayoutChange(layout)}
@ -159,6 +180,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
Add Issue Add Issue
</Button> </Button>
</div> </div>
</div>
</> </>
); );
}); });

View File

@ -1,18 +1,52 @@
import { useState } from "react"; import { FC, useState } from "react";
import { useRouter } from "next/router";
// icons
import { ArrowLeft, Plus } from "lucide-react";
// components // components
import { CreateUpdateProjectViewModal } from "components/views"; import { CreateUpdateProjectViewModal } from "components/views";
// components
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
// ui // ui
import { PrimaryButton } from "components/ui"; import { PrimaryButton } from "components/ui";
// icons // helpers
import { Plus } from "lucide-react"; import { truncateText } from "helpers/string.helper";
export const ProjectViewsHeader = () => { interface IProjectViewsHeader {
title: string | undefined;
}
export const ProjectViewsHeader: FC<IProjectViewsHeader> = (props) => {
const { title } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// states
const [createViewModal, setCreateViewModal] = useState(false); const [createViewModal, setCreateViewModal] = useState(false);
return ( return (
<> <>
<CreateUpdateProjectViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} /> <CreateUpdateProjectViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(title ?? "Project", 32)} Cycles`} />
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-2">
<div> <div>
<PrimaryButton <PrimaryButton
type="button" type="button"
@ -26,6 +60,8 @@ export const ProjectViewsHeader = () => {
Create View Create View
</PrimaryButton> </PrimaryButton>
</div> </div>
</div>
</div>
</> </>
); );
}; };

View File

@ -2,12 +2,7 @@ import React from "react";
import type { Props } from "./types"; import type { Props } from "./types";
export const ModuleIcon: React.FC<Props> = ({ export const ModuleIcon: React.FC<Props> = ({ width = "24", height = "24", className, color = "#F15B5B" }) => (
width = "24",
height = "24",
className,
color = "#F15B5B",
}) => (
<svg <svg
width={width} width={width}
height={height} height={height}
@ -19,7 +14,7 @@ export const ModuleIcon: React.FC<Props> = ({
<path <path
d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z"
stroke="#F15B5B" stroke="#F15B5B"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />
@ -27,7 +22,7 @@ export const ModuleIcon: React.FC<Props> = ({
d="M5.84925 4.66667H4.81221C4.73039 4.66667 4.66406 4.733 4.66406 4.81482V5.85185C4.66406 5.93367 4.73039 6 4.81221 6H5.84925C5.93107 6 5.9974 5.93367 5.9974 5.85185V4.81482C5.9974 4.733 5.93107 4.66667 5.84925 4.66667Z" d="M5.84925 4.66667H4.81221C4.73039 4.66667 4.66406 4.733 4.66406 4.81482V5.85185C4.66406 5.93367 4.73039 6 4.81221 6H5.84925C5.93107 6 5.9974 5.93367 5.9974 5.85185V4.81482C5.9974 4.733 5.93107 4.66667 5.84925 4.66667Z"
fill={color} fill={color}
stroke="#F15B5B" stroke="#F15B5B"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />
@ -35,7 +30,7 @@ export const ModuleIcon: React.FC<Props> = ({
d="M5.84925 10H4.81221C4.73039 10 4.66406 10.0663 4.66406 10.1481V11.1852C4.66406 11.267 4.73039 11.3333 4.81221 11.3333H5.84925C5.93107 11.3333 5.9974 11.267 5.9974 11.1852V10.1481C5.9974 10.0663 5.93107 10 5.84925 10Z" d="M5.84925 10H4.81221C4.73039 10 4.66406 10.0663 4.66406 10.1481V11.1852C4.66406 11.267 4.73039 11.3333 4.81221 11.3333H5.84925C5.93107 11.3333 5.9974 11.267 5.9974 11.1852V10.1481C5.9974 10.0663 5.93107 10 5.84925 10Z"
fill={color} fill={color}
stroke="#F15B5B" stroke="#F15B5B"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />
@ -43,7 +38,7 @@ export const ModuleIcon: React.FC<Props> = ({
d="M11.1852 4.66667H10.1481C10.0663 4.66667 10 4.733 10 4.81482V5.85185C10 5.93367 10.0663 6 10.1481 6H11.1852C11.267 6 11.3333 5.93367 11.3333 5.85185V4.81482C11.3333 4.733 11.267 4.66667 11.1852 4.66667Z" d="M11.1852 4.66667H10.1481C10.0663 4.66667 10 4.733 10 4.81482V5.85185C10 5.93367 10.0663 6 10.1481 6H11.1852C11.267 6 11.3333 5.93367 11.3333 5.85185V4.81482C11.3333 4.733 11.267 4.66667 11.1852 4.66667Z"
fill={color} fill={color}
stroke="#F15B5B" stroke="#F15B5B"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />
@ -51,7 +46,7 @@ export const ModuleIcon: React.FC<Props> = ({
d="M11.1852 10H10.1481C10.0663 10 10 10.0663 10 10.1481V11.1852C10 11.267 10.0663 11.3333 10.1481 11.3333H11.1852C11.267 11.3333 11.3333 11.267 11.3333 11.1852V10.1481C11.3333 10.0663 11.267 10 11.1852 10Z" d="M11.1852 10H10.1481C10.0663 10 10 10.0663 10 10.1481V11.1852C10 11.267 10.0663 11.3333 10.1481 11.3333H11.1852C11.267 11.3333 11.3333 11.267 11.3333 11.1852V10.1481C11.3333 10.0663 11.267 10 11.1852 10Z"
fill={color} fill={color}
stroke="#F15B5B" stroke="#F15B5B"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />

View File

@ -1,6 +1,17 @@
export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"]; export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"];
export const THEMES_OBJ = [ export interface I_THEME_OPTION {
value: string;
label: string;
type: string;
icon: {
border: string;
color1: string;
color2: string;
};
}
export const THEME_OPTIONS: I_THEME_OPTION[] = [
{ {
value: "system", value: "system",
label: "System Preference", label: "System Preference",

View File

@ -1,6 +1,6 @@
import { FC, ReactNode } from "react"; import { FC, ReactNode } from "react";
// layouts // layouts
import { UserAuthWrapper, WorkspaceAuthWrapper } from "layouts/auth-layout"; import { UserAuthWrapper, WorkspaceAuthWrapper, ProjectAuthWrapper } from "layouts/auth-layout";
// components // components
import { CommandPalette } from "components/command-palette"; import { CommandPalette } from "components/command-palette";
import { AppSidebar } from "./sidebar"; import { AppSidebar } from "./sidebar";
@ -8,10 +8,11 @@ import { AppSidebar } from "./sidebar";
export interface IAppLayout { export interface IAppLayout {
children: ReactNode; children: ReactNode;
header: ReactNode; header: ReactNode;
withProjectWrapper?: boolean;
} }
export const AppLayout: FC<IAppLayout> = (props) => { export const AppLayout: FC<IAppLayout> = (props) => {
const { children, header } = props; const { children, header, withProjectWrapper = false } = props;
return ( return (
<> <>
@ -21,10 +22,11 @@ export const AppLayout: FC<IAppLayout> = (props) => {
<div className="relative flex h-screen w-full overflow-hidden"> <div className="relative flex h-screen w-full overflow-hidden">
<AppSidebar /> <AppSidebar />
<main className="relative flex flex-col h-full w-full overflow-hidden bg-custom-background-100"> <main className="relative flex flex-col h-full w-full overflow-hidden bg-custom-background-100">
{/* <div className="relative w-full">{header}</div> */}
{header} {header}
<div className="h-full w-full overflow-hidden"> <div className="h-full w-full overflow-hidden">
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">{children}</div> <div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">
{withProjectWrapper ? <ProjectAuthWrapper>{children}</ProjectAuthWrapper> : <>{children}</>}
</div>
</div> </div>
</main> </main>
</div> </div>

View File

@ -1,2 +1,3 @@
export * from "./user-wrapper"; export * from "./user-wrapper";
export * from "./workspace-wrapper"; export * from "./workspace-wrapper";
export * from "./project-wrapper";

View File

@ -0,0 +1,91 @@
import { FC, ReactNode } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { Spinner } from "@plane/ui";
import { JoinProject } from "components/auth-screens";
import { EmptyState } from "components/common";
// images
import emptyProject from "public/empty-state/project.svg";
interface IProjectAuthWrapper {
children: ReactNode;
}
export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = (props) => {
const { children } = props;
// store
const { user: userStore, project: projectStore } = useMobxStore();
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// fetching user project member information
useSWR(
workspaceSlug && projectId ? `PROJECT_MEMBERS_ME_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId
? () => userStore.fetchUserProjectInfo(workspaceSlug.toString(), projectId.toString())
: null
);
// fetching project labels
useSWR(
workspaceSlug && projectId ? `PROJECT_LABELS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectLabels(workspaceSlug.toString(), projectId.toString())
: null
);
// fetching project members
useSWR(
workspaceSlug && projectId ? `PROJECT_MEMBERS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectMembers(workspaceSlug.toString(), projectId.toString())
: null
);
// fetching project states
useSWR(
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectStates(workspaceSlug.toString(), projectId.toString())
: null
);
// check if the project member apis is loading
if (!userStore.projectMemberInfo && userStore.hasPermissionToProject === null) {
return (
<div className="grid h-screen place-items-center p-4 bg-custom-background-100">
<div className="flex flex-col items-center gap-3 text-center">
<Spinner />
</div>
</div>
);
}
// check if the user don't have permission to access the project
if (userStore.hasPermissionToProject === false && !userStore.projectNotFound) {
<JoinProject />;
}
// check if the project info is not found.
if (userStore.hasPermissionToProject === false && userStore.projectNotFound) {
<div className="container grid h-screen place-items-center bg-custom-background-100">
<EmptyState
title="No such project exists"
description="Try creating a new project"
image={emptyProject}
primaryButton={{
text: "Create Project",
onClick: () => {
const e = new KeyboardEvent("keydown", {
key: "p",
});
document.dispatchEvent(e);
},
}}
/>
</div>;
}
return <>{children}</>;
};

View File

@ -5,22 +5,23 @@ import useSWR from "swr";
import { UserService } from "services/user.service"; import { UserService } from "services/user.service";
// ui // ui
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
// fetch-keys // store
import { CURRENT_USER } from "constants/fetch-keys"; import { useMobxStore } from "lib/mobx/store-provider";
export interface IUserAuthWrapper { export interface IUserAuthWrapper {
children: ReactNode; children: ReactNode;
} }
// services
const userService = new UserService();
export const UserAuthWrapper: FC<IUserAuthWrapper> = (props) => { export const UserAuthWrapper: FC<IUserAuthWrapper> = (props) => {
const { children } = props; const { children } = props;
// store
const { user: userStore } = useMobxStore();
// router // router
const router = useRouter(); const router = useRouter();
// fetching user information // fetching user information
const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser()); const { data: currentUser, error } = useSWR("CURRENT_USER", () => userStore.fetchCurrentUser());
// fetching user settings
useSWR("CURRENT_USER_SETTINGS", () => userStore.fetchCurrentUserSettings());
if (!currentUser && !error) { if (!currentUser && !error) {
return ( return (

View File

@ -10,11 +10,6 @@ import { useMobxStore } from "lib/mobx/store-provider";
export interface IWorkspaceAuthWrapper { export interface IWorkspaceAuthWrapper {
children: ReactNode; children: ReactNode;
noHeader?: boolean;
bg?: "primary" | "secondary";
breadcrumbs?: JSX.Element;
left?: JSX.Element;
right?: JSX.Element;
} }
export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props) => { export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props) => {
@ -45,12 +40,8 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
workspaceSlug ? () => workspaceStore.fetchWorkspaceLabels(workspaceSlug.toString()) : null workspaceSlug ? () => workspaceStore.fetchWorkspaceLabels(workspaceSlug.toString()) : null
); );
// console.log("workspaceSlug", workspaceSlug);
// console.log("userStore.memberInfo", userStore.memberInfo);
// while data is being loaded // while data is being loaded
if (!userStore.memberInfo && userStore.hasPermissionToWorkspace === null) { if (!userStore.workspaceMemberInfo && userStore.hasPermissionToWorkspace === null) {
return ( return (
<div className="grid h-screen place-items-center p-4 bg-custom-background-100"> <div className="grid h-screen place-items-center p-4 bg-custom-background-100">
<div className="flex flex-col items-center gap-3 text-center"> <div className="flex flex-col items-center gap-3 text-center">

View File

@ -1,11 +1,13 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
// next themes // next themes
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
import { observer } from "mobx-react-lite";
const MobxStoreInit = () => { const MobxStoreInit = observer(() => {
const { const {
theme: themeStore, theme: themeStore,
user: userStore, user: userStore,
@ -16,12 +18,16 @@ const MobxStoreInit = () => {
projectViews: projectViewsStore, projectViews: projectViewsStore,
inbox: inboxStore, inbox: inboxStore,
} = useMobxStore(); } = useMobxStore();
// state
const [dom, setDom] = useState<any>();
// theme // theme
const { setTheme } = useTheme(); const { setTheme } = useTheme();
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId, globalViewId, viewId, inboxId } = router.query; const { workspaceSlug, projectId, moduleId, globalViewId, viewId, inboxId } = router.query;
// const dom = useMemo(() => window && window.document?.querySelector<HTMLElement>("[data-theme='custom']"), [document]);
useEffect(() => { useEffect(() => {
// sidebar collapsed toggle // sidebar collapsed toggle
if (localStorage && localStorage.getItem("app_sidebar_collapsed") && themeStore?.sidebarCollapsed === null) if (localStorage && localStorage.getItem("app_sidebar_collapsed") && themeStore?.sidebarCollapsed === null)
@ -32,20 +38,20 @@ const MobxStoreInit = () => {
: false : false
: false : false
); );
// theme
if (themeStore.theme === null && userStore?.currentUserSettings) {
let currentTheme = localStorage.getItem("theme");
currentTheme = currentTheme && currentTheme != "undefined" ? currentTheme : "system";
// validating the theme and applying for initial state
if (currentTheme) {
setTheme(currentTheme);
themeStore.setTheme({ theme: { theme: currentTheme } });
}
}
}, [themeStore, userStore, setTheme]); }, [themeStore, userStore, setTheme]);
useEffect(() => {
if (!userStore.currentUser) return;
if (window) {
setDom(window.document?.querySelector<HTMLElement>("[data-theme='custom']"));
}
setTheme(userStore.currentUser?.theme?.theme || "system");
if (userStore.currentUser?.theme?.theme === "custom" && dom) {
console.log("userStore.currentUser?.theme?.theme", userStore.currentUser?.theme);
applyTheme(userStore.currentUser?.theme?.palette, false);
} else unsetCustomCssVariables();
}, [userStore.currentUser, setTheme, dom]);
useEffect(() => { useEffect(() => {
if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString()); if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
if (projectId) projectStore.setProjectId(projectId.toString()); if (projectId) projectStore.setProjectId(projectId.toString());
@ -69,6 +75,6 @@ const MobxStoreInit = () => {
]); ]);
return <></>; return <></>;
}; });
export default MobxStoreInit; export default MobxStoreInit;

View File

@ -1,60 +1,53 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import { useTheme } from "next-themes";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth"; import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy"; import { AppLayout } from "layouts/app-layout";
// components // components
import { CustomThemeSelector, ThemeSwitch } from "components/core"; import { CustomThemeSelector, ThemeSwitch } from "components/core";
import { SettingsSidebar } from "components/project";
import { ProfilePreferencesHeader } from "components/headers";
// ui // ui
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // constants
// types import { I_THEME_OPTION, THEME_OPTIONS } from "constants/themes";
import { ICustomTheme } from "types";
// mobx react lite
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { SettingsSidebar } from "components/project";
const ProfilePreferences = observer(() => { const ProfilePreferencesPage = observer(() => {
const { user: myProfile } = useUserAuth(); const { user: userStore } = useMobxStore();
// states
const store: any = useMobxStore(); const [currentTheme, setCurrentTheme] = useState<I_THEME_OPTION | null>(null);
// computed
// console.log("store", store?.theme?.theme); const userTheme = userStore.currentUser?.theme;
// console.log("theme", theme); // hooks
const { setTheme } = useTheme();
const [customThemeSelectorOptions, setCustomThemeSelectorOptions] = useState(false); const { setToastAlert } = useToast();
const [preLoadedData, setPreLoadedData] = useState<ICustomTheme | null>(null);
useEffect(() => { useEffect(() => {
if (store?.user && store?.theme?.theme === "custom") { if (userTheme) {
const currentTheme = store?.user?.currentUserSettings?.theme; const userThemeOption = THEME_OPTIONS.find((t) => t.value === userTheme?.theme);
if (currentTheme.palette) if (userThemeOption) {
setPreLoadedData({ setCurrentTheme(userThemeOption);
background: currentTheme.background !== "" ? currentTheme.background : "#0d101b",
text: currentTheme.text !== "" ? currentTheme.text : "#c5c5c5",
primary: currentTheme.primary !== "" ? currentTheme.primary : "#3f76ff",
sidebarBackground: currentTheme.sidebarBackground !== "" ? currentTheme.sidebarBackground : "#0d101b",
sidebarText: currentTheme.sidebarText !== "" ? currentTheme.sidebarText : "#c5c5c5",
darkPalette: false,
palette: currentTheme.palette !== ",,,," ? currentTheme.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
theme: "custom",
});
setCustomThemeSelectorOptions(() => true);
} }
}, [store, store?.theme?.theme]); }
}, [userTheme]);
const handleThemeChange = (themeOption: I_THEME_OPTION) => {
setTheme(themeOption.value);
userStore.updateCurrentUserTheme(themeOption.value).catch(() => {
setToastAlert({
title: "Failed to Update the theme",
type: "error",
});
});
};
return ( return (
<WorkspaceAuthorizationLayout <AppLayout header={<ProfilePreferencesHeader />}>
breadcrumbs={ <>
<Breadcrumbs> {userStore.currentUser ? (
<BreadcrumbItem title="My Profile Preferences" />
</Breadcrumbs>
}
>
{myProfile ? (
<div className="flex flex-row gap-2 h-full"> <div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0"> <div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar /> <SettingsSidebar />
@ -70,14 +63,10 @@ const ProfilePreferences = observer(() => {
<p className="text-sm text-custom-text-200">Select or customize your interface color scheme.</p> <p className="text-sm text-custom-text-200">Select or customize your interface color scheme.</p>
</div> </div>
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<ThemeSwitch <ThemeSwitch value={currentTheme} onChange={handleThemeChange} />
setPreLoadedData={setPreLoadedData}
customThemeSelectorOptions={customThemeSelectorOptions}
setCustomThemeSelectorOptions={setCustomThemeSelectorOptions}
/>
</div> </div>
</div> </div>
{customThemeSelectorOptions && <CustomThemeSelector preLoadedData={preLoadedData} />} {userTheme?.theme === "custom" && <CustomThemeSelector />}
</div> </div>
</div> </div>
) : ( ) : (
@ -85,8 +74,9 @@ const ProfilePreferences = observer(() => {
<Spinner /> <Spinner />
</div> </div>
)} )}
</WorkspaceAuthorizationLayout> </>
</AppLayout>
); );
}); });
export default ProfilePreferences; export default ProfilePreferencesPage;

View File

@ -2,25 +2,23 @@ import { Fragment, useCallback, useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
import useSWR from "swr"; import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy"; import { AppLayout } from "layouts/app-layout";
// components // components
import { CyclesHeader } from "components/headers";
import { CyclesView, ActiveCycleDetails } from "components/cycles"; import { CyclesView, ActiveCycleDetails } from "components/cycles";
import { CycleCreateEditModal } from "components/cycles/cycle-create-edit-modal"; import { CycleCreateEditModal } from "components/cycles/cycle-create-edit-modal";
// ui // ui
import { Button } from "@plane/ui";
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// images // images
import emptyCycle from "public/empty-state/cycle.svg"; import emptyCycle from "public/empty-state/cycle.svg";
// types // types
import { TCycleView, TCycleLayout } from "types"; import { TCycleView, TCycleLayout } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// helper
import { truncateText } from "helpers/string.helper";
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// constants // constants
import { CYCLE_TAB_LIST, CYCLE_VIEWS } from "constants/cycle"; import { CYCLE_TAB_LIST, CYCLE_VIEWS } from "constants/cycle";
// lib cookie // lib cookie
@ -85,25 +83,7 @@ const ProjectCyclesPage: NextPage = observer(() => {
const cycleLayout = cycleStore?.cycleLayout; const cycleLayout = cycleStore?.cycleLayout;
return ( return (
<ProjectAuthorizationWrapper <AppLayout header={<CyclesHeader name={projectDetails?.name} />} withProjectWrapper>
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Cycles`} />
</Breadcrumbs>
}
right={
<Button
variant="primary"
prependIcon={<Plus />}
onClick={() => {
setCreateModal(true);
}}
>
Add Cycle
</Button>
}
>
<CycleCreateEditModal <CycleCreateEditModal
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
@ -226,7 +206,7 @@ const ProjectCyclesPage: NextPage = observer(() => {
</Tab.Panels> </Tab.Panels>
</Tab.Group> </Tab.Group>
)} )}
</ProjectAuthorizationWrapper> </AppLayout>
); );
}); });

View File

@ -1,7 +1,7 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { NextPage } from "next";
import useSWR from "swr"; import useSWR from "swr";
// hooks
// mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
@ -12,8 +12,6 @@ import { ProjectInboxHeader } from "components/headers";
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types
import type { NextPage } from "next";
const ProjectInbox: NextPage = () => { const ProjectInbox: NextPage = () => {
const router = useRouter(); const router = useRouter();

View File

@ -1,38 +1,20 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// mobx // mobx
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// services
import { ProjectService } from "services/project";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// helper
import { truncateText } from "helpers/string.helper";
// components // components
import { ProjectLayoutRoot } from "components/issues"; import { ProjectLayoutRoot } from "components/issues";
import { ProjectIssuesHeader } from "components/headers"; import { ProjectIssuesHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // layouts
import { PROJECT_DETAILS } from "constants/fetch-keys"; import { AppLayout } from "layouts/app-layout";
// services
const projectService = new ProjectService();
const ProjectIssues: NextPage = () => { const ProjectIssues: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { issueFilter: issueFilterStore, project: projectStore, inbox: inboxStore } = useMobxStore(); const { issueFilter: issueFilterStore } = useMobxStore();
const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
);
// TODO: update the fetch keys // TODO: update the fetch keys
useSWR( useSWR(
@ -42,57 +24,12 @@ const ProjectIssues: NextPage = () => {
: null : null
); );
useSWR(
workspaceSlug && projectId ? "REVALIDATE_PROJECT_STATES_LIST" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectStates(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? "REVALIDATE_PROJECT_LABELS_LIST" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectLabels(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? "REVALIDATE_PROJECT_MEMBERS_LIST" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectMembers(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? "REVALIDATE_PROJECT_DETAILS" : null,
workspaceSlug && projectId
? () => projectStore.fetchProjectDetails(workspaceSlug.toString(), projectId.toString())
: null
);
useSWR(
workspaceSlug && projectId ? "REVALIDATE_INBOXES_LIST" : null,
workspaceSlug && projectId
? () => inboxStore.fetchInboxesList(workspaceSlug.toString(), projectId.toString())
: null
);
// TODO: remove all the above fetching logic to project auth wrapper
return ( return (
<ProjectAuthorizationWrapper <AppLayout header={<ProjectIssuesHeader />} withProjectWrapper>
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Issues`} />
</Breadcrumbs>
}
right={<ProjectIssuesHeader />}
bg="secondary"
>
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">
<ProjectLayoutRoot /> <ProjectLayoutRoot />
</div> </div>
</ProjectAuthorizationWrapper> </AppLayout>
); );
}; };

View File

@ -1,11 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { NextPage } from "next";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy"; import { AppLayout } from "layouts/app-layout";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// services // services
@ -13,45 +11,30 @@ import { ProjectService } from "services/project";
import { ModuleService } from "services/module.service"; import { ModuleService } from "services/module.service";
// components // components
import { CreateUpdateModuleModal, ModulesListGanttChartView, SingleModuleCard } from "components/modules"; import { CreateUpdateModuleModal, ModulesListGanttChartView, SingleModuleCard } from "components/modules";
import { ModulesHeader } from "components/headers";
// ui // ui
import { Button, Loader, Tooltip } from "@plane/ui"; import { Loader } from "@plane/ui";
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { GanttChart, LayoutGrid, Plus } from "lucide-react"; import { Plus } from "lucide-react";
// images // images
import emptyModule from "public/empty-state/module.svg"; import emptyModule from "public/empty-state/module.svg";
// types // types
import { IModule, SelectModuleType } from "types/modules"; import { IModule, SelectModuleType } from "types/modules";
import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { MODULE_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
// helper
import { replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper";
const moduleViewOptions: { type: "grid" | "gantt_chart"; icon: any }[] = [
{
type: "gantt_chart",
icon: GanttChart,
},
{
type: "grid",
icon: LayoutGrid,
},
];
// services // services
const projectService = new ProjectService(); const projectService = new ProjectService();
const moduleService = new ModuleService(); const moduleService = new ModuleService();
const ProjectModules: NextPage = () => { const ProjectModules: NextPage = () => {
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
const [createUpdateModule, setCreateUpdateModule] = useState(false);
const [modulesView, setModulesView] = useState<"grid" | "gantt_chart">("grid");
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// states
const [modulesView, setModulesView] = useState<"grid" | "gantt_chart">("grid");
const [selectedModule, setSelectedModule] = useState<SelectModuleType>();
const [createUpdateModule, setCreateUpdateModule] = useState(false);
const { user } = useUserAuth(); const { user } = useUserAuth();
@ -80,44 +63,8 @@ const ProjectModules: NextPage = () => {
}, [createUpdateModule]); }, [createUpdateModule]);
return ( return (
<ProjectAuthorizationWrapper <AppLayout
breadcrumbs={ header={<ModulesHeader name={activeProject?.name} modulesView={modulesView} setModulesView={setModulesView} />}
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(activeProject?.name ?? "Project", 32)} Modules`} />
</Breadcrumbs>
}
right={
<div className="flex items-center gap-2">
{moduleViewOptions.map((option) => (
<Tooltip
key={option.type}
tooltipContent={<span className="capitalize">{replaceUnderscoreIfSnakeCase(option.type)} Layout</span>}
position="bottom"
>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
modulesView === option.type ? "bg-custom-sidebar-background-80" : "text-custom-sidebar-text-200"
}`}
onClick={() => setModulesView(option.type)}
>
<option.icon className="h-4 w-4" />
</button>
</Tooltip>
))}
<Button
variant="primary"
prependIcon={<Plus />}
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "m" });
document.dispatchEvent(e);
}}
>
Add Module
</Button>
</div>
}
> >
<CreateUpdateModuleModal <CreateUpdateModuleModal
isOpen={createUpdateModule} isOpen={createUpdateModule}
@ -173,7 +120,7 @@ const ProjectModules: NextPage = () => {
<Loader.Item height="100px" /> <Loader.Item height="100px" />
</Loader> </Loader>
)} )}
</ProjectAuthorizationWrapper> </AppLayout>
); );
}; };

View File

@ -1,21 +1,14 @@
import React from "react"; import React from "react";
import type { NextPage } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// hooks
// mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { ProjectViewsHeader } from "components/headers"; import { ProjectViewsHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// components
import { ProjectViewsList } from "components/views"; import { ProjectViewsList } from "components/views";
// types // layouts
import type { NextPage } from "next"; import { AppLayout } from "layouts/app-layout";
const ProjectViews: NextPage = () => { const ProjectViews: NextPage = () => {
const router = useRouter(); const router = useRouter();
@ -43,17 +36,9 @@ const ProjectViews: NextPage = () => {
: undefined; : undefined;
return ( return (
<ProjectAuthorizationWrapper <AppLayout header={<ProjectViewsHeader title={projectDetails?.name} />}>
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${projectDetails?.name ?? "Project"} Views`} />
</Breadcrumbs>
}
right={<ProjectViewsHeader />}
>
<ProjectViewsList /> <ProjectViewsList />
</ProjectAuthorizationWrapper> </AppLayout>
); );
}; };

View File

@ -342,13 +342,12 @@ export class ProjectStore implements IProjectStore {
this.error = null; this.error = null;
const labelResponse = await this.issueLabelService.getProjectIssueLabels(workspaceSlug, projectId); const labelResponse = await this.issueLabelService.getProjectIssueLabels(workspaceSlug, projectId);
const _labels = {
runInAction(() => {
this.labels = {
...this.labels, ...this.labels,
[projectId]: labelResponse, [projectId]: labelResponse,
}; };
runInAction(() => {
this.labels = _labels;
this.loader = false; this.loader = false;
this.error = null; this.error = null;
}); });

View File

@ -1,35 +1,58 @@
// mobx // mobx
import { action, observable, runInAction, makeObservable } from "mobx"; import { action, observable, runInAction, makeObservable } from "mobx";
// services // services
import { ProjectService } from "services/project";
import { UserService } from "services/user.service"; import { UserService } from "services/user.service";
import { WorkspaceService } from "services/workspace.service"; import { WorkspaceService } from "services/workspace.service";
// interfaces // interfaces
import { IUser, IUserSettings } from "types/users"; import { IUser, IUserSettings } from "types/users";
import { IWorkspaceMember, IProjectMember } from "types";
interface IUserStore { interface IUserStore {
loader: boolean; loader: boolean;
currentUser: IUser | null; currentUser: IUser | null;
currentUserSettings: IUserSettings | null; currentUserSettings: IUserSettings | null;
dashboardInfo: any; dashboardInfo: any;
memberInfo: any;
workspaceMemberInfo: any;
hasPermissionToWorkspace: boolean | null; hasPermissionToWorkspace: boolean | null;
projectMemberInfo: any;
projectNotFound: boolean;
hasPermissionToProject: boolean | null;
fetchCurrentUser: () => Promise<IUser>; fetchCurrentUser: () => Promise<IUser>;
fetchCurrentUserSettings: () => Promise<IUserSettings>; fetchCurrentUserSettings: () => Promise<IUserSettings>;
fetchUserWorkspaceInfo: (workspaceSlug: string) => Promise<IWorkspaceMember>;
fetchUserProjectInfo: (workspaceSlug: string, projectId: string) => Promise<IProjectMember>;
updateTourCompleted: () => Promise<void>; updateTourCompleted: () => Promise<void>;
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser>;
} }
class UserStore implements IUserStore { class UserStore implements IUserStore {
loader: boolean = false; loader: boolean = false;
currentUser: IUser | null = null; currentUser: IUser | null = null;
currentUserSettings: IUserSettings | null = null; currentUserSettings: IUserSettings | null = null;
dashboardInfo: any = null; dashboardInfo: any = null;
memberInfo: any = null;
workspaceMemberInfo: any = null;
hasPermissionToWorkspace: boolean | null = null; hasPermissionToWorkspace: boolean | null = null;
projectMemberInfo: any = null;
projectNotFound: boolean = false;
hasPermissionToProject: boolean | null = null;
// root store // root store
rootStore; rootStore;
// services // services
userService; userService;
workspaceService; workspaceService;
projectService;
constructor(_rootStore: any) { constructor(_rootStore: any) {
makeObservable(this, { makeObservable(this, {
@ -38,8 +61,11 @@ class UserStore implements IUserStore {
currentUser: observable.ref, currentUser: observable.ref,
currentUserSettings: observable.ref, currentUserSettings: observable.ref,
dashboardInfo: observable.ref, dashboardInfo: observable.ref,
memberInfo: observable.ref, workspaceMemberInfo: observable.ref,
hasPermissionToWorkspace: observable.ref, hasPermissionToWorkspace: observable.ref,
projectMemberInfo: observable.ref,
projectNotFound: observable.ref,
hasPermissionToProject: observable.ref,
// action // action
fetchCurrentUser: action, fetchCurrentUser: action,
fetchCurrentUserSettings: action, fetchCurrentUserSettings: action,
@ -48,6 +74,7 @@ class UserStore implements IUserStore {
this.rootStore = _rootStore; this.rootStore = _rootStore;
this.userService = new UserService(); this.userService = new UserService();
this.workspaceService = new WorkspaceService(); this.workspaceService = new WorkspaceService();
this.projectService = new ProjectService();
} }
fetchCurrentUser = async () => { fetchCurrentUser = async () => {
@ -94,7 +121,7 @@ class UserStore implements IUserStore {
try { try {
const response = await this.workspaceService.workspaceMemberMe(workspaceSlug.toString()); const response = await this.workspaceService.workspaceMemberMe(workspaceSlug.toString());
runInAction(() => { runInAction(() => {
this.memberInfo = response; this.workspaceMemberInfo = response;
this.hasPermissionToWorkspace = true; this.hasPermissionToWorkspace = true;
}); });
return response; return response;
@ -106,6 +133,29 @@ class UserStore implements IUserStore {
} }
}; };
fetchUserProjectInfo = async (workspaceSlug: string, projectId: string) => {
try {
const response = await this.projectService.projectMemberMe(workspaceSlug, projectId);
console.log("response", response);
runInAction(() => {
this.projectMemberInfo = response;
this.hasPermissionToWorkspace = true;
});
return response;
} catch (error: any) {
runInAction(() => {
this.hasPermissionToWorkspace = false;
});
if (error?.status === 404) {
runInAction(() => {
this.projectNotFound = true;
});
}
throw error;
}
};
updateTourCompleted = async () => { updateTourCompleted = async () => {
try { try {
if (this.currentUser) { if (this.currentUser) {
@ -123,107 +173,37 @@ class UserStore implements IUserStore {
} }
}; };
// setCurrentUser = async () => { updateCurrentUser = async (data: Partial<IUser>) => {
// try { try {
// let userResponse: ICurrentUser | null = await UserService.currentUser(); const response = await this.userService.updateUser(data);
// userResponse = userResponse || null; runInAction(() => {
this.currentUser = response;
});
return response;
} catch (error) {
throw error;
}
};
// if (userResponse) { updateCurrentUserTheme = async (theme: string) => {
// const userPayload: ICurrentUser = { try {
// id: userResponse?.id, runInAction(() => {
// avatar: userResponse?.avatar, this.currentUser = {
// first_name: userResponse?.first_name, ...this.currentUser,
// last_name: userResponse?.last_name, theme: {
// username: userResponse?.username, ...this.currentUser?.theme,
// email: userResponse?.email, theme,
// mobile_number: userResponse?.mobile_number, },
// is_email_verified: userResponse?.is_email_verified, } as IUser;
// is_tour_completed: userResponse?.is_tour_completed, });
// onboarding_step: userResponse?.onboarding_step, const response = await this.userService.updateUser({
// is_onboarded: userResponse?.is_onboarded, theme: { ...this.currentUser?.theme, theme },
// role: userResponse?.role, } as IUser);
// }; return response;
// runInAction(() => { } catch (error) {
// this.currentUser = userPayload; throw error;
// }); }
// } };
// } catch (error) {
// console.error("Fetching current user error", error);
// }
// };
// setCurrentUserSettings = async () => {
// try {
// let userSettingsResponse: ICurrentUserSettings | null = await UserService.currentUser();
// userSettingsResponse = userSettingsResponse || null;
// if (userSettingsResponse) {
// const themePayload = {
// theme: { ...userSettingsResponse?.theme },
// };
// runInAction(() => {
// this.currentUserSettings = themePayload;
// this.rootStore.theme.setTheme(themePayload);
// });
// }
// } catch (error) {
// console.error("Fetching current user error", error);
// }
// };
// updateCurrentUser = async (user: ICurrentUser) => {
// try {
// let userResponse: ICurrentUser = await UserService.updateUser(user);
// userResponse = userResponse || null;
// if (userResponse) {
// const userPayload: ICurrentUser = {
// id: userResponse?.id,
// avatar: userResponse?.avatar,
// first_name: userResponse?.first_name,
// last_name: userResponse?.last_name,
// username: userResponse?.username,
// email: userResponse?.email,
// mobile_number: userResponse?.mobile_number,
// is_email_verified: userResponse?.is_email_verified,
// is_tour_completed: userResponse?.is_tour_completed,
// onboarding_step: userResponse?.onboarding_step,
// is_onboarded: userResponse?.is_onboarded,
// role: userResponse?.role,
// };
// runInAction(() => {
// this.currentUser = userPayload;
// });
// return userPayload;
// }
// } catch (error) {
// console.error("Updating user error", error);
// return error;
// }
// };
// updateCurrentUserSettings = async (userTheme: ICurrentUserSettings) => {
// try {
// let userSettingsResponse: ICurrentUserSettings = await UserService.updateUser(userTheme);
// userSettingsResponse = userSettingsResponse || null;
// if (userSettingsResponse) {
// const themePayload = {
// theme: { ...userSettingsResponse?.theme },
// };
// runInAction(() => {
// this.currentUserSettings = themePayload;
// this.rootStore.theme.setTheme(themePayload);
// });
// return themePayload;
// }
// } catch (error) {
// console.error("Updating user settings error", error);
// return error;
// }
// };
// init load
initialLoad() {}
} }
export default UserStore; export default UserStore;

View File

@ -26,7 +26,7 @@ export interface IUser {
last_workspace_id: string; last_workspace_id: string;
user_timezone: string; user_timezone: string;
username: string; username: string;
theme: ICustomTheme; theme: IUserTheme;
} }
export interface IUserSettings { export interface IUserSettings {
@ -41,7 +41,7 @@ export interface IUserSettings {
}; };
} }
export interface ICustomTheme { export interface IUserTheme {
background: string; background: string;
text: string; text: string;
primary: string; primary: string;