fix: aligned the changes

This commit is contained in:
Dakshesh Jain 2023-08-16 21:49:19 +05:30
parent ac89652438
commit 383b3a1bc7
31 changed files with 687 additions and 624 deletions

View File

@ -61,6 +61,16 @@ chmod +x setup.sh
> If running in a cloud env replace localhost with public facing IP address of the VM > If running in a cloud env replace localhost with public facing IP address of the VM
- Setup Tiptap Pro
Visit [Tiptap Pro](https://collab.tiptap.dev/pro-extensions) and signup (it is free).
Create a **`.npmrc`** file, copy the following and replace your registry token generated from Tiptap Pro.
```
@tiptap-pro:registry=https://registry.tiptap.dev/
//registry.tiptap.dev/:_authToken=YOUR_REGISTRY_TOKEN
```
- Run Docker compose up - Run Docker compose up
```bash ```bash

View File

@ -370,7 +370,7 @@ class UserWorkSpaceIssues(BaseAPIView):
) )
) )
.filter(**filters) .filter(**filters)
) ).distinct()
# Priority Ordering # Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority": if order_by_param == "priority" or order_by_param == "-priority":

View File

@ -184,19 +184,24 @@ def track_description(
if current_instance.get("description_html") != requested_data.get( if current_instance.get("description_html") != requested_data.get(
"description_html" "description_html"
): ):
issue_activities.append( last_activity = IssueActivity.objects.filter(issue_id=issue_id).order_by("-created_at").first()
IssueActivity( if(last_activity is not None and last_activity.field == "description" and actor.id == last_activity.actor_id):
issue_id=issue_id, last_activity.created_at = timezone.now()
actor=actor, last_activity.save(update_fields=["created_at"])
verb="updated", else:
old_value=current_instance.get("description_html"), issue_activities.append(
new_value=requested_data.get("description_html"), IssueActivity(
field="description", issue_id=issue_id,
project=project, actor=actor,
workspace=project.workspace, verb="updated",
comment=f"updated the description to {requested_data.get('description_html')}", old_value=current_instance.get("description_html"),
) new_value=requested_data.get("description_html"),
) field="description",
project=project,
workspace=project.workspace,
comment=f"updated the description to {requested_data.get('description_html')}",
)
)
# Track changes in issue target date # Track changes in issue target date

View File

@ -9,12 +9,18 @@ import userService from "services/user.service";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
// helper // helper
import { unsetCustomCssVariables } from "helpers/theme.helper"; 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 = {
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>; setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
}; };
export const ChangeInterfaceTheme: React.FC<Props> = ({ setIsPaletteOpen }) => { export const ChangeInterfaceTheme: React.FC<Props> = observer(({ setIsPaletteOpen }) => {
const store: any = useMobxStore();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const { setTheme } = useTheme(); const { setTheme } = useTheme();
@ -23,29 +29,11 @@ export const ChangeInterfaceTheme: React.FC<Props> = ({ setIsPaletteOpen }) => {
const updateUserTheme = (newTheme: string) => { const updateUserTheme = (newTheme: string) => {
if (!user) return; if (!user) return;
unsetCustomCssVariables();
setTheme(newTheme); setTheme(newTheme);
return store.user
mutateUser((prevData: any) => { .updateCurrentUserSettings({ theme: { ...user.theme, theme: newTheme } })
if (!prevData) return prevData; .then((response: any) => response)
.catch((error: any) => error);
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 only runs on the client, so now we can safely show the UI
@ -74,4 +62,4 @@ export const ChangeInterfaceTheme: React.FC<Props> = ({ setIsPaletteOpen }) => {
))} ))}
</> </>
); );
}; });

View File

