polish(web): favicon, Punktfunk casing, display-save toast, idle stop button
- Favicon: the brand-mark lens SVG at /favicon.svg, linked from the root head. - Branding: user-facing "punktfunk" → "Punktfunk" (tab title, app_name, pairing prose, the logo aria-labels/SVG titles) — protocol identifiers (punktfunk/1, _punktfunk._udp, binary/module names) stay lowercase. - The display policy auto-saves on every preset pick / field edit with no signal; mount @unom/ui's sonner Toaster in the root and fire a success toast from the save funnel's onSuccess (new i18n key, en + de). First toast usage in the console — errors stay inline as before. - The stop-session button rendered destructive-red even with nothing to stop: variant switches to the muted `secondary` while no session is active. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
"app_name": "punktfunk",
|
"app_name": "Punktfunk",
|
||||||
"app_tagline": "Verwaltungskonsole",
|
"app_tagline": "Verwaltungskonsole",
|
||||||
|
"display_settings_saved": "Display-Konfiguration gespeichert",
|
||||||
"nav_dashboard": "Übersicht",
|
"nav_dashboard": "Übersicht",
|
||||||
"nav_host": "Host",
|
"nav_host": "Host",
|
||||||
"nav_displays": "Virtuelle Anzeigen",
|
"nav_displays": "Virtuelle Anzeigen",
|
||||||
@@ -128,8 +129,8 @@
|
|||||||
"pairing_success": "Erfolgreich gekoppelt.",
|
"pairing_success": "Erfolgreich gekoppelt.",
|
||||||
"pairing_failed": "Kopplung fehlgeschlagen — PIN prüfen und erneut versuchen.",
|
"pairing_failed": "Kopplung fehlgeschlagen — PIN prüfen und erneut versuchen.",
|
||||||
"pairing_native_title": "Gerät koppeln",
|
"pairing_native_title": "Gerät koppeln",
|
||||||
"pairing_native_desc": "Zeige hier eine Einmal-PIN an und gib sie in deiner punktfunk-App ein, um dieses Gerät zu koppeln.",
|
"pairing_native_desc": "Zeige hier eine Einmal-PIN an und gib sie in deiner Punktfunk-App ein, um dieses Gerät zu koppeln.",
|
||||||
"pairing_native_disabled": "Der native Host läuft nicht. Starte ihn mit `serve --native`, um punktfunk-Geräte zu koppeln.",
|
"pairing_native_disabled": "Der native Host läuft nicht. Starte ihn mit `serve --native`, um Punktfunk-Geräte zu koppeln.",
|
||||||
"pairing_native_arm": "Gerät koppeln",
|
"pairing_native_arm": "Gerät koppeln",
|
||||||
"pairing_native_enter": "Gib diese PIN auf deinem Gerät ein:",
|
"pairing_native_enter": "Gib diese PIN auf deinem Gerät ein:",
|
||||||
"pairing_native_expires": "Läuft ab in",
|
"pairing_native_expires": "Läuft ab in",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
"app_name": "punktfunk",
|
"app_name": "Punktfunk",
|
||||||
"app_tagline": "management console",
|
"app_tagline": "management console",
|
||||||
|
"display_settings_saved": "Display configuration saved",
|
||||||
"nav_dashboard": "Dashboard",
|
"nav_dashboard": "Dashboard",
|
||||||
"nav_host": "Host",
|
"nav_host": "Host",
|
||||||
"nav_displays": "Virtual displays",
|
"nav_displays": "Virtual displays",
|
||||||
@@ -128,8 +129,8 @@
|
|||||||
"pairing_success": "Paired successfully.",
|
"pairing_success": "Paired successfully.",
|
||||||
"pairing_failed": "Pairing failed — check the PIN and try again.",
|
"pairing_failed": "Pairing failed — check the PIN and try again.",
|
||||||
"pairing_native_title": "Pair a device",
|
"pairing_native_title": "Pair a device",
|
||||||
"pairing_native_desc": "Show a one-time PIN here, then enter it in your punktfunk app to pair this device.",
|
"pairing_native_desc": "Show a one-time PIN here, then enter it in your Punktfunk app to pair this device.",
|
||||||
"pairing_native_disabled": "The native host isn't running. Start it with `serve --native` to pair punktfunk devices.",
|
"pairing_native_disabled": "The native host isn't running. Start it with `serve --native` to pair Punktfunk devices.",
|
||||||
"pairing_native_arm": "Pair a device",
|
"pairing_native_arm": "Pair a device",
|
||||||
"pairing_native_enter": "Enter this PIN on your device:",
|
"pairing_native_enter": "Enter this PIN on your device:",
|
||||||
"pairing_native_expires": "Expires in",
|
"pairing_native_expires": "Expires in",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||||
|
<!-- Punktfunk brand mark: two overlapping circles forming a lens (see src/components/brand-mark.tsx). -->
|
||||||
|
<path d="M403.037,791.672c107.586,0 194.41,-86.824 194.41,-194.41c0,-107.586 -86.824,-194.41 -194.41,-194.41c-107.586,0 -194.41,86.824 -194.41,194.41c0,107.586 86.824,194.41 194.41,194.41Z" fill="#a79ff8"/>
|
||||||
|
<path d="M735.276,540.321c76.075,-76.075 76.075,-198.862 0,-274.937c-76.075,-76.075 -198.862,-76.075 -274.937,0c-76.075,76.075 -76.075,198.862 0,274.937c76.075,76.075 198.862,76.075 274.937,0Z" fill="#6c5bf3"/>
|
||||||
|
<path d="M647.84,590.737c-64.853,17.403 -136.871,0.597 -187.885,-50.416c-51.013,-51.013 -67.819,-123.032 -50.416,-187.885c64.853,-17.403 136.871,-0.597 187.885,50.416c51.013,51.013 67.819,123.032 50.416,187.885Z" fill="#d2c9fb"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 835 B |
@@ -48,7 +48,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
|||||||
<aside className="sticky top-0 hidden h-dvh w-60 shrink-0 flex-col overflow-y-auto border-r bg-card/40 p-4 sm:flex">
|
<aside className="sticky top-0 hidden h-dvh w-60 shrink-0 flex-col overflow-y-auto border-r bg-card/40 p-4 sm:flex">
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
aria-label="punktfunk"
|
aria-label="Punktfunk"
|
||||||
className="mb-7 flex items-center gap-2 px-2 pt-1"
|
className="mb-7 flex items-center gap-2 px-2 pt-1"
|
||||||
>
|
>
|
||||||
<BrandMark className="size-7 drop-shadow-[0_2px_12px_rgba(108,91,243,0.45)]" />
|
<BrandMark className="size-7 drop-shadow-[0_2px_12px_rgba(108,91,243,0.45)]" />
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
export function BrandMark({ className }: { className?: string }) {
|
export function BrandMark({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
aria-label="punktfunk"
|
aria-label="Punktfunk"
|
||||||
role="img"
|
role="img"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 1000 1000"
|
viewBox="0 0 1000 1000"
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
<title>punktfunk</title>
|
<title>Punktfunk</title>
|
||||||
<path
|
<path
|
||||||
d="M403.037,791.672c107.586,0 194.41,-86.824 194.41,-194.41c0,-107.586 -86.824,-194.41 -194.41,-194.41c-107.586,0 -194.41,86.824 -194.41,194.41c0,107.586 86.824,194.41 194.41,194.41Z"
|
d="M403.037,791.672c107.586,0 194.41,-86.824 194.41,-194.41c0,-107.586 -86.824,-194.41 -194.41,-194.41c-107.586,0 -194.41,86.824 -194.41,194.41c0,107.586 86.824,194.41 194.41,194.41Z"
|
||||||
fill="#a79ff8"
|
fill="#a79ff8"
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ export function Wordmark({ className }: { className?: string }) {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="punktfunk"
|
aria-label="Punktfunk"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 579 136"
|
viewBox="0 0 579 136"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
className={cn("w-auto text-highlight", className)}
|
className={cn("w-auto text-highlight", className)}
|
||||||
>
|
>
|
||||||
<title>punktfunk</title>
|
<title>Punktfunk</title>
|
||||||
<path d="M16.782,16.051l0,102.687l31.253,0l0,-35.563l73.436,0l0,-23.555l-73.436,0l0,-19.398l77.285,0l0,-24.171l-108.537,0Z" />
|
<path d="M16.782,16.051l0,102.687l31.253,0l0,-35.563l73.436,0l0,-23.555l-73.436,0l0,-19.398l77.285,0l0,-24.171l-108.537,0Z" />
|
||||||
<path d="M131.785,16.051l0,47.264c0.154,16.627 0.154,16.627 0.308,20.014c0.77,15.087 2.463,21.4 7.544,26.634c7.698,8.16 20.014,10.315 59.272,10.315c23.863,0 34.178,-0.616 43.415,-2.463c11.7,-2.463 19.552,-10.623 21.246,-22.323c0.924,-7.236 1.078,-8.929 1.54,-32.176l0,-47.264l-31.253,0l0,47.264c0,2.155 -0.154,7.082 -0.308,10.623c-0.462,9.699 -1.232,12.47 -3.695,15.087c-3.387,3.695 -9.853,4.619 -31.407,4.619c-26.634,0 -32.638,-1.693 -34.332,-9.853c-0.77,-4.157 -0.77,-4.311 -1.078,-20.476l0,-47.264l-31.253,0Z" />
|
<path d="M131.785,16.051l0,47.264c0.154,16.627 0.154,16.627 0.308,20.014c0.77,15.087 2.463,21.4 7.544,26.634c7.698,8.16 20.014,10.315 59.272,10.315c23.863,0 34.178,-0.616 43.415,-2.463c11.7,-2.463 19.552,-10.623 21.246,-22.323c0.924,-7.236 1.078,-8.929 1.54,-32.176l0,-47.264l-31.253,0l0,47.264c0,2.155 -0.154,7.082 -0.308,10.623c-0.462,9.699 -1.232,12.47 -3.695,15.087c-3.387,3.695 -9.853,4.619 -31.407,4.619c-26.634,0 -32.638,-1.693 -34.332,-9.853c-0.77,-4.157 -0.77,-4.311 -1.078,-20.476l0,-47.264l-31.253,0Z" />
|
||||||
<path d="M271.575,15.943l0,102.687l31.868,0l-0.77,-76.669l3.387,0l54.038,76.669l54.346,0l0,-102.687l-31.868,0l0.77,76.515l-3.233,0l-53.73,-76.515l-54.808,0Z" />
|
<path d="M271.575,15.943l0,102.687l31.868,0l-0.77,-76.669l3.387,0l54.038,76.669l54.346,0l0,-102.687l-31.868,0l0.77,76.515l-3.233,0l-53.73,-76.515l-54.808,0Z" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
useRouterState,
|
useRouterState,
|
||||||
} from "@tanstack/react-router";
|
} from "@tanstack/react-router";
|
||||||
import "@fontsource-variable/geist";
|
import "@fontsource-variable/geist";
|
||||||
|
import { Toaster } from "@unom/ui/toast";
|
||||||
import { AppShell } from "@/components/app-shell";
|
import { AppShell } from "@/components/app-shell";
|
||||||
import appCss from "@/styles.css?url";
|
import appCss from "@/styles.css?url";
|
||||||
|
|
||||||
@@ -22,9 +23,12 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|||||||
{ charSet: "utf-8" },
|
{ charSet: "utf-8" },
|
||||||
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
||||||
{ name: "color-scheme", content: "dark light" },
|
{ name: "color-scheme", content: "dark light" },
|
||||||
{ title: "punktfunk" },
|
{ title: "Punktfunk" },
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{ rel: "stylesheet", href: appCss },
|
||||||
|
{ rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
|
||||||
],
|
],
|
||||||
links: [{ rel: "stylesheet", href: appCss }],
|
|
||||||
}),
|
}),
|
||||||
component: RootComponent,
|
component: RootComponent,
|
||||||
});
|
});
|
||||||
@@ -47,6 +51,8 @@ function RootComponent() {
|
|||||||
<Outlet />
|
<Outlet />
|
||||||
</AppShell>
|
</AppShell>
|
||||||
)}
|
)}
|
||||||
|
{/* Sonner toaster (lazy client-side) — success feedback for auto-saved settings. */}
|
||||||
|
<Toaster />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const DashboardView: FC<{
|
|||||||
{m.action_request_idr()}
|
{m.action_request_idr()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant={s.session ? "destructive" : "secondary"}
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={!s.session || isStopping}
|
disabled={!s.session || isStopping}
|
||||||
onClick={onStopSession}
|
onClick={onStopSession}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { Button } from "@unom/ui/button";
|
import { Button } from "@unom/ui/button";
|
||||||
|
import { toast } from "@unom/ui/toast";
|
||||||
import { Pencil, Plus, RefreshCw, Trash2 } from "lucide-react";
|
import { Pencil, Plus, RefreshCw, Trash2 } from "lucide-react";
|
||||||
import { type FC, type MouseEvent, type ReactNode, useEffect, useState } from "react";
|
import { type FC, type MouseEvent, type ReactNode, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -62,6 +63,9 @@ export const DisplaySection: FC = () => {
|
|||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
setDraft(res.settings);
|
setDraft(res.settings);
|
||||||
qc.invalidateQueries({ queryKey: getGetDisplaySettingsQueryKey() });
|
qc.invalidateQueries({ queryKey: getGetDisplaySettingsQueryKey() });
|
||||||
|
// The policy auto-saves on every preset pick / field edit — without a signal
|
||||||
|
// users kept looking for a Save button. Errors stay inline (apiErrorMessage).
|
||||||
|
toast.success(m.display_settings_saved());
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user