Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,29 @@ const ModuleIssueDetailsPage = () => {
const isOverdue = deadlineUTC < todayUTC
const daysLeft = Math.ceil((deadlineUTC.getTime() - todayUTC.getTime()) / (1000 * 60 * 60 * 24))

const statusText = isOverdue
? '(overdue)'
: daysLeft === 0
? '(today)'
: `(${daysLeft} days left)`
let statusText: string
if (isOverdue) {
statusText = '(overdue)'
} else if (daysLeft === 0) {
statusText = '(today)'
} else {
statusText = `(${daysLeft} days left)`
}

const displayDate = deadlineDate.toLocaleDateString()

let color: string
if (isOverdue) {
color = 'text-[#DA3633]'
} else if (daysLeft <= 3) {
color = 'text-[#F59E0B]'
} else {
color = 'text-gray-600 dark:text-gray-300'
}

return {
text: `${displayDate} ${statusText}`,
color: isOverdue
? 'text-[#DA3633]'
: daysLeft <= 3
? 'text-[#F59E0B]'
: 'text-gray-600 dark:text-gray-300',
color,
}
}
const { data, loading, error } = useQuery(GetModuleIssueViewDocument, {
Expand Down Expand Up @@ -101,6 +109,47 @@ const ModuleIssueDetailsPage = () => {
const remainingLabels = labels.length - visibleLabels.length
const canEditDeadline = assignees.length > 0

let issueStatusClass: string
let issueStatusLabel: string
if (issue.state === 'open') {
issueStatusClass = 'bg-[#238636] text-white'
issueStatusLabel = 'Open'
} else if (issue.isMerged) {
issueStatusClass = 'bg-[#8657E5] text-white'
issueStatusLabel = 'Merged'
} else {
issueStatusClass = 'bg-[#DA3633] text-white'
issueStatusLabel = 'Closed'
}

const getPRStatus = (pr: Exclude<typeof issue.pullRequests, undefined>[0]) => {
let backgroundColor: string
let label: string
if (pr.state === 'closed' && pr.mergedAt) {
backgroundColor = '#8657E5'
label = 'Merged'
} else if (pr.state === 'closed') {
backgroundColor = '#DA3633'
label = 'Closed'
} else {
backgroundColor = '#238636'
label = 'Open'
}
return { backgroundColor, label }
}

const getAssignButtonTitle = (assigning: boolean) => {
let title: string
if (!issueId) {
title = 'Loading issue…'
} else if (assigning) {
title = 'Assigning…'
} else {
title = 'Assign to this user'
}
return title
}

return (
<div className="min-h-screen bg-white p-8 text-gray-700 dark:bg-[#212529] dark:text-gray-300">
<div className="mx-auto max-w-5xl">
Expand All @@ -114,15 +163,9 @@ const ModuleIssueDetailsPage = () => {
{issue.organizationName}/{issue.repositoryName} • #{issue.number}
</span>
<span
className={`inline-flex items-center rounded-lg px-2 py-0.5 text-xs font-medium ${
issue.state === 'open'
? 'bg-[#238636] text-white'
: issue.isMerged
? 'bg-[#8657E5] text-white'
: 'bg-[#DA3633] text-white'
}`}
className={`inline-flex items-center rounded-lg px-2 py-0.5 text-xs font-medium ${issueStatusClass}`}
>
{issue.state === 'open' ? 'Open' : issue.isMerged ? 'Merged' : 'Closed'}
{issueStatusLabel}
</span>
</div>
</div>
Expand Down Expand Up @@ -340,28 +383,17 @@ const ModuleIssueDetailsPage = () => {
</div>
</div>
<div className="flex items-center gap-2">
{pr.state === 'closed' && pr.mergedAt ? (
<span
className="inline-flex items-center rounded-lg px-2 py-0.5 text-xs font-medium text-white"
style={{ backgroundColor: '#8657E5' }}
>
Merged
</span>
) : pr.state === 'closed' ? (
<span
className="inline-flex items-center rounded-lg px-2 py-0.5 text-xs font-medium text-white"
style={{ backgroundColor: '#DA3633' }}
>
Closed
</span>
) : (
<span
className="inline-flex items-center rounded-lg px-2 py-0.5 text-xs font-medium text-white"
style={{ backgroundColor: '#238636' }}
>
Open
</span>
)}
{(() => {
const { backgroundColor, label } = getPRStatus(pr)
return (
<span
className="inline-flex items-center rounded-lg px-2 py-0.5 text-xs font-medium text-white"
style={{ backgroundColor }}
>
{label}
</span>
)
})()}
</div>
</div>
))
Expand Down Expand Up @@ -415,9 +447,7 @@ const ModuleIssueDetailsPage = () => {
})
}}
className={`${getButtonClassName(!issueId || assigning)} px-3 py-1`}
title={
!issueId ? 'Loading issue…' : assigning ? 'Assigning…' : 'Assign to this user'
}
title={getAssignButtonTitle(assigning)}
>
<FaPlus className="text-gray-500" />
<span>Assign</span>
Expand Down
130 changes: 78 additions & 52 deletions frontend/src/app/settings/api-keys/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
RevokeApiKeyDocument,
} from 'types/__generated__/apiKeyQueries.generated'
import type { ApiKey } from 'types/apiKey'
import LoadingSpinner from 'components/LoadingSpinner'
import SecondaryCard from 'components/SecondaryCard'
import { ApiKeysSkeleton } from 'components/skeletons/ApiKeySkelton'

