feat(settings): WhatsApp brand mark on the WhatsApp settings page (#1418)
The page had no visual signal that it configures a third-party channel, so it read like any other Accounted setting. Adds an inline-SVG WhatsApp mark next to the section title and on the button that opens WhatsApp (replacing the generic lucide chat bubble, which was standing in for a logo it is not). Inline SVG rather than a bundled asset: no network request, scales, and survives the CSP. SettingsSectionHeader gains an optional `mark` slot; every other settings tab is untouched and stays mark-less on purpose, so the rail does not turn into a sticker album. The green is the one place brand colour appears in settings, which matches the design rule that colour belongs to actors rather than chrome. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
88760ae6f6
commit
0449b1d0ea
@@ -12,7 +12,8 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Loader2, MessageCircle, ExternalLink } from 'lucide-react'
|
||||
import { Loader2, ExternalLink } from 'lucide-react'
|
||||
import { WhatsAppMark } from '@/components/extensions/general/WhatsAppMark'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
@@ -206,7 +207,7 @@ export function WhatsAppLinkPanel() {
|
||||
{minted.waLink ? (
|
||||
<Button asChild type="button" size="sm">
|
||||
<a href={minted.waLink} target="_blank" rel="noreferrer">
|
||||
<MessageCircle className="mr-1.5 h-4 w-4" aria-hidden="true" />
|
||||
<WhatsAppMark size={16} className="mr-1.5" />
|
||||
{t('open_whatsapp')}
|
||||
<ExternalLink className="ml-1.5 h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* WhatsApp brand mark.
|
||||
*
|
||||
* Inline SVG rather than a bundled asset: it scales, needs no network
|
||||
* request, and survives the CSP. The green (#25D366) is WhatsApp's own and
|
||||
* is deliberately the one place in settings where a brand colour appears:
|
||||
* this marks a third-party channel, so it is identity, not chrome (see
|
||||
* .claude/rules/design.md on colour belonging to actors).
|
||||
*
|
||||
* Decorative by default. Pass a `title` when the mark carries meaning that
|
||||
* the surrounding text does not already state.
|
||||
*/
|
||||
|
||||
interface WhatsAppMarkProps {
|
||||
/** Edge length in px of the rounded tile. */
|
||||
size?: number
|
||||
/** Accessible name. Omit when adjacent text already says "WhatsApp". */
|
||||
title?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function WhatsAppMark({ size = 28, title, className }: WhatsAppMarkProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 48 48"
|
||||
className={className}
|
||||
role={title ? 'img' : undefined}
|
||||
aria-hidden={title ? undefined : true}
|
||||
aria-label={title}
|
||||
>
|
||||
{title ? <title>{title}</title> : null}
|
||||
<rect width="48" height="48" rx="10" fill="#25D366" />
|
||||
<path
|
||||
fill="#FFFFFF"
|
||||
d="M32.9 26.8c-.5-.3-3-1.5-3.4-1.6-.5-.2-.8-.3-1.1.2-.3.5-1.3 1.6-1.6 2-.3.3-.6.4-1.1.1-.5-.3-2.1-.8-4-2.5-1.5-1.3-2.5-3-2.8-3.5-.3-.5 0-.8.2-1 .2-.2.5-.6.8-.9.2-.3.3-.5.5-.8.2-.3.1-.6 0-.9-.1-.3-1.1-2.7-1.5-3.7-.4-1-.8-.8-1.1-.9h-1c-.3 0-.9.1-1.3.6-.5.5-1.7 1.7-1.7 4.2 0 2.4 1.8 4.8 2 5.1.2.3 3.5 5.4 8.6 7.6 1.2.5 2.1.8 2.9 1.1 1.2.4 2.3.3 3.2.2 1-.1 3-1.2 3.4-2.4.4-1.2.4-2.2.3-2.4-.1-.2-.5-.3-1-.6z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFFFFF"
|
||||
d="M24.1 8h-.1c-8.8 0-16 7.2-16 16 0 2.8.7 5.6 2.1 8L8 40l8.2-2.1c2.3 1.3 5 1.9 7.8 1.9h.1c8.8 0 16-7.2 16-16s-7.1-15.8-16-15.8zm9.4 25.2c-2.5 2.5-5.9 3.9-9.4 3.9-2.4 0-4.7-.6-6.7-1.9l-.5-.3-5 1.3 1.3-4.9-.3-.5c-1.4-2.2-2.1-4.7-2.1-7.3 0-7.4 6-13.4 13.4-13.4 3.6 0 6.9 1.4 9.4 3.9 2.5 2.5 3.9 5.9 3.9 9.5.1 3.6-1.4 6.9-3.9 9.4z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -21,13 +21,23 @@ interface SettingsSectionHeaderProps {
|
||||
intro?: React.ReactNode
|
||||
/** Right-aligned header action (e.g. "Förhandsvisa faktura", "Skapa nyckel"). */
|
||||
action?: React.ReactNode
|
||||
/**
|
||||
* Brand mark for a section that represents a third-party channel
|
||||
* (WhatsApp today). Sections that are just Accounted settings leave this
|
||||
* unset: the serif title carries them, and a decorative icon per tab
|
||||
* would turn the settings rail into a sticker album.
|
||||
*/
|
||||
mark?: React.ReactNode
|
||||
}
|
||||
|
||||
export function SettingsSectionHeader({ title, intro, action }: SettingsSectionHeaderProps) {
|
||||
export function SettingsSectionHeader({ title, intro, action, mark }: SettingsSectionHeaderProps) {
|
||||
return (
|
||||
<header>
|
||||
<div className="flex items-baseline justify-between gap-4">
|
||||
<h2 className="font-display text-xl tracking-tight">{title}</h2>
|
||||
<div className="flex items-center gap-2.5">
|
||||
{mark ? <span className="shrink-0 leading-none">{mark}</span> : null}
|
||||
<h2 className="font-display text-xl tracking-tight">{title}</h2>
|
||||
</div>
|
||||
{action ? <div className="flex shrink-0 items-center gap-3">{action}</div> : null}
|
||||
</div>
|
||||
{intro ? (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { WhatsAppLinkPanel } from '@/components/extensions/general/WhatsAppLinkPanel'
|
||||
import { WhatsAppMark } from '@/components/extensions/general/WhatsAppMark'
|
||||
import { SettingsSectionHeader } from '@/components/settings/SettingsRows'
|
||||
|
||||
export function WhatsAppSettingsContent() {
|
||||
@@ -10,7 +11,11 @@ export function WhatsAppSettingsContent() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsSectionHeader title={tNav('whatsapp')} intro={tIntro('whatsapp')} />
|
||||
<SettingsSectionHeader
|
||||
title={tNav('whatsapp')}
|
||||
intro={tIntro('whatsapp')}
|
||||
mark={<WhatsAppMark size={26} />}
|
||||
/>
|
||||
<WhatsAppLinkPanel />
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user