forked from github/plane
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:
parent
e496cec49f
commit
98b1a078de
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from "./input";
|
export * from "./input";
|
||||||
export * from "./textarea";
|
export * from "./textarea";
|
||||||
|
export * from "./input-color-picker"
|
91
packages/ui/src/form-fields/input-color-picker.tsx
Normal file
91
packages/ui/src/form-fields/input-color-picker.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -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={() => {
|
||||||
|
@ -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";
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
56
web/components/headers/cycles.tsx
Normal file
56
web/components/headers/cycles.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -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";
|
||||||
|
87
web/components/headers/modules.tsx
Normal file
87
web/components/headers/modules.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
31
web/components/headers/profile-preferences.tsx
Normal file
31
web/components/headers/profile-preferences.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
@ -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",
|
||||||
|
@ -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>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from "./user-wrapper";
|
export * from "./user-wrapper";
|
||||||
export * from "./workspace-wrapper";
|
export * from "./workspace-wrapper";
|
||||||
|
export * from "./project-wrapper";
|
||||||
|
91
web/layouts/auth-layout/project-wrapper.tsx
Normal file
91
web/layouts/auth-layout/project-wrapper.tsx
Normal 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}</>;
|
||||||
|
};
|
@ -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 (
|
||||||
|
@ -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">
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
4
web/types/users.d.ts
vendored
4
web/types/users.d.ts
vendored
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user