fix: replace emoji flags with SVG dropdown, complete Home's third row
Language switcher was two inline emoji-flag buttons -- emoji flag rendering is inconsistent across OS/browsers (some render as two-letter country codes instead of a flag). Replaced with hand-rolled inline SVG flags (no new dependency for two locales) inside a proper dropdown menu, trigger showing the current language's flag. Also added a 9th Home highlight (personalized recommendations, same copy already written for the Features page) so the lg:grid-cols-3 feature grid's last row has 3 items instead of 2. v0.48.2
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
|
||||
All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together.
|
||||
|
||||
## 0.48.2 — 2026-07-18 11:15
|
||||
|
||||
### Fixed
|
||||
- Marketing site's language switcher no longer uses emoji flags (inconsistent rendering across OS/browsers) — now inline SVG flags in a proper dropdown menu. Home page's feature grid also got a 9th highlight (personalized recommendations) so the last row isn't left half-empty.
|
||||
|
||||
## 0.48.1 — 2026-07-18 11:00
|
||||
|
||||
### Added
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getEffectiveMarketingLocale } from "@/lib/marketing-locale";
|
||||
import { isSignupsDisabled } from "@/lib/site-settings";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Sparkles, Calendar, Package, MessageCircle, Users, ChefHat, Utensils, Wand2 } from "lucide-react";
|
||||
import { Sparkles, Calendar, Package, MessageCircle, Users, ChefHat, Utensils, Wand2, Sparkle } from "lucide-react";
|
||||
|
||||
const HIGHLIGHTS = [
|
||||
{ icon: Sparkles, key: "ai" },
|
||||
@@ -18,6 +18,7 @@ const HIGHLIGHTS = [
|
||||
{ icon: Wand2, key: "variations" },
|
||||
{ icon: Users, key: "social" },
|
||||
{ icon: ChefHat, key: "batchCook" },
|
||||
{ icon: Sparkle, key: "recommendations" },
|
||||
] as const;
|
||||
|
||||
export default async function MarketingHomePage() {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Minimal inline SVG flags — no emoji (inconsistent rendering across
|
||||
// OS/browsers) and no new dependency for just two supported locales.
|
||||
|
||||
export function FlagGB({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 14" className={className} aria-hidden="true">
|
||||
<rect width="20" height="14" fill="#00247d" />
|
||||
<path d="M0 0 20 14M20 0 0 14" stroke="#fff" strokeWidth="2.4" />
|
||||
<path d="M0 0 20 14M20 0 0 14" stroke="#cf142b" strokeWidth="1.2" />
|
||||
<path d="M10 0V14M0 7H20" stroke="#fff" strokeWidth="4" />
|
||||
<path d="M10 0V14M0 7H20" stroke="#cf142b" strokeWidth="2.4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function FlagFR({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 14" className={className} aria-hidden="true">
|
||||
<rect width="20" height="14" fill="#fff" />
|
||||
<rect width="6.67" height="14" fill="#0055a4" />
|
||||
<rect x="13.33" width="6.67" height="14" fill="#ef4135" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Check, Globe } from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { MARKETING_LOCALE_COOKIE } from "@/lib/marketing-locale-cookie";
|
||||
import type { Locale } from "@/lib/i18n/provider";
|
||||
import { FlagGB, FlagFR } from "./flag-icons";
|
||||
|
||||
const OPTIONS: { code: Locale; flag: string; label: string }[] = [
|
||||
{ code: "en", flag: "🇬🇧", label: "English" },
|
||||
{ code: "fr", flag: "🇫🇷", label: "Français" },
|
||||
const OPTIONS: { code: Locale; Flag: typeof FlagGB; label: string }[] = [
|
||||
{ code: "en", Flag: FlagGB, label: "English" },
|
||||
{ code: "fr", Flag: FlagFR, label: "Français" },
|
||||
];
|
||||
|
||||
// Plain module-level function, not part of the component body — setting a
|
||||
@@ -26,6 +34,7 @@ function setMarketingLocaleCookie(code: Locale) {
|
||||
*/
|
||||
export function LanguageSwitcher({ current }: { current: Locale }) {
|
||||
const router = useRouter();
|
||||
const CurrentFlag = OPTIONS.find((o) => o.code === current)?.Flag ?? Globe;
|
||||
|
||||
function select(code: Locale) {
|
||||
if (code === current) return;
|
||||
@@ -34,22 +43,22 @@ export function LanguageSwitcher({ current }: { current: Locale }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5" role="group" aria-label="Language">
|
||||
{OPTIONS.map(({ code, flag, label }) => (
|
||||
<button
|
||||
key={code}
|
||||
type="button"
|
||||
onClick={() => select(code)}
|
||||
aria-label={label}
|
||||
aria-pressed={current === code}
|
||||
title={label}
|
||||
className={`text-base leading-none p-1.5 rounded-md transition-opacity ${
|
||||
current === code ? "opacity-100" : "opacity-40 hover:opacity-70"
|
||||
}`}
|
||||
>
|
||||
{flag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className="p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
aria-label="Change language"
|
||||
>
|
||||
<CurrentFlag className="h-4 w-5 rounded-[2px]" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{OPTIONS.map(({ code, Flag, label }) => (
|
||||
<DropdownMenuItem key={code} onClick={() => select(code)} className="gap-2">
|
||||
<Flag className="h-3.5 w-[1.15rem] rounded-[2px] shrink-0" />
|
||||
<span className="flex-1">{label}</span>
|
||||
{current === code && <Check className="h-3.5 w-3.5" />}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mirrors CHANGELOG.md at the repo root — update both together.
|
||||
export const APP_VERSION = "0.48.1";
|
||||
export const APP_VERSION = "0.48.2";
|
||||
|
||||
export type ChangelogEntry = {
|
||||
version: string;
|
||||
@@ -11,6 +11,13 @@ export type ChangelogEntry = {
|
||||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
version: "0.48.2",
|
||||
date: "2026-07-18 11:15",
|
||||
fixed: [
|
||||
"Marketing site's language switcher no longer uses emoji flags (inconsistent rendering across OS/browsers) — now inline SVG flags in a proper dropdown menu. Home page's feature grid also got a 9th highlight (personalized recommendations) so the last row isn't left half-empty.",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.48.1",
|
||||
date: "2026-07-18 11:00",
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"cookMode": { "title": "Hands-free cook mode", "description": "A distraction-free, step-by-step view for actually cooking — large text, timers, no scrolling with messy hands." },
|
||||
"variations": { "title": "Recipe variations", "description": "Ask for a dietary twist, an ingredient swap, or a whole new spin on a recipe — AI proposes a variation, you decide whether to keep it." },
|
||||
"social": { "title": "Share & discover", "description": "Follow other cooks, rate and comment on recipes, and control exactly who can see yours." },
|
||||
"batchCook": { "title": "Batch cooking", "description": "Cook once, eat all week — dedicated batch-cook planning with fridge/freezer guidance." }
|
||||
"batchCook": { "title": "Batch cooking", "description": "Cook once, eat all week — dedicated batch-cook planning with fridge/freezer guidance." },
|
||||
"recommendations": { "title": "Personalized recommendations", "description": "A For You feed ranked from what you've favorited and rated, plus meal/drink pairing suggestions for any recipe." }
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"cookMode": { "title": "Mode cuisine mains libres", "description": "Une vue étape par étape sans distraction pour cuisiner — grand texte, minuteurs intégrés, pas besoin de scroller les mains sales." },
|
||||
"variations": { "title": "Variations de recette", "description": "Demandez une variante diététique, un remplacement d'ingrédient, ou une toute nouvelle interprétation — l'IA propose une variation, vous décidez de la garder." },
|
||||
"social": { "title": "Partager & découvrir", "description": "Suivez d'autres cuisiniers, notez et commentez les recettes, et contrôlez précisément qui peut voir les vôtres." },
|
||||
"batchCook": { "title": "Cuisine en lots", "description": "Cuisinez une fois, mangez toute la semaine — planification dédiée avec conseils de conservation au frigo/congélateur." }
|
||||
"batchCook": { "title": "Cuisine en lots", "description": "Cuisinez une fois, mangez toute la semaine — planification dédiée avec conseils de conservation au frigo/congélateur." },
|
||||
"recommendations": { "title": "Recommandations personnalisées", "description": "Un fil « Pour vous » classé selon vos favoris et vos notes, plus des suggestions d'accords repas/boisson pour chaque recette." }
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@epicure/web",
|
||||
"version": "0.48.1",
|
||||
"version": "0.48.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "epicure",
|
||||
"version": "0.48.1",
|
||||
"version": "0.48.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter web dev",
|
||||
|
||||
Reference in New Issue
Block a user