Skip to content

Commit 208add2

Browse files
authored
Merge pull request #132 from ArjinAlbay/main
feat: implement UI enhancements
2 parents d7209f5 + e9fb8eb commit 208add2

File tree

23 files changed

+1035
-196
lines changed

23 files changed

+1035
-196
lines changed

package-lock.json

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@radix-ui/react-checkbox": "^1.3.3",
1717
"@radix-ui/react-collapsible": "^1.1.11",
1818
"@radix-ui/react-dialog": "^1.1.14",
19+
"@radix-ui/react-hover-card": "^1.1.15",
1920
"@radix-ui/react-label": "^2.1.7",
2021
"@radix-ui/react-select": "^2.2.5",
2122
"@radix-ui/react-separator": "^1.1.7",

src/app/action-required/page.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import {
3333
ExternalLink,
3434
RefreshCw,
3535
LucideIcon,
36-
GitMerge,
3736
AlertCircle,
3837
CheckCircle2,
3938
XCircle,
@@ -74,6 +73,12 @@ interface ActionItem {
7473
const VALID_TABS = ["assigned", "mentions", "stale"] as const;
7574
type ValidTab = (typeof VALID_TABS)[number];
7675

76+
function extractIssueNumber(url?: string): string | null {
77+
if (!url) return null;
78+
const match = url.match(/\/(issues|pull)\/(\d+)/);
79+
return match ? match[2] : null;
80+
}
81+
7782
function formatTimeAgo(dateString: string): string {
7883
const now = new Date();
7984
const past = new Date(dateString);
@@ -436,6 +441,11 @@ function ActionRequiredContent() {
436441
<div className="flex items-center gap-2">
437442
<div className="min-w-0 flex-1">
438443
<div className="flex items-center gap-2">
444+
{extractIssueNumber(item.url) && (
445+
<span className="text-sm text-gray-500 dark:text-gray-400 font-mono flex-shrink-0">
446+
#{extractIssueNumber(item.url)}
447+
</span>
448+
)}
439449
<a
440450
href={isValidUrl(item.url) ? item.url : "#"}
441451
target="_blank"

src/app/login/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Suspense } from "react";
44
import Link from "next/link";
55
import { useSearchParams } from "next/navigation";
66
import { signIn } from "next-auth/react";
7-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
7+
import { Card, CardContent, CardHeader } from "@/components/ui/card";
88
import { Button } from "@/components/ui/button";
99
import { useRequireGuest } from "@/hooks/useAuth";
1010

src/components/command/CommandPalette.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@ import {
1414
import { useNavigationStore, useSearchStore, usePreferencesStore, useAuthStore } from "@/stores"
1515
import { menuItems } from "@/config/menu"
1616
import {
17-
Home,
1817
Search,
1918
Settings,
2019
Moon,
2120
Sun,
2221
LogOut,
2322
Monitor,
2423
Clock,
25-
Star,
26-
Users,
2724
GitBranch,
2825
} from "lucide-react"
2926
import { signOut } from "next-auth/react"
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
"use client";
2+
3+
import { X, ExternalLink, Calendar, MessageCircle, Tag, User as UserIcon } from "lucide-react";
4+
import { Button } from "@/components/ui/button";
5+
import { Badge } from "@/components/ui/badge";
6+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
7+
import { Card, CardContent } from "@/components/ui/card";
8+
import { useEffect, useState } from "react";
9+
10+
export interface IssueDetail {
11+
number: number;
12+
title: string;
13+
body: string | null;
14+
url: string;
15+
repository: string;
16+
repositoryUrl: string;
17+
state: "open" | "closed";
18+
author: {
19+
login: string;
20+
avatar_url: string;
21+
};
22+
labels: Array<{
23+
name: string;
24+
color: string;
25+
}>;
26+
created_at: string;
27+
updated_at: string;
28+
comments: number;
29+
assignees?: Array<{
30+
login: string;
31+
avatar_url: string;
32+
}>;
33+
}
34+
35+
interface DetailPanelProps {
36+
isOpen: boolean;
37+
onClose: () => void;
38+
issue: IssueDetail | null;
39+
comments?: Array<{
40+
id: number;
41+
user: {
42+
login: string;
43+
avatar_url: string;
44+
};
45+
body: string;
46+
created_at: string;
47+
}>;
48+
loading?: boolean;
49+
}
50+
51+
function formatDate(dateString: string): string {
52+
const date = new Date(dateString);
53+
return date.toLocaleDateString("en-US", {
54+
month: "short",
55+
day: "numeric",
56+
year: "numeric",
57+
});
58+
}
59+
60+
export function DetailPanel({ isOpen, onClose, issue, comments, loading }: DetailPanelProps) {
61+
const [isVisible, setIsVisible] = useState(false);
62+
63+
useEffect(() => {
64+
if (isOpen) {
65+
setTimeout(() => setIsVisible(true), 10);
66+
} else {
67+
setIsVisible(false);
68+
}
69+
}, [isOpen]);
70+
71+
if (!isOpen && !isVisible) return null;
72+
73+
return (
74+
<>
75+
<div
76+
className={`fixed inset-0 bg-black/50 z-40 transition-opacity duration-300 ${
77+
isVisible ? "opacity-100" : "opacity-0"
78+
}`}
79+
onClick={onClose}
80+
/>
81+
82+
<div
83+
className={`fixed right-0 top-0 h-full w-full md:w-[600px] bg-background border-l border-border z-50 shadow-2xl transform transition-transform duration-300 ${
84+
isVisible ? "translate-x-0" : "translate-x-full"
85+
}`}
86+
>
87+
<div className="h-full flex flex-col">
88+
<div className="flex items-center justify-between p-4 border-b border-border">
89+
<h2 className="text-lg font-semibold">Issue Details</h2>
90+
<Button variant="ghost" size="icon" onClick={onClose}>
91+
<X className="w-5 h-5" />
92+
</Button>
93+
</div>
94+
95+
{loading && (
96+
<div className="flex-1 flex items-center justify-center">
97+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
98+
</div>
99+
)}
100+
101+
{!loading && issue && (
102+
<div className="flex-1 overflow-y-auto p-6 space-y-6">
103+
<div className="flex items-start justify-between gap-4">
104+
<div className="flex-1">
105+
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-2">
106+
<span className="font-mono">#{issue.number}</span>
107+
<Badge variant={issue.state === "open" ? "default" : "secondary"}>
108+
{issue.state}
109+
</Badge>
110+
</div>
111+
<h3 className="text-xl font-bold mb-2">{issue.title}</h3>
112+
<a
113+
href={issue.repositoryUrl}
114+
target="_blank"
115+
rel="noopener noreferrer"
116+
className="text-sm text-muted-foreground hover:text-foreground flex items-center gap-1"
117+
>
118+
{issue.repository}
119+
<ExternalLink className="w-3 h-3" />
120+
</a>
121+
</div>
122+
</div>
123+
124+
<div className="flex items-center gap-4 text-sm text-muted-foreground">
125+
<div className="flex items-center gap-2">
126+
<Avatar className="w-6 h-6">
127+
<AvatarImage src={issue.author.avatar_url} alt={issue.author.login} />
128+
<AvatarFallback>{issue.author.login.charAt(0).toUpperCase()}</AvatarFallback>
129+
</Avatar>
130+
<span>{issue.author.login}</span>
131+
</div>
132+
<div className="flex items-center gap-1">
133+
<Calendar className="w-4 h-4" />
134+
<span>Opened {formatDate(issue.created_at)}</span>
135+
</div>
136+
<div className="flex items-center gap-1">
137+
<MessageCircle className="w-4 h-4" />
138+
<span>{issue.comments} comments</span>
139+
</div>
140+
</div>
141+
142+
{issue.labels && issue.labels.length > 0 && (
143+
<div className="flex items-start gap-2">
144+
<Tag className="w-4 h-4 mt-1 text-muted-foreground" />
145+
<div className="flex flex-wrap gap-2">
146+
{issue.labels.map((label, idx) => (
147+
<Badge
148+
key={idx}
149+
variant="outline"
150+
style={{
151+
borderColor: `#${label.color}`,
152+
backgroundColor: `#${label.color}20`,
153+
color: `#${label.color}`,
154+
}}
155+
>
156+
{label.name}
157+
</Badge>
158+
))}
159+
</div>
160+
</div>
161+
)}
162+
163+
{issue.assignees && issue.assignees.length > 0 && (
164+
<div className="flex items-start gap-2">
165+
<UserIcon className="w-4 h-4 mt-1 text-muted-foreground" />
166+
<div className="flex flex-wrap gap-2">
167+
{issue.assignees.map((assignee, idx) => (
168+
<div key={idx} className="flex items-center gap-2">
169+
<Avatar className="w-6 h-6">
170+
<AvatarImage src={assignee.avatar_url} alt={assignee.login} />
171+
<AvatarFallback>{assignee.login.charAt(0).toUpperCase()}</AvatarFallback>
172+
</Avatar>
173+
<span className="text-sm">{assignee.login}</span>
174+
</div>
175+
))}
176+
</div>
177+
</div>
178+
)}
179+
180+
<div>
181+
<h4 className="font-semibold mb-3">Description</h4>
182+
<Card>
183+
<CardContent className="p-4">
184+
{issue.body ? (
185+
<div className="text-sm whitespace-pre-wrap break-words">
186+
{issue.body}
187+
</div>
188+
) : (
189+
<p className="text-muted-foreground italic">No description provided.</p>
190+
)}
191+
</CardContent>
192+
</Card>
193+
</div>
194+
195+
{comments && comments.length > 0 && (
196+
<div>
197+
<h4 className="font-semibold mb-3">Recent Comments ({comments.length})</h4>
198+
<div className="space-y-3">
199+
{comments.slice(0, 5).map((comment) => (
200+
<Card key={comment.id}>
201+
<CardContent className="p-4">
202+
<div className="flex items-center gap-2 mb-2">
203+
<Avatar className="w-6 h-6">
204+
<AvatarImage src={comment.user.avatar_url} alt={comment.user.login} />
205+
<AvatarFallback>{comment.user.login.charAt(0).toUpperCase()}</AvatarFallback>
206+
</Avatar>
207+
<span className="text-sm font-medium">{comment.user.login}</span>
208+
<span className="text-xs text-muted-foreground">
209+
{formatDate(comment.created_at)}
210+
</span>
211+
</div>
212+
<div className="text-sm whitespace-pre-wrap break-words">
213+
{comment.body}
214+
</div>
215+
</CardContent>
216+
</Card>
217+
))}
218+
{comments.length > 5 && (
219+
<p className="text-sm text-muted-foreground text-center">
220+
And {comments.length - 5} more comments...
221+
</p>
222+
)}
223+
</div>
224+
</div>
225+
)}
226+
227+
<div className="flex gap-2 pt-4">
228+
<Button asChild className="flex-1">
229+
<a href={issue.url} target="_blank" rel="noopener noreferrer">
230+
<ExternalLink className="w-4 h-4 mr-2" />
231+
View on GitHub
232+
</a>
233+
</Button>
234+
</div>
235+
</div>
236+
)}
237+
</div>
238+
</div>
239+
</>
240+
);
241+
}

src/components/kanban/ColumnManagementModal.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export function ColumnManagementModal({
176176
isOpen,
177177
onClose,
178178
}: ColumnManagementModalProps) {
179-
const { columns, columnOrder, updateColumn, addColumn, deleteColumn, reorderColumns, tasks } =
179+
const { columns, columnOrder, updateColumn, addColumn, deleteColumn, reorderColumns } =
180180
useKanbanStore();
181181
const [showAddColumn, setShowAddColumn] = useState(false);
182182
const [newColumnTitle, setNewColumnTitle] = useState("");
@@ -206,6 +206,10 @@ export function ColumnManagementModal({
206206
}
207207
};
208208

209+
const handleUpdateColumn = (id: string, title: string, color: string) => {
210+
updateColumn(id, { title, color });
211+
};
212+
209213
const handleDeleteColumn = (columnId: string) => {
210214
const column = columns[columnId];
211215
const taskCount = column?.taskIds.length || 0;
@@ -262,7 +266,7 @@ export function ColumnManagementModal({
262266
title={column.title}
263267
color={column.color}
264268
taskCount={column.taskIds.length}
265-
onUpdate={updateColumn}
269+
onUpdate={handleUpdateColumn}
266270
onDelete={handleDeleteColumn}
267271
/>
268272
);

src/components/kanban/KanbanBoard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ export function KanbanBoard() {
391391
const repo = urlParts[urlParts.length - 3];
392392
const number = parseInt(urlParts[urlParts.length - 1], 10);
393393

394-
githubAPIClient.setToken(orgData.token);
394+
githubAPIClient.setUserToken(orgData.token);
395395

396396
const result =
397397
task.type === "github-issue"

0 commit comments

Comments
 (0)