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"; import issuesService from "services/issues.service";
// icons // icons
import { Icon, Tooltip } from "components/ui"; import { Icon, Tooltip } from "components/ui";
import { CopyPlus } from "lucide-react"; import {
import { Squares2X2Icon } from "@heroicons/react/24/outline"; TagIcon,
import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons"; CopyPlus,
Calendar,
Link2Icon,
RocketIcon,
Users2Icon,
ArchiveIcon,
PaperclipIcon,
ContrastIcon,
TriangleIcon,
LayoutGridIcon,
SignalMediumIcon,
MessageSquareIcon,
} from "lucide-react";
import {
BlockedIcon,
BlockerIcon,
RelatedIcon,
StackedLayersHorizontalIcon,
} from "components/icons";
// helpers // helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper"; import { capitalizeFirstLetter } from "helpers/string.helper";
@ -38,7 +56,7 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
{activity.issue_detail {activity.issue_detail
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
: "Issue"} : "Issue"}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</Tooltip> </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: { archived_at: {
message: (activity) => { message: (activity) => {
if (activity.new_value === "restore") return "restored the issue."; if (activity.new_value === "restore") return "restored the issue.";
else return "archived 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: { attachment: {
message: (activity, showIssue) => { message: (activity, showIssue) => {
@ -153,7 +171,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
attachment attachment
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
{showIssue && ( {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: { blocking: {
message: (activity) => { message: (activity) => {
@ -268,7 +286,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} {activity.new_value}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</> </>
); );
@ -283,7 +301,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} {activity.new_value}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</> </>
); );
@ -298,12 +316,12 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.old_value} {activity.old_value}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</> </>
); );
}, },
icon: <Icon iconName="contrast" className="!text-2xl" aria-hidden="true" />, icon: <ContrastIcon size={12} color="#6b7280" aria-hidden="true" />,
}, },
description: { description: {
message: (activity, showIssue) => ( 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: { estimate_point: {
message: (activity, showIssue) => { 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: { issue: {
message: (activity) => { message: (activity) => {
if (activity.verb === "created") return "created the issue."; if (activity.verb === "created") return "created the issue.";
else return "deleted an 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: { labels: {
message: (activity, showIssue) => { 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: { link: {
message: (activity, showIssue) => { message: (activity, showIssue) => {
@ -408,7 +426,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
link link
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
{showIssue && ( {showIssue && (
<> <>
@ -430,7 +448,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
link link
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
{showIssue && ( {showIssue && (
<> <>
@ -452,7 +470,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
link link
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
{showIssue && ( {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: { modules: {
message: (activity, showIssue, workspaceSlug) => { 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" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} {activity.new_value}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</> </>
); );
@ -494,7 +512,7 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} {activity.new_value}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</> </>
); );
@ -509,12 +527,12 @@ const activityDetails: {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.old_value} {activity.old_value}
<Icon iconName="launch" className="!text-xs" /> <RocketIcon size={12} color="#6b7280" />
</a> </a>
</> </>
); );
}, },
icon: <Icon iconName="dataset" className="!text-2xl" aria-hidden="true" />, icon: <Icon iconName="dataset" className="!text-xs !text-[#6b7280]" aria-hidden="true" />,
}, },
name: { name: {
message: (activity, showIssue) => ( 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: { parent: {
message: (activity, showIssue) => { 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: { priority: {
message: (activity, showIssue) => ( 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: { start_date: {
message: (activity, showIssue) => { 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: { state: {
message: (activity, showIssue) => ( 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: { target_date: {
message: (activity, showIssue) => { 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" className="cursor-pointer"
onClick={() => onClick={() =>
setFilters({ 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> </div>
)} )}
<div className="flex items-center justify-between"> {!isArchivedIssues && (
<h4 className="text-custom-text-200">Issue type</h4> <div className="flex items-center justify-between">
<div className="w-28"> <h4 className="text-custom-text-200">Issue type</h4>
<CustomMenu <div className="w-28">
label={ <CustomMenu
FILTER_ISSUE_OPTIONS.find( label={
(option) => option.key === displayFilters.type FILTER_ISSUE_OPTIONS.find(
)?.name ?? "Select" (option) => option.key === displayFilters.type
} )?.name ?? "Select"
className="!w-full" }
buttonClassName="w-full" className="!w-full"
> buttonClassName="w-full"
{FILTER_ISSUE_OPTIONS.map((option) => ( >
<CustomMenu.MenuItem {FILTER_ISSUE_OPTIONS.map((option) => (
key={option.key} <CustomMenu.MenuItem
onClick={() => key={option.key}
setDisplayFilters({ onClick={() =>
type: option.key, setDisplayFilters({
}) type: option.key,
} })
> }
{option.name} >
</CustomMenu.MenuItem> {option.name}
))} </CustomMenu.MenuItem>
</CustomMenu> ))}
</CustomMenu>
</div>
</div> </div>
</div> )}
{displayFilters.layout !== "calendar" && {displayFilters.layout !== "calendar" &&
displayFilters.layout !== "spreadsheet" && ( displayFilters.layout !== "spreadsheet" && (

View File

@ -48,7 +48,7 @@ const InlineInput = () => {
export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => ( export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => (
<> <>
<InlineCreateIssueFormWrapper <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} {...props}
> >
<InlineInput /> <InlineInput />

View File

@ -67,7 +67,7 @@ const InlineInput = () => {
{...register("name", { {...register("name", {
required: "Issue title is required.", 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 <InlineCreateIssueFormWrapper
{...props} {...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 /> <InlineInput />
</InlineCreateIssueFormWrapper> </InlineCreateIssueFormWrapper>

View File

@ -48,7 +48,7 @@ const InlineInput = () => {
export const GanttInlineCreateIssueForm: React.FC<Props> = (props) => ( export const GanttInlineCreateIssueForm: React.FC<Props> = (props) => (
<> <>
<InlineCreateIssueFormWrapper <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} {...props}
> >
<InlineInput /> <InlineInput />

View File

@ -81,7 +81,9 @@ export const IssuesView: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; 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(); const { user } = useUserAuth();
@ -625,6 +627,7 @@ export const IssuesView: React.FC<Props> = ({
params, params,
properties, properties,
}} }}
disableAddIssueOption={isArchivedIssues}
/> />
</> </>
); );

View File

@ -39,7 +39,7 @@ const InlineInput = () => {
{...register("name", { {...register("name", {
required: "Issue title is required.", 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) => ( export const ListInlineCreateIssueForm: React.FC<Props> = (props) => (
<> <>
<InlineCreateIssueFormWrapper <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} {...props}
> >
<InlineInput /> <InlineInput />

View File

@ -328,7 +328,7 @@ export const SingleListIssue: React.FC<Props> = ({
</ContextMenu> </ContextMenu>
<div <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) => { onContextMenu={(e) => {
e.preventDefault(); e.preventDefault();
setContextMenu(true); setContextMenu(true);
@ -352,6 +352,7 @@ export const SingleListIssue: React.FC<Props> = ({
type="button" type="button"
className="truncate text-[0.825rem] text-custom-text-100" className="truncate text-[0.825rem] text-custom-text-100"
onClick={() => { onClick={() => {
if (isArchivedIssues) return router.push(issuePath);
if (!isDraftIssues) openPeekOverview(issue); if (!isDraftIssues) openPeekOverview(issue);
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue); if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
}} }}

View File

@ -239,7 +239,7 @@ export const SingleList: React.FC<Props> = (props) => {
!disableAddIssueOption && ( !disableAddIssueOption && (
<button <button
type="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={() => { onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) { if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) {
addIssueToGroup(); addIssueToGroup();
@ -336,7 +336,8 @@ export const SingleList: React.FC<Props> = (props) => {
/> />
{!disableAddIssueOption && !isCreateIssueFormOpen && ( {!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 <button
type="button" type="button"
onClick={() => { onClick={() => {

View File

@ -54,7 +54,7 @@ export const AssigneeColumn: React.FC<Props> = ({
return ( return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.assignee && (
<MembersSelect <MembersSelect
value={issue.assignees} value={issue.assignees}

View File

@ -23,7 +23,7 @@ export const CreatedOnColumn: React.FC<Props> = ({
isNotAllowed, isNotAllowed,
}) => ( }) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {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"> <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)} {renderLongDetailDateFormat(issue.created_at)}

View File

@ -23,7 +23,7 @@ export const DueDateColumn: React.FC<Props> = ({
isNotAllowed, isNotAllowed,
}) => ( }) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.due_date && (
<ViewDueDateSelect <ViewDueDateSelect
issue={issue} issue={issue}

View File

@ -23,7 +23,7 @@ export const EstimateColumn: React.FC<Props> = ({
isNotAllowed, isNotAllowed,
}) => ( }) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.estimate && (
<ViewEstimateSelect <ViewEstimateSelect
issue={issue} issue={issue}

View File

@ -82,7 +82,7 @@ export const IssueColumn: React.FC<Props> = ({
const isNotAllowed = userAuth.isGuest || userAuth.isViewer; const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return ( 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 <div
className="flex gap-1.5 px-4 pr-0 py-2.5 items-center" className="flex gap-1.5 px-4 pr-0 py-2.5 items-center"
style={issue.parent ? { paddingLeft } : {}} style={issue.parent ? { paddingLeft } : {}}

View File

@ -28,7 +28,7 @@ export const LabelColumn: React.FC<Props> = ({
return ( return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.labels && (
<LabelSelect <LabelSelect
value={issue.labels} value={issue.labels}

View File

@ -48,7 +48,7 @@ export const PriorityColumn: React.FC<Props> = ({
return ( return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.priority && (
<PrioritySelect <PrioritySelect
value={issue.priority} value={issue.priority}

View File

@ -287,7 +287,7 @@ export const SpreadsheetView: React.FC<Props> = ({
)} )}
</div> </div>
<div> <div className="border-t border-custom-border-100">
<div className="mb-3 z-50 sticky bottom-0 left-0"> <div className="mb-3 z-50 sticky bottom-0 left-0">
<ListInlineCreateIssueForm <ListInlineCreateIssueForm
isOpen={isInlineCreateIssueFormOpen} isOpen={isInlineCreateIssueFormOpen}
@ -303,11 +303,11 @@ export const SpreadsheetView: React.FC<Props> = ({
? !disableUserActions && ? !disableUserActions &&
!isInlineCreateIssueFormOpen && ( !isInlineCreateIssueFormOpen && (
<button <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)} onClick={() => setIsInlineCreateIssueFormOpen(true)}
> >
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
Add Issue New Issue
</button> </button>
) )
: !disableUserActions && : !disableUserActions &&
@ -316,11 +316,11 @@ export const SpreadsheetView: React.FC<Props> = ({
className="sticky left-0 z-10" className="sticky left-0 z-10"
customButton={ customButton={
<button <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" type="button"
> >
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
Add Issue New Issue
</button> </button>
} }
position="left" position="left"

View File

@ -23,7 +23,7 @@ export const StartDateColumn: React.FC<Props> = ({
isNotAllowed, isNotAllowed,
}) => ( }) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.due_date && (
<ViewStartDateSelect <ViewStartDateSelect
issue={issue} issue={issue}

View File

@ -70,7 +70,7 @@ export const StateColumn: React.FC<Props> = ({
return ( return (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {properties.state && (
<StateSelect <StateSelect
value={issue.state_detail} value={issue.state_detail}

View File

@ -23,7 +23,7 @@ export const UpdatedOnColumn: React.FC<Props> = ({
isNotAllowed, isNotAllowed,
}) => ( }) => (
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100"> <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 && ( {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"> <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)} {renderLongDetailDateFormat(issue.updated_at)}

View File

@ -347,7 +347,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
<button <button
type="button" type="button"
onClick={() => setIsCreateIssueFormOpen(true)} 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" /> <PlusIcon className="h-4 w-4" />
<span className="text-sm font-medium text-custom-primary-100">New Issue</span> <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 // react-hook-form
import { Controller, useFieldArray, useForm } from "react-hook-form"; import {
Control,
Controller,
FieldArrayWithId,
UseFieldArrayRemove,
useFieldArray,
useForm,
} from "react-hook-form";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // 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 // 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 // types
import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types"; import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types";
// constants // constants
@ -31,12 +43,136 @@ type FormValues = {
emails: EmailRole[]; emails: EmailRole[];
}; };
export const InviteMembers: React.FC<Props> = ({ type InviteMemberFormProps = {
finishOnboarding, index: number;
stepChange, remove: UseFieldArrayRemove;
user, control: Control<FormValues, any>;
workspace, 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 { setToastAlert } = useToast();
const { const {
@ -109,66 +245,15 @@ export const InviteMembers: React.FC<Props> = ({
</div> </div>
<div className="space-y-3 sm:space-y-4 mb-3 h-full overflow-y-auto"> <div className="space-y-3 sm:space-y-4 mb-3 h-full overflow-y-auto">
{fields.map((field, index) => ( {fields.map((field, index) => (
<div key={field.id} className="group relative grid grid-cols-11 gap-4"> <InviteMemberForm
<div className="col-span-7"> control={control}
<Controller errors={errors}
control={control} field={field}
name={`emails.${index}.email`} fields={fields}
rules={{ index={index}
required: "Email ID is required", remove={remove}
pattern: { key={field.id}
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>
))} ))}
</div> </div>
<button <button

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@ const useIssuesView = () => {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.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, labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,