add shadcn ui
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
import type { NextConfig } from 'next';
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
|
||||
/** Registers the cookie-based locale resolver in src/i18n/request.ts. */
|
||||
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useState, type FormEvent } from 'react';
|
||||
|
||||
import { isApiClientError } from '@sport/api-client';
|
||||
import { Button, Input } from '@sport/ui';
|
||||
import { Button, FormError, Input } from '@sport/ui';
|
||||
import { loginSchema } from '@sport/validation';
|
||||
|
||||
import { useSession } from './session-provider';
|
||||
@@ -91,11 +91,7 @@ export function LoginForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
<Button type="submit" fullWidth size="lg" disabled={submitting}>
|
||||
{submitting ? t('signingIn') : t('signIn')}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type ContentType,
|
||||
type Locale,
|
||||
} from '@sport/types';
|
||||
import { Badge, Button, Input, Skeleton, cn } from '@sport/ui';
|
||||
import { Badge, Button, FormError, Input, Skeleton, Textarea, cn } from '@sport/ui';
|
||||
|
||||
import { browserApi } from '@/lib/api';
|
||||
|
||||
@@ -275,12 +275,11 @@ export function ContentEditor({
|
||||
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold uppercase tracking-widest">{t('body')}</span>
|
||||
<textarea
|
||||
<Textarea
|
||||
value={fields.body}
|
||||
onChange={(event) => setField('body', event.target.value)}
|
||||
rows={22}
|
||||
spellCheck
|
||||
className="border-ink-200 focus:border-ink-950 w-full border px-3 py-2 font-mono text-sm leading-relaxed outline-none"
|
||||
/>
|
||||
<span className="text-ink-500 text-xs">{t('bodyHint')}</span>
|
||||
</label>
|
||||
@@ -313,11 +312,7 @@ export function ContentEditor({
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
<div className="border-ink-200 flex flex-wrap items-center gap-2 border-t pt-4">
|
||||
<Button type="submit" variant="outline" disabled={saving}>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
DataTableHeader,
|
||||
DataTableRow,
|
||||
EmptyState,
|
||||
FormError,
|
||||
Input,
|
||||
SegmentedControl,
|
||||
Skeleton,
|
||||
@@ -107,11 +108,7 @@ export function ContentTable() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!entries ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
EmptyState,
|
||||
FormError,
|
||||
Input,
|
||||
Skeleton,
|
||||
} from '@sport/ui';
|
||||
@@ -77,11 +78,7 @@ export function CustomersTable() {
|
||||
aria-label={t('searchPlaceholder')}
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!customers ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -7,12 +7,14 @@ import { isApiClientError, type DiscountPayload } from '@sport/api-client';
|
||||
import { DEFAULT_LOCALE, LOCALES, type AdminDiscount, type Locale } from '@sport/types';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
FormError,
|
||||
Input,
|
||||
cn,
|
||||
} from '@sport/ui';
|
||||
@@ -356,11 +358,7 @@ export function DiscountDialog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
</form>
|
||||
|
||||
<DialogFooter className="sm:justify-between">
|
||||
@@ -458,12 +456,10 @@ function Toggle({
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-2.5">
|
||||
<input
|
||||
<Checkbox
|
||||
id={id}
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(event) => onChange(event.target.checked)}
|
||||
className="border-ink-300 accent-ink-950 mt-0.5 size-4"
|
||||
onCheckedChange={(checked) => onChange(checked === true)}
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<label htmlFor={id} className="block text-sm font-medium">
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DataTableHeader,
|
||||
DataTableRow,
|
||||
EmptyState,
|
||||
FormError,
|
||||
Input,
|
||||
SegmentedControl,
|
||||
Skeleton,
|
||||
@@ -114,11 +115,7 @@ export function DiscountsTable() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!discounts ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
DataTableHead,
|
||||
DataTableHeader,
|
||||
DataTableRow,
|
||||
FormError,
|
||||
Input,
|
||||
Skeleton,
|
||||
cn,
|
||||
@@ -106,11 +107,7 @@ export function InventoryTable() {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!levels ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { isApiClientError } from '@sport/api-client';
|
||||
import { PERMISSIONS, type MediaAssetSummary } from '@sport/types';
|
||||
import { Button, Skeleton } from '@sport/ui';
|
||||
import { Button, FormError, Skeleton } from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -145,11 +145,7 @@ export function MediaLibrary() {
|
||||
</ul>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!assets ? (
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4 lg:grid-cols-6" aria-busy="true">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { isApiClientError } from '@sport/api-client';
|
||||
import { PERMISSIONS, type Order, type OrderStatus } from '@sport/types';
|
||||
import { Badge, Button, Input, Skeleton } from '@sport/ui';
|
||||
import { Badge, Button, FormError, Input, Skeleton } from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -103,11 +103,7 @@ export function OrderDetail({ orderId }: { orderId: string }) {
|
||||
<Badge variant="outline">{t(`payment.${order.paymentStatus}`)}</Badge>
|
||||
</header>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{can(PERMISSIONS.ORDER_UPDATE) && next.length > 0 ? (
|
||||
<section className="border-ink-200 space-y-3 border bg-white p-6">
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
DataTableHead,
|
||||
DataTableHeader,
|
||||
DataTableRow,
|
||||
FormError,
|
||||
Input,
|
||||
Skeleton,
|
||||
cn,
|
||||
@@ -115,11 +116,7 @@ export function OrdersTable() {
|
||||
<span className="text-ink-500 ml-auto text-xs">{t('count', { count: total })}</span>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!orders ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type AdminProductDetail,
|
||||
type MediaAssetSummary,
|
||||
} from '@sport/types';
|
||||
import { Button, Skeleton, cn } from '@sport/ui';
|
||||
import { Button, FormError, Skeleton, cn } from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -139,11 +139,7 @@ export function ImageManager({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{slots.length === 0 ? (
|
||||
<p className="text-ink-500 text-sm">{t('noImages')}</p>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { isApiClientError } from '@sport/api-client';
|
||||
import { PERMISSIONS, type AdminProductDetail } from '@sport/types';
|
||||
import { Badge, Button, Skeleton, cn } from '@sport/ui';
|
||||
import { Badge, Button, FormError, Skeleton, cn } from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -127,11 +127,7 @@ export function ProductEditor({ productId }: { productId: string }) {
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
<nav className="border-ink-200 flex gap-1 border-b">
|
||||
{(['details', 'variants', 'images'] as const).map((value) => (
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type AdminProductDetail,
|
||||
type Locale,
|
||||
} from '@sport/types';
|
||||
import { Badge, Button, Input, cn } from '@sport/ui';
|
||||
import { Badge, Button, FormError, Input, Textarea, cn } from '@sport/ui';
|
||||
|
||||
import { browserApi } from '@/lib/api';
|
||||
|
||||
@@ -264,7 +264,7 @@ export function ProductForm({
|
||||
<span className="text-xs font-semibold uppercase tracking-widest">
|
||||
{t('description')}
|
||||
</span>
|
||||
<textarea
|
||||
<Textarea
|
||||
rows={5}
|
||||
value={translations[locale].description}
|
||||
onChange={(event) =>
|
||||
@@ -347,11 +347,7 @@ export function ProductForm({
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Button size="lg" disabled={saving} onClick={() => void save()}>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DataTableHead,
|
||||
DataTableHeader,
|
||||
DataTableRow,
|
||||
FormError,
|
||||
Input,
|
||||
Skeleton,
|
||||
cn,
|
||||
@@ -141,11 +142,7 @@ export function ProductsTable() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!items ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useState } from 'react';
|
||||
|
||||
import { isApiClientError, type VariantPatch } from '@sport/api-client';
|
||||
import { PERMISSIONS, type AdminProductDetail, type AdminProductVariant } from '@sport/types';
|
||||
import { Badge, Button, Input, cn } from '@sport/ui';
|
||||
import { Badge, Button, FormError, Input, cn } from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -131,11 +131,7 @@ export function VariantGrid({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
<div className="border-ink-200 overflow-x-auto border bg-white">
|
||||
<table className="min-w-3xl w-full text-sm">
|
||||
|
||||
@@ -5,7 +5,16 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { isApiClientError } from '@sport/api-client';
|
||||
import { PERMISSIONS, type AdminReview, type ReviewStatus } from '@sport/types';
|
||||
import { Badge, Button, EmptyState, Input, Rating, SegmentedControl, Skeleton } from '@sport/ui';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
EmptyState,
|
||||
FormError,
|
||||
Input,
|
||||
Rating,
|
||||
SegmentedControl,
|
||||
Skeleton,
|
||||
} from '@sport/ui';
|
||||
|
||||
import { useSession } from '@/features/auth/session-provider';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -97,11 +106,7 @@ export function ReviewsQueue() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!reviews ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
DialogTitle,
|
||||
EmptyState,
|
||||
Field,
|
||||
FormError,
|
||||
Input,
|
||||
Skeleton,
|
||||
cn,
|
||||
@@ -171,11 +172,7 @@ export function TaxonomyManager<TRow extends TaxonomyRow>({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
|
||||
{!rows ? (
|
||||
<div className="space-y-2" aria-busy="true">
|
||||
@@ -437,11 +434,7 @@ function TaxonomyDialog<TRow extends TaxonomyRow>({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<p role="alert" className="text-danger text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<FormError message={error} />
|
||||
</form>
|
||||
|
||||
<DialogFooter className="sm:justify-between">
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"extends": "@sport/config/typescript/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user