mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
frontend indentation set to 2
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 110
|
||||
},
|
||||
"css": {
|
||||
|
||||
@@ -131,10 +131,7 @@ function Account({ trigger }: { trigger?: ReactNode }) {
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<Label className="text-sm">Icon Style</Label>
|
||||
<Select
|
||||
value={iconPreference}
|
||||
onValueChange={(v) => setIconPreference(v as IconStyle)}
|
||||
>
|
||||
<Select value={iconPreference} onValueChange={(v) => setIconPreference(v as IconStyle)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -111,10 +111,7 @@ export function AddMember({
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={submitting || (username.trim() === "" && submitAttempted)}
|
||||
>
|
||||
<Button type="submit" disabled={submitting || (username.trim() === "" && submitAttempted)}>
|
||||
{submitting ? "Adding..." : "Add"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -286,9 +286,7 @@ export function IssueDetails({
|
||||
} catch (error) {
|
||||
console.error(`error deleting issue ${issueID(projectKey, issueData.Issue.number)}`, error);
|
||||
toast.error(
|
||||
`Error deleting issue ${issueID(projectKey, issueData.Issue.number)}: ${parseError(
|
||||
error as Error,
|
||||
)}`,
|
||||
`Error deleting issue ${issueID(projectKey, issueData.Issue.number)}: ${parseError(error as Error)}`,
|
||||
{
|
||||
dismissible: false,
|
||||
},
|
||||
@@ -303,9 +301,7 @@ export function IssueDetails({
|
||||
{showHeader && (
|
||||
<div className="flex flex-row items-center justify-end border-b h-[25px]">
|
||||
<span className="w-full">
|
||||
<p className="text-sm w-fit px-1 font-700">
|
||||
{issueID(projectKey, issueData.Issue.number)}
|
||||
</p>
|
||||
<p className="text-sm w-fit px-1 font-700">{issueID(projectKey, issueData.Issue.number)}</p>
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<IconButton onClick={handleCopyLink} title={linkCopied ? "Copied" : "Copy link"}>
|
||||
@@ -334,11 +330,7 @@ export function IssueDetails({
|
||||
chevronClassName="hidden"
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<StatusTag
|
||||
status={value}
|
||||
colour={statuses[value]}
|
||||
className="hover:opacity-85"
|
||||
/>
|
||||
<StatusTag status={value} colour={statuses[value]} className="hover:opacity-85" />
|
||||
</SelectTrigger>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -153,11 +153,7 @@ export function IssueForm({ trigger }: { trigger?: React.ReactNode }) {
|
||||
chevronClassName="hidden"
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<StatusTag
|
||||
status={value}
|
||||
colour={statuses[value]}
|
||||
className="hover:opacity-85"
|
||||
/>
|
||||
<StatusTag status={value} colour={statuses[value]} className="hover:opacity-85" />
|
||||
</SelectTrigger>
|
||||
)}
|
||||
/>
|
||||
@@ -203,11 +199,7 @@ export function IssueForm({ trigger }: { trigger?: React.ReactNode }) {
|
||||
{members.length > 0 && (
|
||||
<div className="flex items-start gap-2 mt-4">
|
||||
<Label className="text-sm pt-2">Assignees</Label>
|
||||
<MultiAssigneeSelect
|
||||
users={members}
|
||||
assigneeIds={assigneeIds}
|
||||
onChange={setAssigneeIds}
|
||||
/>
|
||||
<MultiAssigneeSelect users={members} assigneeIds={assigneeIds} onChange={setAssigneeIds} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -231,8 +223,7 @@ export function IssueForm({ trigger }: { trigger?: React.ReactNode }) {
|
||||
submitting ||
|
||||
((title.trim() === "" || title.trim().length > ISSUE_TITLE_MAX_LENGTH) &&
|
||||
submitAttempted) ||
|
||||
(description.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH &&
|
||||
submitAttempted)
|
||||
(description.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH && submitAttempted)
|
||||
}
|
||||
>
|
||||
{submitting ? "Creating..." : "Create"}
|
||||
|
||||
@@ -64,11 +64,7 @@ export function IssueTimer({ issueId, onEnd }: { issueId: number; onEnd?: (data:
|
||||
<Button onClick={handleToggle}>
|
||||
{!timerState ? "Start" : timerState.isRunning ? "Pause" : "Resume"}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleEnd}
|
||||
variant="outline"
|
||||
disabled={!timerState || timerState.endedAt != null}
|
||||
>
|
||||
<Button onClick={handleEnd} variant="outline" disabled={!timerState || timerState.endedAt != null}>
|
||||
End
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -87,10 +87,7 @@ export function IssuesTable({
|
||||
className="flex items-center gap-2 min-w-0 w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
||||
>
|
||||
{(columns.status == null || columns.status === true) && (
|
||||
<StatusTag
|
||||
status={issueData.Issue.status}
|
||||
colour={statuses[issueData.Issue.status]}
|
||||
/>
|
||||
<StatusTag status={issueData.Issue.status} colour={statuses[issueData.Issue.status]} />
|
||||
)}
|
||||
<span className="truncate">{issueData.Issue.title}</span>
|
||||
</a>
|
||||
|
||||
@@ -4,9 +4,7 @@ export default function Loading({ message, children }: { message?: string; child
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 w-full h-[100vh]">
|
||||
<Spinner className="size-6" />
|
||||
{message && (
|
||||
<span className="text-xs px-2 py-1 border-2 border-input border-dashed">{message}</span>
|
||||
)}
|
||||
{message && <span className="text-xs px-2 py-1 border-2 border-input border-dashed">{message}</span>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -158,8 +158,8 @@ export default function LogInForm() {
|
||||
<Icon icon="alertTriangle" className="w-16 h-16 text-yellow-500" />
|
||||
<div className="text-center text-sm text-muted-foreground font-500">
|
||||
<p>
|
||||
This application is currently under construction. Your data is very likely to be
|
||||
lost at some point.
|
||||
This application is currently under construction. Your data is very likely to be lost at some
|
||||
point.
|
||||
</p>
|
||||
<p className="font-700 underline underline-offset-3 text-foreground/85 decoration-yellow-500 mt-2">
|
||||
It is not recommended for production use.
|
||||
@@ -191,16 +191,10 @@ export default function LogInForm() {
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<p>
|
||||
<span className="font-medium text-foreground">
|
||||
Username:
|
||||
</span>{" "}
|
||||
{user.username}
|
||||
<span className="font-medium text-foreground">Username:</span> {user.username}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium text-foreground">
|
||||
Password:
|
||||
</span>{" "}
|
||||
{user.password}
|
||||
<span className="font-medium text-foreground">Password:</span> {user.password}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -68,11 +68,7 @@ export default function OrgIcon({
|
||||
)}
|
||||
>
|
||||
{iconURL ? (
|
||||
<img
|
||||
src={iconURL}
|
||||
alt={name}
|
||||
className={`rounded-md object-cover w-${size || 6} h-${size || 6}`}
|
||||
/>
|
||||
<img src={iconURL} alt={name} className={`rounded-md object-cover w-${size || 6} h-${size || 6}`} />
|
||||
) : (
|
||||
<span className={textClass}>{getInitials(name)}</span>
|
||||
)}
|
||||
|
||||
@@ -92,10 +92,7 @@ export function OrganisationSelect({
|
||||
<SelectGroup>
|
||||
<SelectLabel>Organisations</SelectLabel>
|
||||
{organisations.map((organisation) => (
|
||||
<SelectItem
|
||||
key={organisation.Organisation.id}
|
||||
value={`${organisation.Organisation.id}`}
|
||||
>
|
||||
<SelectItem key={organisation.Organisation.id} value={`${organisation.Organisation.id}`}>
|
||||
<OrgIcon
|
||||
name={organisation.Organisation.name}
|
||||
slug={organisation.Organisation.slug}
|
||||
|
||||
@@ -186,12 +186,9 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(
|
||||
`Error ${action.slice(0, -1)}ing ${memberName} to ${newRole}: ${String(err)}`,
|
||||
{
|
||||
toast.error(`Error ${action.slice(0, -1)}ing ${memberName} to ${newRole}: ${String(err)}`, {
|
||||
dismissible: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -217,12 +214,9 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
userId: memberUserId,
|
||||
});
|
||||
closeConfirmDialog();
|
||||
toast.success(
|
||||
`Removed ${memberName} from ${selectedOrganisation.Organisation.name} successfully`,
|
||||
{
|
||||
toast.success(`Removed ${memberName} from ${selectedOrganisation.Organisation.name} successfully`, {
|
||||
dismissible: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(
|
||||
@@ -259,8 +253,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
if (statusAdded) {
|
||||
toast.success(
|
||||
<>
|
||||
Created <StatusTag status={statusAdded.name} colour={statusAdded.colour} /> status
|
||||
successfully
|
||||
Created <StatusTag status={statusAdded.name} colour={statusAdded.colour} /> status successfully
|
||||
</>,
|
||||
{
|
||||
dismissible: false,
|
||||
@@ -279,8 +272,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
} else if (statusMoved) {
|
||||
toast.success(
|
||||
<>
|
||||
Moved <StatusTag status={statusMoved.name} colour={statusMoved.colour} /> from
|
||||
position
|
||||
Moved <StatusTag status={statusMoved.name} colour={statusMoved.colour} /> from position
|
||||
{statusMoved.currentIndex + 1} to {statusMoved.nextIndex + 1} successfully
|
||||
</>,
|
||||
{
|
||||
@@ -304,8 +296,8 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
} else if (statusRemoved) {
|
||||
toast.error(
|
||||
<>
|
||||
Error removing <StatusTag status={statusRemoved.name} colour={statusRemoved.colour} />{" "}
|
||||
from {selectedOrganisation.Organisation.name}: {String(err)}
|
||||
Error removing <StatusTag status={statusRemoved.name} colour={statusRemoved.colour} /> from{" "}
|
||||
{selectedOrganisation.Organisation.name}: {String(err)}
|
||||
</>,
|
||||
{
|
||||
dismissible: false,
|
||||
@@ -314,8 +306,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
} else if (statusMoved) {
|
||||
toast.error(
|
||||
<>
|
||||
Error moving <StatusTag status={statusMoved.name} colour={statusMoved.colour} /> from
|
||||
position
|
||||
Error moving <StatusTag status={statusMoved.name} colour={statusMoved.colour} /> from position
|
||||
{statusMoved.currentIndex + 1} to {statusMoved.nextIndex + 1}{" "}
|
||||
{selectedOrganisation.Organisation.name}: {String(err)}
|
||||
</>,
|
||||
@@ -414,18 +405,17 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
newStatus: reassignToStatus,
|
||||
});
|
||||
const newStatuses = Object.keys(statuses).filter((item) => item !== statusToRemove);
|
||||
await updateStatuses(
|
||||
Object.fromEntries(newStatuses.map((status) => [status, statuses[status]])),
|
||||
{ name: statusToRemove, colour: statuses[statusToRemove] },
|
||||
);
|
||||
await updateStatuses(Object.fromEntries(newStatuses.map((status) => [status, statuses[status]])), {
|
||||
name: statusToRemove,
|
||||
colour: statuses[statusToRemove],
|
||||
});
|
||||
setStatusToRemove(null);
|
||||
setReassignToStatus("");
|
||||
} catch (error) {
|
||||
console.error("error replacing status:", error);
|
||||
toast.error(
|
||||
<>
|
||||
Error removing <StatusTag status={statusToRemove} colour={statuses[statusToRemove]} />{" "}
|
||||
from
|
||||
Error removing <StatusTag status={statusToRemove} colour={statuses[statusToRemove]} /> from
|
||||
{selectedOrganisation.Organisation.name}: {String(error)}
|
||||
</>,
|
||||
{
|
||||
@@ -460,9 +450,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full min-w-0">
|
||||
<div className="flex flex-wrap gap-2 items-center w-full min-w-0">
|
||||
<OrganisationSelect
|
||||
contentClass={
|
||||
"data-[side=bottom]:translate-y-2 data-[side=bottom]:translate-x-0.25"
|
||||
}
|
||||
contentClass={"data-[side=bottom]:translate-y-2 data-[side=bottom]:translate-x-0.25"}
|
||||
/>
|
||||
<TabsList>
|
||||
<TabsTrigger value="info">Info</TabsTrigger>
|
||||
@@ -477,9 +465,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-col gap-0 mb-2">
|
||||
{" "}
|
||||
<h2 className="text-xl font-600 break-all">
|
||||
{selectedOrganisation.Organisation.name}
|
||||
</h2>
|
||||
<h2 className="text-xl font-600 break-all">{selectedOrganisation.Organisation.name}</h2>
|
||||
<p className="text-sm text-muted-foreground break-all">
|
||||
Slug: {selectedOrganisation.Organisation.slug}
|
||||
</p>
|
||||
@@ -498,22 +484,14 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{selectedOrganisation.Organisation.description ? (
|
||||
<p className="text-sm break-words">
|
||||
{selectedOrganisation.Organisation.description}
|
||||
</p>
|
||||
<p className="text-sm break-words">{selectedOrganisation.Organisation.description}</p>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground break-words">
|
||||
No description
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground break-words">No description</p>
|
||||
)}
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<div className="flex gap-2 mt-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setEditOrgOpen(true)}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={() => setEditOrgOpen(true)}>
|
||||
<Icon icon="edit" className="size-4" />
|
||||
Edit
|
||||
</Button>
|
||||
@@ -531,9 +509,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
variant: "destructive",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await deleteOrganisation.mutateAsync(
|
||||
selectedOrganisation.Organisation.id,
|
||||
);
|
||||
await deleteOrganisation.mutateAsync(selectedOrganisation.Organisation.id);
|
||||
closeConfirmDialog();
|
||||
toast.success(
|
||||
`Deleted organisation "${selectedOrganisation.Organisation.name}"`,
|
||||
@@ -593,38 +569,20 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
handleRoleChange(
|
||||
member.User.id,
|
||||
member.User.name,
|
||||
member.OrganisationMember
|
||||
.role,
|
||||
member.OrganisationMember.role,
|
||||
)
|
||||
}
|
||||
variant={
|
||||
member.OrganisationMember.role ===
|
||||
"admin"
|
||||
? "yellow"
|
||||
: "green"
|
||||
}
|
||||
variant={member.OrganisationMember.role === "admin" ? "yellow" : "green"}
|
||||
>
|
||||
{member.OrganisationMember.role ===
|
||||
"admin" ? (
|
||||
<Icon
|
||||
icon="chevronDown"
|
||||
className="size-5"
|
||||
/>
|
||||
{member.OrganisationMember.role === "admin" ? (
|
||||
<Icon icon="chevronDown" className="size-5" />
|
||||
) : (
|
||||
<Icon
|
||||
icon="chevronUp"
|
||||
className="size-5"
|
||||
/>
|
||||
<Icon icon="chevronUp" className="size-5" />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
handleRemoveMember(
|
||||
member.User.id,
|
||||
member.User.name,
|
||||
)
|
||||
}
|
||||
onClick={() => handleRemoveMember(member.User.id, member.User.name)}
|
||||
>
|
||||
<Icon icon="x" className="size-5" />
|
||||
</IconButton>
|
||||
@@ -680,11 +638,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<div className="flex gap-2 mt-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setEditProjectOpen(true)}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={() => setEditProjectOpen(true)}>
|
||||
<Icon icon="edit" className="size-4" />
|
||||
Edit
|
||||
</Button>
|
||||
@@ -702,10 +656,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
variant: "destructive",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await deleteProject.mutateAsync(
|
||||
selectedProject
|
||||
.Project.id,
|
||||
);
|
||||
await deleteProject.mutateAsync(selectedProject.Project.id);
|
||||
closeConfirmDialog();
|
||||
toast.success(
|
||||
`Deleted project "${selectedProject.Project.name}"`,
|
||||
@@ -719,10 +670,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon="trash"
|
||||
className="size-4"
|
||||
/>
|
||||
<Icon icon="trash" className="size-4" />
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
@@ -730,9 +678,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Select a project to view details.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Select a project to view details.</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 min-w-0 flex-1">
|
||||
@@ -746,17 +692,13 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
<div
|
||||
key={sprintItem.id}
|
||||
className={`flex items-center justify-between p-2 border ${
|
||||
isCurrent
|
||||
? "border-emerald-500/60 bg-emerald-500/10"
|
||||
: ""
|
||||
isCurrent ? "border-emerald-500/60 bg-emerald-500/10" : ""
|
||||
}`}
|
||||
>
|
||||
<SmallSprintDisplay sprint={sprintItem} />
|
||||
<div className="flex items-center gap-2">
|
||||
{dateRange && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{dateRange}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">{dateRange}</span>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<DropdownMenu>
|
||||
@@ -766,10 +708,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
noStyle
|
||||
className="hover:opacity-80 cursor-pointer"
|
||||
>
|
||||
<Icon
|
||||
icon="ellipsisVertical"
|
||||
className="size-4 text-foreground"
|
||||
/>
|
||||
<Icon icon="ellipsisVertical" className="size-4 text-foreground" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
@@ -778,19 +717,12 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
setEditingSprint(
|
||||
sprintItem,
|
||||
);
|
||||
setEditSprintOpen(
|
||||
true,
|
||||
);
|
||||
setEditingSprint(sprintItem);
|
||||
setEditSprintOpen(true);
|
||||
}}
|
||||
className="hover:bg-primary-foreground"
|
||||
>
|
||||
<Icon
|
||||
icon="edit"
|
||||
className="size-4 text-muted-foreground"
|
||||
/>
|
||||
<Icon icon="edit" className="size-4 text-muted-foreground" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@@ -800,37 +732,24 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
open: true,
|
||||
title: "Delete Sprint",
|
||||
message: `Are you sure you want to delete "${sprintItem.name}"? Issues assigned to this sprint will become unassigned.`,
|
||||
confirmText:
|
||||
"Delete",
|
||||
processingText:
|
||||
"Deleting...",
|
||||
variant:
|
||||
"destructive",
|
||||
onConfirm:
|
||||
async () => {
|
||||
confirmText: "Delete",
|
||||
processingText: "Deleting...",
|
||||
variant: "destructive",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await deleteSprint.mutateAsync(
|
||||
sprintItem.id,
|
||||
);
|
||||
await deleteSprint.mutateAsync(sprintItem.id);
|
||||
closeConfirmDialog();
|
||||
toast.success(
|
||||
`Deleted sprint "${sprintItem.name}"`,
|
||||
);
|
||||
toast.success(`Deleted sprint "${sprintItem.name}"`);
|
||||
await invalidateSprints();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
error,
|
||||
);
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="hover:bg-destructive/10"
|
||||
>
|
||||
<Icon
|
||||
icon="trash"
|
||||
className="size-4"
|
||||
/>
|
||||
<Icon icon="trash" className="size-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -845,11 +764,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
projectId={selectedProject?.Project.id}
|
||||
trigger={
|
||||
<Button variant="outline" size="sm">
|
||||
Create sprint{" "}
|
||||
<Icon
|
||||
icon="plus"
|
||||
className="size-4"
|
||||
/>
|
||||
Create sprint <Icon icon="plus" className="size-4" />
|
||||
</Button>
|
||||
}
|
||||
sprints={sprints}
|
||||
@@ -857,9 +772,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Select a project to view sprints.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Select a project to view sprints.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -900,16 +813,10 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex flex-col gap-2 max-h-86 overflow-y-scroll grid grid-cols-2">
|
||||
{Object.keys(statuses).map((status, index) => (
|
||||
<div
|
||||
key={status}
|
||||
className="flex items-center justify-between p-2 border"
|
||||
>
|
||||
<div key={status} className="flex items-center justify-between p-2 border">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">{index + 1}</span>
|
||||
<StatusTag
|
||||
status={status}
|
||||
colour={statuses[status]}
|
||||
/>
|
||||
<StatusTag status={status} colour={statuses[status]} />
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<DropdownMenu>
|
||||
@@ -919,53 +826,29 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
noStyle
|
||||
className="hover:opacity-80 cursor-pointer"
|
||||
>
|
||||
<Icon
|
||||
icon="ellipsisVertical"
|
||||
className="size-4 text-foreground"
|
||||
/>
|
||||
<Icon icon="ellipsisVertical" className="size-4 text-foreground" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
className="bg-background"
|
||||
>
|
||||
<DropdownMenuContent align="end" sideOffset={4} className="bg-background">
|
||||
<DropdownMenuItem
|
||||
disabled={index === 0}
|
||||
onSelect={() =>
|
||||
void moveStatus(status, "up")
|
||||
}
|
||||
onSelect={() => void moveStatus(status, "up")}
|
||||
className="hover:bg-primary-foreground"
|
||||
>
|
||||
<Icon
|
||||
icon="chevronUp"
|
||||
className="size-4 text-muted-foreground"
|
||||
/>
|
||||
<Icon icon="chevronUp" className="size-4 text-muted-foreground" />
|
||||
Move up
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={
|
||||
index ===
|
||||
Object.keys(statuses).length - 1
|
||||
}
|
||||
onSelect={() =>
|
||||
void moveStatus(status, "down")
|
||||
}
|
||||
disabled={index === Object.keys(statuses).length - 1}
|
||||
onSelect={() => void moveStatus(status, "down")}
|
||||
className="hover:bg-primary-foreground"
|
||||
>
|
||||
<Icon
|
||||
icon="chevronDown"
|
||||
className="size-4 text-muted-foreground"
|
||||
/>
|
||||
<Icon icon="chevronDown" className="size-4 text-muted-foreground" />
|
||||
Move down
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
disabled={
|
||||
Object.keys(statuses).length <= 1
|
||||
}
|
||||
onSelect={() =>
|
||||
void handleRemoveStatusClick(status)
|
||||
}
|
||||
disabled={Object.keys(statuses).length <= 1}
|
||||
onSelect={() => void handleRemoveStatusClick(status)}
|
||||
className="hover:bg-destructive/10"
|
||||
>
|
||||
<Icon icon="x" className="size-4" />
|
||||
@@ -1012,19 +895,12 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
variant="outline"
|
||||
size="md"
|
||||
onClick={() => void handleCreateStatus()}
|
||||
disabled={
|
||||
newStatusName.trim().length >
|
||||
ISSUE_STATUS_MAX_LENGTH
|
||||
}
|
||||
disabled={newStatusName.trim().length > ISSUE_STATUS_MAX_LENGTH}
|
||||
>
|
||||
<Icon icon="plus" className="size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
{statusError && (
|
||||
<p className="text-xs text-destructive">
|
||||
{statusError}
|
||||
</p>
|
||||
)}
|
||||
{statusError && <p className="text-xs text-destructive">{statusError}</p>}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
@@ -1045,9 +921,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
) : (
|
||||
<div className="flex flex-col gap-2 w-full min-w-0">
|
||||
<OrganisationSelect
|
||||
contentClass={
|
||||
"data-[side=bottom]:translate-y-2 data-[side=bottom]:translate-x-0.25"
|
||||
}
|
||||
contentClass={"data-[side=bottom]:translate-y-2 data-[side=bottom]:translate-x-0.25"}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">No organisations yet.</p>
|
||||
</div>
|
||||
@@ -1083,8 +957,8 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
{statusToRemove ? (
|
||||
<StatusTag status={statusToRemove} colour={statuses[statusToRemove]} />
|
||||
) : null}{" "}
|
||||
status? <span className="font-700 text-foreground">{issuesUsingStatus}</span>{" "}
|
||||
issues are using it. Which status would you like these issues to use instead?
|
||||
status? <span className="font-700 text-foreground">{issuesUsingStatus}</span> issues are using
|
||||
it. Which status would you like these issues to use instead?
|
||||
</p>
|
||||
<Select value={reassignToStatus} onValueChange={setReassignToStatus}>
|
||||
<SelectTrigger className="w-min">
|
||||
|
||||
@@ -84,12 +84,7 @@ export function ProjectSelect({
|
||||
<ProjectForm
|
||||
organisationId={selectedOrganisationId ?? undefined}
|
||||
trigger={
|
||||
<Button
|
||||
size={"sm"}
|
||||
variant="ghost"
|
||||
className={"w-full"}
|
||||
disabled={!selectedOrganisationId}
|
||||
>
|
||||
<Button size={"sm"} variant="ghost" className={"w-full"} disabled={!selectedOrganisationId}>
|
||||
Create Project
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -113,11 +113,7 @@ export function ServerConfiguration({ trigger }: { trigger?: ReactNode }) {
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger || (
|
||||
<IconButton
|
||||
size="lg"
|
||||
className="absolute top-2 right-2"
|
||||
title={"Server Configuration"}
|
||||
>
|
||||
<IconButton size="lg" className="absolute top-2 right-2" title={"Server Configuration"}>
|
||||
<Icon icon="serverIcon" className="size-4" />
|
||||
</IconButton>
|
||||
)}
|
||||
@@ -164,9 +160,7 @@ export function ServerConfiguration({ trigger }: { trigger?: ReactNode }) {
|
||||
<Icon icon="undo2" className="size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
{!isValid && (
|
||||
<Label className="text-destructive text-sm">Please enter a valid URL</Label>
|
||||
)}
|
||||
{!isValid && <Label className="text-destructive text-sm">Please enter a valid URL</Label>}
|
||||
{healthError && <Label className="text-destructive text-sm">{healthError}</Label>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -303,8 +303,7 @@ export function SprintForm({
|
||||
type="submit"
|
||||
disabled={
|
||||
submitting ||
|
||||
((name.trim() === "" || name.trim().length > SPRINT_NAME_MAX_LENGTH) &&
|
||||
submitAttempted) ||
|
||||
((name.trim() === "" || name.trim().length > SPRINT_NAME_MAX_LENGTH) && submitAttempted) ||
|
||||
(dateError !== "" && submitAttempted)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -83,10 +83,7 @@ function Calendar({
|
||||
),
|
||||
week: cn("flex w-full mt-2", defaultClassNames.week),
|
||||
week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
|
||||
week_number: cn(
|
||||
"text-[0.8rem] select-none text-muted-foreground",
|
||||
defaultClassNames.week_number,
|
||||
),
|
||||
week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number),
|
||||
day: cn(
|
||||
"relative w-full h-full p-0 text-center group/day aspect-square select-none",
|
||||
defaultClassNames.day,
|
||||
@@ -95,10 +92,7 @@ function Calendar({
|
||||
range_middle: cn(defaultClassNames.range_middle),
|
||||
range_end: cn("bg-accent", defaultClassNames.range_end),
|
||||
today: cn("border border-dashed -m-px", defaultClassNames.today),
|
||||
outside: cn(
|
||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||
defaultClassNames.outside,
|
||||
),
|
||||
outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
|
||||
disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
|
||||
hidden: cn("invisible", defaultClassNames.hidden),
|
||||
...classNames,
|
||||
@@ -113,9 +107,7 @@ function Calendar({
|
||||
}
|
||||
|
||||
if (orientation === "right") {
|
||||
return (
|
||||
<Icon icon="chevronRightIcon" className={cn("size-4", className)} {...props} />
|
||||
);
|
||||
return <Icon icon="chevronRightIcon" className={cn("size-4", className)} {...props} />;
|
||||
}
|
||||
|
||||
return <Icon icon="chevronDownIcon" className={cn("size-4", className)} {...props} />;
|
||||
@@ -171,10 +163,7 @@ function CalendarDayButton({
|
||||
size="icon"
|
||||
data-day={day.date.toLocaleDateString()}
|
||||
data-selected-single={
|
||||
modifiers.selected &&
|
||||
!modifiers.range_start &&
|
||||
!modifiers.range_end &&
|
||||
!modifiers.range_middle
|
||||
modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
|
||||
}
|
||||
data-range-start={modifiers.range_start}
|
||||
data-range-end={modifiers.range_end}
|
||||
|
||||
@@ -18,11 +18,7 @@ export default function ColourPicker({
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild={asChild}>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn("w-8 h-8", className)}
|
||||
style={{ backgroundColor: colour }}
|
||||
/>
|
||||
<Button type="button" className={cn("w-8 h-8", className)} style={{ backgroundColor: colour }} />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit grid gap-2 p-2" align="start" side={"top"}>
|
||||
<HexColorPicker color={colour} onChange={onChange} className="p-0 m-0" />
|
||||
|
||||
@@ -36,9 +36,7 @@ function IconButton({
|
||||
size,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> & VariantProps<typeof iconButtonVariants>) {
|
||||
return (
|
||||
<button type="button" className={cn(iconButtonVariants({ variant, size, className }))} {...props} />
|
||||
);
|
||||
return <button type="button" className={cn(iconButtonVariants({ variant, size, className }))} {...props} />;
|
||||
}
|
||||
|
||||
export { IconButton, iconButtonVariants };
|
||||
|
||||
@@ -15,9 +15,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||
}
|
||||
|
||||
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||
return (
|
||||
<tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
);
|
||||
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
|
||||
}
|
||||
|
||||
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||
|
||||
@@ -4,9 +4,7 @@ import type * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||
return (
|
||||
<TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />
|
||||
);
|
||||
return <TabsPrimitive.Root data-slot="tabs" className={cn("flex flex-col gap-2", className)} {...props} />;
|
||||
}
|
||||
|
||||
function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||
|
||||
@@ -40,14 +40,9 @@ export function UploadAvatar({
|
||||
onAvatarUploaded(url);
|
||||
setUploading(false);
|
||||
|
||||
toast.success(
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
Avatar uploaded successfully
|
||||
</div>,
|
||||
{
|
||||
toast.success(<div className="flex flex-col items-center gap-4">Avatar uploaded successfully</div>, {
|
||||
dismissible: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
const message = parseError(err as Error);
|
||||
setError(message);
|
||||
|
||||
@@ -42,9 +42,7 @@ export function UploadOrgIcon({
|
||||
setUploading(false);
|
||||
|
||||
toast.success(
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
Organisation icon uploaded successfully
|
||||
</div>,
|
||||
<div className="flex flex-col items-center gap-4">Organisation icon uploaded successfully</div>,
|
||||
{
|
||||
dismissible: false,
|
||||
},
|
||||
|
||||
@@ -16,9 +16,7 @@ export default function Landing() {
|
||||
{!isLoading && user ? (
|
||||
<>
|
||||
{user && (
|
||||
<h1 className="text-xl font-basteleur font-400">
|
||||
Welcome back {user.name.split(" ")[0]}!
|
||||
</h1>
|
||||
<h1 className="text-xl font-basteleur font-400">Welcome back {user.name.split(" ")[0]}!</h1>
|
||||
)}
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link to="/app">Open app</Link>
|
||||
@@ -34,9 +32,7 @@ export default function Landing() {
|
||||
|
||||
<main className="flex-1 flex flex-col items-center justify-center gap-8">
|
||||
<div className="max-w-3xl text-center space-y-4">
|
||||
<h1 className="text-[54px] font-basteleur font-700">
|
||||
Need a snappy project management tool?
|
||||
</h1>
|
||||
<h1 className="text-[54px] font-basteleur font-700">Need a snappy project management tool?</h1>
|
||||
<p className="text-[24px] font-goudy text-muted-foreground">
|
||||
Build your next project with <span className="font-goudy font-700">Sprint.</span>
|
||||
</p>
|
||||
|
||||
@@ -245,11 +245,7 @@ export default function Timeline() {
|
||||
const barStyle = getSprintBarStyle(sprint);
|
||||
const showTodayLabel = sprintIndex === 0;
|
||||
return (
|
||||
<div
|
||||
key={sprint.id}
|
||||
className="grid border-b"
|
||||
style={{ gridTemplateColumns }}
|
||||
>
|
||||
<div key={sprint.id} className="grid border-b" style={{ gridTemplateColumns }}>
|
||||
<div
|
||||
className={`px-${BREATHING_ROOM} pt-0.5 py-${BREATHING_ROOM} flex flex-col gap-${BREATHING_ROOM} bg-background relative z-20 border-r`}
|
||||
>
|
||||
@@ -267,9 +263,7 @@ export default function Timeline() {
|
||||
{getSprintDateRange(sprint)}
|
||||
</div>
|
||||
{sprintIssues.length === 0 && (
|
||||
<div className="text-xs text-muted-foreground text-pretty">
|
||||
No issues assigned.
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground text-pretty">No issues assigned.</div>
|
||||
)}
|
||||
{sprintIssues.length > 0 && (
|
||||
<div className={`flex flex-col gap-${BREATHING_ROOM}`}>
|
||||
@@ -277,30 +271,21 @@ export default function Timeline() {
|
||||
<IssueLine
|
||||
key={issue.Issue.id}
|
||||
issue={issue}
|
||||
statusColour={
|
||||
statuses[issue.Issue.status] ??
|
||||
DEFAULT_STATUS_COLOUR
|
||||
}
|
||||
statusColour={statuses[issue.Issue.status] ?? DEFAULT_STATUS_COLOUR}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
`py-${BREATHING_ROOM} relative min-h-12`,
|
||||
"border-l",
|
||||
)}
|
||||
className={cn(`py-${BREATHING_ROOM} relative min-h-12`, "border-l")}
|
||||
style={{ gridColumn: "2 / -1" }}
|
||||
>
|
||||
<div className="absolute inset-0 flex z-10 pointer-events-none">
|
||||
{weeks.map((week, index) => (
|
||||
<div
|
||||
key={`${week.toISOString()}-${sprint.id}`}
|
||||
className={cn(
|
||||
"flex-1",
|
||||
index === 0 ? "" : "border-l",
|
||||
)}
|
||||
className={cn("flex-1", index === 0 ? "" : "border-l")}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -344,13 +329,9 @@ export default function Timeline() {
|
||||
<div
|
||||
className={`px-${BREATHING_ROOM} pt-0.5 py-${BREATHING_ROOM} flex flex-col gap-${BREATHING_ROOM} bg-background relative z-20 border-r`}
|
||||
>
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
Backlog
|
||||
</div>
|
||||
<div className="text-sm font-medium text-muted-foreground">Backlog</div>
|
||||
{issueGroup.unassigned.length === 0 && (
|
||||
<div className="text-xs text-muted-foreground text-pretty">
|
||||
No unassigned issues.
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground text-pretty">No unassigned issues.</div>
|
||||
)}
|
||||
{issueGroup.unassigned.length > 0 && (
|
||||
<div className={`flex flex-col gap-${BREATHING_ROOM}`}>
|
||||
@@ -358,10 +339,7 @@ export default function Timeline() {
|
||||
<IssueLine
|
||||
key={issue.Issue.id}
|
||||
issue={issue}
|
||||
statusColour={
|
||||
statuses[issue.Issue.status] ??
|
||||
DEFAULT_STATUS_COLOUR
|
||||
}
|
||||
statusColour={statuses[issue.Issue.status] ?? DEFAULT_STATUS_COLOUR}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user