mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge pull request #1784 from makeplane/stage-release
promote: stage-release to master v0.10.1-patch
This commit is contained in:
commit
9ff8994c0e
@ -332,7 +332,7 @@ class BulkImportIssuesEndpoint(BaseAPIView):
|
||||
# if there is no default state assign any random state
|
||||
if default_state is None:
|
||||
default_state = State.objects.filter(
|
||||
~Q(name="Triage"), sproject_id=project_id
|
||||
~Q(name="Triage"), project_id=project_id
|
||||
).first()
|
||||
|
||||
# Get the maximum sequence_id
|
||||
|
@ -75,6 +75,7 @@ from plane.db.models import (
|
||||
Label,
|
||||
WorkspaceMember,
|
||||
CycleIssue,
|
||||
IssueReaction,
|
||||
)
|
||||
from plane.api.permissions import (
|
||||
WorkSpaceBasePermission,
|
||||
@ -1321,6 +1322,12 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
||||
)
|
||||
.select_related("project", "workspace", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
queryset=IssueReaction.objects.select_related("actor"),
|
||||
)
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.annotate(
|
||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
|
@ -5,6 +5,8 @@ import { Command } from "cmdk";
|
||||
import { THEMES_OBJ } from "constants/themes";
|
||||
import { useTheme } from "next-themes";
|
||||
import { SettingIcon } from "components/icons";
|
||||
import userService from "services/user.service";
|
||||
import useUser from "hooks/use-user";
|
||||
|
||||
type Props = {
|
||||
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
||||
@ -12,24 +14,50 @@ type Props = {
|
||||
|
||||
export const ChangeInterfaceTheme: React.FC<Props> = ({ setIsPaletteOpen }) => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const { user, mutateUser } = useUser();
|
||||
|
||||
const updateUserTheme = (newTheme: string) => {
|
||||
if (!user) return;
|
||||
|
||||
setTheme(newTheme);
|
||||
|
||||
mutateUser((prevData) => {
|
||||
if (!prevData) return prevData;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
theme: {
|
||||
...prevData.theme,
|
||||
theme: newTheme,
|
||||
},
|
||||
};
|
||||
}, false);
|
||||
|
||||
userService.updateUser({
|
||||
theme: {
|
||||
...user.theme,
|
||||
theme: newTheme,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// useEffect only runs on the client, so now we can safely show the UI
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{THEMES_OBJ.map((theme) => (
|
||||
{THEMES_OBJ.filter((t) => t.value !== "custom").map((theme) => (
|
||||
<Command.Item
|
||||
key={theme.value}
|
||||
onSelect={() => {
|
||||
setTheme(theme.value);
|
||||
updateUserTheme(theme.value);
|
||||
setIsPaletteOpen(false);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
@ -1,342 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
// icons
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ChatBubbleLeftEllipsisIcon,
|
||||
Squares2X2Icon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { BlockedIcon, BlockerIcon } from "components/icons";
|
||||
import { Icon } from "components/ui";
|
||||
// helpers
|
||||
import { renderShortDateWithYearFormat, timeAgo } from "helpers/date-time.helper";
|
||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||
// types
|
||||
import RemirrorRichTextEditor from "components/rich-text-editor";
|
||||
|
||||
const activityDetails: {
|
||||
[key: string]: {
|
||||
message?: string;
|
||||
icon: JSX.Element;
|
||||
};
|
||||
} = {
|
||||
assignee: {
|
||||
message: "removed the assignee",
|
||||
icon: <Icon iconName="group" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
assignees: {
|
||||
message: "added a new assignee",
|
||||
icon: <Icon iconName="group" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
blocks: {
|
||||
message: "marked this issue being blocked by",
|
||||
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
||||
},
|
||||
blocking: {
|
||||
message: "marked this issue is blocking",
|
||||
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
||||
},
|
||||
cycles: {
|
||||
message: "set the cycle to",
|
||||
icon: <Icon iconName="contrast" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
labels: {
|
||||
icon: <Icon iconName="sell" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
modules: {
|
||||
message: "set the module to",
|
||||
icon: <Icon iconName="dataset" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
state: {
|
||||
message: "set the state to",
|
||||
icon: <Squares2X2Icon className="h-3 w-3 text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
priority: {
|
||||
message: "set the priority to",
|
||||
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
name: {
|
||||
message: "set the name to",
|
||||
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
description: {
|
||||
message: "updated the description.",
|
||||
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
estimate_point: {
|
||||
message: "set the estimate point to",
|
||||
icon: <Icon iconName="change_history" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
target_date: {
|
||||
message: "set the due date to",
|
||||
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
parent: {
|
||||
message: "set the parent to",
|
||||
icon: <Icon iconName="supervised_user_circle" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
issue: {
|
||||
message: "deleted the issue.",
|
||||
icon: <Icon iconName="delete" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
estimate: {
|
||||
message: "updated the estimate",
|
||||
icon: <Icon iconName="change_history" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
link: {
|
||||
message: "updated the link",
|
||||
icon: <Icon iconName="link" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
attachment: {
|
||||
message: "updated the attachment",
|
||||
icon: <Icon iconName="attach_file" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
archived_at: {
|
||||
message: "archived",
|
||||
icon: <Icon iconName="archive" className="!text-sm text-custom-text-200" aria-hidden="true" />,
|
||||
},
|
||||
};
|
||||
|
||||
export const Feeds: React.FC<any> = ({ activities }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul role="list" className="-mb-4">
|
||||
{activities.map((activity: any, activityIdx: number) => {
|
||||
// determines what type of action is performed
|
||||
let action = activityDetails[activity.field as keyof typeof activityDetails]?.message;
|
||||
if (activity.field === "labels") {
|
||||
action = activity.new_value !== "" ? "added a new label" : "removed the label";
|
||||
} else if (activity.field === "blocking") {
|
||||
action =
|
||||
activity.new_value !== ""
|
||||
? "marked this issue is blocking"
|
||||
: "removed the issue from blocking";
|
||||
} else if (activity.field === "blocks") {
|
||||
action =
|
||||
activity.new_value !== "" ? "marked this issue being blocked by" : "removed blocker";
|
||||
} else if (activity.field === "target_date") {
|
||||
action =
|
||||
activity.new_value && activity.new_value !== ""
|
||||
? "set the due date to"
|
||||
: "removed the due date";
|
||||
} else if (activity.field === "parent") {
|
||||
action =
|
||||
activity.new_value && activity.new_value !== ""
|
||||
? "set the parent to"
|
||||
: "removed the parent";
|
||||
} else if (activity.field === "priority") {
|
||||
action =
|
||||
activity.new_value && activity.new_value !== ""
|
||||
? "set the priority to"
|
||||
: "removed the priority";
|
||||
} else if (activity.field === "description") {
|
||||
action = "updated the";
|
||||
} else if (activity.field === "attachment") {
|
||||
action = `${activity.verb} the`;
|
||||
} else if (activity.field === "link") {
|
||||
action = `${activity.verb} the`;
|
||||
} else if (activity.field === "archived_at") {
|
||||
action =
|
||||
activity.new_value && activity.new_value === "restore"
|
||||
? "restored the issue"
|
||||
: "archived the issue";
|
||||
}
|
||||
// for values that are after the action clause
|
||||
let value: any = activity.new_value ? activity.new_value : activity.old_value;
|
||||
if (
|
||||
activity.verb === "created" &&
|
||||
activity.field !== "cycles" &&
|
||||
activity.field !== "modules" &&
|
||||
activity.field !== "attachment" &&
|
||||
activity.field !== "link" &&
|
||||
activity.field !== "estimate"
|
||||
) {
|
||||
const { project, issue } = activity;
|
||||
value = (
|
||||
<span className="text-custom-text-200">
|
||||
created{" "}
|
||||
<Link href={`/${workspaceSlug}/projects/${project}/issues/${issue}`}>
|
||||
<a className="inline-flex items-center hover:underline">
|
||||
this issue. <ArrowTopRightOnSquareIcon className="ml-1 h-3.5 w-3.5" />
|
||||
</a>
|
||||
</Link>
|
||||
</span>
|
||||
);
|
||||
} else if (activity.field === "state") {
|
||||
value = activity.new_value ? addSpaceIfCamelCase(activity.new_value) : "None";
|
||||
} else if (activity.field === "labels") {
|
||||
let name;
|
||||
let id = "#000000";
|
||||
if (activity.new_value !== "") {
|
||||
name = activity.new_value;
|
||||
id = activity.new_identifier ? activity.new_identifier : id;
|
||||
} else {
|
||||
name = activity.old_value;
|
||||
id = activity.old_identifier ? activity.old_identifier : id;
|
||||
}
|
||||
|
||||
value = name;
|
||||
} else if (activity.field === "assignees") {
|
||||
value = activity.new_value;
|
||||
} else if (activity.field === "target_date") {
|
||||
const date =
|
||||
activity.new_value && activity.new_value !== ""
|
||||
? activity.new_value
|
||||
: activity.old_value;
|
||||
value = renderShortDateWithYearFormat(date as string);
|
||||
} else if (activity.field === "description") {
|
||||
value = "description";
|
||||
} else if (activity.field === "attachment") {
|
||||
value = "attachment";
|
||||
} else if (activity.field === "link") {
|
||||
value = "link";
|
||||
} else if (activity.field === "estimate_point") {
|
||||
value = activity.new_value
|
||||
? activity.new_value +
|
||||
` Point${parseInt(activity.new_value ?? "", 10) > 1 ? "s" : ""}`
|
||||
: "None";
|
||||
}
|
||||
|
||||
if (activity.field === "comment") {
|
||||
return (
|
||||
<div key={activity.id} className="mt-2">
|
||||
<div className="relative flex items-start space-x-3">
|
||||
<div className="relative px-1">
|
||||
{activity.field ? (
|
||||
activity.new_value === "restore" ? (
|
||||
<Icon iconName="history" className="text-sm text-custom-text-200" />
|
||||
) : (
|
||||
activityDetails[activity.field as keyof typeof activityDetails]?.icon
|
||||
)
|
||||
) : activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
|
||||
<img
|
||||
src={activity.actor_detail.avatar}
|
||||
alt={activity.actor_detail.first_name}
|
||||
height={30}
|
||||
width={30}
|
||||
className="grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}
|
||||
>
|
||||
{activity.actor_detail.first_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span className="absolute -bottom-0.5 -right-1 rounded-tl bg-custom-background-80 px-0.5 py-px">
|
||||
<ChatBubbleLeftEllipsisIcon
|
||||
className="h-3.5 w-3.5 text-custom-text-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<div className="text-xs">
|
||||
{activity.actor_detail.first_name}
|
||||
{activity.actor_detail.is_bot
|
||||
? "Bot"
|
||||
: " " + activity.actor_detail.last_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-xs text-custom-text-200">
|
||||
Commented {timeAgo(activity.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="issue-comments-section p-0">
|
||||
<RemirrorRichTextEditor
|
||||
value={
|
||||
activity.new_value && activity.new_value !== ""
|
||||
? activity.new_value
|
||||
: activity.old_value
|
||||
}
|
||||
editable={false}
|
||||
noBorder
|
||||
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ("field" in activity && activity.field !== "updated_by") {
|
||||
return (
|
||||
<li key={activity.id}>
|
||||
<div className="relative pb-1">
|
||||
{activities.length > 1 && activityIdx !== activities.length - 1 ? (
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-custom-background-80"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<div className="relative flex items-start space-x-2">
|
||||
<>
|
||||
<div>
|
||||
<div className="relative px-1.5">
|
||||
<div className="mt-1.5">
|
||||
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
||||
{activity.field ? (
|
||||
activityDetails[activity.field as keyof typeof activityDetails]
|
||||
?.icon
|
||||
) : activity.actor_detail.avatar &&
|
||||
activity.actor_detail.avatar !== "" ? (
|
||||
<img
|
||||
src={activity.actor_detail.avatar}
|
||||
alt={activity.actor_detail.first_name}
|
||||
height={24}
|
||||
width={24}
|
||||
className="rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white`}
|
||||
>
|
||||
{activity.actor_detail.first_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 py-3">
|
||||
<div className="text-xs text-custom-text-200">
|
||||
{activity.field === "archived_at" && activity.new_value !== "restore" ? (
|
||||
<span className="text-gray font-medium">Plane</span>
|
||||
) : (
|
||||
<span className="text-gray font-medium">
|
||||
{activity.actor_detail.first_name}
|
||||
{activity.actor_detail.is_bot
|
||||
? " Bot"
|
||||
: " " + activity.actor_detail.last_name}
|
||||
</span>
|
||||
)}
|
||||
<span> {action} </span>
|
||||
{activity.field !== "archived_at" && (
|
||||
<span className="text-xs font-medium text-custom-text-100">
|
||||
{" "}
|
||||
{value}{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="whitespace-nowrap">{timeAgo(activity.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -4,6 +4,5 @@ export * from "./sidebar";
|
||||
export * from "./theme";
|
||||
export * from "./views";
|
||||
export * from "./activity";
|
||||
export * from "./feeds";
|
||||
export * from "./reaction-selector";
|
||||
export * from "./image-picker-popover";
|
||||
|
@ -28,6 +28,7 @@ const defaultValues: ICustomTheme = {
|
||||
sidebarText: "#c5c5c5",
|
||||
darkPalette: false,
|
||||
palette: "",
|
||||
theme: "custom",
|
||||
};
|
||||
|
||||
export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
|
||||
@ -56,6 +57,7 @@ export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
|
||||
sidebarText: formData.sidebarText,
|
||||
darkPalette: darkPalette,
|
||||
palette: `${formData.background},${formData.text},${formData.primary},${formData.sidebarBackground},${formData.sidebarText}`,
|
||||
theme: "custom",
|
||||
};
|
||||
|
||||
await userService
|
||||
|
@ -1,43 +1,71 @@
|
||||
import { useState, useEffect, Dispatch, SetStateAction } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// constants
|
||||
import { THEMES_OBJ } from "constants/themes";
|
||||
// ui
|
||||
import { CustomSelect } from "components/ui";
|
||||
// types
|
||||
import { ICustomTheme, IUser } from "types";
|
||||
import { ICustomTheme } from "types";
|
||||
import { unsetCustomCssVariables } from "helpers/theme.helper";
|
||||
|
||||
type Props = {
|
||||
user: IUser | undefined;
|
||||
setPreLoadedData: Dispatch<SetStateAction<ICustomTheme | null>>;
|
||||
setPreLoadedData: React.Dispatch<React.SetStateAction<ICustomTheme | null>>;
|
||||
customThemeSelectorOptions: boolean;
|
||||
setCustomThemeSelectorOptions: Dispatch<SetStateAction<boolean>>;
|
||||
setCustomThemeSelectorOptions: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const ThemeSwitch: React.FC<Props> = ({
|
||||
user,
|
||||
setPreLoadedData,
|
||||
customThemeSelectorOptions,
|
||||
setCustomThemeSelectorOptions,
|
||||
}) => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const { user, mutateUser } = useUser();
|
||||
|
||||
const updateUserTheme = (newTheme: string) => {
|
||||
if (!user) return;
|
||||
|
||||
setTheme(newTheme);
|
||||
|
||||
mutateUser((prevData) => {
|
||||
if (!prevData) return prevData;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
theme: {
|
||||
...prevData.theme,
|
||||
theme: newTheme,
|
||||
},
|
||||
};
|
||||
}, false);
|
||||
|
||||
userService.updateUser({
|
||||
theme: {
|
||||
...user.theme,
|
||||
theme: newTheme,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// useEffect only runs on the client, so now we can safely show the UI
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
if (!mounted) return null;
|
||||
|
||||
const currentThemeObj = THEMES_OBJ.find((t) => t.value === theme);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomSelect
|
||||
value={theme}
|
||||
label={
|
||||
@ -84,26 +112,18 @@ export const ThemeSwitch: React.FC<Props> = ({
|
||||
user.theme.palette !== ",,,,"
|
||||
? user.theme.palette
|
||||
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
|
||||
theme: "custom",
|
||||
});
|
||||
}
|
||||
|
||||
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
|
||||
} else {
|
||||
if (customThemeSelectorOptions) setCustomThemeSelectorOptions(false);
|
||||
|
||||
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) {
|
||||
document.documentElement.style.removeProperty(`--color-background-${i}`);
|
||||
document.documentElement.style.removeProperty(`--color-text-${i}`);
|
||||
document.documentElement.style.removeProperty(`--color-border-${i}`);
|
||||
document.documentElement.style.removeProperty(`--color-primary-${i}`);
|
||||
document.documentElement.style.removeProperty(`--color-sidebar-background-${i}`);
|
||||
document.documentElement.style.removeProperty(`--color-sidebar-text-${i}`);
|
||||
document.documentElement.style.removeProperty(`--color-sidebar-border-${i}`);
|
||||
}
|
||||
unsetCustomCssVariables();
|
||||
}
|
||||
|
||||
setTheme(value);
|
||||
document.documentElement.style.setProperty("color-scheme", type);
|
||||
updateUserTheme(value);
|
||||
document.documentElement.style.setProperty("--color-scheme", type);
|
||||
}}
|
||||
input
|
||||
width="w-full"
|
||||
@ -137,6 +157,5 @@ export const ThemeSwitch: React.FC<Props> = ({
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@ -98,20 +98,36 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""],
|
||||
};
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
handleClose();
|
||||
setActiveProject(null);
|
||||
}, [handleClose]);
|
||||
|
||||
useEffect(() => {
|
||||
// if modal is closed, reset active project to null
|
||||
// and return to avoid activeProject being set to some other project
|
||||
if (!isOpen) {
|
||||
setActiveProject(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// if data is present, set active project to the project of the
|
||||
// issue. This has more priority than the project in the url.
|
||||
if (data && data.project) {
|
||||
setActiveProject(data.project);
|
||||
return;
|
||||
}
|
||||
|
||||
// if data is not present, set active project to the project
|
||||
// in the url. This has the least priority.
|
||||
if (projects && projects.length > 0 && !activeProject)
|
||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||
}, [activeProject, data, projectId, projects]);
|
||||
}, [activeProject, data, projectId, projects, isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
handleClose();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
@ -119,7 +135,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [handleClose]);
|
||||
}, [onClose]);
|
||||
|
||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
@ -267,7 +283,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
if (!createMore) handleClose();
|
||||
if (!createMore) onClose();
|
||||
};
|
||||
|
||||
const updateIssue = async (payload: Partial<IIssue>) => {
|
||||
@ -286,7 +302,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle);
|
||||
if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module);
|
||||
|
||||
if (!createMore) handleClose();
|
||||
if (!createMore) onClose();
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
@ -324,7 +340,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={() => handleClose()}>
|
||||
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
@ -354,7 +370,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
||||
initialData={data ?? prePopulateData}
|
||||
createMore={createMore}
|
||||
setCreateMore={setCreateMore}
|
||||
handleClose={handleClose}
|
||||
handleClose={onClose}
|
||||
projectId={activeProject ?? ""}
|
||||
setActiveProject={setActiveProject}
|
||||
status={data ? true : false}
|
||||
|
@ -27,6 +27,7 @@ import { snoozeOptions } from "constants/notification";
|
||||
type NotificationCardProps = {
|
||||
notification: IUserNotification;
|
||||
markNotificationReadStatus: (notificationId: string) => Promise<void>;
|
||||
markNotificationReadStatusToggle: (notificationId: string) => Promise<void>;
|
||||
markNotificationArchivedStatus: (notificationId: string) => Promise<void>;
|
||||
setSelectedNotificationForSnooze: (notificationId: string) => void;
|
||||
markSnoozeNotification: (notificationId: string, dateTime?: Date | undefined) => Promise<void>;
|
||||
@ -36,6 +37,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
const {
|
||||
notification,
|
||||
markNotificationReadStatus,
|
||||
markNotificationReadStatusToggle,
|
||||
markNotificationArchivedStatus,
|
||||
setSelectedNotificationForSnooze,
|
||||
markSnoozeNotification,
|
||||
@ -159,7 +161,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
name: notification.read_at ? "Mark as unread" : "Mark as read",
|
||||
icon: "chat_bubble",
|
||||
onClick: () => {
|
||||
markNotificationReadStatus(notification.id).then(() => {
|
||||
markNotificationReadStatusToggle(notification.id).then(() => {
|
||||
setToastAlert({
|
||||
title: notification.read_at
|
||||
? "Notification marked as unread"
|
||||
|
@ -38,6 +38,7 @@ export const NotificationPopover = () => {
|
||||
notificationMutate,
|
||||
markNotificationArchivedStatus,
|
||||
markNotificationReadStatus,
|
||||
markNotificationAsRead,
|
||||
markSnoozeNotification,
|
||||
notificationCount,
|
||||
totalNotificationCount,
|
||||
@ -128,7 +129,8 @@ export const NotificationPopover = () => {
|
||||
key={notification.id}
|
||||
notification={notification}
|
||||
markNotificationArchivedStatus={markNotificationArchivedStatus}
|
||||
markNotificationReadStatus={markNotificationReadStatus}
|
||||
markNotificationReadStatus={markNotificationAsRead}
|
||||
markNotificationReadStatusToggle={markNotificationReadStatus}
|
||||
setSelectedNotificationForSnooze={setSelectedNotificationForSnooze}
|
||||
markSnoozeNotification={markSnoozeNotification}
|
||||
/>
|
||||
|
@ -130,7 +130,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
data={projectToDelete}
|
||||
user={user}
|
||||
/>
|
||||
<div className="h-full overflow-y-auto px-5 space-y-3 pt-3 border-t border-custom-sidebar-border-300">
|
||||
<div className="h-full overflow-y-auto px-4 space-y-3 pt-3 border-t border-custom-sidebar-border-300">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="favorite-projects">
|
||||
{(provided) => (
|
||||
|
@ -137,21 +137,30 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div
|
||||
className={`group relative text-custom-sidebar-text-10 px-2 py-1 ml-1.5 w-full flex items-center hover:bg-custom-sidebar-background-80 rounded-md ${
|
||||
className={`group relative text-custom-sidebar-text-10 px-2 py-1 w-full flex items-center hover:bg-custom-sidebar-background-80 rounded-md ${
|
||||
snapshot?.isDragging ? "opacity-60" : ""
|
||||
}`}
|
||||
>
|
||||
{provided && (
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
project.sort_order === null
|
||||
? "Join the project to rearrange"
|
||||
: "Drag to rearrange"
|
||||
}
|
||||
position="top-right"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`absolute top-1/2 -translate-y-1/2 -left-4 hidden rounded p-0.5 ${
|
||||
sidebarCollapse ? "" : "group-hover:!flex"
|
||||
}`}
|
||||
} ${project.sort_order === null ? "opacity-60 cursor-not-allowed" : ""}`}
|
||||
{...provided?.dragHandleProps}
|
||||
>
|
||||
<EllipsisVerticalIcon className="h-4" />
|
||||
<EllipsisVerticalIcon className="-ml-5 h-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
tooltipContent={`${project.name}`}
|
||||
@ -161,17 +170,21 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
>
|
||||
<Disclosure.Button
|
||||
as="div"
|
||||
className={`flex items-center w-full cursor-pointer select-none text-left text-sm font-medium ${
|
||||
className={`flex items-center flex-grow truncate cursor-pointer select-none text-left text-sm font-medium ${
|
||||
sidebarCollapse ? "justify-center" : `justify-between`
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div
|
||||
className={`flex items-center flex-grow w-full truncate gap-x-2 ${
|
||||
sidebarCollapse ? "justify-center" : ""
|
||||
}`}
|
||||
>
|
||||
{project.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
{renderEmoji(project.emoji)}
|
||||
</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="h-7 w-7 grid place-items-center">
|
||||
<div className="h-7 w-7 flex-shrink-0 grid place-items-center">
|
||||
{renderEmoji(project.icon_prop)}
|
||||
</div>
|
||||
) : (
|
||||
@ -181,19 +194,15 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
)}
|
||||
|
||||
{!sidebarCollapse && (
|
||||
<p
|
||||
className={`overflow-hidden text-ellipsis ${
|
||||
open ? "" : "text-custom-sidebar-text-200"
|
||||
}`}
|
||||
>
|
||||
{truncateText(project.name, 15)}
|
||||
<p className={`truncate ${open ? "" : "text-custom-sidebar-text-200"}`}>
|
||||
{project.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{!sidebarCollapse && (
|
||||
<ExpandMoreOutlined
|
||||
fontSize="small"
|
||||
className={`${
|
||||
className={`flex-shrink-0 ${
|
||||
open ? "rotate-180" : ""
|
||||
} !hidden group-hover:!block text-custom-sidebar-text-200 duration-300`}
|
||||
/>
|
||||
@ -202,7 +211,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
</Tooltip>
|
||||
|
||||
{!sidebarCollapse && (
|
||||
<CustomMenu className="hidden group-hover:block" ellipsis>
|
||||
<CustomMenu className="hidden group-hover:block flex-shrink-0" ellipsis>
|
||||
{!shortContextMenu && (
|
||||
<CustomMenu.MenuItem onClick={handleDeleteProject}>
|
||||
<span className="flex items-center justify-start gap-2 ">
|
||||
@ -257,7 +266,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
<Disclosure.Panel className={`space-y-2 ${sidebarCollapse ? "" : "ml-[2.25rem]"}`}>
|
||||
<Disclosure.Panel className={`space-y-2 mt-1 ${sidebarCollapse ? "" : "ml-[2.25rem]"}`}>
|
||||
{navigation(workspaceSlug as string, project?.id).map((item) => {
|
||||
if (
|
||||
(item.name === "Cycles" && !project.cycle_view) ||
|
||||
|
@ -7,7 +7,7 @@ import { PrimaryButton } from "components/ui";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: React.ReactNode | string;
|
||||
description?: React.ReactNode;
|
||||
image: any;
|
||||
primaryButton?: {
|
||||
icon?: any;
|
||||
@ -34,7 +34,7 @@ export const EmptyState: React.FC<Props> = ({
|
||||
<div className="text-center flex flex-col items-center w-full">
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
|
||||
<h6 className="text-xl font-semibold mt-6 sm:mt-8 mb-3">{title}</h6>
|
||||
<p className="text-custom-text-300 mb-7 sm:mb-8">{description}</p>
|
||||
{description && <p className="text-custom-text-300 mb-7 sm:mb-8">{description}</p>}
|
||||
<div className="flex items-center gap-4">
|
||||
{primaryButton && (
|
||||
<PrimaryButton className="flex items-center gap-1.5" onClick={primaryButton.onClick}>
|
||||
|
@ -3,10 +3,10 @@ import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// headless ui
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useThemeHook from "hooks/use-theme";
|
||||
@ -91,7 +91,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
.then(() => {
|
||||
mutateUser(undefined);
|
||||
router.push("/");
|
||||
setTheme("dark");
|
||||
setTheme("system");
|
||||
})
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
|
@ -1,6 +1,16 @@
|
||||
export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"];
|
||||
|
||||
export const THEMES_OBJ = [
|
||||
{
|
||||
value: "system",
|
||||
label: "System Preference",
|
||||
type: "light",
|
||||
icon: {
|
||||
border: "#DEE2E6",
|
||||
color1: "#FAFAFA",
|
||||
color2: "#3F76FF",
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "light",
|
||||
label: "Light",
|
||||
|
@ -11,7 +11,7 @@ import projectService from "services/project.service";
|
||||
// fetch-keys
|
||||
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
|
||||
// helper
|
||||
import { applyTheme } from "helpers/theme.helper";
|
||||
import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
|
||||
// constants
|
||||
|
||||
export const themeContext = createContext<ContextType>({} as ContextType);
|
||||
@ -92,7 +92,9 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const theme = localStorage.getItem("theme");
|
||||
if (theme && theme === "custom") {
|
||||
|
||||
if (theme) {
|
||||
if (theme === "custom") {
|
||||
if (user && user.theme.palette) {
|
||||
applyTheme(
|
||||
user.theme.palette !== ",,,,"
|
||||
@ -101,6 +103,7 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
user.theme.darkPalette
|
||||
);
|
||||
}
|
||||
} else unsetCustomCssVariables();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
|
@ -60,6 +60,7 @@ const calculateShades = (hexValue: string): TShades => {
|
||||
};
|
||||
|
||||
export const applyTheme = (palette: string, isDarkPalette: boolean) => {
|
||||
const dom = document?.querySelector<HTMLElement>("[data-theme='custom']");
|
||||
// palette: [bg, text, primary, sidebarBg, sidebarText]
|
||||
const values: string[] = palette.split(",");
|
||||
values.push(isDarkPalette ? "dark" : "light");
|
||||
@ -79,41 +80,40 @@ export const applyTheme = (palette: string, isDarkPalette: boolean) => {
|
||||
const sidebarBackgroundRgbValues = `${sidebarBackgroundShades[shade].r}, ${sidebarBackgroundShades[shade].g}, ${sidebarBackgroundShades[shade].b}`;
|
||||
const sidebarTextRgbValues = `${sidebarTextShades[shade].r}, ${sidebarTextShades[shade].g}, ${sidebarTextShades[shade].b}`;
|
||||
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(`--color-background-${shade}`, bgRgbValues);
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(`--color-text-${shade}`, textRgbValues);
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(`--color-primary-${shade}`, primaryRgbValues);
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(`--color-sidebar-background-${shade}`, sidebarBackgroundRgbValues);
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(`--color-sidebar-text-${shade}`, sidebarTextRgbValues);
|
||||
dom?.style.setProperty(`--color-background-${shade}`, bgRgbValues);
|
||||
dom?.style.setProperty(`--color-text-${shade}`, textRgbValues);
|
||||
dom?.style.setProperty(`--color-primary-${shade}`, primaryRgbValues);
|
||||
dom?.style.setProperty(`--color-sidebar-background-${shade}`, sidebarBackgroundRgbValues);
|
||||
dom?.style.setProperty(`--color-sidebar-text-${shade}`, sidebarTextRgbValues);
|
||||
|
||||
if (i >= 100 && i <= 400) {
|
||||
const borderShade = i === 100 ? 70 : i === 200 ? 80 : i === 300 ? 90 : 100;
|
||||
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(
|
||||
dom?.style.setProperty(
|
||||
`--color-border-${shade}`,
|
||||
`${bgShades[borderShade].r}, ${bgShades[borderShade].g}, ${bgShades[borderShade].b}`
|
||||
);
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty(
|
||||
dom?.style.setProperty(
|
||||
`--color-sidebar-border-${shade}`,
|
||||
`${sidebarBackgroundShades[borderShade].r}, ${sidebarBackgroundShades[borderShade].g}, ${sidebarBackgroundShades[borderShade].b}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-theme='custom']")
|
||||
?.style.setProperty("--color-scheme", values[5]);
|
||||
dom?.style.setProperty("--color-scheme", values[5]);
|
||||
};
|
||||
|
||||
export const unsetCustomCssVariables = () => {
|
||||
for (let i = 10; i <= 900; i >= 100 ? (i += 100) : (i += 10)) {
|
||||
const dom = document.querySelector<HTMLElement>("[data-theme='custom']");
|
||||
|
||||
dom?.style.removeProperty(`--color-background-${i}`);
|
||||
dom?.style.removeProperty(`--color-text-${i}`);
|
||||
dom?.style.removeProperty(`--color-border-${i}`);
|
||||
dom?.style.removeProperty(`--color-primary-${i}`);
|
||||
dom?.style.removeProperty(`--color-sidebar-background-${i}`);
|
||||
dom?.style.removeProperty(`--color-sidebar-text-${i}`);
|
||||
dom?.style.removeProperty(`--color-sidebar-border-${i}`);
|
||||
dom?.style.removeProperty("--color-scheme");
|
||||
}
|
||||
};
|
||||
|
@ -70,7 +70,8 @@ const useCommentReaction = (
|
||||
|
||||
mutateCommentReactions(
|
||||
(prevData) =>
|
||||
prevData?.filter((r) => r.actor !== user?.user?.id || r.reaction !== reaction) || []
|
||||
prevData?.filter((r) => r.actor !== user?.user?.id || r.reaction !== reaction) || [],
|
||||
false
|
||||
);
|
||||
|
||||
await reactionService.deleteIssueCommentReaction(
|
||||
|
@ -185,6 +185,26 @@ const useUserNotification = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const markNotificationAsRead = async (notificationId: string) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const isRead =
|
||||
notifications?.find((notification) => notification.id === notificationId)?.read_at !== null;
|
||||
|
||||
if (isRead) return;
|
||||
|
||||
mutateNotification(notificationId, { read_at: new Date() });
|
||||
handleReadMutation("read");
|
||||
|
||||
await userNotificationServices
|
||||
.markUserNotificationAsRead(workspaceSlug.toString(), notificationId)
|
||||
.catch(() => {
|
||||
throw new Error("Something went wrong");
|
||||
});
|
||||
|
||||
mutateNotificationCount();
|
||||
};
|
||||
|
||||
const markNotificationArchivedStatus = async (notificationId: string) => {
|
||||
if (!workspaceSlug) return;
|
||||
const isArchived =
|
||||
@ -283,6 +303,7 @@ const useUserNotification = () => {
|
||||
hasMore,
|
||||
isRefreshing,
|
||||
setFetchNotifications,
|
||||
markNotificationAsRead,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,21 +1,43 @@
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
// layouts
|
||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||
import SettingsNavbar from "layouts/settings-navbar";
|
||||
// components
|
||||
import { Feeds } from "components/core";
|
||||
import { ActivityIcon, ActivityMessage } from "components/core";
|
||||
import RemirrorRichTextEditor from "components/rich-text-editor";
|
||||
// icons
|
||||
import { ArrowTopRightOnSquareIcon, ChatBubbleLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
import { Icon, Loader } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// fetch-keys
|
||||
import { USER_ACTIVITY } from "constants/fetch-keys";
|
||||
// helper
|
||||
import { timeAgo } from "helpers/date-time.helper";
|
||||
|
||||
const ProfileActivity = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
|
||||
|
||||
if (!userActivity) {
|
||||
return (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkspaceAuthorizationLayout
|
||||
breadcrumbs={
|
||||
@ -34,17 +56,176 @@ const ProfileActivity = () => {
|
||||
</div>
|
||||
<SettingsNavbar profilePage />
|
||||
</div>
|
||||
{userActivity ? (
|
||||
userActivity.results.length > 0 ? (
|
||||
<Feeds activities={userActivity.results} />
|
||||
) : null
|
||||
{userActivity && userActivity.results.length > 0 && (
|
||||
<div>
|
||||
<ul role="list" className="-mb-4">
|
||||
{userActivity.results.map((activityItem: any, activityIdx: number) => {
|
||||
if (activityItem.field === "comment") {
|
||||
return (
|
||||
<div key={activityItem.id} className="mt-2">
|
||||
<div className="relative flex items-start space-x-3">
|
||||
<div className="relative px-1">
|
||||
{activityItem.field ? (
|
||||
activityItem.new_value === "restore" && (
|
||||
<Icon iconName="history" className="text-sm text-custom-text-200" />
|
||||
)
|
||||
) : activityItem.actor_detail.avatar &&
|
||||
activityItem.actor_detail.avatar !== "" ? (
|
||||
<img
|
||||
src={activityItem.actor_detail.avatar}
|
||||
alt={activityItem.actor_detail.first_name}
|
||||
height={30}
|
||||
width={30}
|
||||
className="grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white"
|
||||
/>
|
||||
) : (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
<div
|
||||
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}
|
||||
>
|
||||
{activityItem.actor_detail.first_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
||||
<ChatBubbleLeftEllipsisIcon
|
||||
className="h-3.5 w-3.5 text-custom-text-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<div className="text-xs">
|
||||
{activityItem.actor_detail.first_name}
|
||||
{activityItem.actor_detail.is_bot
|
||||
? "Bot"
|
||||
: " " + activityItem.actor_detail.last_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-xs text-custom-text-200">
|
||||
Commented {timeAgo(activityItem.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="issue-comments-section p-0">
|
||||
<RemirrorRichTextEditor
|
||||
value={
|
||||
activityItem.new_value && activityItem.new_value !== ""
|
||||
? activityItem.new_value
|
||||
: activityItem.old_value
|
||||
}
|
||||
editable={false}
|
||||
noBorder
|
||||
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const message =
|
||||
activityItem.verb === "created" &&
|
||||
activityItem.field !== "cycles" &&
|
||||
activityItem.field !== "modules" &&
|
||||
activityItem.field !== "attachment" &&
|
||||
activityItem.field !== "link" &&
|
||||
activityItem.field !== "estimate" ? (
|
||||
<span className="text-custom-text-200">
|
||||
created{" "}
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${activityItem.project}/issues/${activityItem.issue}`}
|
||||
>
|
||||
<a className="inline-flex items-center hover:underline">
|
||||
this issue. <ArrowTopRightOnSquareIcon className="ml-1 h-3.5 w-3.5" />
|
||||
</a>
|
||||
</Link>
|
||||
</span>
|
||||
) : activityItem.field ? (
|
||||
<ActivityMessage activity={activityItem} showIssue />
|
||||
) : (
|
||||
"created the issue."
|
||||
);
|
||||
|
||||
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||
return (
|
||||
<li key={activityItem.id}>
|
||||
<div className="relative pb-1">
|
||||
{userActivity.results.length > 1 &&
|
||||
activityIdx !== userActivity.results.length - 1 ? (
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-custom-background-80"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<div className="relative flex items-start space-x-2">
|
||||
<>
|
||||
<div>
|
||||
<div className="relative px-1.5">
|
||||
<div className="mt-1.5">
|
||||
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
||||
{activityItem.field ? (
|
||||
activityItem.new_value === "restore" ? (
|
||||
<Icon
|
||||
iconName="history"
|
||||
className="text-sm text-custom-text-200"
|
||||
/>
|
||||
) : (
|
||||
<ActivityIcon activity={activityItem} />
|
||||
)
|
||||
) : activityItem.actor_detail.avatar &&
|
||||
activityItem.actor_detail.avatar !== "" ? (
|
||||
<img
|
||||
src={activityItem.actor_detail.avatar}
|
||||
alt={activityItem.actor_detail.first_name}
|
||||
height={24}
|
||||
width={24}
|
||||
className="rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white`}
|
||||
>
|
||||
{activityItem.actor_detail.first_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 py-3">
|
||||
<div className="text-xs text-custom-text-200 break-words">
|
||||
{activityItem.field === "archived_at" &&
|
||||
activityItem.new_value !== "restore" ? (
|
||||
<span className="text-gray font-medium">Plane</span>
|
||||
) : activityItem.actor_detail.is_bot ? (
|
||||
<span className="text-gray font-medium">
|
||||
{activityItem.actor_detail.first_name} Bot
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}
|
||||
>
|
||||
<a className="text-gray font-medium">
|
||||
{activityItem.actor_detail.first_name}{" "}
|
||||
{activityItem.actor_detail.last_name}
|
||||
</a>
|
||||
</Link>
|
||||
)}{" "}
|
||||
{message}{" "}
|
||||
<span className="whitespace-nowrap">
|
||||
{timeAgo(activityItem.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</WorkspaceAuthorizationLayout>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
// layouts
|
||||
@ -15,11 +16,13 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
import { ICustomTheme } from "types";
|
||||
|
||||
const ProfilePreferences = () => {
|
||||
const { user: myProfile } = useUserAuth();
|
||||
const { theme } = useTheme();
|
||||
const [customThemeSelectorOptions, setCustomThemeSelectorOptions] = useState(false);
|
||||
const [preLoadedData, setPreLoadedData] = useState<ICustomTheme | null>(null);
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const { user: myProfile } = useUserAuth();
|
||||
|
||||
useEffect(() => {
|
||||
if (theme === "custom") {
|
||||
if (myProfile?.theme.palette)
|
||||
@ -37,6 +40,7 @@ const ProfilePreferences = () => {
|
||||
myProfile.theme.palette !== ",,,,"
|
||||
? myProfile.theme.palette
|
||||
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
|
||||
theme: "custom",
|
||||
});
|
||||
if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true);
|
||||
}
|
||||
@ -71,7 +75,6 @@ const ProfilePreferences = () => {
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<ThemeSwitch
|
||||
user={myProfile}
|
||||
setPreLoadedData={setPreLoadedData}
|
||||
customThemeSelectorOptions={customThemeSelectorOptions}
|
||||
setCustomThemeSelectorOptions={setCustomThemeSelectorOptions}
|
||||
|
@ -22,8 +22,10 @@ import useUserAuth from "hooks/use-user-auth";
|
||||
// components
|
||||
import { AnalyticsProjectModal } from "components/analytics";
|
||||
// ui
|
||||
import { CustomMenu, SecondaryButton } from "components/ui";
|
||||
import { CustomMenu, EmptyState, SecondaryButton } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// images
|
||||
import emptyCycle from "public/empty-state/cycle.svg";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||
@ -52,14 +54,14 @@ const SingleCycle: React.FC = () => {
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: cycleDetails } = useSWR(
|
||||
cycleId ? CYCLE_DETAILS(cycleId as string) : null,
|
||||
const { data: cycleDetails, error } = useSWR(
|
||||
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
||||
workspaceSlug && projectId && cycleId
|
||||
? () =>
|
||||
cycleServices.getCycleDetails(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
cycleId as string
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
cycleId.toString()
|
||||
)
|
||||
: null
|
||||
);
|
||||
@ -159,11 +161,26 @@ const SingleCycle: React.FC = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyCycle}
|
||||
title="Cycle does not exist"
|
||||
description="The cycle you are looking for does not exist or has been deleted."
|
||||
primaryButton={{
|
||||
text: "View other cycles",
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/cycles`),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<TransferIssuesModal
|
||||
handleClose={() => setTransferIssuesModal(false)}
|
||||
isOpen={transferIssuesModal}
|
||||
/>
|
||||
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
|
||||
<AnalyticsProjectModal
|
||||
isOpen={analyticsModal}
|
||||
onClose={() => setAnalyticsModal(false)}
|
||||
/>
|
||||
<div
|
||||
className={`h-full flex flex-col ${cycleSidebar ? "mr-[24rem]" : ""} ${
|
||||
analyticsModal ? "mr-[50%]" : ""
|
||||
@ -184,6 +201,8 @@ const SingleCycle: React.FC = () => {
|
||||
isCompleted={cycleStatus === "completed" ?? false}
|
||||
user={user}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ProjectAuthorizationWrapper>
|
||||
</IssueViewContextProvider>
|
||||
);
|
||||
|
@ -15,8 +15,10 @@ import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||
// components
|
||||
import { IssueDetailsSidebar, IssueMainContent } from "components/issues";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
import { EmptyState, Loader } from "components/ui";
|
||||
import { Breadcrumbs } from "components/breadcrumbs";
|
||||
// images
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import type { NextPage } from "next";
|
||||
@ -45,7 +47,11 @@ const IssueDetailsPage: NextPage = () => {
|
||||
|
||||
const { user } = useUserAuth();
|
||||
|
||||
const { data: issueDetails, mutate: mutateIssueDetails } = useSWR<IIssue | undefined>(
|
||||
const {
|
||||
data: issueDetails,
|
||||
mutate: mutateIssueDetails,
|
||||
error,
|
||||
} = useSWR(
|
||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||
workspaceSlug && projectId && issueId
|
||||
? () =>
|
||||
@ -125,7 +131,17 @@ const IssueDetailsPage: NextPage = () => {
|
||||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
{issueDetails && projectId ? (
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyIssue}
|
||||
title="Issue does not exist"
|
||||
description="The issue you are looking for does not exist, has been archived, or has been deleted."
|
||||
primaryButton={{
|
||||
text: "View other issues",
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/issues`),
|
||||
}}
|
||||
/>
|
||||
) : issueDetails && projectId ? (
|
||||
<div className="flex h-full overflow-hidden">
|
||||
<div className="w-2/3 h-full overflow-y-auto space-y-5 divide-y-2 divide-custom-border-300 p-5">
|
||||
<IssueMainContent issueDetails={issueDetails} submitChanges={submitChanges} />
|
||||
|
@ -20,8 +20,10 @@ import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "component
|
||||
import { ModuleDetailsSidebar } from "components/modules";
|
||||
import { AnalyticsProjectModal } from "components/analytics";
|
||||
// ui
|
||||
import { CustomMenu, SecondaryButton } from "components/ui";
|
||||
import { CustomMenu, EmptyState, SecondaryButton } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// images
|
||||
import emptyModule from "public/empty-state/module.svg";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
// types
|
||||
@ -60,7 +62,7 @@ const SingleModule: React.FC = () => {
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: moduleDetails } = useSWR(
|
||||
const { data: moduleDetails, error } = useSWR(
|
||||
moduleId ? MODULE_DETAILS(moduleId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () =>
|
||||
@ -162,8 +164,22 @@ const SingleModule: React.FC = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
|
||||
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyModule}
|
||||
title="Module does not exist"
|
||||
description="The module you are looking for does not exist or has been deleted."
|
||||
primaryButton={{
|
||||
text: "View other modules",
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/modules`),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<AnalyticsProjectModal
|
||||
isOpen={analyticsModal}
|
||||
onClose={() => setAnalyticsModal(false)}
|
||||
/>
|
||||
<div
|
||||
className={`h-full flex flex-col ${moduleSidebar ? "mr-[24rem]" : ""} ${
|
||||
analyticsModal ? "mr-[50%]" : ""
|
||||
@ -171,13 +187,14 @@ const SingleModule: React.FC = () => {
|
||||
>
|
||||
<IssuesView openIssuesListModal={openIssuesListModal} />
|
||||
</div>
|
||||
|
||||
<ModuleDetailsSidebar
|
||||
module={moduleDetails}
|
||||
isOpen={moduleSidebar}
|
||||
moduleIssues={moduleIssues}
|
||||
user={user}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ProjectAuthorizationWrapper>
|
||||
</IssueViewContextProvider>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
@ -28,7 +28,16 @@ import { CreateLabelModal } from "components/labels";
|
||||
import { CreateBlock } from "components/pages/create-block";
|
||||
// ui
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
import { CustomSearchSelect, Loader, TextArea, ToggleSwitch, Tooltip } from "components/ui";
|
||||
import {
|
||||
CustomSearchSelect,
|
||||
EmptyState,
|
||||
Loader,
|
||||
TextArea,
|
||||
ToggleSwitch,
|
||||
Tooltip,
|
||||
} from "components/ui";
|
||||
// images
|
||||
import emptyPage from "public/empty-state/page.svg";
|
||||
// icons
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
@ -40,7 +49,7 @@ import {
|
||||
XMarkIcon,
|
||||
ChevronDownIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ColorPalletteIcon, ClipboardIcon } from "components/icons";
|
||||
import { ColorPalletteIcon } from "components/icons";
|
||||
// helpers
|
||||
import { render24HourFormatTime, renderShortDate } from "helpers/date-time.helper";
|
||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||
@ -82,7 +91,7 @@ const SinglePage: NextPage = () => {
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: pageDetails } = useSWR(
|
||||
const { data: pageDetails, error } = useSWR(
|
||||
workspaceSlug && projectId && pageId ? PAGE_DETAILS(pageId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () =>
|
||||
@ -267,13 +276,6 @@ const SinglePage: NextPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleNewBlock = useCallback(() => {
|
||||
setCreateBlockForm(true);
|
||||
scrollToRef.current?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, [setCreateBlockForm, scrollToRef]);
|
||||
|
||||
const handleShowBlockToggle = async () => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
@ -311,8 +313,7 @@ const SinglePage: NextPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const options =
|
||||
labels?.map((label) => ({
|
||||
const options = labels?.map((label) => ({
|
||||
value: label.id,
|
||||
query: label.name,
|
||||
content: (
|
||||
@ -326,7 +327,7 @@ const SinglePage: NextPage = () => {
|
||||
{label.name}
|
||||
</div>
|
||||
),
|
||||
})) ?? [];
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (!pageDetails) return;
|
||||
@ -346,11 +347,21 @@ const SinglePage: NextPage = () => {
|
||||
breadcrumbs={
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
|
||||
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project",32)} Pages`} />
|
||||
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Pages`} />
|
||||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
{pageDetails ? (
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyPage}
|
||||
title="Page does not exist"
|
||||
description="The page you are looking for does not exist or has been deleted."
|
||||
primaryButton={{
|
||||
text: "View other pages",
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/pages`),
|
||||
}}
|
||||
/>
|
||||
) : pageDetails ? (
|
||||
<div className="flex h-full flex-col justify-between space-y-4 overflow-hidden p-4">
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
|
@ -12,11 +12,13 @@ import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||
// components
|
||||
import { IssuesFilterView, IssuesView } from "components/core";
|
||||
// ui
|
||||
import { CustomMenu, PrimaryButton } from "components/ui";
|
||||
import { CustomMenu, EmptyState, PrimaryButton } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { StackedLayersIcon } from "components/icons";
|
||||
// images
|
||||
import emptyView from "public/empty-state/view.svg";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
// fetch-keys
|
||||
@ -40,7 +42,7 @@ const SingleView: React.FC = () => {
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: viewDetails } = useSWR(
|
||||
const { data: viewDetails, error } = useSWR(
|
||||
workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null,
|
||||
workspaceSlug && projectId && viewId
|
||||
? () =>
|
||||
@ -101,9 +103,21 @@ const SingleView: React.FC = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyView}
|
||||
title="View does not exist"
|
||||
description="The view you are looking for does not exist or has been deleted."
|
||||
primaryButton={{
|
||||
text: "View other views",
|
||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/views`),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<IssuesView />
|
||||
</div>
|
||||
)}
|
||||
</ProjectAuthorizationWrapper>
|
||||
</IssueViewContextProvider>
|
||||
);
|
||||
|
@ -45,7 +45,7 @@ Router.events.on("routeChangeComplete", NProgress.done);
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
// <UserProvider>
|
||||
<ThemeProvider themes={THEMES} defaultTheme="dark">
|
||||
<ThemeProvider themes={THEMES} defaultTheme="system">
|
||||
<ToastContextProvider>
|
||||
<ThemeContextProvider>
|
||||
<CrispWithNoSSR />
|
||||
|
@ -1,143 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
|
||||
const Colors: NextPage = () => (
|
||||
<UserAuthorizationLayout>
|
||||
<DefaultLayout>
|
||||
<div className="space-y-8 p-8">
|
||||
<div>
|
||||
Primary:
|
||||
<div className="flex flex-wrap">
|
||||
<div className="h-12 w-12 bg-custom-primary-0" />
|
||||
<div className="h-12 w-12 bg-custom-primary-10" />
|
||||
<div className="h-12 w-12 bg-custom-primary-20" />
|
||||
<div className="h-12 w-12 bg-custom-primary-30" />
|
||||
<div className="h-12 w-12 bg-custom-primary-40" />
|
||||
<div className="h-12 w-12 bg-custom-primary-50" />
|
||||
<div className="h-12 w-12 bg-custom-primary-60" />
|
||||
<div className="h-12 w-12 bg-custom-primary-70" />
|
||||
<div className="h-12 w-12 bg-custom-primary-80" />
|
||||
<div className="h-12 w-12 bg-custom-primary-90" />
|
||||
<div className="h-12 w-12 bg-custom-primary-100" />
|
||||
<div className="h-12 w-12 bg-custom-primary-200" />
|
||||
<div className="h-12 w-12 bg-custom-primary-300" />
|
||||
<div className="h-12 w-12 bg-custom-primary-400" />
|
||||
<div className="h-12 w-12 bg-custom-primary-500" />
|
||||
<div className="h-12 w-12 bg-custom-primary-600" />
|
||||
<div className="h-12 w-12 bg-custom-primary-700" />
|
||||
<div className="h-12 w-12 bg-custom-primary-800" />
|
||||
<div className="h-12 w-12 bg-custom-primary-900" />
|
||||
<div className="h-12 w-12 bg-custom-primary-1000" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Background:
|
||||
<div className="flex flex-wrap">
|
||||
<div className="h-12 w-12 bg-custom-background-0" />
|
||||
<div className="h-12 w-12 bg-custom-background-10" />
|
||||
<div className="h-12 w-12 bg-custom-background-20" />
|
||||
<div className="h-12 w-12 bg-custom-background-30" />
|
||||
<div className="h-12 w-12 bg-custom-background-40" />
|
||||
<div className="h-12 w-12 bg-custom-background-50" />
|
||||
<div className="h-12 w-12 bg-custom-background-60" />
|
||||
<div className="h-12 w-12 bg-custom-background-70" />
|
||||
<div className="h-12 w-12 bg-custom-background-80" />
|
||||
<div className="h-12 w-12 bg-custom-background-90" />
|
||||
<div className="h-12 w-12 bg-custom-background-100" />
|
||||
<div className="h-12 w-12 bg-custom-background-200" />
|
||||
<div className="h-12 w-12 bg-custom-background-300" />
|
||||
<div className="h-12 w-12 bg-custom-background-400" />
|
||||
<div className="h-12 w-12 bg-custom-background-500" />
|
||||
<div className="h-12 w-12 bg-custom-background-600" />
|
||||
<div className="h-12 w-12 bg-custom-background-700" />
|
||||
<div className="h-12 w-12 bg-custom-background-800" />
|
||||
<div className="h-12 w-12 bg-custom-background-900" />
|
||||
<div className="h-12 w-12 bg-custom-background-1000" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Text:
|
||||
<div className="flex flex-wrap">
|
||||
<div className="h-12 w-12 bg-custom-text-0" />
|
||||
<div className="h-12 w-12 bg-custom-text-10" />
|
||||
<div className="h-12 w-12 bg-custom-text-20" />
|
||||
<div className="h-12 w-12 bg-custom-text-30" />
|
||||
<div className="h-12 w-12 bg-custom-text-40" />
|
||||
<div className="h-12 w-12 bg-custom-text-50" />
|
||||
<div className="h-12 w-12 bg-custom-text-60" />
|
||||
<div className="h-12 w-12 bg-custom-text-70" />
|
||||
<div className="h-12 w-12 bg-custom-text-80" />
|
||||
<div className="h-12 w-12 bg-custom-text-90" />
|
||||
<div className="h-12 w-12 bg-custom-text-100" />
|
||||
<div className="h-12 w-12 bg-custom-text-200" />
|
||||
<div className="h-12 w-12 bg-custom-text-300" />
|
||||
<div className="h-12 w-12 bg-custom-text-400" />
|
||||
<div className="h-12 w-12 bg-custom-text-500" />
|
||||
<div className="h-12 w-12 bg-custom-text-600" />
|
||||
<div className="h-12 w-12 bg-custom-text-700" />
|
||||
<div className="h-12 w-12 bg-custom-text-800" />
|
||||
<div className="h-12 w-12 bg-custom-text-900" />
|
||||
<div className="h-12 w-12 bg-custom-text-1000" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Sidebar Background:
|
||||
<div className="flex flex-wrap">
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-0" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-10" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-20" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-30" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-40" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-50" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-60" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-70" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-80" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-90" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-100" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-200" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-300" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-400" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-500" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-600" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-700" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-800" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-900" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-background-1000" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Sidebar Text:
|
||||
<div className="flex flex-wrap">
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-0" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-10" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-20" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-30" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-40" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-50" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-60" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-70" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-80" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-90" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-100" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-200" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-300" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-400" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-500" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-600" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-700" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-800" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-900" />
|
||||
<div className="h-12 w-12 bg-custom-sidebar-text-1000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
</UserAuthorizationLayout>
|
||||
);
|
||||
|
||||
export default Colors;
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
@ -22,6 +22,8 @@ import {
|
||||
import { Spinner } from "components/ui";
|
||||
// images
|
||||
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||
import { useTheme } from "next-themes";
|
||||
import { ICurrentUserResponse, IUser } from "types";
|
||||
// types
|
||||
type EmailPasswordFormValues = {
|
||||
email: string;
|
||||
@ -34,6 +36,12 @@ const HomePage: NextPage = () => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const changeTheme = (user: IUser) => {
|
||||
setTheme(user.theme.theme ?? "system");
|
||||
};
|
||||
|
||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
||||
try {
|
||||
if (clientId && credential) {
|
||||
@ -43,7 +51,10 @@ const HomePage: NextPage = () => {
|
||||
clientId,
|
||||
};
|
||||
const response = await authenticationService.socialAuth(socialAuthPayload);
|
||||
if (response && response?.user) mutateUser();
|
||||
if (response && response?.user) {
|
||||
mutateUser();
|
||||
changeTheme(response.user);
|
||||
}
|
||||
} else {
|
||||
throw Error("Cant find credentials");
|
||||
}
|
||||
@ -66,7 +77,10 @@ const HomePage: NextPage = () => {
|
||||
clientId: process.env.NEXT_PUBLIC_GITHUB_ID,
|
||||
};
|
||||
const response = await authenticationService.socialAuth(socialAuthPayload);
|
||||
if (response && response?.user) mutateUser();
|
||||
if (response && response?.user) {
|
||||
mutateUser();
|
||||
changeTheme(response.user);
|
||||
}
|
||||
} else {
|
||||
throw Error("Cant find credentials");
|
||||
}
|
||||
@ -85,7 +99,10 @@ const HomePage: NextPage = () => {
|
||||
.emailLogin(formData)
|
||||
.then((response) => {
|
||||
try {
|
||||
if (response) mutateUser();
|
||||
if (response) {
|
||||
mutateUser();
|
||||
changeTheme(response.user);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
@ -109,7 +126,10 @@ const HomePage: NextPage = () => {
|
||||
|
||||
const handleEmailCodeSignIn = async (response: any) => {
|
||||
try {
|
||||
if (response) mutateUser();
|
||||
if (response) {
|
||||
mutateUser();
|
||||
changeTheme(response.user);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
@ -120,6 +140,10 @@ const HomePage: NextPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTheme("system");
|
||||
}, [setTheme]);
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
{isLoading ? (
|
||||
|
@ -1,6 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
// next imports
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// services
|
||||
@ -17,11 +20,17 @@ const MagicSignIn: NextPage = () => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { user, isLoading, mutateUser } = useUserAuth("sign-in");
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const { mutateUser } = useUserAuth("sign-in");
|
||||
|
||||
const [isSigningIn, setIsSigningIn] = useState(false);
|
||||
const [errorSigningIn, setErrorSignIn] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setTheme("system");
|
||||
}, [setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsSigningIn(() => false);
|
||||
setErrorSignIn(() => undefined);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import Router from "next/router";
|
||||
import Image from "next/image";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
@ -32,7 +31,7 @@ import { CURRENT_USER, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
||||
const Onboarding: NextPage = () => {
|
||||
const [step, setStep] = useState<number | null>(null);
|
||||
|
||||
const { theme } = useTheme();
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const { user, isLoading: userLoading } = useUserAuth("onboarding");
|
||||
|
||||
@ -117,6 +116,10 @@ const Onboarding: NextPage = () => {
|
||||
await userService.updateUserOnBoard({ userRole: user.role }, user);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTheme("system");
|
||||
}, [setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStepChange = async () => {
|
||||
if (!user || !invitations) return;
|
||||
|
@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Image from "next/image";
|
||||
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// react-hook-form
|
||||
import { useForm } from "react-hook-form";
|
||||
// hooks
|
||||
@ -31,6 +33,8 @@ const ResetPasswordPage: NextPage = () => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -76,6 +80,10 @@ const ResetPasswordPage: NextPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTheme("system");
|
||||
}, [setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0")) router.push("/");
|
||||
else setIsLoading(false);
|
||||
|
@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// services
|
||||
import authenticationService from "services/authentication.service";
|
||||
// hooks
|
||||
@ -31,6 +33,8 @@ const SignUp: NextPage = () => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const { mutateUser } = useUserAuth("sign-in");
|
||||
|
||||
const handleSignUp = async (formData: EmailPasswordFormValues) => {
|
||||
@ -62,6 +66,10 @@ const SignUp: NextPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTheme("system");
|
||||
}, [setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0")) router.push("/");
|
||||
else setIsLoading(false);
|
||||
|
@ -1,5 +1,6 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
import { ICurrentUserResponse } from "types";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
@ -32,7 +33,11 @@ class AuthService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async socialAuth(data: any) {
|
||||
async socialAuth(data: any): Promise<{
|
||||
access_token: string;
|
||||
refresh_toke: string;
|
||||
user: ICurrentUserResponse;
|
||||
}> {
|
||||
return this.post("/api/social-auth/", data, { headers: {} })
|
||||
.then((response) => {
|
||||
this.setAccessToken(response?.data?.access_token);
|
||||
|
1
apps/app/types/users.d.ts
vendored
1
apps/app/types/users.d.ts
vendored
@ -46,6 +46,7 @@ export interface ICustomTheme {
|
||||
sidebarText: string;
|
||||
darkPalette: boolean;
|
||||
palette: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface ICurrentUserResponse extends IUser {
|
||||
|
Loading…
Reference in New Issue
Block a user