Files
Epicure/apps/web/app/(app)/support/page.tsx
T
Arnaud 2f3ba14093 feat: per-tier feature toggles for recipe variations/pairings (v0.50.0)
Admins can now disable specific AI features per tier from Admin > Tier
Limits — new feature_flags table (feature x tier -> enabled, defaulting
to true so adding a new gated feature never needs a backfill).

Covers recipe variations, drink pairing, and meal pairing to start.
When disabled for a user's tier, the button stays visible (with a small
lock badge) but opens an upgrade dialog instead of running; the API
route rejects the call server-side either way (requireFeatureEnabled,
re-reads tier from the DB rather than trusting the session's cache,
same rationale as checkAndIncrementTierLimit).

The upgrade dialog is informational only — no Stripe checkout exists
yet (STRIPE_PLAN.md is still just a plan) — its CTA links to /support
prefilled as an upgrade-interest suggestion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 23:35:12 +02:00

59 lines
1.9 KiB
TypeScript

import type { Metadata } from "next";
import { headers } from "next/headers";
import { auth } from "@/lib/auth/server";
import { db, supportTickets, eq, desc } from "@epicure/db";
import { SupportManager } from "@/components/support/support-manager";
import { getMessages } from "@/lib/i18n/server";
import { getPublicUrl } from "@/lib/storage";
import { FEATURE_DEFINITIONS } from "@/lib/feature-flags";
export const metadata: Metadata = {};
export default async function SupportPage({
searchParams,
}: {
searchParams: Promise<{ upgrade?: string }>;
}) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) return null;
const m = getMessages((session.user as { locale?: string }).locale);
const { upgrade } = await searchParams;
const upgradeFeature = FEATURE_DEFINITIONS.find((f) => f.key === upgrade);
const prefill = upgradeFeature
? { type: "suggestion" as const, title: `Interested in upgrading for: ${upgradeFeature.label}` }
: undefined;
const rows = await db.query.supportTickets.findMany({
where: eq(supportTickets.userId, session.user.id),
orderBy: desc(supportTickets.createdAt),
with: { attachments: true },
});
return (
<div className="space-y-8 max-w-3xl">
<div>
<h1 className="text-2xl font-bold tracking-tight">{m.support.title}</h1>
<p className="text-muted-foreground text-sm mt-1">{m.support.subtitle}</p>
</div>
<SupportManager
prefill={prefill}
initialTickets={rows.map((r) => ({
id: r.id,
type: r.type,
title: r.title,
description: r.description,
status: r.status,
giteaIssueUrl: r.giteaIssueUrl,
createdAt: r.createdAt.toISOString(),
attachments: r.attachments.map((a) => ({
id: a.id,
contentType: a.contentType,
url: getPublicUrl(a.storageKey),
})),
}))}
/>
</div>
);
}