fix: ui and bugs (#2289)

* fix: 24 character limit on first & last name in onboarding page

* fix: no option: 'Add Issue' in archive issue page

* fix: in archive issue directly sending to issue detail page

* fix: issue type showing in archive issue

* fix: custom menu overflowing

* fix: changing subscriber in filters has no effect

* style: border in quick-add

* fix: on onboarding member role overflowing

* fix: inconsistent icons in issue detail

* style: spacing, borders and shadows in quick-add

* fix: custom menu truncate
This commit is contained in:
Dakshesh Jain 2023-09-28 14:02:03 +05:30 committed by GitHub
parent 60a69e28e3
commit ec91a0d2e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 289 additions and 158 deletions

View File

@ -8,9 +8,27 @@ import useEstimateOption from "hooks/use-estimate-option";
import issuesService from "services/issues.service";
// icons
import { Icon, Tooltip } from "components/ui";
import { CopyPlus } from "lucide-react";
import { Squares2X2Icon } from "@heroicons/react/24/outline";
import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons";
import {
TagIcon,
CopyPlus,
Calendar,
Link2Icon,
RocketIcon,
Users2Icon,
ArchiveIcon,
PaperclipIcon,
ContrastIcon,
TriangleIcon,
LayoutGridIcon,
SignalMediumIcon,
MessageSquareIcon,
} from "lucide-react";
import {
BlockedIcon,
BlockerIcon,
RelatedIcon,
StackedLayersHorizontalIcon,
} from "components/icons";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper";
@ -38,7 +56,7 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
{activity.issue_detail
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
: "Issue"}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</Tooltip>
);
@ -131,14 +149,14 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="group" className="!text-2xl" aria-hidden="true" />,
icon: <Users2Icon size={12} color="#6b7280" aria-hidden="true" />,
},
archived_at: {
message: (activity) => {
if (activity.new_value === "restore") return "restored the issue.";
else return "archived the issue.";
},
icon: <Icon iconName="archive" className="!text-2xl" aria-hidden="true" />,
icon: <ArchiveIcon size={12} color="#6b7280" aria-hidden="true" />,
},
attachment: {
message: (activity, showIssue) => {
@ -153,7 +171,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
attachment
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
{showIssue && (
<>
@ -177,7 +195,7 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="attach_file" className="!text-2xl" aria-hidden="true" />,
icon: <PaperclipIcon size={12} color="#6b7280" aria-hidden="true" />,
},
blocking: {
message: (activity) => {
@ -268,7 +286,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.new_value}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</>
);
@ -283,7 +301,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.new_value}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</>
);
@ -298,12 +316,12 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.old_value}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</>
);
},
icon: <Icon iconName="contrast" className="!text-2xl" aria-hidden="true" />,
icon: <ContrastIcon size={12} color="#6b7280" aria-hidden="true" />,
},
description: {
message: (activity, showIssue) => (
@ -318,7 +336,7 @@ const activityDetails: {
.
</>
),
icon: <Icon iconName="chat" className="!text-2xl" aria-hidden="true" />,
icon: <MessageSquareIcon size={12} color="#6b7280" aria-hidden="true" />,
},
estimate_point: {
message: (activity, showIssue) => {
@ -349,14 +367,14 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="change_history" className="!text-2xl" aria-hidden="true" />,
icon: <TriangleIcon size={12} color="#6b7280" aria-hidden="true" />,
},
issue: {
message: (activity) => {
if (activity.verb === "created") return "created the issue.";
else return "deleted an issue.";
},
icon: <Icon iconName="stack" className="!text-2xl" aria-hidden="true" />,
icon: <StackedLayersHorizontalIcon width={12} height={12} color="#6b7280" aria-hidden="true" />,
},
labels: {
message: (activity, showIssue) => {
@ -393,7 +411,7 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="sell" className="!text-2xl" aria-hidden="true" />,
icon: <TagIcon size={12} color="#6b7280" aria-hidden="true" />,
},
link: {
message: (activity, showIssue) => {
@ -408,7 +426,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
link
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
{showIssue && (
<>
@ -430,7 +448,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
link
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
{showIssue && (
<>
@ -452,7 +470,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
link
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
{showIssue && (
<>
@ -464,7 +482,7 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="link" className="!text-2xl" aria-hidden="true" />,
icon: <Link2Icon size={12} color="#6b7280" aria-hidden="true" />,
},
modules: {
message: (activity, showIssue, workspaceSlug) => {
@ -479,7 +497,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.new_value}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</>
);
@ -494,7 +512,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.new_value}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</>
);
@ -509,12 +527,12 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.old_value}
<Icon iconName="launch" className="!text-xs" />
<RocketIcon size={12} color="#6b7280" />
</a>
</>
);
},
icon: <Icon iconName="dataset" className="!text-2xl" aria-hidden="true" />,
icon: <Icon iconName="dataset" className="!text-xs !text-[#6b7280]" aria-hidden="true" />,
},
name: {
message: (activity, showIssue) => (
@ -529,7 +547,7 @@ const activityDetails: {
.
</>
),
icon: <Icon iconName="chat" className="!text-2xl" aria-hidden="true" />,
icon: <MessageSquareIcon size={12} color="#6b7280" aria-hidden="true" />,
},
parent: {
message: (activity, showIssue) => {
@ -562,7 +580,13 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="supervised_user_circle" className="!text-2xl" aria-hidden="true" />,
icon: (
<Icon
iconName="supervised_user_circle"
className="!text-xs !text-[#6b7280]"
aria-hidden="true"
/>
),
},
priority: {
message: (activity, showIssue) => (
@ -580,7 +604,7 @@ const activityDetails: {
.
</>
),
icon: <Icon iconName="signal_cellular_alt" className="!text-2xl" aria-hidden="true" />,
icon: <SignalMediumIcon size={12} color="#6b7280" aria-hidden="true" />,
},
start_date: {
message: (activity, showIssue) => {
@ -614,7 +638,7 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="calendar_today" className="!text-2xl" aria-hidden="true" />,
icon: <Calendar size={12} color="#6b7280" aria-hidden="true" />,
},
state: {
message: (activity, showIssue) => (
@ -630,7 +654,7 @@ const activityDetails: {
.
</>
),
icon: <Squares2X2Icon className="h-6 w-6 text-custom-sidebar-200" aria-hidden="true" />,
icon: <LayoutGridIcon size={12} color="#6b7280" aria-hidden="true" />,
},
target_date: {
message: (activity, showIssue) => {
@ -664,7 +688,7 @@ const activityDetails: {
</>
);
},
icon: <Icon iconName="calendar_today" className="!text-2xl" aria-hidden="true" />,
icon: <Calendar size={12} color="#6b7280" aria-hidden="true" />,
},
};

View File

@ -200,7 +200,9 @@ export const FiltersList: React.FC<Props> = ({
className="cursor-pointer"
onClick={() =>
setFilters({
assignees: filters.assignees?.filter((p: any) => p !== memberId),
subscriber: filters.subscriber?.filter(
(p: any) => p !== memberId
),
})
}
>

View File

@ -277,33 +277,35 @@ export const IssuesFilterView: React.FC = () => {
</div>
</div>
)}
<div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Issue type</h4>
<div className="w-28">
<CustomMenu
label={
FILTER_ISSUE_OPTIONS.find(
(option) => option.key === displayFilters.type
)?.name ?? "Select"
}
className="!w-full"
buttonClassName="w-full"
>
{FILTER_ISSUE_OPTIONS.map((option) => (
<CustomMenu.MenuItem
key={option.key}
onClick={() =>
setDisplayFilters({
type: option.key,
})
}
>
{option.name}
</CustomMenu.MenuItem>
))}
</CustomMenu>
{!isArchivedIssues && (
<div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Issue type</h4>
<div className="w-28">
<CustomMenu
label={
FILTER_ISSUE_OPTIONS.find(
(option) => option.key === displayFilters.type
)?.name ?? "Select"
}
className="!w-full"
buttonClassName="w-full"
>
{FILTER_ISSUE_OPTIONS.map((option) => (
<CustomMenu.MenuItem
key={option.key}
onClick={() =>
setDisplayFilters({
type: option.key,
})
}
>
{option.name}
</CustomMenu.MenuItem>
))}
</CustomMenu>
</div>
</div>
</div>
)}
{displayFilters.layout !== "calendar" &&
displayFilters.layout !== "spreadsheet" && (

View File

@ -48,7 +48,7 @@ const InlineInput = () => {
export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => (
<>
<InlineCreateIssueFormWrapper
className="flex flex-col justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow-custom-shadow-sm"
className="flex flex-col border-[0.5px] border-custom-border-100 justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow-custom-shadow-sm"
{...props}
>
<InlineInput />

View File

@ -67,7 +67,7 @@ const InlineInput = () => {
{...register("name", {
required: "Issue title is required.",
})}
className="w-full pr-2 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
className="w-full pr-2 py-2.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
/>
</>
);
@ -90,7 +90,7 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = (props) => {
>
<InlineCreateIssueFormWrapper
{...props}
className="flex w-full p-1 px-1.5 rounded z-50 items-center gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm transition-opacity"
className="flex w-full px-1.5 border-[0.5px] border-custom-border-100 rounded z-50 items-center gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm transition-opacity"
>
<InlineInput />
</InlineCreateIssueFormWrapper>

View File

@ -48,7 +48,7 @@ const InlineInput = () => {
export const GanttInlineCreateIssueForm: React.FC<Props> = (props) => (
<>
<InlineCreateIssueFormWrapper
className="flex py-3 px-4 mr-2.5 items-center rounded gap-x-2 border bg-custom-background-100 shadow-custom-shadow-sm"
className="flex py-3 px-4 border-[0.5px] border-custom-border-100 mr-2.5 items-center rounded gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm"
{...props}
>
<InlineInput />

View File

@ -81,7 +81,9 @@ export const IssuesView: React.FC<Props> = ({
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const isDraftIssues = router.asPath.includes("draft-issues");
const isDraftIssues = router.pathname?.split("/")?.[4] === "draft-issues";
const isArchivedIssues = router.pathname?.split("/")?.[4] === "archived-issues";
const { user } = useUserAuth();
@ -625,6 +627,7 @@ export const IssuesView: React.FC<Props> = ({
params,
properties,
}}
disableAddIssueOption={isArchivedIssues}
/>
</>
);

View File

@ -39,7 +39,7 @@ const InlineInput = () => {
{...register("name", {
required: "Issue title is required.",
})}
className="w-full px-2 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
className="w-full px-2 py-3 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
/>
</>
);
@ -48,7 +48,7 @@ const InlineInput = () => {
export const ListInlineCreateIssueForm: React.FC<Props> = (props) => (
<>
<InlineCreateIssueFormWrapper
className="flex py-3 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
className="flex border-[0.5px] border-t-0 border-custom-border-100 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
{...props}
>
<InlineInput />

View File

@ -328,7 +328,7 @@ export const SingleListIssue: React.FC<Props> = ({
</ContextMenu>
<div
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b-[0.5px] border-custom-border-100 bg-custom-background-100 last:border-b-0"
onContextMenu={(e) => {
e.preventDefault();
setContextMenu(true);
@ -352,6 +352,7 @@ export const SingleListIssue: React.FC<Props> = ({
type="button"
className="truncate text-[0.825rem] text-custom-text-100"
onClick={() => {
if (isArchivedIssues) return router.push(issuePath);
if (!isDraftIssues) openPeekOverview(issue);
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
}}

View File

@ -239,7 +239,7 @@ export const SingleList: React.FC<Props> = (props) => {
!disableAddIssueOption && (
<button
type="button"
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) {
addIssueToGroup();
@ -336,7 +336,8 @@ export const SingleList: React.FC<Props> = (props) => {
/>
{!disableAddIssueOption && !isCreateIssueFormOpen && (
<div className="w-full bg-custom-background-100 px-6 py-3">
// TODO: add border here
<div className="w-full bg-custom-background-100 px-6 py-3 border-b border-custom-border-100">
<button
type="button"
onClick={() => {

View File

@ -54,7 +54,7 @@ export const AssigneeColumn: React.FC<Props> = ({
return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.assignee && (
<MembersSelect
value={issue.assignees}

View File

@ -23,7 +23,7 @@ export const CreatedOnColumn: React.FC<Props> = ({
isNotAllowed,
}) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.created_on && (
<div className="flex items-center text-xs cursor-default text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
{renderLongDetailDateFormat(issue.created_at)}

View File

@ -23,7 +23,7 @@ export const DueDateColumn: React.FC<Props> = ({
isNotAllowed,
}) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.due_date && (
<ViewDueDateSelect
issue={issue}

View File

@ -23,7 +23,7 @@ export const EstimateColumn: React.FC<Props> = ({
isNotAllowed,
}) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.estimate && (
<ViewEstimateSelect
issue={issue}

View File

@ -82,7 +82,7 @@ export const IssueColumn: React.FC<Props> = ({
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return (
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-r border-custom-border-200 ">
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-r border-custom-border-100">
<div
className="flex gap-1.5 px-4 pr-0 py-2.5 items-center"
style={issue.parent ? { paddingLeft } : {}}

View File

@ -28,7 +28,7 @@ export const LabelColumn: React.FC<Props> = ({
return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.labels && (
<LabelSelect
value={issue.labels}

View File

@ -48,7 +48,7 @@ export const PriorityColumn: React.FC<Props> = ({
return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.priority && (
<PrioritySelect
value={issue.priority}

View File

@ -287,7 +287,7 @@ export const SpreadsheetView: React.FC<Props> = ({
)}
</div>
<div>
<div className="border-t border-custom-border-100">
<div className="mb-3 z-50 sticky bottom-0 left-0">
<ListInlineCreateIssueForm
isOpen={isInlineCreateIssueFormOpen}
@ -303,11 +303,11 @@ export const SpreadsheetView: React.FC<Props> = ({
? !disableUserActions &&
!isInlineCreateIssueFormOpen && (
<button
className="flex gap-1.5 items-center text-custom-primary-100 pl-7 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] w-full"
onClick={() => setIsInlineCreateIssueFormOpen(true)}
>
<PlusIcon className="h-4 w-4" />
Add Issue
New Issue
</button>
)
: !disableUserActions &&
@ -316,11 +316,11 @@ export const SpreadsheetView: React.FC<Props> = ({
className="sticky left-0 z-10"
customButton={
<button
className="flex gap-1.5 items-center text-custom-primary-100 pl-7 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
type="button"
>
<PlusIcon className="h-4 w-4" />
Add Issue
New Issue
</button>
}
position="left"

View File

@ -23,7 +23,7 @@ export const StartDateColumn: React.FC<Props> = ({
isNotAllowed,
}) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.due_date && (
<ViewStartDateSelect
issue={issue}

View File

@ -70,7 +70,7 @@ export const StateColumn: React.FC<Props> = ({
return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.state && (
<StateSelect
value={issue.state_detail}

View File

@ -23,7 +23,7 @@ export const UpdatedOnColumn: React.FC<Props> = ({
isNotAllowed,
}) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
{properties.updated_on && (
<div className="flex items-center text-xs cursor-default text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
{renderLongDetailDateFormat(issue.updated_at)}

View File

@ -347,7 +347,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
<button
type="button"
onClick={() => setIsCreateIssueFormOpen(true)}
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md"
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 pl-[1.875rem] py-1 rounded-md"
>
<PlusIcon className="h-4 w-4" />
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>

View File

@ -1,15 +1,27 @@
import React, { useEffect } from "react";
import React, { useEffect, useRef, useState } from "react";
// headless ui
import { Listbox, Transition } from "@headlessui/react";
// react-hook-form
import { Controller, useFieldArray, useForm } from "react-hook-form";
import {
Control,
Controller,
FieldArrayWithId,
UseFieldArrayRemove,
useFieldArray,
useForm,
} from "react-hook-form";
// services
import workspaceService from "services/workspace.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui";
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// icons
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { PlusIcon, XMarkIcon, CheckIcon } from "@heroicons/react/24/outline";
// types
import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types";
// constants
@ -31,12 +43,136 @@ type FormValues = {
emails: EmailRole[];
};
export const InviteMembers: React.FC<Props> = ({
finishOnboarding,
stepChange,
user,
workspace,
}) => {
type InviteMemberFormProps = {
index: number;
remove: UseFieldArrayRemove;
control: Control<FormValues, any>;
field: FieldArrayWithId<FormValues, "emails", "id">;
fields: FieldArrayWithId<FormValues, "emails", "id">[];
errors: any;
};
const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
const { control, index, fields, remove, errors } = props;
const buttonRef = useRef<HTMLButtonElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
useDynamicDropdownPosition(
isDropdownOpen,
() => setIsDropdownOpen(false),
buttonRef,
dropdownRef
);
return (
<div className="group relative grid grid-cols-11 gap-4">
<div className="col-span-7">
<Controller
control={control}
name={`emails.${index}.email`}
rules={{
required: "Email ID is required",
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid Email ID",
},
}}
render={({ field }) => (
<>
<Input {...field} className="text-xs sm:text-sm" placeholder="Enter their email..." />
{errors.emails?.[index]?.email && (
<span className="text-red-500 text-xs">
{errors.emails?.[index]?.email?.message}
</span>
)}
</>
)}
/>
</div>
<div className="col-span-3">
<Controller
control={control}
name={`emails.${index}.role`}
rules={{ required: true }}
render={({ field: { value, onChange } }) => (
<Listbox
as="div"
value={value}
onChange={(val) => {
onChange(val);
setIsDropdownOpen(false);
}}
className="flex-shrink-0 text-left w-full"
>
<Listbox.Button
type="button"
ref={buttonRef}
onClick={() => setIsDropdownOpen((prev) => !prev)}
className="flex items-center px-2.5 py-2 text-xs justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none"
>
<span className="text-xs sm:text-sm">{ROLE[value]}</span>
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Listbox.Button>
<Transition
show={isDropdownOpen}
as={React.Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Listbox.Options
ref={dropdownRef}
className="fixed w-36 z-10 border border-custom-border-300 mt-1 overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-lg focus:outline-none max-h-48"
>
<div className="space-y-1 p-2">
{Object.entries(ROLE).map(([key, value]) => (
<Listbox.Option
key={key}
value={parseInt(key)}
className={({ active, selected }) =>
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
active || selected ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">{value}</div>
{selected && <CheckIcon className="h-4 w-4 flex-shrink-0" />}
</div>
)}
</Listbox.Option>
))}
</div>
</Listbox.Options>
</Transition>
</Listbox>
)}
/>
</div>
{fields.length > 1 && (
<button
type="button"
className="hidden group-hover:grid self-center place-items-center rounded -ml-3"
onClick={() => remove(index)}
>
<XMarkIcon className="h-3.5 w-3.5 text-custom-text-200" />
</button>
)}
</div>
);
};
export const InviteMembers: React.FC<Props> = (props) => {
const { finishOnboarding, stepChange, user, workspace } = props;
const { setToastAlert } = useToast();
const {
@ -109,66 +245,15 @@ export const InviteMembers: React.FC<Props> = ({
</div>
<div className="space-y-3 sm:space-y-4 mb-3 h-full overflow-y-auto">
{fields.map((field, index) => (
<div key={field.id} className="group relative grid grid-cols-11 gap-4">
<div className="col-span-7">
<Controller
control={control}
name={`emails.${index}.email`}
rules={{
required: "Email ID is required",
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid Email ID",
},
}}
render={({ field }) => (
<>
<Input
{...field}
className="text-xs sm:text-sm"
placeholder="Enter their email..."
/>
{errors.emails?.[index]?.email && (
<span className="text-red-500 text-xs">
{errors.emails?.[index]?.email?.message}
</span>
)}
</>
)}
/>
</div>
<div className="col-span-3">
<Controller
control={control}
name={`emails.${index}.role`}
rules={{ required: true }}
render={({ field: { value, onChange } }) => (
<CustomSelect
value={value}
label={<span className="text-xs sm:text-sm">{ROLE[value]}</span>}
onChange={onChange}
width="w-full"
input
>
{Object.entries(ROLE).map(([key, value]) => (
<CustomSelect.Option key={key} value={parseInt(key)}>
{value}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div>
{fields.length > 1 && (
<button
type="button"
className="hidden group-hover:grid self-center place-items-center rounded -ml-3"
onClick={() => remove(index)}
>
<XMarkIcon className="h-3.5 w-3.5 text-custom-text-200" />
</button>
)}
</div>
<InviteMemberForm
control={control}
errors={errors}
field={field}
fields={fields}
index={index}
remove={remove}
key={field.id}
/>
))}
</div>
<button

View File

@ -121,6 +121,10 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
register={register}
validations={{
required: "First name is required",
maxLength: {
value: 24,
message: "First name cannot exceed the limit of 24 characters",
},
}}
error={errors.first_name}
/>
@ -135,6 +139,10 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
placeholder="Enter your last name..."
validations={{
required: "Last name is required",
maxLength: {
value: 24,
message: "Last name cannot exceed the limit of 24 characters",
},
}}
error={errors.last_name}
/>

View File

@ -239,9 +239,11 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<div className="w-28">
<CustomMenu
label={
FILTER_ISSUE_OPTIONS.find(
(option) => option.key === displayFilters?.type
)?.name ?? "Select"
<span className="truncate">
{FILTER_ISSUE_OPTIONS.find(
(option) => option.key === displayFilters?.type
)?.name ?? "Select"}
</span>
}
className="!w-full"
buttonClassName="w-full"

View File

@ -14,6 +14,7 @@ const paramsToKey = (params: any) => {
sub_issue,
start_target_date,
project,
subscriber,
} = params;
let projectKey = project ? project.split(",") : [];
@ -23,6 +24,7 @@ const paramsToKey = (params: any) => {
let assigneesKey = assignees ? assignees.split(",") : [];
let createdByKey = created_by ? created_by.split(",") : [];
let labelsKey = labels ? labels.split(",") : [];
let subscriberKey = subscriber ? subscriber.split(",") : [];
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
const startDateKey = start_date ?? "";
const targetDateKey = target_date ?? "";
@ -38,8 +40,9 @@ const paramsToKey = (params: any) => {
assigneesKey = assigneesKey.sort().join("_");
createdByKey = createdByKey.sort().join("_");
labelsKey = labelsKey.sort().join("_");
subscriberKey = subscriberKey.sort().join("_");
return `${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}`;
return `${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}_${subscriberKey}`;
};
const inboxParamsToKey = (params: any) => {

View File

@ -47,7 +47,7 @@ const useIssuesView = () => {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined,
type: displayFilters?.type ? displayFilters?.type : undefined,
type: !isArchivedIssues ? (displayFilters?.type ? displayFilters?.type : undefined) : undefined,
labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,