@ -145,7 +145,7 @@ export const GptAssistantModal: React.FC<Props> = ({
}`} }`}
> >
{((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && ( {((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && (
<div id="tiptap-container" className="text-sm"> <div className="text-sm">
Content: Content:
<TiptapEditor <TiptapEditor
value={htmlContent ?? `<p>${content}</p>`} value={htmlContent ?? `<p>${content}</p>`}

View File

@ -45,18 +45,26 @@ export const ThemeSwitch: React.FC<Props> = observer(
currentThemeObj ? ( currentThemeObj ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="h-full w-1/2 rounded-l-full" className="border-1 relative flex h-4 w-4 rotate-45 transform items-center justify-center rounded-full border"
style={{ style={{
background: currentThemeObj.icon.color1, borderColor: currentThemeObj.icon.border,
}} }}
/> >
<div <div
className="h-full w-1/2 rounded-r-full border-l" className="h-full w-1/2 rounded-l-full"
style={{ style={{
borderLeftColor: currentThemeObj.icon.border, background: currentThemeObj.icon.color1,
background: currentThemeObj.icon.color2, }}
}} />
/> <div
className="h-full w-1/2 rounded-r-full border-l"
style={{
borderLeftColor: currentThemeObj.icon.border,
background: currentThemeObj.icon.color2,
}}
/>
</div>
{currentThemeObj.label}
</div> </div>
) : ( ) : (
"Select your theme" "Select your theme"
@ -98,18 +106,26 @@ export const ThemeSwitch: React.FC<Props> = observer(
<CustomSelect.Option key={value} value={{ value, type }}> <CustomSelect.Option key={value} value={{ value, type }}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="h-full w-1/2 rounded-l-full" className="border-1 relative flex h-4 w-4 rotate-45 transform items-center justify-center rounded-full border"
style={{ style={{
background: icon.color1, borderColor: icon.border,
}} }}
/> >
<div <div
className="h-full w-1/2 rounded-r-full border-l" className="h-full w-1/2 rounded-l-full"
style={{ style={{
borderLeftColor: icon.border, background: icon.color1,
background: icon.color2, }}
}} />
/> <div
className="h-full w-1/2 rounded-r-full border-l"
style={{
borderLeftColor: icon.border,
background: icon.color2,
}}
/>
</div>
{label}
</div> </div>
</CustomSelect.Option> </CustomSelect.Option>
))} ))}

View File

@ -134,9 +134,7 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
<div <div
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white`} 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.is_bot {activityItem.actor_detail.display_name.charAt(0)}
? activityItem.actor_detail.first_name.charAt(0)
: activityItem.actor_detail.display_name.charAt(0)}
</div> </div>
)} )}
</div> </div>
@ -155,9 +153,7 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
) : ( ) : (
<Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}> <Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
<a className="text-gray font-medium"> <a className="text-gray font-medium">
{activityItem.actor_detail.is_bot {activityItem.actor_detail.display_name}
? activityItem.actor_detail.first_name
: activityItem.actor_detail.display_name}
</a> </a>
</Link> </Link>
)}{" "} )}{" "}

View File

@ -87,7 +87,7 @@ export const AddComment: React.FC<Props> = ({ issueId, user, disabled = false })
return ( return (
<div> <div>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div id="tiptap-container" className="issue-comments-section"> <div className="issue-comments-section">
<Controller <Controller
name="comment_html" name="comment_html"
control={control} control={control}

View File

@ -65,11 +65,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
{comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? ( {comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? (
<img <img
src={comment.actor_detail.avatar} src={comment.actor_detail.avatar}
alt={ alt={comment.actor_detail.display_name}
comment.actor_detail.is_bot
? comment.actor_detail.first_name + " Bot"
: comment.actor_detail.display_name
}
height={30} height={30}
width={30} width={30}
className="grid h-7 w-7 place-items-center rounded-full border-2 border-custom-border-200" className="grid h-7 w-7 place-items-center rounded-full border-2 border-custom-border-200"
@ -78,9 +74,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
<div <div
className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`} className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}
> >
{comment.actor_detail.is_bot {comment.actor_detail.display_name.charAt(0)}
? comment.actor_detail.first_name.charAt(0)
: comment.actor_detail.display_name.charAt(0)}
</div> </div>
)} )}
@ -107,7 +101,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`} className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`}
onSubmit={handleSubmit(onEnter)} onSubmit={handleSubmit(onEnter)}
> >
<div id="tiptap-container"> <div>
<TiptapEditor <TiptapEditor
ref={editorRef} ref={editorRef}
value={watch("comment_html")} value={watch("comment_html")}

View File

