8aab968e09
Build & Deploy / Build & Push Docker Image (push) Successful in 1m2s
Test / Type Check (all packages) (push) Failing after 28s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Storefront Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
Build & Deploy / Deploy to VPS (push) Successful in 3s
Comprehensive rename of all marketplace references to storefront: - API module: apps/api/src/modules/marketplace/ → storefront/ - Components: MarketplaceHeader → StorefrontHeader, MarketplaceShell → StorefrontShell, MarketplaceFooter → StorefrontFooter - Types: marketplace-homepage.ts → storefront-homepage.ts - Test files: employee-marketplace-* → employee-storefront-* - All source code identifiers, imports, route paths, and strings - Documentation (docs/), CI config (.gitlab-ci.yml), scripts - Dashboard, admin, storefront workspace references - Prisma field names preserved (isListedOnMarketplace, marketplaceRating, marketplaceFunnelEvent) as they map to database schema Validation: - API type-check: 0 errors - Storefront type-check: 0 errors - Dashboard type-check: 0 errors - Full monorepo type-check: only pre-existing admin TS18046
160 lines
8.0 KiB
TypeScript
160 lines
8.0 KiB
TypeScript
import Link from 'next/link'
|
|
import { storefrontFetchOrDefault } from '@/lib/api'
|
|
import { getStorefrontLanguage } from '@/lib/i18n.server'
|
|
import { formatCurrency } from '@rentaldrivego/types'
|
|
|
|
interface CompanyProfile {
|
|
id: string
|
|
name: string
|
|
slug: string
|
|
brand: {
|
|
displayName: string
|
|
logoUrl: string | null
|
|
publicCity: string | null
|
|
publicCountry: string | null
|
|
publicPhone: string | null
|
|
whatsappNumber: string | null
|
|
marketplaceRating: number | null
|
|
} | null
|
|
vehicles: Array<{ id: string; make: string; model: string; dailyRate: number; photos: string[]; category: string; availability: boolean; nextAvailableAt: string | null }>
|
|
offers: Array<{ id: string; title: string; discountValue: number; validUntil: string }>
|
|
}
|
|
|
|
export default async function CompanyProfilePage({ params }: { params: { slug: string } }) {
|
|
const language = await getStorefrontLanguage()
|
|
const dict = {
|
|
en: {
|
|
unavailable: 'Storefront unavailable',
|
|
unavailableTitle: 'Company details are temporarily unavailable.',
|
|
unavailableBody: 'The storefront API is running, but the database is not reachable in this local environment.',
|
|
backToExplore: 'Back to explore',
|
|
partner: 'Storefront partner',
|
|
vehicles: 'vehicles',
|
|
offers: 'active offers',
|
|
rating: 'Rating',
|
|
new: 'New',
|
|
currentOffers: 'Current offers',
|
|
validUntil: 'Valid until',
|
|
noOffers: 'No active offers right now.',
|
|
fleet: 'Fleet',
|
|
viewVehicle: 'View vehicle',
|
|
availableFrom: 'Available from',
|
|
unavailableNoDate: 'Temporarily unavailable for new reservations.',
|
|
},
|
|
fr: {
|
|
unavailable: 'Storefront indisponible',
|
|
unavailableTitle: "Les détails de l'entreprise sont temporairement indisponibles.",
|
|
unavailableBody: "L'API storefront fonctionne, mais la base de données n'est pas accessible dans cet environnement local.",
|
|
backToExplore: "Retour à l'exploration",
|
|
partner: 'Partenaire storefront',
|
|
vehicles: 'véhicules',
|
|
offers: 'offres actives',
|
|
rating: 'Note',
|
|
new: 'Nouveau',
|
|
currentOffers: 'Offres actuelles',
|
|
validUntil: "Valable jusqu'au",
|
|
noOffers: 'Aucune offre active pour le moment.',
|
|
fleet: 'Flotte',
|
|
viewVehicle: 'Voir le véhicule',
|
|
availableFrom: 'Disponible à partir du',
|
|
unavailableNoDate: 'Temporairement indisponible pour les nouvelles réservations.',
|
|
},
|
|
ar: {
|
|
unavailable: 'السوق غير متاح',
|
|
unavailableTitle: 'تفاصيل الشركة غير متاحة مؤقتاً.',
|
|
unavailableBody: 'واجهة السوق تعمل، لكن قاعدة البيانات غير متاحة في هذه البيئة المحلية.',
|
|
backToExplore: 'العودة إلى الاستكشاف',
|
|
partner: 'شريك في السوق',
|
|
vehicles: 'مركبات',
|
|
offers: 'عروض نشطة',
|
|
rating: 'التقييم',
|
|
new: 'جديد',
|
|
currentOffers: 'العروض الحالية',
|
|
validUntil: 'صالح حتى',
|
|
noOffers: 'لا توجد عروض نشطة حالياً.',
|
|
fleet: 'الأسطول',
|
|
viewVehicle: 'عرض السيارة',
|
|
availableFrom: 'متاح ابتداءً من',
|
|
unavailableNoDate: 'غير متاحة مؤقتاً للحجوزات الجديدة.',
|
|
},
|
|
}[language]
|
|
const company = await storefrontFetchOrDefault<CompanyProfile | null>(`/storefront/${params.slug}`, null)
|
|
|
|
if (!company) {
|
|
return (
|
|
<main className="site-page">
|
|
<div className="site-section">
|
|
<section className="card p-8">
|
|
<p className="text-sm uppercase tracking-[0.18em] text-orange-700 dark:text-orange-400">{dict.unavailable}</p>
|
|
<h1 className="mt-3 text-3xl font-black text-stone-900 dark:text-stone-100">{dict.unavailableTitle}</h1>
|
|
<p className="mt-3 text-stone-600 dark:text-stone-400">{dict.unavailableBody}</p>
|
|
<div className="mt-6">
|
|
<Link href="/explore" className="rounded-full bg-blue-900 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-orange-700 dark:bg-orange-500 dark:text-white dark:hover:bg-orange-400">{dict.backToExplore}</Link>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<main className="site-page">
|
|
<div className="site-section space-y-8">
|
|
<section className="card p-8">
|
|
<p className="text-sm uppercase tracking-[0.18em] text-orange-700 dark:text-orange-400">{company.brand?.publicCity ?? dict.partner}</p>
|
|
<h1 className="mt-3 text-4xl font-black text-stone-900 dark:text-stone-100">{company.brand?.displayName ?? company.name}</h1>
|
|
<p className="mt-3 text-stone-600 dark:text-stone-400">{company.brand?.publicCountry ?? ''}</p>
|
|
<div className="mt-6 flex flex-wrap gap-3 text-sm text-stone-500 dark:text-stone-400">
|
|
<span>{company.vehicles.length} {dict.vehicles}</span>
|
|
<span>{company.offers.length} {dict.offers}</span>
|
|
<span>{dict.rating} {company.brand?.marketplaceRating?.toFixed(1) ?? dict.new}</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.currentOffers}</h2>
|
|
<div className="mt-4 grid gap-4 md:grid-cols-2">
|
|
{company.offers.map((offer) => (
|
|
<div key={offer.id} className="card p-5">
|
|
<p className="text-sm font-semibold text-stone-900 dark:text-stone-100">{offer.title}</p>
|
|
<p className="mt-2 text-3xl font-black text-orange-700 dark:text-orange-400">{offer.discountValue}%</p>
|
|
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">{dict.validUntil} {new Date(offer.validUntil).toLocaleDateString()}</p>
|
|
</div>
|
|
))}
|
|
{company.offers.length === 0 && <div className="card p-6 text-sm text-stone-500 dark:text-stone-400">{dict.noOffers}</div>}
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2 className="text-2xl font-bold text-stone-900 dark:text-stone-100">{dict.fleet}</h2>
|
|
<div className="mt-4 grid gap-5 md:grid-cols-2 xl:grid-cols-3">
|
|
{company.vehicles.map((vehicle) => (
|
|
<article key={vehicle.id} className="card">
|
|
<div className="aspect-[16/10] overflow-hidden rounded-t-[2rem] bg-stone-100 dark:bg-blue-900/30">
|
|
{vehicle.photos[0] ? (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img src={vehicle.photos[0]} alt={`${vehicle.make} ${vehicle.model}`} className="h-full w-full object-cover" />
|
|
) : null}
|
|
</div>
|
|
<div className="p-4">
|
|
<h3 className="truncate text-lg font-bold text-stone-900 dark:text-stone-100">{vehicle.make} {vehicle.model}</h3>
|
|
<p className="mt-1 truncate text-sm text-stone-500 dark:text-stone-400">{vehicle.category}</p>
|
|
{!vehicle.availability && (
|
|
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
|
{vehicle.nextAvailableAt ? `${dict.availableFrom} ${new Date(vehicle.nextAvailableAt).toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' })}` : dict.unavailableNoDate}
|
|
</p>
|
|
)}
|
|
<div className="mt-4 flex items-end justify-between gap-3">
|
|
<p className="min-w-0 truncate text-xl font-black text-stone-900 dark:text-stone-100">{formatCurrency(vehicle.dailyRate, 'MAD', language)}</p>
|
|
<Link href={`/explore/${params.slug}/vehicles/${vehicle.id}`} className="shrink-0 rounded-full bg-blue-900 px-4 py-2 text-sm font-semibold text-white transition hover:bg-orange-700 dark:bg-orange-500 dark:text-white dark:hover:bg-orange-400">{dict.viewVehicle}</Link>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|