Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 4 additions & 6 deletions frontend/app/[locale]/shop/admin/orders/[id]/RefundButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useRouter } from 'next/navigation';
import { useId, useState, useTransition } from 'react';
import { useTranslations } from 'next-intl';

type Props = {
orderId: string;
Expand All @@ -10,6 +11,7 @@ type Props = {

export function RefundButton({ orderId, disabled }: Props) {
const router = useRouter();
const t = useTranslations('shop.admin.refund');
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const errorId = useId();
Expand Down Expand Up @@ -59,13 +61,9 @@ export function RefundButton({ orderId, disabled }: Props) {
aria-busy={isPending}
aria-describedby={error ? errorId : undefined}
className="rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-50"
title={
disabled
? 'Refund is only available for paid Stripe orders'
: undefined
}
title={disabled ? t('onlyForPaid') : undefined}
>
{isPending ? 'Refunding…' : 'Refund'}
{isPending ? t('refunding') : t('refund')}
</button>

{error ? (
Expand Down
40 changes: 21 additions & 19 deletions frontend/app/[locale]/shop/admin/orders/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from '@/i18n/routing';
import { getTranslations } from 'next-intl/server';

import { getAdminOrdersPage } from '@/db/queries/shop/admin-orders';
import {
Expand Down Expand Up @@ -47,6 +48,7 @@ export default async function AdminOrdersPage({

const { locale } = await params;
const sp = await searchParams;
const t = await getTranslations('shop.admin.orders');
const csrfToken = issueCsrfToken('admin:orders:reconcile-stale');

const page = parsePage(sp.page);
Expand Down Expand Up @@ -74,7 +76,7 @@ export default async function AdminOrdersPage({
itemCount: order.itemCount,
paymentProvider: order.paymentProvider ?? '-',
viewHref: `/shop/admin/orders/${order.id}`,
viewAriaLabel: `View order ${order.id}`,
viewAriaLabel: t('viewOrder', { id: order.id }),
};
});

Expand All @@ -91,7 +93,7 @@ export default async function AdminOrdersPage({
id="admin-orders-title"
className="text-2xl font-bold text-foreground"
>
Admin · Orders
{t('title')}
</h1>

<form action="/api/shop/admin/orders/reconcile-stale" method="post">
Expand All @@ -100,17 +102,17 @@ export default async function AdminOrdersPage({
type="submit"
className="inline-flex w-full items-center justify-center rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary sm:w-auto"
>
Reconcile stale
{t('reconcileStale')}
</button>
</form>
</header>

<section className="mt-6" aria-label="Orders list">
<section className="mt-6" aria-label={t('listCaption')}>
{/* Mobile cards */}
<div className="md:hidden">
{viewModels.length === 0 ? (
<div className="rounded-md border border-border p-4 text-sm text-muted-foreground">
No orders yet.
{t('empty')}
</div>
) : (
<ul className="space-y-3">
Expand Down Expand Up @@ -138,12 +140,12 @@ export default async function AdminOrdersPage({

<dl className="mt-3 grid grid-cols-2 gap-x-3 gap-y-2 text-xs">
<div>
<dt className="text-muted-foreground">Items</dt>
<dt className="text-muted-foreground">{t('table.items')}</dt>
<dd className="text-foreground">{vm.itemCount}</dd>
</div>

<div className="min-w-0">
<dt className="text-muted-foreground">Provider</dt>
<dt className="text-muted-foreground">{t('table.provider')}</dt>
<dd
className="truncate text-foreground"
title={vm.paymentProvider}
Expand All @@ -153,7 +155,7 @@ export default async function AdminOrdersPage({
</div>

<div className="col-span-2">
<dt className="text-muted-foreground">Order ID</dt>
<dt className="text-muted-foreground">{t('table.orderId')}</dt>
<dd
className="break-all font-mono text-[11px] text-muted-foreground"
title={vm.id}
Expand All @@ -169,7 +171,7 @@ export default async function AdminOrdersPage({
className="inline-flex items-center justify-center rounded-md border border-border px-2 py-1 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
aria-label={vm.viewAriaLabel}
>
View
{t('actions.view')}
</Link>
</div>
</li>
Expand All @@ -182,51 +184,51 @@ export default async function AdminOrdersPage({
<div className="hidden md:block">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-border text-sm">
<caption className="sr-only">Orders list</caption>
<caption className="sr-only">{t('listCaption')}</caption>

<thead className="bg-muted/50">
<tr>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Created
{t('table.created')}
</th>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Status
{t('table.status')}
</th>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Total
{t('table.total')}
</th>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Items
{t('table.items')}
</th>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Provider
{t('table.provider')}
</th>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Order ID
{t('table.orderId')}
</th>
<th
scope="col"
className="px-3 py-2 text-left font-semibold text-foreground"
>
Actions
{t('table.actions')}
</th>
</tr>
</thead>
Expand All @@ -238,7 +240,7 @@ export default async function AdminOrdersPage({
className="px-3 py-6 text-muted-foreground"
colSpan={7}
>
No orders yet.
{t('empty')}
</td>
</tr>
) : (
Expand Down Expand Up @@ -276,7 +278,7 @@ export default async function AdminOrdersPage({
className="rounded-md border border-border px-2 py-1 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
aria-label={vm.viewAriaLabel}
>
View
{t('actions.view')}
</Link>
</td>
</tr>
Expand Down
14 changes: 8 additions & 6 deletions frontend/app/[locale]/shop/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// frontend/app/[locale]/shop/admin/page.tsx
import { Link } from '@/i18n/routing';
import { getTranslations } from 'next-intl/server';

import { ShopAdminTopbar } from '@/components/shop/admin/shop-admin-topbar';
import { guardShopAdminPage } from '@/lib/auth/guard-shop-admin-page';
Expand All @@ -8,6 +9,7 @@ export const dynamic = 'force-dynamic';

export default async function ShopAdminHomePage() {
await guardShopAdminPage();
const t = await getTranslations('shop.admin.page');

return (
<>
Expand All @@ -22,10 +24,10 @@ export default async function ShopAdminHomePage() {
id="shop-admin-title"
className="text-2xl font-bold text-foreground"
>
Shop Admin
{t('title')}
</h1>
<p className="mt-2 text-sm text-muted-foreground">
Administrative tools for the merch shop.
{t('description')}
</p>
</header>

Expand All @@ -37,10 +39,10 @@ export default async function ShopAdminHomePage() {
className="block rounded-lg border border-border p-4 transition-colors hover:bg-muted/50"
>
<div className="text-base font-semibold text-foreground">
Products
{t('productsSection.title')}
</div>
<div className="mt-1 text-sm text-muted-foreground">
Create, edit, activate, feature.
{t('productsSection.description')}
</div>
</Link>
</li>
Expand All @@ -51,10 +53,10 @@ export default async function ShopAdminHomePage() {
className="block rounded-lg border border-border p-4 transition-colors hover:bg-muted/50"
>
<div className="text-base font-semibold text-foreground">
Orders
{t('ordersSection.title')}
</div>
<div className="mt-1 text-sm text-muted-foreground">
Review and manage orders.
{t('ordersSection.description')}
</div>
</Link>
</li>
Expand Down
Loading