Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 7 additions & 6 deletions app/[locale]/admin/sellers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { container } from '@/composition-root/container';
import { ListSellersUseCase } from '@/modules/sellers/application/use-cases/list-sellers-use-case';
import { listSellersQuerySchema } from '@/modules/sellers/presentation/schemas/seller-schemas';
Expand Down Expand Up @@ -123,18 +124,18 @@ export default async function AdminSellersPage({
header: dict.admin.actions,
render: (seller) => (
<div className={styles.actionsCell}>
<a
<Link
href={`/${locale}/admin/sellers/${seller.sellerId.value}/products`}
className={styles.viewProducts}
>
{dict.admin.viewProducts}
</a>
<a
</Link>
<Link
href={`/${locale}/admin/sellers/${seller.sellerId.value}`}
className={styles.editLink}
>
{dict.admin.edit}
</a>
</Link>
<SellerActions
sellerId={seller.sellerId.value}
currentStatus={seller.status}
Expand All @@ -153,12 +154,12 @@ export default async function AdminSellersPage({
<div className={styles.header}>
<h2 className={styles.title}>{dict.admin.sellersTitle}</h2>
<div className={styles.headerActions}>
<a
<Link
href={`/${locale}/admin/sellers/create`}
className={styles.createButton}
>
+ {dict.admin.createSeller}
</a>
</Link>
<SearchForm
placeholder={dict.admin.searchSellersPlaceholder}
ariaLabel={dict.admin.searchSellers}
Expand Down
42 changes: 28 additions & 14 deletions app/[locale]/auth/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import { useState } from 'react';
import { useRouter, useParams } from 'next/navigation';
import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { TextField } from '@/shared/ui/text-field';
import { Button } from '@/shared/ui/button';
import { EyeToggleWrapper } from '@/shared/ui/eye-toggle-wrapper';
import { AuthCard } from '@/shared/ui/auth-card';
import { ErrorMessage } from '@/shared/ui/error-message';
import { useDictionary } from '@/shared/i18n/dictionary-context';
import styles from './page.module.css';

Expand All @@ -16,6 +18,7 @@ export default function SignInPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const dict = useDictionary();

const redirectByRole = (role: string) => {
Expand All @@ -30,26 +33,37 @@ export default function SignInPage() {

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
const result = await signIn('credentials', {
email,
password,
redirect: false,
});
try {
const result = await signIn('credentials', {
email,
password,
redirect: false,
});

if (result?.ok) {
const res = await fetch('/api/auth/session');
const session = await res.json();
const role = session?.user?.role ?? 'CUSTOMER';
redirectByRole(role);
}
if (result?.error) {
setError(dict.auth.invalidCredentials);
return;
}

setLoading(false);
if (result?.ok) {
const res = await fetch('/api/auth/session');
const session = await res.json();
const role = session?.user?.role ?? 'CUSTOMER';
redirectByRole(role);
}
} catch {
setError(dict.auth.invalidCredentials);
} finally {
setLoading(false);
}
};

return (
<AuthCard className={styles.card}>
<h2>{dict.auth.signInTitle}</h2>
{error && <ErrorMessage message={error} />}

<button
type="button"
Expand Down Expand Up @@ -98,9 +112,9 @@ export default function SignInPage() {

<p className={styles.footer}>
{dict.auth.dontHaveAccount}{' '}
<a href={`/${locale}/auth/signup`} className={styles.footerLink}>
<Link href={`/${locale}/auth/signup`} className={styles.footerLink}>
{dict.auth.signUpButton}
</a>
</Link>
</p>
</AuthCard>
);
Expand Down
5 changes: 3 additions & 2 deletions app/[locale]/auth/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useState } from 'react';
import { useRouter, useParams } from 'next/navigation';
import Link from 'next/link';
import { signIn, useSession } from 'next-auth/react';
import type { ZodIssue } from 'zod';
import { TextField } from '@/shared/ui/text-field';
Expand Down Expand Up @@ -287,9 +288,9 @@ export default function SignUpPage() {
</form>
<p className={styles.footer}>
{dict.auth.alreadyHaveAccount}{' '}
<a href={`/${locale}/auth/signin`} className={styles.footerLink}>
<Link href={`/${locale}/auth/signin`} className={styles.footerLink}>
{dict.auth.loginButton}
</a>
</Link>
</p>
</AuthCard>
);
Expand Down
5 changes: 3 additions & 2 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getServerSession } from 'next-auth';
import type { Metadata, Viewport } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import { authOptions } from '@/shared/infrastructure/auth-options';
import { prisma } from '@/shared/infrastructure/prisma';
import LanguageSelector from '@/shared/layout/language-selector';
Expand Down Expand Up @@ -131,7 +132,7 @@ export default async function RootLayout({
)}
<header className={styles.header}>
<div className={styles.spacer} />
<a href={`/${locale}`} className={styles.logo}>
<Link href={`/${locale}`} className={styles.logo}>
<Image
src="/img/logo/logo.svg"
alt="Siete 28 Logo"
Expand All @@ -140,7 +141,7 @@ export default async function RootLayout({
className={styles.logoImg}
loading="eager"
/>
</a>
</Link>
<div className={styles.userIcons}>
<HeaderNav
loginLabel={dict.common.login}
Expand Down
5 changes: 3 additions & 2 deletions app/[locale]/orders/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getServerSession } from 'next-auth';
import { authOptions } from '@/shared/infrastructure/auth-options';
import { container } from '@/composition-root/container';
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { orderListQuerySchema } from '@/modules/orders/presentation/schemas/order-schemas';
import { ListCustomerOrdersUseCase } from '@/modules/orders/application/list-customer-orders-use-case';
import { getDictionary } from '@/shared/i18n/get-dictionary';
Expand Down Expand Up @@ -77,12 +78,12 @@ export default async function CustomerOrdersPage({
header: dict.orders?.actions ?? 'Actions',
render: (order) => (
<div className={styles.actionCell}>
<a
<Link
href={`/${locale}/orders/${order.id}`}
className={styles.viewButton}
>
{dict.orders?.viewOrder ?? 'View order'}
</a>
</Link>
{order.checkoutGroupId &&
order.checkoutGroupPaymentStatus === 'failed' && (
<form
Expand Down
6 changes: 5 additions & 1 deletion app/[locale]/products/[id]/customization-experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,16 @@ function CustomizationExperienceInner({
publicUrl: string;
};

await fetch(result.uploadUrl, {
const uploadResponse = await fetch(result.uploadUrl, {
method: 'PUT',
headers: { 'content-type': file.type },
body: file,
});

if (!uploadResponse.ok) {
throw new Error('File storage failed');
}

const imageUrl = toAbsoluteUrl(result.publicUrl);
setImage({ imageUploadId: result.id, imageUrl });

Expand Down
71 changes: 71 additions & 0 deletions app/[locale]/products/[id]/error.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.container {
display: grid;
min-height: min(58vh, 42rem);
place-items: center;
padding: 3rem 1.5rem;
background:
radial-gradient(
circle at 12% 18%,
rgba(213, 92, 92, 0.12),
transparent 24rem
),
linear-gradient(
135deg,
rgba(247, 241, 232, 0.72),
rgba(255, 255, 255, 0.94)
);
}

.card {
width: min(100%, 38rem);
border: 1px solid rgba(13, 92, 70, 0.16);
border-top: 5px solid var(--color-coral);
border-radius: var(--radius-md);
padding: clamp(2rem, 7vw, 4.5rem);
background: var(--color-white);
box-shadow: 0 1.5rem 4rem rgba(8, 20, 18, 0.1);
}

.eyebrow {
color: var(--color-coral);
font-size: 0.78rem;
font-weight: var(--font-weight-bold);
letter-spacing: 0.1em;
text-transform: uppercase;
}

.card h1 {
margin: 0.75rem 0;
color: var(--color-black);
font-size: clamp(2rem, 5vw, 3.25rem);
line-height: 1;
}

.card p {
max-width: 31rem;
margin: 0;
color: var(--color-text-secondary);
font-size: 1.05rem;
line-height: 1.6;
}

.retry {
margin-top: 2rem;
border: 0;
border-radius: 999px;
padding: 0.8rem 1.3rem;
background: var(--color-primary);
color: var(--color-white);
cursor: pointer;
font: inherit;
font-weight: var(--font-weight-bold);
}

.retry:hover {
background: #08473f;
}

.retry:focus-visible {
outline: 3px solid rgba(213, 92, 92, 0.48);
outline-offset: 3px;
}
29 changes: 29 additions & 0 deletions app/[locale]/products/[id]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import styles from './error.module.css';
import { useDictionary } from '@/shared/i18n/dictionary-context';

export default function ProductDetailError({
error: _error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
const dict = useDictionary();

return (
<main className={styles.container}>
<section className={styles.card} aria-labelledby="product-error-title">
<span className={styles.eyebrow}>
{dict.common.unexpectedErrorStatus}
</span>
<h1 id="product-error-title">{dict.common.unexpectedErrorTitle}</h1>
<p>{dict.common.unexpectedErrorDescription}</p>
<button type="button" className={styles.retry} onClick={reset}>
{dict.common.retry}
</button>
</section>
</main>
);
}
11 changes: 7 additions & 4 deletions app/[locale]/products/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { APP_BASE_URL } from '@/shared/kernel/config';
import { BackLink } from '@/shared/ui/back-link';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { NotFoundError } from '@/shared/kernel/app-error';
import {
CustomizationExperience,
type CustomizationExperienceLabels,
Expand All @@ -34,8 +35,9 @@ export async function generateMetadata({

try {
product = await getPublicProduct(id, locale);
} catch {
notFound();
} catch (error) {
if (error instanceof NotFoundError) notFound();
throw error;
}

const canonical = `${APP_BASE_URL}/${locale}/products/${id}`;
Expand Down Expand Up @@ -89,8 +91,9 @@ export default async function ProductDetailPage({

try {
product = await getPublicProduct(id, locale);
} catch {
notFound();
} catch (error) {
if (error instanceof NotFoundError) notFound();
throw error;
}

const viewerContext = await resolveProductViewerContext(
Expand Down
Loading
Loading