@ -112,8 +112,9 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
{characterLimit && ( {characterLimit && (
<div className="pointer-events-none absolute bottom-1 right-1 z-[2] rounded bg-custom-background-100 text-custom-text-200 p-0.5 text-xs"> <div className="pointer-events-none absolute bottom-1 right-1 z-[2] rounded bg-custom-background-100 text-custom-text-200 p-0.5 text-xs">
<span <span
className={`${watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : "" className={`${
}`} watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : ""
}`}
> >
{watch("name").length} {watch("name").length}
</span> </span>
@ -122,7 +123,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
)} )}
</div> </div>
<span>{errors.name ? errors.name.message : null}</span> <span>{errors.name ? errors.name.message : null}</span>
<div id="tiptap-container" className="relative"> <div className="relative">
<Controller <Controller
name="description_html" name="description_html"
control={control} control={control}
@ -133,8 +134,8 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
<Tiptap <Tiptap
value={ value={
!value || !value ||
value === "" || value === "" ||
(typeof value === "object" && Object.keys(value).length === 0) (typeof value === "object" && Object.keys(value).length === 0)
? watch("description_html") ? watch("description_html")
: value : value
} }
@ -154,8 +155,12 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
); );
}} }}
/> />
<div className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${isSubmitting === 'saved' ? 'fadeOut' : 'fadeIn'}`}> <div
{isSubmitting === 'submitting' ? 'Saving...' : 'Saved'} className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
</div> </div>
</div> </div>
</div> </div>

View File

@ -333,7 +333,7 @@ export const IssueForm: FC<IssueFormProps> = ({
</div> </div>
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("description")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("description")) && (
<div id="tiptap-container" className="relative"> <div className="relative">
<div className="flex justify-end"> <div className="flex justify-end">
{issueName && issueName !== "" && ( {issueName && issueName !== "" && (
<button <button

View File

@ -80,9 +80,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
) : ( ) : (
<div className="w-12 h-12 bg-custom-background-80 rounded-full flex justify-center items-center"> <div className="w-12 h-12 bg-custom-background-80 rounded-full flex justify-center items-center">
<span className="text-custom-text-100 font-medium text-lg"> <span className="text-custom-text-100 font-medium text-lg">
{notification.triggered_by_details.is_bot ? ( {notification.triggered_by_details.display_name?.[0] ? (
notification.triggered_by_details.first_name?.[0]?.toUpperCase()
) : notification.triggered_by_details.display_name?.[0] ? (
notification.triggered_by_details.display_name?.[0]?.toUpperCase() notification.triggered_by_details.display_name?.[0]?.toUpperCase()
) : ( ) : (
<Icon iconName="person" className="h-6 w-6" /> <Icon iconName="person" className="h-6 w-6" />
@ -93,11 +91,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
</div> </div>
<div className="space-y-2.5 w-full overflow-hidden"> <div className="space-y-2.5 w-full overflow-hidden">
<div className="text-sm w-full break-words"> <div className="text-sm w-full break-words">
<span className="font-semibold"> <span className="font-semibold">{notification.triggered_by_details.display_name} </span>
{notification.triggered_by_details.is_bot
? notification.triggered_by_details.first_name
: notification.triggered_by_details.display_name}{" "}
</span>
{notification.data.issue_activity.field !== "comment" && {notification.data.issue_activity.field !== "comment" &&
notification.data.issue_activity.verb}{" "} notification.data.issue_activity.verb}{" "}
{notification.data.issue_activity.field === "comment" {notification.data.issue_activity.field === "comment"

View File

@ -284,10 +284,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
maxLength={255} maxLength={255}
/> />
</div> </div>
<div <div className="page-block-section relative -mt-2 text-custom-text-200">
id="tiptap-container"
className="page-block-section relative -mt-2 text-custom-text-200"
>
<Controller <Controller
name="description_html" name="description_html"
control={control} control={control}

View File

@ -105,9 +105,11 @@ export const ProfileSidebar = () => {
</div> </div>
<div className="px-5"> <div className="px-5">
<div className="mt-[38px]"> <div className="mt-[38px]">
<h4 className="text-lg font-semibold">{userProjectsData.user_data.display_name}</h4> <h4 className="text-lg font-semibold">
{userProjectsData.user_data.first_name} {userProjectsData.user_data.last_name}
</h4>
<h6 className="text-custom-text-200 text-sm"> <h6 className="text-custom-text-200 text-sm">
{userProjectsData.user_data.display_name} ({userProjectsData.user_data.display_name})
</h6> </h6>
</div> </div>
<div className="mt-6 space-y-5"> <div className="mt-6 space-y-5">

View File

@ -14,6 +14,9 @@ import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
import { IProjectPublishSettingsViews } from "store/project-publish"; import { IProjectPublishSettingsViews } from "store/project-publish";
// hooks
import useToast from "hooks/use-toast";
import useProjectDetails from "hooks/use-project-details";
type Props = { type Props = {
// user: ICurrentUserResponse | undefined; // user: ICurrentUserResponse | undefined;
@ -25,7 +28,7 @@ const defaultValues: Partial<any> = {
reactions: false, reactions: false,
votes: false, votes: false,
inbox: null, inbox: null,
views: [], views: ["list", "kanban"],
}; };
const viewOptions = [ const viewOptions = [
@ -40,6 +43,17 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
const store: RootStore = useMobxStore(); const store: RootStore = useMobxStore();
const { projectPublish } = store; const { projectPublish } = store;
const { projectDetails, mutateProjectDetails } = useProjectDetails();
const { setToastAlert } = useToast();
const handleToastAlert = (title: string, type: string, message: string) => {
setToastAlert({
title: title || "Title",
type: "error" || "warning",
message: message || "Message",
});
};
const { NEXT_PUBLIC_DEPLOY_URL } = process.env; const { NEXT_PUBLIC_DEPLOY_URL } = process.env;
const plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL const plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL
? NEXT_PUBLIC_DEPLOY_URL ? NEXT_PUBLIC_DEPLOY_URL
@ -111,32 +125,41 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
}, [workspaceSlug, projectPublish, projectPublish.projectPublishModal]); }, [workspaceSlug, projectPublish, projectPublish.projectPublishModal]);
const onSettingsPublish = async (formData: any) => { const onSettingsPublish = async (formData: any) => {
const payload = { if (formData.views && formData.views.length > 0) {
comments: formData.comments || false, const payload = {
reactions: formData.reactions || false, comments: formData.comments || false,
votes: formData.votes || false, reactions: formData.reactions || false,
inbox: formData.inbox || null, votes: formData.votes || false,
views: { inbox: formData.inbox || null,
list: formData.views.includes("list") || false, views: {
kanban: formData.views.includes("kanban") || false, list: formData.views.includes("list") || false,
calendar: formData.views.includes("calendar") || false, kanban: formData.views.includes("kanban") || false,
gantt: formData.views.includes("gantt") || false, calendar: formData.views.includes("calendar") || false,
spreadsheet: formData.views.includes("spreadsheet") || false, gantt: formData.views.includes("gantt") || false,
}, spreadsheet: formData.views.includes("spreadsheet") || false,
}; },
};
return projectPublish const _workspaceSlug = workspaceSlug;
.createProjectSettingsAsync( const _projectId = projectPublish.project_id;
workspaceSlug as string,
projectPublish.project_id as string, return projectPublish
payload, .createProjectSettingsAsync(_workspaceSlug as string, _projectId as string, payload, null)
null .then((response) => {
) mutateProjectDetails();
.then((response) => response) handleClose();
.catch((error) => { console.log("_projectId", _projectId);
console.error("error", error); if (_projectId)
return error; window.open(`${plane_deploy_url}/${_workspaceSlug}/${_projectId}`, "_blank");
}); return response;
})
.catch((error) => {
console.error("error", error);
return error;
});
} else {
handleToastAlert("Missing fields", "warning", "Please select at least one view to publish");
}
}; };
const onSettingsUpdate = async (key: string, value: any) => { const onSettingsUpdate = async (key: string, value: any) => {
@ -171,7 +194,10 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
payload, payload,
null null
) )
.then((response) => response) .then((response) => {
mutateProjectDetails();
return response;
})
.catch((error) => { .catch((error) => {
console.log("error", error); console.log("error", error);
return error; return error;
@ -187,7 +213,9 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
null null
) )
.then((response) => { .then((response) => {
mutateProjectDetails();
reset({ ...defaultValues }); reset({ ...defaultValues });
handleClose();
return response; return response;
}) })
.catch((error) => { .catch((error) => {

View File

@ -97,10 +97,14 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
{items.map((item, index) => ( {items.map((item, index) => (
<button <button
key={index} key={index}
type="button"
onClick={item.command} onClick={item.command}
className={cn("p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors", { className={cn(
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(), "p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
})} {
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
}
)}
> >
<item.icon <item.icon
className={cn("h-4 w-4", { className={cn("h-4 w-4", {

View File

@ -19,7 +19,11 @@ export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen
return ( return (
<div className="relative"> <div className="relative">
<button <button
className={cn("flex h-full items-center space-x-2 px-3 py-1.5 text-sm font-medium text-custom-text-300 hover:bg-custom-background-100 active:bg-custom-background-100", { "bg-custom-background-100": isOpen })} type="button"
className={cn(
"flex h-full items-center space-x-2 px-3 py-1.5 text-sm font-medium text-custom-text-300 hover:bg-custom-background-100 active:bg-custom-background-100",
{ "bg-custom-background-100": isOpen }
)}
onClick={() => { onClick={() => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
}} }}
@ -48,7 +52,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen
ref={inputRef} ref={inputRef}
type="url" type="url"
placeholder="Paste a link" placeholder="Paste a link"
className="flex-1 bg-custom-background-100 border border-custom-primary-300 p-1 text-sm outline-none placeholder:text-custom-text-400" className="flex-1 bg-custom-background-100 border-r border-custom-border-300 p-1 text-sm outline-none placeholder:text-custom-text-400"
defaultValue={editor.getAttributes("link").href || ""} defaultValue={editor.getAttributes("link").href || ""}
/> />
{editor.getAttributes("link").href ? ( {editor.getAttributes("link").href ? (

View File

@ -91,8 +91,9 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
return ( return (
<div className="relative h-full"> <div className="relative h-full">
<button <button
className="flex h-full items-center gap-1 whitespace-nowrap p-2 text-sm font-medium text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5" type="button"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className="flex h-full items-center gap-1 whitespace-nowrap p-2 text-sm font-medium text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5"
> >
<span>{activeItem?.name}</span> <span>{activeItem?.name}</span>
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
@ -103,11 +104,15 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
{items.map((item, index) => ( {items.map((item, index) => (
<button <button
key={index} key={index}
type="button"
onClick={() => { onClick={() => {
item.command(); item.command();
setIsOpen(false); setIsOpen(false);
}} }}
className={cn("flex items-center justify-between rounded-sm px-2 py-1 text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100", { "bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name })} className={cn(
"flex items-center justify-between rounded-sm px-2 py-1 text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100",
{ "bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name }
)}
> >
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="rounded-sm border border-custom-border-300 p-1"> <div className="rounded-sm border border-custom-border-300 p-1">

View File

@ -113,19 +113,20 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
}, 1000); }, 1000);
const editorClassNames = `relative w-full max-w-screen-lg sm:rounded-lg sm:shadow-lg mt-2 p-3 relative focus:outline-none rounded-md const editorClassNames = `relative w-full max-w-screen-lg sm:rounded-lg sm:shadow-lg mt-2 p-3 relative focus:outline-none rounded-md
${noBorder ? '' : 'border border-custom-border-200' ${noBorder ? "" : "border border-custom-border-200"} ${
} ${borderOnFocus ? 'focus:border border-custom-border-300' : 'focus:border-0' borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
} ${customClassName}`; } ${customClassName}`;
if (!editor) return null; if (!editor) return null;
editorRef.current = editor; editorRef.current = editor;
return ( return (
<div <div
id="tiptap-container"
onClick={() => { onClick={() => {
editor?.chain().focus().run(); editor?.chain().focus().run();
}} }}
className={`tiptap-editor-container ${editorClassNames}`} className={`tiptap-editor-container cursor-text ${editorClassNames}`}
> >
{editor && <EditorBubbleMenu editor={editor} />} {editor && <EditorBubbleMenu editor={editor} />}
<div className={`${editorContentCustomClassNames}`}> <div className={`${editorContentCustomClassNames}`}>

View File

@ -264,7 +264,10 @@ const CommandList = ({
> >
{items.map((item: CommandItemProps, index: number) => ( {items.map((item: CommandItemProps, index: number) => (
<button <button
className={cn(`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100`, { "bg-custom-primary-100/5 text-custom-text-100": index === selectedIndex })} className={cn(
`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100`,
{ "bg-custom-primary-100/5 text-custom-text-100": index === selectedIndex }
)}
key={index} key={index}
onClick={() => selectItem(index)} onClick={() => selectItem(index)}
> >
@ -282,8 +285,6 @@ const renderItems = () => {
let component: ReactRenderer | null = null; let component: ReactRenderer | null = null;
let popup: any | null = null; let popup: any | null = null;
const container = document.querySelector("#tiptap-container") as HTMLElement;
return { return {
onStart: (props: { editor: Editor; clientRect: DOMRect }) => { onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
component = new ReactRenderer(CommandList, { component = new ReactRenderer(CommandList, {
@ -294,7 +295,7 @@ const renderItems = () => {
// @ts-ignore // @ts-ignore
popup = tippy("body", { popup = tippy("body", {
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
appendTo: () => container, appendTo: () => document.querySelector("#tiptap-container"),
content: component.element, content: component.element,
showOnCreate: true, showOnCreate: true,
interactive: true, interactive: true,

View File

@ -1,5 +1,12 @@
// next imports
import { useRouter } from "next/router";
import Link from "next/link";
// icons // icons
import { Bars3Icon } from "@heroicons/react/24/outline"; import { Bars3Icon } from "@heroicons/react/24/outline";
// ui components
import { Tooltip } from "components/ui";
// hooks
import useProjectDetails from "hooks/use-project-details";
type Props = { type Props = {
breadcrumbs?: JSX.Element; breadcrumbs?: JSX.Element;
@ -9,27 +16,61 @@ type Props = {
noHeader: boolean; noHeader: boolean;
}; };
const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => ( const { NEXT_PUBLIC_DEPLOY_URL } = process.env;
<div const plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL ? NEXT_PUBLIC_DEPLOY_URL : "http://localhost:3001";
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 px-5 py-4 ${
noHeader ? "md:hidden" : "" const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => {
}`} const { projectDetails } = useProjectDetails();
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> const router = useRouter();
<div className="block md:hidden"> const { workspaceSlug, projectId } = router.query;
<button
type="button" return (
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200" <div
onClick={() => setToggleSidebar((prevData) => !prevData)} 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 px-5 py-4 ${
> noHeader ? "md:hidden" : ""
<Bars3Icon className="h-5 w-5" /> }`}
</button> >
<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={() => setToggleSidebar((prevData) => !prevData)}
>
<Bars3Icon className="h-5 w-5" />
</button>
</div>
<div>{breadcrumbs}</div>
{projectDetails && projectDetails?.is_deployed && (
<Link href={`${plane_deploy_url}/${workspaceSlug}/${projectId}`}>
<a target="_blank" rel="noreferrer">
<Tooltip
tooltipContent="This project is public, and live on web."
position="bottom-left"
>
<div className="transition-all flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 p-1 rounded overflow-hidden relative flex items-center gap-1 cursor-pointer group">
<div className="w-[14px] h-[14px] flex justify-center items-center">
<span className="material-symbols-rounded text-[14px]">
radio_button_checked
</span>
</div>
<div className="text-xs font-medium">Public</div>
<div className="w-[14px] h-[14px] hidden group-hover:flex justify-center items-center">
<span className="material-symbols-rounded text-[14px]">open_in_new</span>
</div>
</div>
</Tooltip>
</a>
</Link>
)}
<div className="flex-shrink-0">{left}</div>
</div> </div>
{breadcrumbs} <div className="flex-shrink-0">{right}</div>
<div className="flex-shrink-0">{left}</div>
</div> </div>
<div className="flex-shrink-0">{right}</div> );
</div> };
);
export default Header; export default Header;

View File

@ -259,15 +259,13 @@ class ProjectPublishStore implements IProjectPublishStore {
user user
); );
if (response) { runInAction(() => {
runInAction(() => { this.projectPublishSettings = "not-initialized";
this.projectPublishSettings = "not-initialized"; this.loader = false;
this.loader = false; this.error = null;
this.error = null; });
});
return response; return response;
}
} catch (error) { } catch (error) {
this.loader = false; this.loader = false;
this.error = error; this.error = error;

View File

@ -57,6 +57,7 @@ export interface IProject {
updated_by: string; updated_by: string;
workspace: IWorkspace | string; workspace: IWorkspace | string;
workspace_detail: IWorkspaceLite; workspace_detail: IWorkspaceLite;
is_deployed: boolean;
} }
export interface IProjectLite { export interface IProjectLite {

View File

@ -0,0 +1,30 @@
// next imports
import Image from "next/image";
const Custom404Error = () => (
<div className="relative w-screen min-h-screen h-full flex justify-center items-center py-5">
<div className="max-w-[700px] space-y-5">
<div className="flex items-center flex-col gap-3 text-center">
<div className="relative w-[240px] h-[240px]">
<Image src={`/404.svg`} layout="fill" alt="404- Page not found" />
</div>
<div className="text-xl font-medium">Oops! Something went wrong.</div>
<div className="text-sm text-custom-text-200">
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
temporarily unavailable.
</div>
</div>
<div className="text-center flex justify-center items-center">
<a
href={`https://app.plane.so/`}
className="transition-all border border-gray-200 bg-gray-50 hover:bg-gray-100 text-gray-700 hover:text-gray-800 cursor-pointer p-1.5 px-2.5 rounded-sm text-sm font-medium hover:scale-105 select-none"
>
Go to your Workspace
</a>
</div>
</div>
</div>
);
export default Custom404Error;

