Skip to content
Open
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
34 changes: 33 additions & 1 deletion messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@
"showPassword": "Passwort anzeigen",
"hidePassword": "Passwort verbergen",
"forgotPassword": "Passwort vergessen?",
"policyConsentRequired": "Sie müssen den Geschäftsrichtlinien zustimmen, um ein Konto zu erstellen"
"policyConsentRequired": "Sie müssen den Geschäftsrichtlinien zustimmen, um ein Konto zu erstellen",
"companies": "Unternehmen"
},
"register": {
"createAccount": "Konto erstellen",
Expand Down Expand Up @@ -628,5 +629,36 @@
"signInForPricing": "Zur Preisanzeige anmelden",
"signInToOrder": "Zum Bestellen anmelden"
}
},
"companies": {
"title": "Unternehmen",
"subtitle": "Die Organisationen, für die Sie einkaufen können.",
"empty": "Sie sind noch in keinem Unternehmen Mitglied.",
"members": "Mitglieder",
"addMember": "Hinzufügen",
"addMemberPlaceholder": "kollege@beispiel.de",
"addMemberFailed": "Diese Person konnte nicht hinzugefügt werden.",
"removeMember": "Mitglied entfernen",
"invitationPending": "Einladung gesendet",
"revoke": "Zurückziehen",
"addresses": "Adressbuch",
"addressesHelp": "Wohin diese Organisation beliefert wird. Von einer übergeordneten Einheit geerbte Standorte werden ebenfalls angezeigt.",
"viewOrders": "Unternehmensbestellungen ansehen",
"ordersTitle": "Unternehmensbestellungen",
"invitationTitle": "{company} beitreten",
"invitationBody": "Sie wurden eingeladen, bei {store} als {email} einzukaufen.",
"invitationMissingToken": "Diesem Link fehlt der Einladungscode.",
"invitationInvalidTitle": "Diese Einladung ist nicht mehr gültig",
"invitationInvalidBody": "Sie wurde möglicherweise bereits angenommen, zurückgezogen oder ist abgelaufen. Bitten Sie um eine neue Einladung.",
"invitationWrongAccount": "Diese Einladung ging an {invited}, Sie sind aber als {current} angemeldet. Melden Sie sich mit der eingeladenen Adresse an.",
"invitationAcceptFailed": "Die Einladung konnte nicht angenommen werden.",
"acceptAsSignedIn": "Einladung annehmen",
"acceptAndRegister": "Konto erstellen und beitreten",
"accepting": "Wird angenommen…",
"firstName": "Vorname",
"lastName": "Nachname",
"password": "Passwort",
"buyingFor": "Einkauf für",
"buyPersonally": "Privat einkaufen"
}
}
34 changes: 33 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@
"showPassword": "Show password",
"hidePassword": "Hide password",
"forgotPassword": "Forgot password?",
"policyConsentRequired": "You must agree to the store policies to create an account"
"policyConsentRequired": "You must agree to the store policies to create an account",
"companies": "Companies"
},
"register": {
"createAccount": "Create Account",
Expand Down Expand Up @@ -628,5 +629,36 @@
"signInForPricing": "Sign in for pricing",
"signInToOrder": "Sign in to order"
}
},
"companies": {
"title": "Companies",
"subtitle": "The organizations you can buy for.",
"empty": "You are not a member of any company yet.",
"members": "Members",
"addMember": "Add",
"addMemberPlaceholder": "colleague@example.com",
"addMemberFailed": "Could not add that person.",
"removeMember": "Remove member",
"invitationPending": "Invitation sent",
"revoke": "Revoke",
"addresses": "Address book",
"addressesHelp": "Where this organization takes delivery. Sites inherited from a parent are shown too.",
"viewOrders": "View company orders",
"ordersTitle": "Company orders",
"invitationTitle": "Join {company}",
"invitationBody": "You have been invited to buy on {store} as {email}.",
"invitationMissingToken": "This link is missing its invitation code.",
"invitationInvalidTitle": "This invitation is no longer valid",
"invitationInvalidBody": "It may have been accepted already, revoked, or simply expired. Ask whoever invited you to send a new one.",
"invitationWrongAccount": "This invitation was sent to {invited}, but you are signed in as {current}. Sign in as the invited address to accept it.",
"invitationAcceptFailed": "Could not accept the invitation.",
"acceptAsSignedIn": "Accept invitation",
"acceptAndRegister": "Create account and join",
"accepting": "Accepting…",
"firstName": "First name",
"lastName": "Last name",
"password": "Password",
"buyingFor": "Buying for",
"buyPersonally": "Buy personally"
}
}
34 changes: 33 additions & 1 deletion messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@
"showPassword": "Mostrar contrasena",
"hidePassword": "Ocultar contrasena",
"forgotPassword": "¿Olvidaste tu contrasena?",
"policyConsentRequired": "Debes aceptar las politicas de la tienda para crear una cuenta"
"policyConsentRequired": "Debes aceptar las politicas de la tienda para crear una cuenta",
"companies": "Empresas"
},
"register": {
"createAccount": "Crear cuenta",
Expand Down Expand Up @@ -628,5 +629,36 @@
"signInForPricing": "Inicia sesión para ver precios",
"signInToOrder": "Inicia sesión para pedir"
}
},
"companies": {
"title": "Empresas",
"subtitle": "Las organizaciones para las que puede comprar.",
"empty": "Todavía no es miembro de ninguna empresa.",
"members": "Miembros",
"addMember": "Añadir",
"addMemberPlaceholder": "colega@ejemplo.es",
"addMemberFailed": "No se pudo añadir a esa persona.",
"removeMember": "Eliminar miembro",
"invitationPending": "Invitación enviada",
"revoke": "Revocar",
"addresses": "Libreta de direcciones",
"addressesHelp": "Dónde recibe esta organización sus entregas. También se muestran las sedes heredadas de una entidad superior.",
"viewOrders": "Ver pedidos de la empresa",
"ordersTitle": "Pedidos de la empresa",
"invitationTitle": "Unirse a {company}",
"invitationBody": "Le han invitado a comprar en {store} como {email}.",
"invitationMissingToken": "A este enlace le falta el código de invitación.",
"invitationInvalidTitle": "Esta invitación ya no es válida",
"invitationInvalidBody": "Puede que ya se haya aceptado, se haya revocado o haya caducado. Pida una nueva.",
"invitationWrongAccount": "Esta invitación se envió a {invited}, pero ha iniciado sesión como {current}. Inicie sesión con la dirección invitada.",
"invitationAcceptFailed": "No se pudo aceptar la invitación.",
"acceptAsSignedIn": "Aceptar invitación",
"acceptAndRegister": "Crear cuenta y unirse",
"accepting": "Aceptando…",
"firstName": "Nombre",
"lastName": "Apellidos",
"password": "Contraseña",
"buyingFor": "Comprando para",
"buyPersonally": "Comprar a título personal"
}
}
34 changes: 33 additions & 1 deletion messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@
"showPassword": "Afficher le mot de passe",
"hidePassword": "Masquer le mot de passe",
"forgotPassword": "Mot de passe oublie ?",
"policyConsentRequired": "Vous devez accepter les politiques du magasin pour creer un compte"
"policyConsentRequired": "Vous devez accepter les politiques du magasin pour creer un compte",
"companies": "Entreprises"
},
"register": {
"createAccount": "Creer un compte",
Expand Down Expand Up @@ -628,5 +629,36 @@
"signInForPricing": "Connectez-vous pour voir les prix",
"signInToOrder": "Connectez-vous pour commander"
}
},
"companies": {
"title": "Entreprises",
"subtitle": "Les organisations pour lesquelles vous pouvez acheter.",
"empty": "Vous n'êtes membre d'aucune entreprise pour le moment.",
"members": "Membres",
"addMember": "Ajouter",
"addMemberPlaceholder": "collegue@exemple.fr",
"addMemberFailed": "Impossible d'ajouter cette personne.",
"removeMember": "Retirer le membre",
"invitationPending": "Invitation envoyée",
"revoke": "Révoquer",
"addresses": "Carnet d'adresses",
"addressesHelp": "Où cette organisation est livrée. Les sites hérités d'une entité parente sont également affichés.",
"viewOrders": "Voir les commandes de l'entreprise",
"ordersTitle": "Commandes de l'entreprise",
"invitationTitle": "Rejoindre {company}",
"invitationBody": "Vous avez été invité à acheter sur {store} en tant que {email}.",
"invitationMissingToken": "Ce lien ne contient pas de code d'invitation.",
"invitationInvalidTitle": "Cette invitation n'est plus valide",
"invitationInvalidBody": "Elle a peut-être déjà été acceptée, révoquée, ou elle a expiré. Demandez-en une nouvelle.",
"invitationWrongAccount": "Cette invitation a été envoyée à {invited}, mais vous êtes connecté en tant que {current}. Connectez-vous avec l'adresse invitée.",
"invitationAcceptFailed": "Impossible d'accepter l'invitation.",
"acceptAsSignedIn": "Accepter l'invitation",
"acceptAndRegister": "Créer un compte et rejoindre",
"accepting": "Acceptation…",
"firstName": "Prénom",
"lastName": "Nom",
"password": "Mot de passe",
"buyingFor": "Achat pour",
"buyPersonally": "Acheter à titre personnel"
}
}
34 changes: 33 additions & 1 deletion messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@
"showPassword": "Pokaż hasło",
"hidePassword": "Ukryj hasło",
"forgotPassword": "Zapomniałeś hasła?",
"policyConsentRequired": "Musisz zaakceptować regulamin sklepu, aby utworzyć konto"
"policyConsentRequired": "Musisz zaakceptować regulamin sklepu, aby utworzyć konto",
"companies": "Firmy"
},
"register": {
"createAccount": "Utwórz konto",
Expand Down Expand Up @@ -628,5 +629,36 @@
"signInForPricing": "Zaloguj się, aby zobaczyć ceny",
"signInToOrder": "Zaloguj się, aby zamówić"
}
},
"companies": {
"title": "Firmy",
"subtitle": "Organizacje, dla których możesz kupować.",
"empty": "Nie należysz jeszcze do żadnej firmy.",
"members": "Członkowie",
"addMember": "Dodaj",
"addMemberPlaceholder": "wspolpracownik@przyklad.pl",
"addMemberFailed": "Nie udało się dodać tej osoby.",
"removeMember": "Usuń członka",
"invitationPending": "Zaproszenie wysłane",
"revoke": "Cofnij",
"addresses": "Książka adresowa",
"addressesHelp": "Dokąd dostarczane są zamówienia tej organizacji. Pokazujemy też lokalizacje odziedziczone po jednostce nadrzędnej.",
"viewOrders": "Zobacz zamówienia firmy",
"ordersTitle": "Zamówienia firmy",
"invitationTitle": "Dołącz do {company}",
"invitationBody": "Zaproszono Cię do zakupów w {store} jako {email}.",
"invitationMissingToken": "W tym linku brakuje kodu zaproszenia.",
"invitationInvalidTitle": "To zaproszenie jest już nieaktualne",
"invitationInvalidBody": "Mogło zostać przyjęte, cofnięte albo po prostu wygasło. Poproś o nowe.",
"invitationWrongAccount": "To zaproszenie wysłano na {invited}, a jesteś zalogowany jako {current}. Zaloguj się na zaproszony adres.",
"invitationAcceptFailed": "Nie udało się przyjąć zaproszenia.",
"acceptAsSignedIn": "Przyjmij zaproszenie",
"acceptAndRegister": "Załóż konto i dołącz",
"accepting": "Przyjmowanie…",
"firstName": "Imię",
"lastName": "Nazwisko",
"password": "Hasło",
"buyingFor": "Zakup dla",
"buyPersonally": "Kup prywatnie"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
PaymentSection,
type PaymentSectionHandle,
} from "@/components/checkout/PaymentSection";
import { BuyingForSelector } from "@/components/companies/BuyingForSelector";
import { PolicyConsent } from "@/components/policy/PolicyConsent";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { useAuth } from "@/contexts/AuthContext";
Expand Down Expand Up @@ -699,6 +700,12 @@ function CheckoutPageContentInner({
: "relative"
}
>
{/* Whose purchase this is, before the addresses it decides. */}
<BuyingForSelector
cartId={cart.id}
selectedCompanyId={cart.company_id ?? null}
/>

{/* Contact + Delivery */}
<div id="checkout-section-address">
<AddressSection
Expand Down
11 changes: 10 additions & 1 deletion src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connection } from "next/server";
import { Suspense } from "react";
import { getAddresses } from "@/lib/data/addresses";
import { getCheckoutOrder } from "@/lib/data/checkout";
import { getCompanyAddresses } from "@/lib/data/companies";
import { isAuthenticated as checkAuth } from "@/lib/data/cookies";
import { getCountry } from "@/lib/data/countries";
import { getMarketCountries, resolveMarket } from "@/lib/data/markets";
Expand Down Expand Up @@ -34,12 +35,20 @@ async function CheckoutDataLoader({ params }: CheckoutPageProps) {
const authStatus = await checkAuth();

// Fetch initial data in parallel during SSR
const [cartData, market, addressesData] = await Promise.all([
const [cartData, market, personalAddresses] = await Promise.all([
getCheckoutOrder(cartId),
resolveMarket(urlCountry).catch(() => null),
authStatus ? getAddresses() : Promise.resolve({ data: [] as Address[] }),
]);

// Buying for a company ships to the company's sites, so its book replaces
// the buyer's own rather than joining it — offering someone's home address
// beside their employer's invites the mis-click. The set matches what the
// server will accept: the node's own sites and the ones it inherits.
const addressesData = cartData?.company_id
? await getCompanyAddresses(cartData.company_id)
: personalAddresses;

// Redirect to order-placed if already complete
if (cartData?.current_step === "complete") {
const basePath = `/${urlCountry}/en`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { notFound } from "next/navigation";
import { connection } from "next/server";
import { getTranslations } from "next-intl/server";
import { OrderList } from "@/components/account/OrderList";
import { getCompany, getCompanyOrders } from "@/lib/data/companies";

interface CompanyOrdersPageProps {
params: Promise<{ country: string; locale: string; id: string }>;
}

/**
* What the organization has bought — every completed order across the node's
* subtree, not only the ones placed by the buyer looking at the page. That is
* the point of a company view: a purchasing team sees the department's spend,
* not just their own.
*/
export default async function CompanyOrdersPage({
params,
}: CompanyOrdersPageProps) {
await connection();
const { country, locale, id } = await params;
const t = await getTranslations({
locale: locale as Locale,
namespace: "companies",
});

const [company, orders] = await Promise.all([
getCompany(id),
getCompanyOrders(id),
]);

if (!company) notFound();

const basePath = `/${country}/${locale}`;

return (
<div className="space-y-6">
<div>
<h1 className="font-semibold text-2xl">{t("ordersTitle")}</h1>
<p className="text-muted-foreground text-sm">{company.name}</p>
</div>

<OrderList orders={orders.data} basePath={basePath} locale={locale} />
</div>
);
}
Loading