diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx index c8bcf54c50..666cd9a937 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx @@ -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, { @@ -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[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 (
@@ -114,15 +163,9 @@ const ModuleIssueDetailsPage = () => { {issue.organizationName}/{issue.repositoryName} • #{issue.number} - {issue.state === 'open' ? 'Open' : issue.isMerged ? 'Merged' : 'Closed'} + {issueStatusLabel}
@@ -340,28 +383,17 @@ const ModuleIssueDetailsPage = () => {
- {pr.state === 'closed' && pr.mergedAt ? ( - - Merged - - ) : pr.state === 'closed' ? ( - - Closed - - ) : ( - - Open - - )} + {(() => { + const { backgroundColor, label } = getPRStatus(pr) + return ( + + {label} + + ) + })()}
)) @@ -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)} > Assign diff --git a/frontend/src/app/settings/api-keys/page.tsx b/frontend/src/app/settings/api-keys/page.tsx index b042c21955..d65febe491 100644 --- a/frontend/src/app/settings/api-keys/page.tsx +++ b/frontend/src/app/settings/api-keys/page.tsx @@ -5,7 +5,7 @@ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@herou import { Input } from '@heroui/react' import { addToast } from '@heroui/toast' import { format, addDays } from 'date-fns' -import { useState } from 'react' +import React, { useState } from 'react' import { FaInfoCircle } from 'react-icons/fa' import { FaSpinner, FaKey, FaPlus, FaCopy, FaEye, FaEyeSlash, FaTrash } from 'react-icons/fa6' import { @@ -14,11 +14,80 @@ 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' const MAX_ACTIVE_KEYS = 3 +// Content state components +const ErrorState = () => ( +
+ Error loading API keys +
+) + +const EmptyState = () => ( +
+ You don't have any API keys yet. +
+) + +interface ApiKeysTableProps { + data: { apiKeys?: ApiKey[] } | undefined + onRevoke: (key: ApiKey) => void +} + +const ApiKeysTable = ({ data, onRevoke }: ApiKeysTableProps) => ( +
+ + + + + + + + + + + + {(data?.apiKeys ?? []).map((key: ApiKey) => ( + + + + + + + + ))} + +
NameIDCreatedExpiresActions
{key.name}{key.uuid}{format(new Date(key.createdAt), 'PP')} + {key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'} + + +
+
+) + +type ContentType = 'error' | 'loading' | 'empty' | 'table' + +const getContentComponents = ( + data: { apiKeys?: ApiKey[] } | undefined, + onRevoke: (key: ApiKey) => void +): Record React.ReactNode> => ({ + error: () => , + loading: () => , + empty: () => , + table: () => , +}) + export default function Page() { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false) const [newKeyName, setNewKeyName] = useState('') @@ -70,6 +139,21 @@ export default function Page() { const canCreateNewKey = activeKeyCount < MAX_ACTIVE_KEYS const defaultExpiryDate = format(addDays(new Date(), 30), 'yyyy-MM-dd') + const getContentType = (): ContentType => { + if (error) { + return 'error' + } else if (loading) { + return 'loading' + } else if (data?.apiKeys?.length === 0) { + return 'empty' + } else { + return 'table' + } + } + + const contentType = getContentType() + const contentComponents = getContentComponents(data, setKeyToRevoke) + const handleCreateKey = () => { if (!newKeyName.trim()) { addToast({ title: 'Error', description: 'Please provide a name', color: 'danger' }) @@ -202,58 +286,7 @@ export default function Page() { - {error ? ( -
- Error loading API keys -
- ) : loading ? ( -
- -
- ) : !data?.apiKeys?.length ? ( -
- You don't have any API keys yet. -
- ) : ( -
- - - - - - - - - - - - {data.apiKeys.map((key: ApiKey) => ( - - - - - - - - ))} - -
NameIDCreatedExpiresActions
{key.name}{key.uuid}{format(new Date(key.createdAt), 'PP')} - {key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'} - - -
-
- )} + {contentComponents[contentType]()}