Expand Down Expand Up @@ -70,6 +71,22 @@ export default function Page() {
const canCreateNewKey = activeKeyCount < MAX_ACTIVE_KEYS
const defaultExpiryDate = format(addDays(new Date(), 30), 'yyyy-MM-dd')

type ContentType = 'error' | 'loading' | 'empty' | 'table'

const getContentType = (): ContentType => {
if (error) {
return 'error'
} else if (loading) {
return 'loading'
} else if (!data?.apiKeys?.length) {
return 'empty'
} else {
return 'table'
}
}

const contentType = getContentType()

const handleCreateKey = () => {
if (!newKeyName.trim()) {
addToast({ title: 'Error', description: 'Please provide a name', color: 'danger' })
Expand Down Expand Up @@ -202,58 +219,67 @@ export default function Page() {
</Button>
</div>

{error ? (
<div className="rounded-md bg-red-50 p-4 text-red-700 dark:bg-red-900/20 dark:text-red-400">
Error loading API keys
</div>
) : loading ? (
<div className="flex flex-col gap-4">
<ApiKeysSkeleton />
</div>
) : !data?.apiKeys?.length ? (
<div className="rounded-md bg-gray-50 p-8 text-center text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
You don't have any API keys yet.
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="border-b-1 border-b-gray-200 dark:border-b-gray-700">
<th className="py-3 text-left font-semibold">Name</th>
<th className="py-3 text-left font-semibold">ID</th>
<th className="py-3 text-left font-semibold">Created</th>
<th className="py-3 text-left font-semibold">Expires</th>
<th className="py-3 text-right font-semibold">Actions</th>
</tr>
</thead>
<tbody>
{data.apiKeys.map((key: ApiKey) => (
<tr
key={key.uuid}
className="border-b border-b-gray-200 dark:border-b-gray-700"
>
<td className="py-3">{key.name}</td>
<td className="py-3 font-mono text-sm">{key.uuid}</td>
<td className="py-3">{format(new Date(key.createdAt), 'PP')}</td>
<td className="py-3">
{key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'}
</td>
<td className="py-3 text-right">
<Button
variant="light"
size="sm"
onPress={() => setKeyToRevoke(key)}
className="text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
>
<FaTrash />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{(() => {
switch (contentType) {
case 'error':
return (
<div className="rounded-md bg-red-50 p-4 text-red-700 dark:bg-red-900/20 dark:text-red-400">
Error loading API keys
</div>
)
case 'loading':
return <LoadingSpinner />
case 'empty':
return (
<div className="rounded-md bg-gray-50 p-8 text-center text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
You don't have any API keys yet.
</div>
)
case 'table':
return (
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="border-b-1 border-b-gray-200 dark:border-b-gray-700">
<th className="py-3 text-left font-semibold">Name</th>
<th className="py-3 text-left font-semibold">ID</th>
<th className="py-3 text-left font-semibold">Created</th>
<th className="py-3 text-left font-semibold">Expires</th>
<th className="py-3 text-right font-semibold">Actions</th>
</tr>
</thead>
<tbody>
{data.apiKeys.map((key: ApiKey) => (
<tr
key={key.uuid}
className="border-b border-b-gray-200 dark:border-b-gray-700"
>
<td className="py-3">{key.name}</td>
<td className="py-3 font-mono text-sm">{key.uuid}</td>
<td className="py-3">{format(new Date(key.createdAt), 'PP')}</td>
<td className="py-3">
{key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'}
</td>
<td className="py-3 text-right">
<Button
variant="light"
size="sm"
onPress={() => setKeyToRevoke(key)}
className="text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
>
<FaTrash />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)
default:
return null
}
})()}
</SecondaryCard>

<SecondaryCard>
Expand Down