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: 8 additions & 2 deletions app/(control)/admin/fleet/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,15 @@ export default async function AdminFleetPage() {
);
}

// Scope the reads to the tenants we'll actually render (the registry), so orphaned/decommissioned
// rows don't get loaded as the fleet grows (Gemini review on #71).
const tenantSlugs = registry.tenants.map((t) => t.slug);
const [devices, summaries] = await Promise.all([
db.fleetDevice.findMany({ orderBy: [{ tenantSlug: "asc" }, { label: "asc" }] }),
db.fleetSummary.findMany(),
db.fleetDevice.findMany({
where: { tenantSlug: { in: tenantSlugs } },
orderBy: [{ tenantSlug: "asc" }, { label: "asc" }],
}),
db.fleetSummary.findMany({ where: { tenantSlug: { in: tenantSlugs } } }),
]);
Comment thread
mahmutkaya marked this conversation as resolved.

const devicesBySlug = new Map<string, FleetDeviceRow[]>();
Expand Down
2 changes: 1 addition & 1 deletion app/(control)/billing/thanks/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default async function BillingThanksPage() {
return (
<main className="mx-auto max-w-md px-6 py-24 text-center">
<Link href="/" className="font-hand text-4xl font-bold text-primary">
Sofra
SofraPiwas
</Link>
<h1 className="mt-8 font-display font-bold text-5xl">{t("title")}</h1>
<p className="mt-4 text-muted-foreground">{t("body")}</p>
Expand Down
2 changes: 1 addition & 1 deletion app/(control)/forgot/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default async function ForgotPage() {
return (
<main className="mx-auto max-w-md px-6 py-24">
<Link href="/" className="font-hand text-4xl font-bold text-primary">
Sofra
SofraPiwas
</Link>
<h1 className="mt-8 font-display font-bold text-5xl">{t("forgotTitle")}</h1>
<p className="mt-3 text-muted-foreground">{t("forgotIntro")}</p>
Expand Down
2 changes: 1 addition & 1 deletion app/(control)/invite/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function InvitePage({
return (
<main className="mx-auto max-w-md px-6 py-24">
<Link href="/" className="font-hand text-4xl font-bold text-primary">
Sofra
SofraPiwas
</Link>
<h1 className="mt-8 font-display font-bold text-5xl">{t("inviteTitle")}</h1>
{valid ? (
Expand Down
4 changes: 2 additions & 2 deletions app/(control)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { controlLocale } from "@/lib/control-locale";
import { fontClassNames, themeInitScript } from "@/lib/fonts";

export const metadata: Metadata = {
title: "Sofra — Partner area",
title: "SofraPiwas — Partner area",
robots: { index: false, follow: false },
icons: { icon: "/favicon.svg" },
icons: { icon: "/favicon.svg", apple: "/apple-touch-icon.png" },
};

export default async function ControlLayout({ children }: { children: React.ReactNode }) {
Expand Down
2 changes: 1 addition & 1 deletion app/(control)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default async function LoginPage({
return (
<main className="mx-auto max-w-md px-6 py-24">
<Link href="/" className="font-hand text-4xl font-bold text-primary">
Sofra
SofraPiwas
</Link>
<h1 className="mt-8 font-display font-bold text-5xl">{t("loginTitle")}</h1>
<p className="mt-3 text-muted-foreground">
Expand Down
2 changes: 1 addition & 1 deletion app/(control)/reset/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function ResetPage({
return (
<main className="mx-auto max-w-md px-6 py-24">
<Link href="/" className="font-hand text-4xl font-bold text-primary">
Sofra
SofraPiwas
</Link>
<h1 className="mt-8 font-display font-bold text-5xl">{t("resetTitle")}</h1>
{valid ? (
Expand Down
4 changes: 2 additions & 2 deletions app/[locale]/case/rumi/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export default async function RumiCaseStudyPage({
dateModified: PUBLISHED,
inLanguage: locale,
mainEntityOfPage: `${SITE_URL}/${locale}/case/rumi`,
author: { "@type": "Organization", name: "Sofra", url: SITE_URL },
publisher: { "@type": "Organization", name: "Sofra", url: SITE_URL },
author: { "@type": "Organization", name: "SofraPiwas", url: SITE_URL },
publisher: { "@type": "Organization", name: "SofraPiwas", url: SITE_URL },
about: {
"@type": "Restaurant",
name: "RUMI Restaurant",
Expand Down
2 changes: 1 addition & 1 deletion app/[locale]/compare/gloriafood/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "@/components/compare-gloriafood-data";
import { SITE_URL, marketingPageMetadata } from "@/lib/seo";

// Honest, dated Sofra-vs-GloriaFood comparison (AEO plan §2 priority 2).
// Honest, dated SofraPiwas-vs-GloriaFood comparison (AEO plan §2 priority 2).
// Every GloriaFood claim is sourced (see compare-gloriafood-data.ts) —
// re-verify the sources before editing any factual cell.

Expand Down
13 changes: 10 additions & 3 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NextIntlClientProvider, hasLocale } from "next-intl";
import { setRequestLocale, getTranslations } from "next-intl/server";
import "../globals.css";
import { routing } from "@/i18n/routing";
import { SITE_URL, pageAlternates } from "@/lib/seo";
import { SITE_URL, pageAlternates, OG_IMAGE } from "@/lib/seo";
import { fontClassNames, themeInitScript } from "@/lib/fonts";
import ThemeSync from "@/components/ThemeSync";

Expand All @@ -24,14 +24,21 @@ export async function generateMetadata({
metadataBase: new URL(SITE_URL),
title: t("title"),
description: t("description"),
icons: { icon: "/favicon.svg" },
icons: { icon: "/favicon.svg", apple: "/apple-touch-icon.png" },
alternates: pageAlternates(locale, ""),
openGraph: {
title: t("title"),
description: t("description"),
siteName: "Sofra",
siteName: "SofraPiwas",
locale,
type: "website",
images: [OG_IMAGE],
},
twitter: {
card: "summary_large_image",
title: t("title"),
description: t("description"),
images: [OG_IMAGE],
},
robots: { index: true, follow: true },
};
Expand Down
2 changes: 1 addition & 1 deletion app/api/partner/apply/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function POST(request: Request) {
await sendEmail({
to,
replyTo: data.email,
subject: `Sofra — Partner application: ${data.name}`,
subject: `SofraPiwas — Partner application: ${data.name}`,
html: craftEmail({
kicker: "Partner program",
title: "New partner application",
Expand Down
2 changes: 1 addition & 1 deletion app/api/signup/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export async function POST(request: Request) {
await sendEmail({
to,
replyTo: data.email,
subject: `Sofra — New signup: ${data.restaurantName}`,
subject: `SofraPiwas — New signup: ${data.restaurantName}`,
html: craftEmail({
kicker: "Signups",
title: "New restaurant signup",
Expand Down
2 changes: 1 addition & 1 deletion app/api/waitlist/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function POST(request: Request) {
const { sent } = await sendEmail({
to,
replyTo: email,
subject: `Sofra — ${meta.subject}: ${restaurant}`,
subject: `SofraPiwas — ${meta.subject}: ${restaurant}`,
html: craftEmail({
kicker: meta.kicker,
title: meta.title,
Expand Down
21 changes: 21 additions & 0 deletions components/BrandMark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type BrandMarkProps = { className?: string };

/**
* The SofraPiwas onion mark (piwas). Decorative in the brand lockup — the
* adjacent "SofraPiwas" wordmark carries the accessible name — so it's
* `aria-hidden` with an empty alt. A static SVG served from /public; next/image
* would add no value (no optimization for inline vector) so a plain <img> is used.
*/
export default function BrandMark({ className }: Readonly<BrandMarkProps>) {
return (
// eslint-disable-next-line @next/next/no-img-element -- static SVG logo, no next/image optimization
<img
src="/mark.svg"
alt=""
aria-hidden="true"
width={452}
height={501}
className={className}
/>
);
}
2 changes: 1 addition & 1 deletion components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function Footer() {
<footer className="border-t-2 border-border">
<div className="mx-auto max-w-6xl px-6 py-12 flex flex-col md:flex-row items-start md:items-center justify-between gap-8">
<div>
<p className="font-hand text-4xl font-bold text-primary">Sofra</p>
<p className="font-hand text-4xl font-bold text-primary">SofraPiwas</p>
<p className="mt-1 font-label text-sm text-muted-foreground">
{t("tagline")}
</p>
Expand Down
9 changes: 7 additions & 2 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Link from "next/link";
import { Link as LocaleLink } from "@/i18n/navigation";
import { useTranslations } from "next-intl";
import BrandMark from "./BrandMark";
import ThemeToggle from "./ThemeToggle";
import LocaleSwitcher from "./LocaleSwitcher";

Expand All @@ -12,8 +13,12 @@ export default function Header() {
return (
<header className="sticky top-0 z-40 bg-background/90 backdrop-blur-xs border-b-2 border-border">
<div className="mx-auto max-w-6xl px-6 h-16 flex items-center justify-between">
<LocaleLink href="/#top" className="font-hand text-3xl font-bold text-primary">
Sofra
<LocaleLink
href="/#top"
className="flex items-center gap-2.5 text-primary"
>
<BrandMark className="h-10 w-auto" />
<span className="font-hand text-3xl font-bold">SofraPiwas</span>
</LocaleLink>

<nav className="hidden md:flex items-center gap-8 font-label text-lg">
Expand Down
8 changes: 4 additions & 4 deletions components/JsonLd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function JsonLd({ locale }: { locale: string }) {
"@context": "https://schema.org",
"@type": "Organization",
"@id": `${SITE_URL}/#organization`,
name: "Sofra",
name: "SofraPiwas",
url: SITE_URL,
logo: `${SITE_URL}/favicon.svg`,
description: meta("description"),
Expand All @@ -31,7 +31,7 @@ export default async function JsonLd({ locale }: { locale: string }) {
"@context": "https://schema.org",
"@type": "WebSite",
"@id": `${SITE_URL}/#website`,
name: "Sofra",
name: "SofraPiwas",
url: SITE_URL,
inLanguage: [...routing.locales],
publisher: { "@id": `${SITE_URL}/#organization` },
Expand All @@ -40,14 +40,14 @@ export default async function JsonLd({ locale }: { locale: string }) {
const softwareApplication = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Sofra",
name: "SofraPiwas",
url: SITE_URL,
description: meta("description"),
applicationCategory: "BusinessApplication",
operatingSystem: "Web",
inLanguage: locale,
// Free applies to the early-access founding cohort only — name the offer
// so answer engines don't lift a bare "Sofra is free" claim.
// so answer engines don't lift a bare "SofraPiwas is free" claim.
offers: {
"@type": "Offer",
name: "Early access — founding restaurants",
Expand Down
2 changes: 1 addition & 1 deletion components/control/ClientPipelineControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PARTNER_STATUSES = [
] as const;

/** Status stepper + "request onboarding", shown while the partner still owns
* the pipeline (server rejects changes once Sofra takes over). */
* the pipeline (server rejects changes once SofraPiwas takes over). */
export default function ClientPipelineControls({
clientId,
status,
Expand Down
9 changes: 7 additions & 2 deletions components/control/ControlShell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Link from "next/link";
import BrandMark from "@/components/BrandMark";
import { logoutAction } from "@/lib/actions/auth-actions";

/** Shared chrome for the partner dashboard and founder admin. Labels arrive
Expand All @@ -21,8 +22,12 @@ export default function ControlShell({
<header className="sticky top-0 z-40 bg-background/90 backdrop-blur-xs border-b-2 border-border">
<div className="mx-auto max-w-6xl px-6 h-16 flex items-center justify-between gap-4">
<div className="flex items-center gap-6 min-w-0">
<Link href="/" className="font-hand text-3xl font-bold text-primary shrink-0">
Sofra
<Link
href="/"
className="flex items-center gap-2 text-primary shrink-0"
>
<BrandMark className="h-9 w-auto" />
<span className="font-hand text-3xl font-bold">SofraPiwas</span>
</Link>
<span className="masking-tape font-label text-sm px-3 py-0.5 text-muted-foreground shrink-0">
{title}
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/admin-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ export async function approveApplicationAction(
const inviteLink = `${siteUrl()}/invite/${raw}`;
await sendEmail({
to: user.email,
subject: "Welcome to the Sofra partner program",
subject: "Welcome to the SofraPiwas partner program",
html: craftEmail({
kicker: "Partner program",
title: "Welcome aboard 🎉",
bodyHtml: `<p style="margin:0 0 12px;">Hi ${escapeHtml(user.name)},</p>
<p style="margin:0;">Your Sofra partner application is approved. Set your password to open your partner dashboard — afiyet olsun.</p>`,
<p style="margin:0;">Your SofraPiwas partner application is approved. Set your password to open your partner dashboard — afiyet olsun.</p>`,
cta: { label: "Set your password", url: inviteLink },
footerNote: "The link works once and expires in 24 hours.",
}),
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/auth-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ export async function forgotPasswordAction(_prev: FormState, formData: FormData)
const link = `${siteUrl()}/reset/${raw}`;
await sendEmail({
to: user.email,
subject: "Sofra — reset your password",
subject: "SofraPiwas — reset your password",
html: craftEmail({
kicker: "Partner area",
title: "Reset your password",
bodyHtml: `<p style="margin:0 0 12px;">Hi ${escapeHtml(user.name)},</p>
<p style="margin:0;">Someone (hopefully you) asked to reset your Sofra partner password. If this wasn't you, you can safely ignore this email.</p>`,
<p style="margin:0;">Someone (hopefully you) asked to reset your SofraPiwas partner password. If this wasn't you, you can safely ignore this email.</p>`,
cta: { label: "Set a new password", url: link },
footerNote: "The link works once and expires in 24 hours.",
}),
Expand Down
8 changes: 4 additions & 4 deletions lib/actions/onboarding-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ async function emailOnboardInvite(
await sendEmail({
to: email,
subject: needsPassword
? "Welcome to Sofra — set your password"
: `Sofra — ${restaurantName} is ready for your subscription`,
? "Welcome to SofraPiwas — set your password"
: `SofraPiwas — ${restaurantName} is ready for your subscription`,
html: craftEmail({
kicker: ownerFlow ? "Welcome to Sofra" : "Partner program",
kicker: ownerFlow ? "Welcome to SofraPiwas" : "Partner program",
title: needsPassword ? "Welcome aboard 🎉" : "A new plan is waiting",
bodyHtml: `<p style="margin:0 0 12px;">Hi ${escapeHtml(user.name)},</p>
<p style="margin:0;">${escapeHtml(restaurantName)} is set up on Sofra. ${
<p style="margin:0;">${escapeHtml(restaurantName)} is set up on SofraPiwas. ${
needsPassword ? "Set your password to open your dashboard" : "Sign in to your dashboard"
} and start the monthly subscription — afiyet olsun.</p>`,
cta: { label: needsPassword ? "Set your password" : "Open your dashboard", url: link },
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/partner-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export async function requestOnboardingAction(
if (to) {
await sendEmail({
to,
subject: `Sofra — Onboarding request: ${client.restaurantName}`,
subject: `SofraPiwas — Onboarding request: ${client.restaurantName}`,
html: craftEmail({
kicker: "Partner pipeline",
title: "Onboarding requested",
Expand Down
2 changes: 1 addition & 1 deletion lib/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ async function notifyFounder(tenantSlug: string, payment: MolliePayment, amountC
const ok = payment.status === "paid";
await sendEmail({
to: inbox,
subject: `[Sofra billing] ${tenantSlug}: ${payment.sequenceType} payment ${payment.status} (${eur(amountCents)})`,
subject: `[SofraPiwas billing] ${tenantSlug}: ${payment.sequenceType} payment ${payment.status} (${eur(amountCents)})`,
html: craftEmail({
kicker: "Billing",
title: ok ? "Payment received" : `Payment ${payment.status}`,
Expand Down
4 changes: 2 additions & 2 deletions lib/email-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function craftEmail(opts: {
<tr><td align="center">
<table role="presentation" width="560" cellpadding="0" cellspacing="0" style="max-width:560px;width:100%;">
<tr><td style="padding:0 8px 18px;">
<span style="font-family:Georgia,'Times New Roman',serif;font-size:30px;font-weight:bold;color:${C.primary};">Sofra</span>
<span style="font-family:Georgia,'Times New Roman',serif;font-size:30px;font-weight:bold;color:${C.primary};">SofraPiwas</span>
<span style="font-family:Georgia,serif;font-style:italic;font-size:14px;color:${C.muted};">&nbsp;— a table laid for guests</span>
</td></tr>
<tr><td style="background:${C.card};border:2px dashed ${C.border};border-radius:14px;padding:32px 28px;">
Expand All @@ -53,7 +53,7 @@ export function craftEmail(opts: {
${opts.footerNote ? `<p style="margin:18px 0 0;font-family:Verdana,Geneva,sans-serif;font-size:12px;color:${C.muted};">${opts.footerNote}</p>` : ""}
</td></tr>
<tr><td style="padding:18px 8px 0;font-family:Verdana,Geneva,sans-serif;font-size:11px;color:${C.muted};">
Sofra · restaurant software with a warm heart · <a href="https://sofrapiwas.com" style="color:${C.muted};">sofrapiwas.com</a>
SofraPiwas · restaurant software with a warm heart · <a href="https://sofrapiwas.com" style="color:${C.muted};">sofrapiwas.com</a>
</td></tr>
</table>
</td></tr>
Expand Down
2 changes: 1 addition & 1 deletion lib/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export async function sendEmail(opts: {
replyTo?: string;
}): Promise<{ sent: boolean }> {
const apiKey = process.env.RESEND_API_KEY;
const from = process.env.WAITLIST_FROM ?? "Sofra <onboarding@resend.dev>";
const from = process.env.WAITLIST_FROM ?? "SofraPiwas <onboarding@resend.dev>";
if (!apiKey) {
console.warn(`email (not sent, no RESEND_API_KEY): to=${opts.to} subject=${opts.subject}`);
return { sent: false };
Expand Down
2 changes: 1 addition & 1 deletion lib/fonts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Sofra craft typography stack — shared by both root layouts
// SofraPiwas craft typography stack — shared by both root layouts
// (the localized marketing tree and the en-only control plane).
import {
Quicksand,
Expand Down
2 changes: 1 addition & 1 deletion lib/format.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Sofra's books are EUR (NL-registered company) — flipped from CHF on
// SofraPiwas's books are EUR (NL-registered company) — flipped from CHF on
// 2026-07-06 while the ledger was still empty. Per-tenant display currency
// is a separate product concern (tenant registry).
export function eur(cents: number): string {
Expand Down
2 changes: 1 addition & 1 deletion lib/mollie.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Minimal typed Mollie v2 client (fetch-based, no SDK dep — mirrors the
// Resend pattern in lib/email.ts). Sofra→tenant subscription billing only
// Resend pattern in lib/email.ts). SofraPiwas→tenant subscription billing only
// (ADR-005/ADR-011 Job A): customers + first payments (mandate creation) +
// subscriptions. Auth is a plain API key; test_/live_ prefix selects mode.
//
Expand Down
Loading
Loading