View File

@ -7,6 +7,7 @@ import IssueNavbar from "components/issues/navbar";
import IssueFilter from "components/issues/filters-render"; import IssueFilter from "components/issues/filters-render";
// service // service
import ProjectService from "services/project.service"; import ProjectService from "services/project.service";
import { redirect } from "next/navigation";
type LayoutProps = { type LayoutProps = {
params: { workspace_slug: string; project_slug: string }; params: { workspace_slug: string; project_slug: string };
@ -17,17 +18,26 @@ export async function generateMetadata({ params }: LayoutProps): Promise<Metadat
const { workspace_slug, project_slug } = params; const { workspace_slug, project_slug } = params;
const projectServiceInstance = new ProjectService(); const projectServiceInstance = new ProjectService();
const project = await projectServiceInstance?.getProjectSettingsAsync(workspace_slug, project_slug); try {
const project = await projectServiceInstance?.getProjectSettingsAsync(workspace_slug, project_slug);
return { return {
title: `${project?.project_details?.name} | ${workspace_slug}`, title: `${project?.project_details?.name} | ${workspace_slug}`,
description: `${project?.project_details?.description || `${project?.project_details?.name} | ${workspace_slug}`}`, description: `${
icons: `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${ project?.project_details?.description || `${project?.project_details?.name} | ${workspace_slug}`
typeof project?.project_details?.emoji != "object" }`,
? String.fromCodePoint(parseInt(project?.project_details?.emoji)) icons: `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${
: "✈️" typeof project?.project_details?.emoji != "object"
}</text></svg>`, ? String.fromCodePoint(parseInt(project?.project_details?.emoji))
}; : "✈️"
}</text></svg>`,
};
} catch (error: any) {
if (error?.data?.error) {
redirect(`/project-not-published`);
}
return {};
}
} }
const RootLayout = ({ children }: { children: React.ReactNode }) => ( const RootLayout = ({ children }: { children: React.ReactNode }) => (

View File

@ -0,0 +1,31 @@
// next imports
import Image from "next/image";
const CustomProjectNotPublishedError = () => (
<div className="relative w-screen min-h-screen h-full flex justify-center items-center py-5">
<div className="max-w-[700px] space-y-5">
<div className="flex items-center flex-col gap-3 text-center">
<div className="relative w-[240px] h-[240px]">
<Image src={`/project-not-published.svg`} layout="fill" alt="404- Page not found" />
</div>
<div className="text-xl font-medium">
Oops! The page you{`'`}re looking for isn{`'`}t live at the moment.
</div>
<div className="text-sm text-custom-text-200">
If this is your project, login to your workspace to adjust its visibility settings and make it public.
</div>
</div>
<div className="text-center flex justify-center items-center">
<a
href={`https://app.plane.so/`}
className="transition-all border border-gray-200 bg-gray-50 hover:bg-gray-100 text-gray-700 hover:text-gray-800 cursor-pointer p-1.5 px-2.5 rounded-sm text-sm font-medium hover:scale-105 select-none"
>
Go to your Workspace
</a>
</div>
</div>
</div>
);
export default CustomProjectNotPublishedError;

View File

@ -27,7 +27,7 @@ export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
<div className="font-medium text-gray-800 h-full line-clamp-2">{issue.name}</div> <div className="font-medium text-gray-800 h-full line-clamp-2">{issue.name}</div>
{/* priority */} {/* priority */}
<div className="relative flex items-center gap-3 w-full"> <div className="relative flex flex-wrap items-center gap-2 w-full">
{issue?.priority && ( {issue?.priority && (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<IssueBlockPriority priority={issue?.priority} /> <IssueBlockPriority priority={issue?.priority} />

View File

@ -20,7 +20,7 @@
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"mobx": "^6.10.0", "mobx": "^6.10.0",
"mobx-react-lite": "^4.0.3", "mobx-react-lite": "^4.0.3",
"next": "^13.4.13", "next": "^13.4.16",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

17
apps/space/public/404.svg Normal file
View File

@ -0,0 +1,17 @@
<svg width="596" height="568" viewBox="0 0 596 568" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="293" cy="284" r="284" fill="#F1F3F5"/>
<path d="M191.016 181.117H178.242V174.008H190.293V169.477H178.242V162.68H191.016V157.816H172.344V186H191.016V181.117ZM230.172 162.426H235.191C238.121 162.426 239.957 164.184 239.957 166.918C239.957 169.711 238.219 171.41 235.25 171.41H230.172V162.426ZM230.172 175.688H234.898L240.152 186H246.832L240.895 174.809C244.137 173.539 246.012 170.414 246.012 166.801C246.012 161.234 242.301 157.816 235.816 157.816H224.273V186H230.172V175.688ZM284.797 162.426H289.816C292.746 162.426 294.582 164.184 294.582 166.918C294.582 169.711 292.844 171.41 289.875 171.41H284.797V162.426ZM284.797 175.688H289.523L294.777 186H301.457L295.52 174.809C298.762 173.539 300.637 170.414 300.637 166.801C300.637 161.234 296.926 157.816 290.441 157.816H278.898V186H284.797V175.688ZM346.238 157.328C337.879 157.328 332.645 162.934 332.645 171.918C332.645 180.883 337.879 186.488 346.238 186.488C354.578 186.488 359.832 180.883 359.832 171.918C359.832 162.934 354.578 157.328 346.238 157.328ZM346.238 162.25C350.848 162.25 353.797 166 353.797 171.918C353.797 177.816 350.848 181.547 346.238 181.547C341.609 181.547 338.66 177.816 338.66 171.918C338.66 166 341.629 162.25 346.238 162.25ZM398.539 162.426H403.559C406.488 162.426 408.324 164.184 408.324 166.918C408.324 169.711 406.586 171.41 403.617 171.41H398.539V162.426ZM398.539 175.688H403.266L408.52 186H415.199L409.262 174.809C412.504 173.539 414.379 170.414 414.379 166.801C414.379 161.234 410.668 157.816 404.184 157.816H392.641V186H398.539V175.688Z" fill="black"/>
<path d="M191.016 181.117H178.242V174.008H190.293V169.477H178.242V162.68H191.016V157.816H172.344V186H191.016V181.117ZM230.172 162.426H235.191C238.121 162.426 239.957 164.184 239.957 166.918C239.957 169.711 238.219 171.41 235.25 171.41H230.172V162.426ZM230.172 175.688H234.898L240.152 186H246.832L240.895 174.809C244.137 173.539 246.012 170.414 246.012 166.801C246.012 161.234 242.301 157.816 235.816 157.816H224.273V186H230.172V175.688ZM284.797 162.426H289.816C292.746 162.426 294.582 164.184 294.582 166.918C294.582 169.711 292.844 171.41 289.875 171.41H284.797V162.426ZM284.797 175.688H289.523L294.777 186H301.457L295.52 174.809C298.762 173.539 300.637 170.414 300.637 166.801C300.637 161.234 296.926 157.816 290.441 157.816H278.898V186H284.797V175.688ZM346.238 157.328C337.879 157.328 332.645 162.934 332.645 171.918C332.645 180.883 337.879 186.488 346.238 186.488C354.578 186.488 359.832 180.883 359.832 171.918C359.832 162.934 354.578 157.328 346.238 157.328ZM346.238 162.25C350.848 162.25 353.797 166 353.797 171.918C353.797 177.816 350.848 181.547 346.238 181.547C341.609 181.547 338.66 177.816 338.66 171.918C338.66 166 341.629 162.25 346.238 162.25ZM398.539 162.426H403.559C406.488 162.426 408.324 164.184 408.324 166.918C408.324 169.711 406.586 171.41 403.617 171.41H398.539V162.426ZM398.539 175.688H403.266L408.52 186H415.199L409.262 174.809C412.504 173.539 414.379 170.414 414.379 166.801C414.379 161.234 410.668 157.816 404.184 157.816H392.641V186H398.539V175.688Z" fill="black"/>
<mask id="mask0_468_2978" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="10" y="258" width="568" height="222">
<path d="M115.723 475H157.764V437.354H185.303V401.904H157.764V263.623H95.3613C52.002 327.49 29.0039 364.551 10.5469 399.707V437.354H115.723V475ZM49.3652 402.051C66.5039 368.945 84.9609 340.088 115.723 294.971H116.602V403.223H49.3652V402.051ZM292.857 479.688C346.031 479.688 378.258 437.061 378.258 368.799C378.258 300.537 345.738 258.789 292.857 258.789C239.977 258.789 207.311 300.684 207.311 368.945C207.311 437.354 239.684 479.688 292.857 479.688ZM292.857 444.238C267.662 444.238 252.281 416.992 252.281 368.945C252.281 321.338 267.955 294.238 292.857 294.238C317.906 294.238 333.287 321.191 333.287 368.945C333.287 417.139 318.053 444.238 292.857 444.238ZM507.785 475H549.826V437.354H577.365V401.904H549.826V263.623H487.424C444.064 327.49 421.066 364.551 402.609 399.707V437.354H507.785V475ZM441.428 402.051C458.566 368.945 477.023 340.088 507.785 294.971H508.664V403.223H441.428V402.051Z" fill="#858E96"/>
</mask>
<g mask="url(#mask0_468_2978)">
<path d="M369.5 527L243 223.5H-27.5V527H369.5Z" fill="#858E96"/>
</g>
<mask id="mask1_468_2978" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="19" y="277" width="568" height="222">
<path d="M124.723 494H166.764V456.354H194.303V420.904H166.764V282.623H104.361C61.002 346.49 38.0039 383.551 19.5469 418.707V456.354H124.723V494ZM58.3652 421.051C75.5039 387.945 93.9609 359.088 124.723 313.971H125.602V422.223H58.3652V421.051ZM301.857 498.688C355.031 498.688 387.258 456.061 387.258 387.799C387.258 319.537 354.738 277.789 301.857 277.789C248.977 277.789 216.311 319.684 216.311 387.945C216.311 456.354 248.684 498.688 301.857 498.688ZM301.857 463.238C276.662 463.238 261.281 435.992 261.281 387.945C261.281 340.338 276.955 313.238 301.857 313.238C326.906 313.238 342.287 340.191 342.287 387.945C342.287 436.139 327.053 463.238 301.857 463.238ZM516.785 494H558.826V456.354H586.365V420.904H558.826V282.623H496.424C453.064 346.49 430.066 383.551 411.609 418.707V456.354H516.785V494ZM450.428 421.051C467.566 387.945 486.023 359.088 516.785 313.971H517.664V422.223H450.428V421.051Z" fill="#ACB5BD"/>
</mask>
<g mask="url(#mask1_468_2978)">
<path d="M250 242.5L376.5 546H647V242.5H250Z" fill="#858E96"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,4 @@
<svg width="219" height="219" viewBox="0 0 219 219" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="109.5" cy="109.5" r="109.5" fill="#FAFAFA"/>
<path d="M110.423 153.361C104.339 153.361 98.6853 152.27 93.4608 150.087C88.2363 147.904 83.6917 144.88 79.827 141.016C75.9623 137.151 72.9385 132.606 70.7557 127.382C68.5728 122.157 67.4814 116.504 67.4814 110.42C67.4814 105.554 68.1971 100.973 69.6285 96.679C71.0598 92.3849 73.0638 88.4486 75.6402 84.8702L63.9388 73.1687C63.2946 72.5246 62.9905 71.7552 63.0263 70.8606C63.062 69.966 63.402 69.1967 64.0461 68.5525C64.6902 67.9084 65.4596 67.5864 66.3542 67.5864C67.2488 67.5864 68.0182 67.9084 68.6623 68.5525L150.68 150.678C151.324 151.322 151.646 152.073 151.646 152.932C151.646 153.791 151.324 154.542 150.68 155.186C150.036 155.83 149.266 156.153 148.372 156.153C147.477 156.153 146.708 155.83 146.064 155.186L135.973 145.203C132.394 147.779 128.458 149.783 124.164 151.214C119.87 152.646 115.289 153.361 110.423 153.361ZM110.423 146.92C114.359 146.92 118.08 146.366 121.587 145.256C125.094 144.147 128.351 142.59 131.356 140.586L112.355 121.585L106.343 127.597C105.699 128.241 104.948 128.581 104.089 128.617C103.23 128.652 102.478 128.348 101.834 127.704L88.7373 114.5C88.0932 113.855 87.789 113.05 87.8248 112.084C87.8606 111.118 88.2005 110.313 88.8446 109.669C89.4888 109.025 90.2939 108.703 91.2601 108.703C92.2263 108.703 93.0314 109.025 93.6755 109.669L104.089 120.189L107.524 116.754L80.2564 89.4864C78.2525 92.4922 76.6959 95.7486 75.5866 99.2555C74.4772 102.762 73.9226 106.484 73.9226 110.42C73.9226 120.798 77.4116 129.475 84.3895 136.453C91.3674 143.431 100.045 146.92 110.423 146.92ZM145.205 135.97L140.589 131.354C142.593 128.348 144.149 125.092 145.259 121.585C146.368 118.078 146.923 114.356 146.923 110.42C146.923 100.043 143.434 91.365 136.456 84.3871C129.478 77.4092 120.8 73.9202 110.423 73.9202C106.486 73.9202 102.765 74.4748 99.2579 75.5842C95.751 76.6935 92.4946 78.2501 89.4888 80.254L84.8726 75.6378C88.451 73.0614 92.3873 71.0574 96.6814 69.6261C100.976 68.1947 105.556 67.479 110.423 67.479C116.434 67.479 122.052 68.5883 127.277 70.8069C132.502 73.0256 137.046 76.0672 140.911 79.9319C144.776 83.7967 147.817 88.3413 150.036 93.5658C152.254 98.7903 153.364 104.408 153.364 110.42C153.364 115.287 152.648 119.867 151.217 124.161C149.785 128.455 147.781 132.392 145.205 135.97ZM121.695 112.353L116.756 107.522L127.17 97.1084C127.814 96.4643 128.601 96.1422 129.531 96.1422C130.462 96.1422 131.285 96.4643 132.001 97.1084C132.716 97.8241 133.074 98.6471 133.074 99.5775C133.074 100.508 132.716 101.331 132.001 102.047L121.695 112.353Z" fill="#B9B9B9"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

771
yarn.lock

File diff suppressed because it is too large Load Diff