feat(themes): add Amber Artisan 2-column bento gourmet dark theme

This commit is contained in:
AyrisAI
2026-08-20 02:56:38 +03:00
parent da3d4d291b
commit 89c2f62995
5 changed files with 173 additions and 63 deletions
+18 -7
View File
@@ -155,20 +155,31 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => {
return reply.code(400).send({ message: "themeKey is required" }); return reply.code(400).send({ message: "themeKey is required" });
} }
const { data: themeData } = await supabase let themeId = themeData?.id;
.from("themes") if (!themeId) {
.select("id") const { data: newTheme } = await supabase
.eq("key", themeKey) .from("themes")
.maybeSingle(); .insert({
key: themeKey,
name: themeKey === "artisan" ? "Amber Artisan" : themeKey,
config: { primaryColor: "#E5A855", background: "#121316", productLayout: "card", categoryLayout: "tabs" },
})
.select("id")
.maybeSingle();
if (!themeData) { if (newTheme?.id) {
themeId = newTheme.id;
}
}
if (!themeId) {
return reply.code(404).send({ message: "Theme not found" }); return reply.code(404).send({ message: "Theme not found" });
} }
const { error } = await supabase.from("restaurant_themes").upsert( const { error } = await supabase.from("restaurant_themes").upsert(
{ {
restaurant_id: id, restaurant_id: id,
theme_id: themeData.id, theme_id: themeId,
}, },
{ onConflict: "restaurant_id" }, { onConflict: "restaurant_id" },
); );
@@ -31,6 +31,18 @@ export interface ThemeOption {
} }
export const THEME_OPTIONS: ThemeOption[] = [ export const THEME_OPTIONS: ThemeOption[] = [
{
key: "artisan",
name: "Amber Artisan",
category: "Gurme & Organik / Koyu",
color: "#E5A855",
badgeBg: "#282B34",
badgeText: "#E5A855",
bgPreview: "#121316",
accent: "Amber Bal & Mat Oniks",
desc: "Koyu antrasit mat zemin, kehribar vurgular, modern bento ızgarası ve yuvarlak ürün kartları.",
suitableFor: "Gurme şarküteri, kuruyemiş & organik market, fıçı bira evleri, artisan kahveciler",
},
{ {
key: "elegant", key: "elegant",
name: "Elegant Gold", name: "Elegant Gold",
+124 -56
View File
@@ -315,7 +315,7 @@ export function PublicMenuClient({
<div className="bg-stone-900 text-white px-5 py-4 border-b border-stone-800 animate-slide-up"> <div className="bg-stone-900 text-white px-5 py-4 border-b border-stone-800 animate-slide-up">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<p className="text-xs font-bold uppercase tracking-wider text-stone-400"> <p className="text-xs font-bold uppercase tracking-wider text-stone-400">
Canlı Tema Önizleme (5 Şablon) Canlı Tema Önizleme (6 Şablon)
</p> </p>
<button <button
type="button" type="button"
@@ -325,7 +325,7 @@ export function PublicMenuClient({
Kapat Kapat
</button> </button>
</div> </div>
<div className="grid grid-cols-5 gap-2"> <div className="grid grid-cols-3 sm:grid-cols-6 gap-2">
{Object.values(THEME_PRESETS).map((preset) => { {Object.values(THEME_PRESETS).map((preset) => {
const isSelected = selectedThemeKey === preset.key; const isSelected = selectedThemeKey === preset.key;
return ( return (
@@ -451,70 +451,138 @@ export function PublicMenuClient({
</span> </span>
</div> </div>
{/* Items Container: Grid / Card / List Layout based on Theme */}
{/* Items Container: Grid / Card / List Layout based on Theme */} {/* Items Container: Grid / Card / List Layout based on Theme */}
<div <div
className={ className={
theme.config.productLayout === "list" theme.key === "artisan"
? "grid grid-cols-2 gap-3 sm:gap-4"
: theme.config.productLayout === "list"
? "space-y-3" ? "space-y-3"
: "grid grid-cols-1 gap-3.5" : "grid grid-cols-1 gap-3.5"
} }
> >
{category.menu_items.map((item) => ( {category.menu_items.map((item) => {
<article if (theme.key === "artisan") {
key={item.id} return (
onClick={() => setSelectedItem(item)} <article
className={`group relative rounded-2xl p-4 transition-all duration-200 cursor-pointer border hover:shadow-lg active:scale-[0.99] flex gap-3.5 items-center ${ key={item.id}
theme.config.productLayout === "list" ? "justify-between" : "" onClick={() => setSelectedItem(item)}
}`} className="group relative rounded-3xl p-3.5 transition-all duration-300 cursor-pointer border flex flex-col justify-between hover:scale-[1.02] active:scale-[0.98] shadow-md"
style={{ style={{
backgroundColor: theme.cardBg, backgroundColor: theme.cardBg,
borderColor: theme.cardBorder, borderColor: theme.cardBorder,
}} }}
> >
{/* Item Details */} {/* Image Container with 3D Dish presentation */}
<div className="flex-1 min-w-0 pr-1"> <div className="relative w-full aspect-square rounded-2xl overflow-hidden bg-stone-900/60 mb-3 flex items-center justify-center border border-white/5">
<div className="flex items-start justify-between gap-2"> {item.image_url ? (
<h3 className="font-bold text-sm tracking-tight leading-snug group-hover:text-amber-600 transition-colors"> <img
{item.name} src={item.image_url}
</h3> alt={item.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
loading="lazy"
/>
) : (
<div className="text-3xl opacity-60"></div>
)}
<span
className="absolute top-2 right-2 px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase tracking-wide shadow-md"
style={{ backgroundColor: theme.config.primaryColor, color: "#121316" }}
>
ÖZEL
</span>
</div>
{/* Info */}
<div className="flex-1 flex flex-col justify-between">
<div>
<h3 className="font-bold text-xs sm:text-sm text-white tracking-tight leading-snug line-clamp-1 group-hover:text-amber-400 transition-colors">
{item.name}
</h3>
{item.description ? (
<p className="text-[11px] text-stone-400 mt-1 line-clamp-2 leading-relaxed">
{item.description}
</p>
) : null}
</div>
<div className="mt-3 pt-2.5 border-t border-white/5 flex items-center justify-between">
<span
className="text-sm sm:text-base font-black tracking-tight"
style={{ color: theme.config.primaryColor }}
>
{item.price.toFixed(0)}
</span>
<span
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold text-stone-950 shadow-sm transition-transform group-hover:scale-110"
style={{ backgroundColor: theme.config.primaryColor }}
>
+
</span>
</div>
</div>
</article>
);
}
return (
<article
key={item.id}
onClick={() => setSelectedItem(item)}
className={`group relative rounded-2xl p-4 transition-all duration-200 cursor-pointer border hover:shadow-lg active:scale-[0.99] flex gap-3.5 items-center ${
theme.config.productLayout === "list" ? "justify-between" : ""
}`}
style={{
backgroundColor: theme.cardBg,
borderColor: theme.cardBorder,
}}
>
{/* Item Details */}
<div className="flex-1 min-w-0 pr-1">
<div className="flex items-start justify-between gap-2">
<h3 className="font-bold text-sm tracking-tight leading-snug group-hover:text-amber-600 transition-colors">
{item.name}
</h3>
</div>
{item.description && (
<p
className="text-xs mt-1 leading-relaxed line-clamp-2"
style={{ color: theme.textSecondary }}
>
{item.description}
</p>
)}
<div className="mt-2.5 flex items-center justify-between">
<span
className="text-base font-extrabold tracking-tight"
style={{ color: theme.config.primaryColor }}
>
{item.price.toFixed(0)}
</span>
<span className="text-[10px] font-bold px-2 py-0.5 rounded-md bg-black/5 dark:bg-white/10 group-hover:bg-amber-100 dark:group-hover:bg-amber-900/40 transition-colors">
İncele
</span>
</div>
</div> </div>
{item.description && ( {/* Item Thumbnail Image */}
<p {item.image_url && (
className="text-xs mt-1 leading-relaxed line-clamp-2" <div className="relative w-20 h-20 rounded-xl overflow-hidden flex-shrink-0 bg-stone-100 dark:bg-stone-800 shadow-sm">
style={{ color: theme.textSecondary }} <img
> src={item.image_url}
{item.description} alt={item.name}
</p> className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
</div>
)} )}
</article>
<div className="mt-2.5 flex items-center justify-between"> );
<span })}
className="text-base font-extrabold tracking-tight"
style={{ color: theme.config.primaryColor }}
>
{item.price.toFixed(0)}
</span>
<span className="text-[10px] font-bold px-2 py-0.5 rounded-md bg-black/5 dark:bg-white/10 group-hover:bg-amber-100 dark:group-hover:bg-amber-900/40 transition-colors">
İncele
</span>
</div>
</div>
{/* Item Thumbnail Image */}
{item.image_url && (
<div className="relative w-20 h-20 rounded-xl overflow-hidden flex-shrink-0 bg-stone-100 dark:bg-stone-800 shadow-sm">
<img
src={item.image_url}
alt={item.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
</div>
)}
</article>
))}
</div> </div>
</section> </section>
)) ))
+19
View File
@@ -16,6 +16,25 @@ export interface ThemePreset {
} }
export const THEME_PRESETS: Record<string, ThemePreset> = { export const THEME_PRESETS: Record<string, ThemePreset> = {
artisan: {
key: "artisan",
name: "Amber Artisan",
fontFamily: "heading",
config: {
primaryColor: "#E5A855",
background: "#121316",
productLayout: "card",
categoryLayout: "tabs",
},
cardBg: "#1C1E24",
cardBorder: "rgba(255, 255, 255, 0.08)",
textPrimary: "#FFFFFF",
textSecondary: "#9DA3AF",
accentBg: "rgba(229, 168, 85, 0.14)",
headerGradient: "linear-gradient(180deg, #1A1C22 0%, #121316 100%)",
badgeBg: "#282B34",
badgeText: "#E5A855",
},
elegant: { elegant: {
key: "elegant", key: "elegant",
name: "Elegant Gold", name: "Elegant Gold",