Finish admin page
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { Star } from 'lucide-react';
|
||||
import { useFormatter, useTranslations } from 'next-intl';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { isApiClientError } from '@sport/api-client';
|
||||
import { PERMISSIONS, type AdminReview, type ReviewStatus } from '@sport/types';
|
||||
import { Badge, Button, Input, Skeleton, cn } from '@sport/ui';
|
||||
import { Badge, Button, EmptyState, Input, Rating, SegmentedControl, Skeleton } from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
import { formatDateTime } from '@/lib/format';
|
||||
|
||||
type StatusFilter = ReviewStatus | 'ALL';
|
||||
const STATUS_FILTERS = ['PENDING', 'APPROVED', 'REJECTED', 'ALL'] as const satisfies readonly (
|
||||
ReviewStatus | 'ALL'
|
||||
)[];
|
||||
type StatusFilter = (typeof STATUS_FILTERS)[number];
|
||||
|
||||
const STATUS_VARIANT = {
|
||||
PENDING: 'warning',
|
||||
@@ -87,22 +89,12 @@ export function ReviewsQueue() {
|
||||
aria-label={t('searchPlaceholder')}
|
||||
/>
|
||||
|
||||
<div className="border-ink-200 rounded-card flex overflow-hidden border">
|
||||
{(['PENDING', 'APPROVED', 'REJECTED', 'ALL'] as const).map((value) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
aria-pressed={status === value}
|
||||
onClick={() => setStatus(value)}
|
||||
className={cn(
|
||||
'px-3 py-2 text-sm transition-colors',
|
||||
status === value ? 'bg-ink-950 text-white' : 'hover:bg-ink-100 bg-white',
|
||||
)}
|
||||
>
|
||||
{t(`filter.${value}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
label={t('searchPlaceholder')}
|
||||
value={status}
|
||||
onChange={setStatus}
|
||||
options={STATUS_FILTERS.map((value) => ({ value, label: t(`filter.${value}`) }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
@@ -117,9 +109,7 @@ export function ReviewsQueue() {
|
||||
<Skeleton className="h-28 w-full" />
|
||||
</div>
|
||||
) : reviews.length === 0 ? (
|
||||
<p className="text-ink-500 border-ink-200 border border-dashed p-8 text-center text-sm">
|
||||
{status === 'PENDING' ? t('emptyQueue') : t('empty')}
|
||||
</p>
|
||||
<EmptyState title={status === 'PENDING' ? t('emptyQueue') : t('empty')} />
|
||||
) : (
|
||||
<ul className="space-y-3">
|
||||
{reviews.map((review) => (
|
||||
@@ -159,7 +149,7 @@ function ReviewCard({
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Stars rating={review.rating} />
|
||||
<Rating value={review.rating} size="sm" />
|
||||
<span className="text-sm font-semibold">{review.title || t('untitled')}</span>
|
||||
</div>
|
||||
<p className="text-ink-500 text-xs">
|
||||
@@ -173,7 +163,7 @@ function ReviewCard({
|
||||
{review.body ? (
|
||||
<p className="text-ink-700 whitespace-pre-line text-sm">{review.body}</p>
|
||||
) : (
|
||||
<p className="text-ink-400 text-sm italic">{t('noBody')}</p>
|
||||
<p className="text-ink-500 text-sm italic">{t('noBody')}</p>
|
||||
)}
|
||||
|
||||
<p className="text-ink-500 text-xs">
|
||||
@@ -235,17 +225,3 @@ function ReviewCard({
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Stars({ rating }: { rating: number }) {
|
||||
return (
|
||||
<span className="flex items-center gap-0.5" aria-label={`${rating}/5`}>
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<Star
|
||||
key={star}
|
||||
aria-hidden
|
||||
className={cn('size-3.5', star <= rating ? 'fill-ink-950 text-ink-950' : 'text-ink-300')}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user