build: setup turbo repo

This commit is contained in:
pablohashescobar 2022-11-30 02:21:17 +05:30
parent 976e5b9c27
commit ba47c273b1
148 changed files with 3177 additions and 515 deletions

View file

@ -0,0 +1,357 @@
import React, { useState } from "react";
// Next imports
import Link from "next/link";
// React beautiful dnd
import { Draggable } from "react-beautiful-dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
// common
import { addSpaceIfCamelCase, renderShortNumericDateFormat } from "constants/common";
// types
import { IIssue, Properties, NestedKeyOf } from "types";
// icons
import {
ArrowsPointingInIcon,
ArrowsPointingOutIcon,
CalendarDaysIcon,
EllipsisHorizontalIcon,
PencilIcon,
PlusIcon,
} from "@heroicons/react/24/outline";
import Image from "next/image";
type Props = {
selectedGroup: NestedKeyOf<IIssue> | null;
groupTitle: string;
groupedByIssues: any;
index: number;
setIsIssueOpen: React.Dispatch<React.SetStateAction<boolean>>;
properties: Properties;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| undefined
>
>;
bgColor?: string;
stateId?: string;
createdBy?: string;
};
const SingleBoard: React.FC<Props> = ({
selectedGroup,
groupTitle,
groupedByIssues,
index,
setIsIssueOpen,
properties,
setPreloadedData,
bgColor = "#0f2b16",
stateId,
createdBy,
}) => {
// Collapse/Expand
const [show, setState] = useState<any>(true);
// Edit state name
const [showInput, setInput] = useState<any>(false);
if (selectedGroup === "priority")
groupTitle === "high"
? (bgColor = "#dc2626")
: groupTitle === "medium"
? (bgColor = "#f97316")
: groupTitle === "low"
? (bgColor = "#22c55e")
: (bgColor = "#ff0000");
return (
<Draggable draggableId={groupTitle} index={index}>
{(provided, snapshot) => (
<div
className={`rounded flex-shrink-0 h-full ${
snapshot.isDragging ? "border-indigo-600 shadow-lg" : ""
} ${!show ? "" : "w-80 bg-gray-50 border"}`}
ref={provided.innerRef}
{...provided.draggableProps}
>
<div className={`${!show ? "" : "h-full space-y-3 overflow-y-auto flex flex-col"}`}>
<div
className={`flex justify-between p-3 pb-0 ${
snapshot.isDragging ? "bg-indigo-50 border-indigo-100 border-b" : ""
} ${!show ? "flex-col bg-gray-50 rounded-md border" : ""}`}
>
{showInput ? null : (
<div className={`flex items-center ${!show ? "flex-col gap-2" : "gap-1"}`}>
<button
type="button"
{...provided.dragHandleProps}
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
!show ? "" : "rotate-90"
} ${selectedGroup !== "state_detail.name" ? "hidden" : ""}`}
>
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600" />
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600 mt-[-0.7rem]" />
</button>
<div
className={`flex items-center gap-x-1 px-2 bg-slate-900 rounded-md cursor-pointer ${
!show ? "py-2 mb-2 flex-col gap-y-2" : ""
}`}
style={{
border: `2px solid ${bgColor}`,
backgroundColor: `${bgColor}20`,
}}
onClick={() => {
// setInput(true);
}}
>
<span
className={`w-3 h-3 block rounded-full ${!show ? "" : "mr-1"}`}
style={{
backgroundColor: bgColor,
}}
/>
<h2
className={`text-[0.9rem] font-medium capitalize`}
style={{
writingMode: !show ? "vertical-rl" : "horizontal-tb",
}}
>
{groupTitle === null || groupTitle === "null"
? "None"
: createdBy
? createdBy
: addSpaceIfCamelCase(groupTitle)}
</h2>
<span className="text-gray-500 text-sm ml-0.5">
{groupedByIssues[groupTitle].length}
</span>
</div>
</div>
)}
<div className={`flex items-center ${!show ? "flex-col pb-2" : ""}`}>
<button
type="button"
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none"
onClick={() => {
setState(!show);
setInput(false);
}}
>
{show ? (
<ArrowsPointingInIcon className="h-4 w-4" />
) : (
<ArrowsPointingOutIcon className="h-4 w-4" />
)}
</button>
<button
type="button"
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none"
onClick={() => {
setIsIssueOpen(true);
if (selectedGroup !== null)
setPreloadedData({
state: stateId,
[selectedGroup]: groupTitle,
actionType: "createIssue",
});
}}
>
<PlusIcon className="h-4 w-4" />
</button>
<button
type="button"
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none"
onClick={() =>
setPreloadedData({
// ...state,
actionType: "edit",
})
}
>
<PencilIcon className="h-4 w-4" />
</button>
{/* <button
type="button"
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300"
onClick={() =>
setSelectedState({
...state,
actionType: "delete",
})
}
>
<TrashIcon className="h-4 w-4 text-red-500" />
</button> */}
</div>
</div>
<StrictModeDroppable key={groupTitle} droppableId={groupTitle}>
{(provided, snapshot) => (
<div
className={`mt-3 space-y-3 h-full overflow-y-auto px-3 ${
snapshot.isDraggingOver ? "bg-indigo-50 bg-opacity-50" : ""
} ${!show ? "hidden" : "block"}`}
{...provided.droppableProps}
ref={provided.innerRef}
>
{groupedByIssues[groupTitle].map((childIssue: any, index: number) => (
<Draggable key={childIssue.id} draggableId={childIssue.id} index={index}>
{(provided, snapshot) => (
<Link href={`/projects/${childIssue.project}/issues/${childIssue.id}`}>
<a
className={`group block border rounded bg-white shadow-sm ${
snapshot.isDragging ? "border-indigo-600 shadow-lg bg-indigo-50" : ""
}`}
ref={provided.innerRef}
{...provided.draggableProps}
>
<div
className="px-2 py-3 space-y-1.5 select-none"
{...provided.dragHandleProps}
>
{Object.keys(properties).map(
(key) =>
properties[key as keyof Properties] &&
!Array.isArray(childIssue[key as keyof IIssue]) && (
<div
key={key}
className={`${
key === "name"
? "text-sm font-medium mb-2"
: key === "description"
? "text-xs text-black"
: key === "priority"
? `text-xs bg-gray-200 px-2 py-1 mt-2 flex items-center gap-x-1 rounded w-min whitespace-nowrap capitalize font-medium ${
childIssue.priority === "high"
? "bg-red-100 text-red-600"
: childIssue.priority === "medium"
? "bg-orange-100 text-orange-500"
: childIssue.priority === "low"
? "bg-green-100 text-green-500"
: "hidden"
}`
: key === "target_date"
? "text-xs bg-indigo-50 px-2 py-1 mt-2 flex items-center gap-x-1 rounded w-min whitespace-nowrap"
: "text-sm text-gray-500"
} gap-1
`}
>
{key === "target_date" ? (
<>
<CalendarDaysIcon className="h-4 w-4" />{" "}
{childIssue.target_date
? renderShortNumericDateFormat(childIssue.target_date)
: "N/A"}
</>
) : (
""
)}
{key === "name" && (
<span className="group-hover:text-theme">
{childIssue.name}
</span>
)}
{key === "state" && (
<>{addSpaceIfCamelCase(childIssue["state_detail"].name)}</>
)}
{key === "priority" && <>{childIssue.priority}</>}
{key === "description" && <>{childIssue.description}</>}
{key === "assignee" ? (
<div className="flex items-center gap-1 text-xs">
{childIssue?.assignee_details?.length > 0 ? (
childIssue?.assignee_details?.map(
(assignee: any, index: number) => (
<div
key={index}
className={`relative z-[1] h-5 w-5 rounded-full ${
index !== 0 ? "-ml-2.5" : ""
}`}
>
{assignee.avatar && assignee.avatar !== "" ? (
<div className="h-5 w-5 border-2 bg-white border-white rounded-full">
<Image
src={assignee.avatar}
height="100%"
width="100%"
className="rounded-full"
alt={assignee.name}
/>
</div>
) : (
<div
className={`h-5 w-5 bg-gray-700 text-white border-2 border-white grid place-items-center rounded-full`}
>
{assignee.first_name.charAt(0)}
</div>
)}
</div>
)
)
) : (
<span>None</span>
)}
</div>
) : null}
</div>
)
)}
</div>
{/* <div
className={`p-2 bg-indigo-50 flex items-center justify-between ${
snapshot.isDragging ? "bg-indigo-200" : ""
}`}
>
<button
type="button"
className="flex flex-col"
{...provided.dragHandleProps}
>
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600" />
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600 mt-[-0.7rem]" />
</button>
<div className="flex gap-1 items-center">
<button type="button">
<HeartIcon className="h-4 w-4 text-yellow-500" />
</button>
<button type="button">
<CheckCircleIcon className="h-4 w-4 text-green-500" />
</button>
</div>
</div> */}
</a>
</Link>
)}
</Draggable>
))}
{provided.placeholder}
<button
type="button"
className="flex items-center text-xs font-medium hover:bg-gray-200 p-2 rounded duration-300 outline-none"
onClick={() => {
setIsIssueOpen(true);
if (selectedGroup !== null) {
setPreloadedData({
state: stateId,
[selectedGroup]: groupTitle,
actionType: "createIssue",
});
}
}}
>
<PlusIcon className="h-3 w-3 mr-1" />
Create
</button>
</div>
)}
</StrictModeDroppable>
</div>
</div>
)}
</Draggable>
);
};
export default SingleBoard;

View file

@ -0,0 +1,225 @@
import React, { useCallback, useEffect, useState } from "react";
// next
import { useRouter } from "next/router";
// swr
import useSWR from "swr";
// react beautiful dnd
import type { DropResult } from "react-beautiful-dnd";
import { DragDropContext } from "react-beautiful-dnd";
// services
import stateServices from "lib/services/state.services";
import issuesServices from "lib/services/issues.services";
// hooks
import useUser from "lib/hooks/useUser";
// fetching keys
import { STATE_LIST } from "constants/fetch-keys";
// components
import SingleBoard from "components/project/issues/BoardView/SingleBoard";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal";
// ui
import { Spinner } from "ui";
// types
import type { IState, IIssue, Properties, NestedKeyOf, ProjectMember } from "types";
type Props = {
properties: Properties;
selectedGroup: NestedKeyOf<IIssue> | null;
groupedByIssues: {
[key: string]: IIssue[];
};
members: ProjectMember[] | undefined;
};
const BoardView: React.FC<Props> = ({ properties, selectedGroup, groupedByIssues, members }) => {
const [isOpen, setIsOpen] = useState(false);
const [isIssueOpen, setIsIssueOpen] = useState(false);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
const { activeWorkspace } = useUser();
const router = useRouter();
const { projectId } = router.query;
const { data: states, mutate: mutateState } = useSWR<IState[]>(
projectId && activeWorkspace ? STATE_LIST(projectId as string) : null,
activeWorkspace
? () => stateServices.getStates(activeWorkspace.slug, projectId as string)
: null
);
const handleOnDragEnd = useCallback(
(result: DropResult) => {
if (!result.destination) return;
const { source, destination, type } = result;
if (type === "state") {
const newStates = Array.from(states ?? []);
const [reorderedState] = newStates.splice(source.index, 1);
newStates.splice(destination.index, 0, reorderedState);
const prevSequenceNumber = newStates[destination.index - 1]?.sequence;
const nextSequenceNumber = newStates[destination.index + 1]?.sequence;
const sequenceNumber =
prevSequenceNumber && nextSequenceNumber
? (prevSequenceNumber + nextSequenceNumber) / 2
: nextSequenceNumber
? nextSequenceNumber - 15000 / 2
: prevSequenceNumber
? prevSequenceNumber + 15000 / 2
: 15000;
newStates[destination.index].sequence = sequenceNumber;
mutateState(newStates, false);
if (!activeWorkspace) return;
stateServices
.patchState(activeWorkspace.slug, projectId as string, newStates[destination.index].id, {
sequence: sequenceNumber,
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.error(err);
});
} else {
if (source.droppableId !== destination.droppableId) {
const sourceGroup = source.droppableId; // source group id
const destinationGroup = destination.droppableId; // destination group id
if (!sourceGroup || !destinationGroup) return;
// removed/dragged item
const removedItem = groupedByIssues[source.droppableId][source.index];
if (selectedGroup === "priority") {
// update the removed item for mutation
removedItem.priority = destinationGroup;
// patch request
issuesServices.patchIssue(activeWorkspace!.slug, projectId as string, removedItem.id, {
priority: destinationGroup,
});
} else if (selectedGroup === "state_detail.name") {
const destinationState = states?.find((s) => s.name === destinationGroup);
const destinationStateId = destinationState?.id;
// update the removed item for mutation
if (!destinationStateId || !destinationState) return;
removedItem.state = destinationStateId;
removedItem.state_detail = destinationState;
// patch request
issuesServices.patchIssue(activeWorkspace!.slug, projectId as string, removedItem.id, {
state: destinationStateId,
});
}
// remove item from the source group
groupedByIssues[source.droppableId].splice(source.index, 1);
// add item to the destination group
groupedByIssues[destination.droppableId].splice(destination.index, 0, removedItem);
}
}
},
[activeWorkspace, mutateState, groupedByIssues, projectId, selectedGroup, states]
);
useEffect(() => {
if (isOpen) return;
const timer = setTimeout(() => {
setPreloadedData(undefined);
clearTimeout(timer);
}, 500);
}, [isOpen]);
return (
<>
{/* <CreateUpdateStateModal
isOpen={
isOpen &&
preloadedData?.actionType !== "delete" &&
preloadedData?.actionType !== "createIssue"
}
setIsOpen={setIsOpen}
data={preloadedData as Partial<IIssue>}
projectId={projectId as string}
/> */}
{/* <ConfirmStateDeletion
isOpen={isOpen && preloadedData?.actionType === "delete"}
setIsOpen={setIsOpen}
data={preloadedData as Partial<IIssue>}
/> */}
<CreateUpdateIssuesModal
isOpen={isIssueOpen && preloadedData?.actionType === "createIssue"}
setIsOpen={setIsIssueOpen}
prePopulateData={{
...preloadedData,
}}
projectId={projectId as string}
/>
{groupedByIssues ? (
groupedByIssues ? (
<div className="w-full" style={{ height: "calc(82vh - 1.5rem)" }}>
<DragDropContext onDragEnd={handleOnDragEnd}>
<div className="h-full w-full overflow-hidden">
<StrictModeDroppable droppableId="state" type="state" direction="horizontal">
{(provided) => (
<div
className="h-full w-full"
{...provided.droppableProps}
ref={provided.innerRef}
>
<div className="flex gap-x-4 h-full overflow-x-auto overflow-y-hidden pb-3">
{Object.keys(groupedByIssues).map((singleGroup, index) => (
<SingleBoard
key={singleGroup}
selectedGroup={selectedGroup}
groupTitle={singleGroup}
createdBy={
members
? members?.find((m) => m.member.id === singleGroup)?.member
.first_name
: undefined
}
groupedByIssues={groupedByIssues}
index={index}
setIsIssueOpen={setIsIssueOpen}
properties={properties}
setPreloadedData={setPreloadedData}
stateId={
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.id
: undefined
}
bgColor={
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.color
: undefined
}
/>
))}
</div>
{provided.placeholder}
</div>
)}
</StrictModeDroppable>
</div>
</DragDropContext>
</div>
) : null
) : (
<div className="w-full h-full flex justify-center items-center">
<Spinner />
</div>
)}
</>
);
};
export default BoardView;

View file

@ -0,0 +1,147 @@
import React, { useEffect, useRef, useState } from "react";
// swr
import { mutate } from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
import stateServices from "lib/services/state.services";
// fetch api
import { STATE_LIST } from "constants/fetch-keys";
// hooks
import useUser from "lib/hooks/useUser";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { Button } from "ui";
// types
import type { IState } from "types";
type Props = {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
data?: IState;
};
const ConfirmStateDeletion: React.FC<Props> = ({ isOpen, setIsOpen, data }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const { activeWorkspace } = useUser();
const cancelButtonRef = useRef(null);
const handleClose = () => {
setIsOpen(false);
setIsDeleteLoading(false);
};
const handleDeletion = async () => {
setIsDeleteLoading(true);
if (!data || !activeWorkspace) return;
await stateServices
.deleteState(activeWorkspace.slug, data.project, data.id)
.then(() => {
mutate<IState[]>(
STATE_LIST(data.project),
(prevData) => prevData?.filter((state) => state.id !== data?.id),
false,
);
handleClose();
})
.catch((error) => {
console.log(error);
setIsDeleteLoading(false);
});
};
useEffect(() => {
data && setIsOpen(true);
}, [data, setIsOpen]);
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog
as="div"
className="relative z-10"
initialFocus={cancelButtonRef}
onClose={handleClose}
>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Delete State
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete state - {`"`}
<span className="italic">{data?.name}</span>
{`"`} ? All of the data related to the state will be
permanently removed. This action cannot be undone.
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<Button
type="button"
onClick={handleDeletion}
theme="danger"
disabled={isDeleteLoading}
className="inline-flex sm:ml-3"
>
{isDeleteLoading ? "Deleting..." : "Delete"}
</Button>
<Button
type="button"
theme="secondary"
className="inline-flex sm:ml-3"
onClick={handleClose}
ref={cancelButtonRef}
>
Cancel
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
export default ConfirmStateDeletion;

View file

@ -0,0 +1,253 @@
import React, { useEffect } from "react";
// swr
import { mutate } from "swr";
// react hook form
import { Controller, useForm } from "react-hook-form";
// react color
import { TwitterPicker } from "react-color";
// headless
import { Dialog, Popover, Transition } from "@headlessui/react";
// services
import stateService from "lib/services/state.services";
// fetch keys
import { STATE_LIST } from "constants/fetch-keys";
// hooks
import useUser from "lib/hooks/useUser";
// ui
import { Button, Input, TextArea } from "ui";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
// types
import type { IState } from "types";
type Props = {
isOpen: boolean;
projectId: string;
data?: IState;
handleClose: () => void;
};
const defaultValues: Partial<IState> = {
name: "",
description: "",
color: "#000000",
};
const CreateUpdateStateModal: React.FC<Props> = ({ isOpen, data, projectId, handleClose }) => {
const onClose = () => {
handleClose();
const timeout = setTimeout(() => {
reset(defaultValues);
clearTimeout(timeout);
}, 500);
};
const { activeWorkspace } = useUser();
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
watch,
control,
reset,
setError,
} = useForm<IState>({
defaultValues,
});
const onSubmit = async (formData: IState) => {
if (!activeWorkspace) return;
const payload: IState = {
...formData,
};
if (!data) {
await stateService
.createState(activeWorkspace.slug, projectId, payload)
.then((res) => {
mutate<IState[]>(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false);
onClose();
})
.catch((err) => {
Object.keys(err).map((key) => {
setError(key as keyof IState, {
message: err[key].join(", "),
});
});
});
} else {
await stateService
.updateState(activeWorkspace.slug, projectId, data.id, payload)
.then((res) => {
mutate<IState[]>(
STATE_LIST(projectId),
(prevData) => {
const newData = prevData?.map((item) => {
if (item.id === res.id) {
return res;
}
return item;
});
return newData;
},
false
);
onClose();
})
.catch((err) => {
Object.keys(err).map((key) => {
setError(key as keyof IState, {
message: err[key].join(", "),
});
});
});
}
};
useEffect(() => {
if (data) {
reset(data);
} else {
reset(defaultValues);
}
}, [data, reset]);
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<div className="mt-3 sm:mt-5">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
{data ? "Update" : "Create"} State
</Dialog.Title>
<div className="mt-2 space-y-3">
<div>
<Input
id="name"
label="Name"
name="name"
type="name"
placeholder="Enter name"
autoComplete="off"
error={errors.name}
register={register}
validations={{
required: "Name is required",
}}
/>
</div>
<div>
<Popover className="relative">
{({ open }) => (
<>
<Popover.Button
className={`group bg-white rounded-md inline-flex items-center text-base font-medium hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
open ? "text-gray-900" : "text-gray-500"
}`}
>
<span>Color</span>
{watch("color") && watch("color") !== "" && (
<span
className="w-4 h-4 ml-2 rounded"
style={{
backgroundColor: watch("color") ?? "green",
}}
></span>
)}
<ChevronDownIcon
className={`ml-2 h-5 w-5 group-hover:text-gray-500 ${
open ? "text-gray-600" : "text-gray-400"
}`}
aria-hidden="true"
/>
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="fixed z-50 transform left-5 mt-3 px-2 w-screen max-w-xs sm:px-0">
<Controller
name="color"
control={control}
render={({ field: { value, onChange } }) => (
<TwitterPicker
color={value}
onChange={(value) => onChange(value.hex)}
/>
)}
/>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</div>
<div>
<TextArea
id="description"
name="description"
label="Description"
placeholder="Enter description"
error={errors.description}
register={register}
/>
</div>
</div>
</div>
</div>
<div className="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3">
<Button theme="secondary" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{data
? isSubmitting
? "Updating State..."
: "Update State"
: isSubmitting
? "Creating State..."
: "Create State"}
</Button>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
export default CreateUpdateStateModal;