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
114 changes: 114 additions & 0 deletions app/[locale]/admin/sellers/[sellerId]/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
.container {
max-width: 760px;
margin: 0 auto;
padding: var(--spacing-xl);
}

.header {
margin-bottom: var(--spacing-lg);
}

.backLink {
display: inline-flex;
margin-bottom: var(--spacing-sm);
color: var(--color-green-dark);
font-weight: var(--font-weight-medium);
}

.backLink:hover {
Comment on lines +11 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Este módulo no pasa las reglas de Stylelint configuradas.

Ahora mismo hay errores por selector-class-pattern (selectores en camelCase) y alpha-value-notation (0.08/0.25), así que el lint seguirá fallando hasta normalizar esos selectores y valores alpha.

Also applies to: 41-41, 44-49, 57-57, 69-69, 72-72, 104-111

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 11-11: Expected class selector ".backLink" to be kebab-case (selector-class-pattern)

(selector-class-pattern)


[error] 18-18: Expected class selector ".backLink" to be kebab-case (selector-class-pattern)

(selector-class-pattern)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`[locale]/admin/sellers/[sellerId]/page.module.css around lines 11 - 18,
This stylesheet is failing Stylelint because several class selectors still use
camelCase and some alpha colors use decimal notation. Update the affected
selectors in page.module.css (including backLink and the other class names
flagged in this module) to the configured kebab-case pattern, and change all
alpha values like 0.08 and 0.25 to the required percentage notation. Make the
edits across the same CSS module so the lint rules pass consistently.

Source: Linters/SAST tools

text-decoration: underline;
}

.kicker {
margin: 0 0 var(--spacing-xs);
color: var(--color-lila);
font-size: 0.9rem;
}

.title {
margin: 0;
font-size: 1.75rem;
color: var(--color-green-dark);
}

.summary {
display: grid;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
padding: var(--spacing-lg);
border-radius: 12px;
background: var(--color-white);
box-shadow: 0 1px 4px rgb(0 0 0 / 0.08);
}

.summaryRow {
display: grid;
gap: var(--spacing-xs);
}

.summaryLabel {
font-size: 0.8rem;
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--color-lila);
}

.summaryValue {
margin: 0;
font-size: 1rem;
color: #222;
}

.form {
display: grid;
gap: var(--spacing-md);
padding: var(--spacing-lg);
border-radius: 12px;
background: var(--color-white);
box-shadow: 0 1px 4px rgb(0 0 0 / 0.08);
}

.sectionTitle {
margin: 0;
font-size: 1.2rem;
color: var(--color-green-dark);
}

.field {
display: grid;
gap: var(--spacing-xs);
}

.label {
font-size: 0.9rem;
font-weight: var(--font-weight-medium);
color: #222;
}

.input,
.textarea {
width: 100%;
padding: 0.8rem 0.95rem;
border: 1px solid var(--color-lila);
border-radius: 8px;
background: var(--color-white);
color: #222;
}

.textarea {
resize: vertical;
min-height: 120px;
}

.successMessage {
padding: 0.75rem 1rem;
border-radius: 8px;
background: rgb(193 224 140 / 0.25);
color: var(--color-green-dark);
}

.formActions {
display: flex;
justify-content: flex-start;
}
80 changes: 80 additions & 0 deletions app/[locale]/admin/sellers/[sellerId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { redirect, notFound } from 'next/navigation';
import Link from 'next/link';
import { container } from '@/composition-root/container';
import { assertRole } from '@/shared/authorization/authorization';
import { getDictionary } from '@/shared/i18n/get-dictionary';
import { NotFoundError } from '@/shared/kernel/app-error';
import { GetSellerUseCase } from '@/modules/sellers/application/use-cases/get-seller-use-case';
import styles from './page.module.css';
import { SellerDetailForm } from './seller-detail-form';

export default async function AdminSellerDetailPage({
params,
}: {
params: Promise<{ locale: string; sellerId: string }>;
}) {
const { locale, sellerId } = await params;

try {
await assertRole('ADMIN');
} catch {
redirect(`/${locale}`);
}

const dict = await getDictionary(locale as 'es' | 'cat');
const sellerRepository = container.getSellerRepository();
const getSeller = new GetSellerUseCase(sellerRepository);

let seller;
try {
seller = await getSeller.execute({ sellerId });
} catch (error) {
if (error instanceof NotFoundError) {
notFound();
}

throw error;
}

return (
<div className={styles.container}>
<header className={styles.header}>
<div>
<Link href={`/${locale}/admin/sellers`} className={styles.backLink}>
{dict.admin.backToSellers}
</Link>
<h2 className={styles.title}>{dict.admin.sellerDetail.editTitle}</h2>
</div>
</header>

<section
className={styles.summary}
aria-label={dict.admin.sellerDetail.editTitle}
>
<div className={styles.summaryRow}>
<span className={styles.summaryLabel}>
{dict.admin.sellerDetail.nameLabel}
</span>
<strong className={styles.summaryValue}>{seller.name}</strong>
</div>
<div className={styles.summaryRow}>
<span className={styles.summaryLabel}>
{dict.admin.sellerDetail.descriptionLabel}
</span>
<p className={styles.summaryValue}>{seller.description ?? '—'}</p>
</div>
</section>

<SellerDetailForm
sellerId={seller.sellerId.value}
nameLabel={dict.admin.sellerDetail.nameLabel}
descriptionLabel={dict.admin.sellerDetail.descriptionLabel}
saveLabel={dict.admin.sellerDetail.save}
savedLabel={dict.admin.sellerDetail.saved}
errorLabel={dict.admin.sellerDetail.error}
initialName={seller.name}
initialDescription={seller.description ?? ''}
/>
</div>
);
}
28 changes: 28 additions & 0 deletions app/[locale]/admin/sellers/[sellerId]/products/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,34 @@
pointer-events: none;
}

.statusBadge {
display: inline-block;
padding: 0.2rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: var(--font-weight-medium);
}

.statusBadge[data-status='active'] {
background: var(--color-green-light);
color: var(--color-green-dark);
}

.statusBadge[data-status='draft'] {
background: var(--color-lila);
color: var(--color-white);
}

.statusBadge[data-status='archived'] {
background: var(--color-coral);
color: var(--color-white);
}

.statusBadge[data-status='eliminated'] {
background: var(--color-gray);
color: var(--color-white);
}
Comment on lines +174 to +200

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Renombra .statusBadge para no romper el lint.

La regla selector-class-pattern exige kebab-case y este selector falla en las cinco apariciones del bloque. Si no se renombra aquí y en el uso de la página, este cambio seguirá rompiendo CI.

💡 Cambio propuesto
-.statusBadge {
+.status-badge {
   display: inline-block;
   padding: 0.2rem 0.6rem;
   border-radius: 4px;
   font-size: 0.8rem;
   font-weight: var(--font-weight-medium);
 }

-.statusBadge[data-status='active'] {
+.status-badge[data-status='active'] {
   background: var(--color-green-light);
   color: var(--color-green-dark);
 }

-.statusBadge[data-status='draft'] {
+.status-badge[data-status='draft'] {
   background: var(--color-lila);
   color: var(--color-white);
 }

-.statusBadge[data-status='archived'] {
+.status-badge[data-status='archived'] {
   background: var(--color-coral);
   color: var(--color-white);
 }

-.statusBadge[data-status='eliminated'] {
+.status-badge[data-status='eliminated'] {
   background: var(--color-gray);
   color: var(--color-white);
 }
<span className={styles['status-badge']} data-status={product.status.toLowerCase()}>
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 174-174: Expected class selector ".statusBadge" to be kebab-case (selector-class-pattern)

(selector-class-pattern)


[error] 182-182: Expected class selector ".statusBadge" to be kebab-case (selector-class-pattern)

(selector-class-pattern)


[error] 187-187: Expected class selector ".statusBadge" to be kebab-case (selector-class-pattern)

(selector-class-pattern)


[error] 192-192: Expected class selector ".statusBadge" to be kebab-case (selector-class-pattern)

(selector-class-pattern)


[error] 197-197: Expected class selector ".statusBadge" to be kebab-case (selector-class-pattern)

(selector-class-pattern)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`[locale]/admin/sellers/[sellerId]/products/page.module.css around lines
174 - 200, Rename the CSS module selector .statusBadge to kebab-case to satisfy
selector-class-pattern, and update every reference in the products page to match
the new class name. Use the existing status badge styling block in
page.module.css and the class usage in the page component (the span rendering
product status) to keep the class name consistent and prevent lint/CI failures.

Source: Linters/SAST tools


@media (max-width: 720px) {
.header {
flex-direction: column;
Expand Down
112 changes: 65 additions & 47 deletions app/[locale]/admin/sellers/[sellerId]/products/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getDictionary } from '@/shared/i18n/get-dictionary';
import { assertRole } from '@/shared/authorization/authorization';
import { LocalizedDate } from '@/shared/kernel/domain/value-objects/localized-date';
import { PaginationDefaults } from '@/shared/kernel/domain/value-objects/pagination';
import { ProductActions } from '@/app/[locale]/seller/products/product-actions';
import styles from './page.module.css';

function buildPageUrl(
Expand Down Expand Up @@ -69,9 +70,24 @@ export default async function AdminSellerProductsPage({
const productRepository = container.getProductRepository();
const useCase = new ProductListQueryUseCase(productRepository);
const result = await useCase.execute(filter);
const { items: products, totalPages, total } = result;
const { items: products, totalPages } = result;
let page = result.page;

function getStatusLabel(status: string): string {
switch (status) {
case 'DRAFT':
return dict.admin.status_draft;
case 'ACTIVE':
return dict.admin.status_active;
case 'ARCHIVED':
return dict.admin.status_archived;
case 'ELIMINATED':
return dict.admin.status_eliminated;
default:
return status;
}
}

if (totalPages > 0 && page > totalPages) {
page = totalPages;
}
Expand Down Expand Up @@ -113,16 +129,6 @@ export default async function AdminSellerProductsPage({

{hasProducts ? (
<>
<div className={styles.summary} aria-live="polite">
<span>
{dict.admin.pageXofY
.replace('{current}', String(page))
.replace('{total}', String(totalPages))}
</span>
<span>
{dict.admin.productCount.replace('{total}', String(total))}
</span>
</div>
<div className={styles.tableWrap}>
<table className={styles.table}>
<thead>
Expand All @@ -131,6 +137,7 @@ export default async function AdminSellerProductsPage({
<th>{dict.admin.productStatus}</th>
<th>{dict.admin.productPrice}</th>
<th>{dict.admin.productUpdated}</th>
<th>{dict.admin.actions}</th>
</tr>
</thead>
<tbody>
Expand All @@ -141,54 +148,65 @@ export default async function AdminSellerProductsPage({
(translation) => translation.locale === locale,
)?.name ?? dict.admin.untranslatedProduct}
</td>
<td>{product.status}</td>
<td>
<span
className={styles.statusBadge}
data-status={product.status.toLowerCase()}
>
{getStatusLabel(product.status)}
</span>
</td>
<td>{product.basePrice.format()}</td>
<td>
{LocalizedDate.create(
product.updatedAt,
locale,
).toString()}
</td>
<td>
<ProductActions
productId={product.id}
currentStatus={product.status}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
{totalPages > 1 ? (
<nav
className={styles.pagination}
aria-label={dict.admin.paginationAriaLabel}
>
{page > 1 ? (
<Link
href={buildPageUrl(locale, sellerId, filter, page - 1)}
className={styles.pageButton}
>
{dict.admin.pagePrev}
</Link>
) : (
<span
className={`${styles.pageButton} ${styles.pageButtonDisabled}`}
>
{dict.admin.pagePrev}
</span>
)}
{page < totalPages ? (
<Link
href={buildPageUrl(locale, sellerId, filter, page + 1)}
className={styles.pageButton}
>
{dict.admin.pageNext}
</Link>
) : (
<span
className={`${styles.pageButton} ${styles.pageButtonDisabled}`}
>
{dict.admin.pageNext}
</span>
)}
</nav>
) : null}
<nav
className={styles.pagination}
aria-label={dict.admin.paginationAriaLabel}
>
{page > 1 ? (
<Link
href={buildPageUrl(locale, sellerId, filter, page - 1)}
className={styles.pageButton}
>
{dict.admin.pagePrev}
</Link>
) : (
<span
className={`${styles.pageButton} ${styles.pageButtonDisabled}`}
>
{dict.admin.pagePrev}
</span>
)}
{page < totalPages ? (
<Link
href={buildPageUrl(locale, sellerId, filter, page + 1)}
className={styles.pageButton}
>
{dict.admin.pageNext}
</Link>
) : (
<span
className={`${styles.pageButton} ${styles.pageButtonDisabled}`}
>
{dict.admin.pageNext}
</span>
)}
</nav>
</>
) : (
<p className={styles.noProducts}>{dict.admin.noProducts}</p>
Expand Down
Loading
Loading