diff --git a/apps/web/app/(app)/recipes/[id]/page.tsx b/apps/web/app/(app)/recipes/[id]/page.tsx index 9e0759e..543bbbf 100644 --- a/apps/web/app/(app)/recipes/[id]/page.tsx +++ b/apps/web/app/(app)/recipes/[id]/page.tsx @@ -29,6 +29,7 @@ import { CommentsSection } from "@/components/social/comments-section"; import { getPublicUrl } from "@/lib/storage"; import { cn } from "@/lib/utils"; import { RecipeChatPanel } from "@/components/recipe/recipe-chat-panel"; +import { KeepScreenAwake } from "@/components/recipe/keep-screen-awake"; import { getMessages, formatMessage } from "@/lib/i18n/server"; type Params = { params: Promise<{ id: string }> }; @@ -86,6 +87,7 @@ export default async function RecipePage({ params }: Params) { return (
+ {/* Header */}

{recipe.title}

@@ -200,6 +202,18 @@ export default async function RecipePage({ params }: Params) {

{recipe.description}

)} + {recipe.sourceUrl && ( + + + {m.recipe.source}: {new URL(recipe.sourceUrl).hostname.replace(/^www\./, "")} + + )} +
{recipe.difficulty && ( {m.recipe.difficulty[recipe.difficulty]} diff --git a/apps/web/components/cooking-mode/cooking-mode.tsx b/apps/web/components/cooking-mode/cooking-mode.tsx index ebad953..74c5344 100644 --- a/apps/web/components/cooking-mode/cooking-mode.tsx +++ b/apps/web/components/cooking-mode/cooking-mode.tsx @@ -7,6 +7,7 @@ import { cn } from "@/lib/utils"; import { useTranslations } from "next-intl"; import { useLocale } from "@/lib/i18n/provider"; import { hasQuantity } from "@/lib/fractions"; +import { useWakeLock } from "@/lib/hooks/use-wake-lock"; type Step = { id: string; instruction: string; timerSeconds: number | null }; type Ingredient = { rawName: string; quantity: string | null; unit: string | null }; @@ -85,7 +86,6 @@ export function CookingMode({ // Parallel timers: keyed by step index const [timers, setTimers] = useState>(() => new Map()); - const wakeLockRef = useRef(null); const recognitionRef = useRef | null>(null); const step = steps[current]!; @@ -95,11 +95,7 @@ export function CookingMode({ const currentTimer = timers.get(current); const otherRunningTimers = [...timers.entries()].filter(([idx, t]) => idx !== current && t.running); - // Wake Lock - useEffect(() => { - navigator.wakeLock?.request("screen").then((lock) => { wakeLockRef.current = lock; }).catch(() => {}); - return () => { wakeLockRef.current?.release().catch(() => {}); }; - }, []); + useWakeLock(); // Master tick: decrement all running timers once per second useEffect(() => { diff --git a/apps/web/components/recipe/keep-screen-awake.tsx b/apps/web/components/recipe/keep-screen-awake.tsx new file mode 100644 index 0000000..6284769 --- /dev/null +++ b/apps/web/components/recipe/keep-screen-awake.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { useWakeLock } from "@/lib/hooks/use-wake-lock"; + +/** Invisible — just keeps the screen awake for as long as it's mounted. */ +export function KeepScreenAwake() { + useWakeLock(); + return null; +} diff --git a/apps/web/lib/hooks/use-wake-lock.ts b/apps/web/lib/hooks/use-wake-lock.ts new file mode 100644 index 0000000..35634b8 --- /dev/null +++ b/apps/web/lib/hooks/use-wake-lock.ts @@ -0,0 +1,43 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +/** Keeps the screen awake while the component is mounted and the tab is visible. */ +export function useWakeLock(enabled = true) { + const wakeLockRef = useRef(null); + + useEffect(() => { + if (!enabled) return; + + let cancelled = false; + + async function acquire() { + try { + const lock = await navigator.wakeLock?.request("screen"); + if (cancelled) { + void lock?.release(); + return; + } + wakeLockRef.current = lock ?? null; + } catch { + // Wake Lock unsupported or denied — silently degrade. + } + } + + function handleVisibilityChange() { + if (document.visibilityState === "visible" && !wakeLockRef.current) { + void acquire(); + } + } + + void acquire(); + document.addEventListener("visibilitychange", handleVisibilityChange); + + return () => { + cancelled = true; + document.removeEventListener("visibilitychange", handleVisibilityChange); + void wakeLockRef.current?.release().catch(() => {}); + wakeLockRef.current = null; + }; + }, [enabled]); +} diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index cae5c70..1d8e70c 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -29,6 +29,7 @@ "mention": "{name} mentioned you in a comment" }, "recipe": { + "source": "Source", "share": "Share", "shareLinkCopied": "Link copied to clipboard", "shareCopyFailed": "Failed to copy link", diff --git a/apps/web/messages/fr.json b/apps/web/messages/fr.json index 4d9a656..b766bff 100644 --- a/apps/web/messages/fr.json +++ b/apps/web/messages/fr.json @@ -29,6 +29,7 @@ "mention": "{name} vous a mentionné dans un commentaire" }, "recipe": { + "source": "Source", "share": "Partager", "shareLinkCopied": "Lien copié dans le presse-papiers", "shareCopyFailed": "Échec de la copie du lien",