Skip to content

Commit 3e5e1ab

Browse files
committed
feat: sub-issues, fix: loading screen after sign out
1 parent 2acada3 commit 3e5e1ab

File tree

7 files changed

+552
-81
lines changed

7 files changed

+552
-81
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import React, { useState } from "react";
2+
// swr
3+
import { mutate } from "swr";
4+
// react hook form
5+
import { useForm } from "react-hook-form";
6+
// headless ui
7+
import { Combobox, Dialog, Transition } from "@headlessui/react";
8+
// hooks
9+
import useUser from "lib/hooks/useUser";
10+
// icons
11+
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
12+
import { FolderIcon } from "@heroicons/react/24/outline";
13+
// commons
14+
import { classNames } from "constants/common";
15+
// types
16+
import { IIssue, IssueResponse } from "types";
17+
import { Button } from "ui";
18+
import { PROJECT_ISSUES_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
19+
import issuesServices from "lib/services/issues.services";
20+
21+
type Props = {
22+
isOpen: boolean;
23+
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
24+
parentId: string;
25+
};
26+
27+
type FormInput = {
28+
issue_ids: string[];
29+
cycleId: string;
30+
};
31+
32+
const AddAsSubIssue: React.FC<Props> = ({ isOpen, setIsOpen, parentId }) => {
33+
const [query, setQuery] = useState("");
34+
35+
const { activeWorkspace, activeProject, issues } = useUser();
36+
37+
const filteredIssues: IIssue[] =
38+
query === ""
39+
? issues?.results ?? []
40+
: issues?.results.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ??
41+
[];
42+
43+
const {
44+
register,
45+
formState: { errors, isSubmitting },
46+
handleSubmit,
47+
control,
48+
reset,
49+
setError,
50+
} = useForm<FormInput>();
51+
52+
const handleCommandPaletteClose = () => {
53+
setIsOpen(false);
54+
setQuery("");
55+
reset();
56+
};
57+
58+
const addAsSubIssue = (issueId: string) => {
59+
if (activeWorkspace && activeProject) {
60+
issuesServices
61+
.patchIssue(activeWorkspace.slug, activeProject.id, issueId, { parent: parentId })
62+
.then((res) => {
63+
mutate<IssueResponse>(
64+
PROJECT_ISSUES_LIST(activeWorkspace.slug, activeProject.id),
65+
(prevData) => ({
66+
...(prevData as IssueResponse),
67+
results: (prevData?.results ?? []).map((p) =>
68+
p.id === issueId ? { ...p, ...res } : p
69+
),
70+
}),
71+
false
72+
);
73+
})
74+
.catch((e) => {
75+
console.log(e);
76+
});
77+
}
78+
};
79+
80+
return (
81+
<>
82+
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
83+
<Dialog as="div" className="relative z-10" onClose={handleCommandPaletteClose}>
84+
<Transition.Child
85+
as={React.Fragment}
86+
enter="ease-out duration-300"
87+
enterFrom="opacity-0"
88+
enterTo="opacity-100"
89+
leave="ease-in duration-200"
90+
leaveFrom="opacity-100"
91+
leaveTo="opacity-0"
92+
>
93+
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
94+
</Transition.Child>
95+
96+
<div className="fixed inset-0 z-10 overflow-y-auto p-4 sm:p-6 md:p-20">
97+
<Transition.Child
98+
as={React.Fragment}
99+
enter="ease-out duration-300"
100+
enterFrom="opacity-0 scale-95"
101+
enterTo="opacity-100 scale-100"
102+
leave="ease-in duration-200"
103+
leaveFrom="opacity-100 scale-100"
104+
leaveTo="opacity-0 scale-95"
105+
>
106+
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white bg-opacity-80 shadow-2xl ring-1 ring-black ring-opacity-5 backdrop-blur backdrop-filter transition-all">
107+
<Combobox
108+
// onChange={(item: ItemType) => {
109+
// const { url, onClick } = item;
110+
// if (url) router.push(url);
111+
// else if (onClick) onClick();
112+
// handleCommandPaletteClose();
113+
// }}
114+
>
115+
<div className="relative m-1">
116+
<MagnifyingGlassIcon
117+
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
118+
aria-hidden="true"
119+
/>
120+
<Combobox.Input
121+
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm outline-none"
122+
placeholder="Search..."
123+
onChange={(e) => setQuery(e.target.value)}
124+
/>
125+
</div>
126+
127+
<Combobox.Options
128+
static
129+
className="max-h-80 scroll-py-2 divide-y divide-gray-500 divide-opacity-10 overflow-y-auto"
130+
>
131+
{filteredIssues.length > 0 && (
132+
<>
133+
<li className="p-2">
134+
{query === "" && (
135+
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
136+
Issues
137+
</h2>
138+
)}
139+
<ul className="text-sm text-gray-700">
140+
{filteredIssues.map((issue) => {
141+
if (issue.parent === "" || issue.parent === null)
142+
return (
143+
<Combobox.Option
144+
key={issue.id}
145+
value={{
146+
name: issue.name,
147+
}}
148+
className={({ active }) =>
149+
classNames(
150+
"flex items-center gap-2 cursor-pointer select-none rounded-md px-3 py-2",
151+
active ? "bg-gray-900 bg-opacity-5 text-gray-900" : ""
152+
)
153+
}
154+
onClick={() => {
155+
addAsSubIssue(issue.id);
156+
setIsOpen(false);
157+
}}
158+
>
159+
<span
160+
className={`h-1.5 w-1.5 block rounded-full`}
161+
style={{
162+
backgroundColor: issue.state_detail.color,
163+
}}
164+
/>
165+
{issue.name}
166+
</Combobox.Option>
167+
);
168+
})}
169+
</ul>
170+
</li>
171+
</>
172+
)}
173+
</Combobox.Options>
174+
175+
{query !== "" && filteredIssues.length === 0 && (
176+
<div className="py-14 px-6 text-center sm:px-14">
177+
<FolderIcon
178+
className="mx-auto h-6 w-6 text-gray-900 text-opacity-40"
179+
aria-hidden="true"
180+
/>
181+
<p className="mt-4 text-sm text-gray-900">
182+
We couldn{"'"}t find any issue with that term. Please try again.
183+
</p>
184+
</div>
185+
)}
186+
</Combobox>
187+
</Dialog.Panel>
188+
</Transition.Child>
189+
</div>
190+
</Dialog>
191+
</Transition.Root>
192+
</>
193+
);
194+
};
195+
196+
export default AddAsSubIssue;

apps/app/components/lexical/editor.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { FC } from "react";
21
import { EditorState, LexicalEditor, $getRoot, $getSelection } from "lexical";
32
import { LexicalComposer } from "@lexical/react/LexicalComposer";
43
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
@@ -25,12 +24,15 @@ export interface RichTextEditorProps {
2524
onChange: (state: string) => void;
2625
id: string;
2726
value: string;
27+
placeholder?: string;
2828
}
2929

30-
const RichTextEditor: FC<RichTextEditorProps> = (props) => {
31-
// props
32-
const { onChange, value, id } = props;
33-
30+
const RichTextEditor: React.FC<RichTextEditorProps> = ({
31+
onChange,
32+
id,
33+
value,
34+
placeholder = "Enter some text...",
35+
}) => {
3436
function handleChange(state: EditorState, editor: LexicalEditor) {
3537
state.read(() => {
3638
onChange(JSON.stringify(state.toJSON()));
@@ -54,8 +56,8 @@ const RichTextEditor: FC<RichTextEditorProps> = (props) => {
5456
}
5557
ErrorBoundary={LexicalErrorBoundary}
5658
placeholder={
57-
<div className="absolute top-[15px] left-[10px] pointer-events-none select-none text-gray-400">
58-
Enter some text...
59+
<div className="absolute top-4 left-3 pointer-events-none select-none text-gray-400">
60+
{placeholder}
5961
</div>
6062
}
6163
/>

apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ const IssueDetailSidebar: React.FC<Props> = ({ control, submitChanges, issueDeta
262262
render={({ field: { value, onChange } }) => (
263263
<input
264264
type="date"
265-
value={value ?? new Date().toString()}
265+
value={""}
266266
onChange={(e: any) => {
267267
submitChanges({ target_date: e.target.value });
268268
onChange(e.target.value);

apps/app/layouts/AdminLayout.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
// react
2-
import React, { useState } from "react";
2+
import React, { useEffect, useState } from "react";
3+
// next
4+
import { useRouter } from "next/router";
5+
// hooks
6+
import useUser from "lib/hooks/useUser";
37
// layouts
48
import Container from "layouts/Container";
59
import Sidebar from "layouts/Navbar/Sidebar";
@@ -11,6 +15,14 @@ import type { Props } from "./types";
1115
const AdminLayout: React.FC<Props> = ({ meta, children }) => {
1216
const [isOpen, setIsOpen] = useState(false);
1317

18+
const router = useRouter();
19+
20+
const { user, isUserLoading } = useUser();
21+
22+
useEffect(() => {
23+
if (!isUserLoading && (!user || user === null)) router.push("/signin");
24+
}, [isUserLoading, user, router]);
25+
1426
return (
1527
<Container meta={meta}>
1628
<CreateProjectModal isOpen={isOpen} setIsOpen={setIsOpen} />

0 commit comments

Comments
 (0)