mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
fix(ui): route status colours through AA-safe semantic tokens
Raw text-dracula-* utilities used as text fail WCAG AA contrast in light mode (the Alucard light theme carries no override for the raw primitives). Swap every text usage and its paired bg/border tint across the settings integration/notification/sharing/api cards, the insights recommendation, confidence, daily-briefing, health-score and coach-feedback surfaces, the bug-report banners, the copy-confirmation icon and the chart comparison caption to the theme-aware --success / --warning / --info / --destructive / --dose-accent tokens, which both themes override to an AA-safe tone. Dark mode is unchanged; decorative aria-hidden gradients and chart series fills stay on the raw primitives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b6126db754
commit
4a88db6966
19 files changed
+106
-103
No files matched your search
@@ -122,8 +122,8 @@ export default function BugReportPage() {
|
||||
{t("bugreport.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-card border-border border-l-dracula-orange flex gap-2 rounded-lg border-l-4 p-3 text-sm">
|
||||
<Info className="text-dracula-orange mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div className="bg-card border-border border-l-warning flex gap-2 rounded-lg border-l-4 p-3 text-sm">
|
||||
<Info className="text-warning mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
<p className="text-foreground font-medium">
|
||||
{t("bugreport.disabledTitle")}
|
||||
@@ -149,16 +149,16 @@ export default function BugReportPage() {
|
||||
</div>
|
||||
|
||||
{status?.configured && (
|
||||
<div className="bg-card border-border border-l-dracula-cyan flex gap-2 rounded-lg border-l-4 p-3 text-sm">
|
||||
<GitPullRequest className="text-dracula-cyan mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div className="bg-card border-border border-l-info flex gap-2 rounded-lg border-l-4 p-3 text-sm">
|
||||
<GitPullRequest className="text-info mt-0.5 h-4 w-4 shrink-0" />
|
||||
<p className="text-muted-foreground">
|
||||
{t("bugreport.githubEscalationNote")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!status?.configured && (
|
||||
<div className="bg-card border-border border-l-dracula-purple flex gap-2 rounded-lg border-l-4 p-3 text-sm">
|
||||
<Info className="text-dracula-purple mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div className="bg-card border-border border-l-dose-accent flex gap-2 rounded-lg border-l-4 p-3 text-sm">
|
||||
<Info className="text-dose-accent mt-0.5 h-4 w-4 shrink-0" />
|
||||
<p className="text-muted-foreground">
|
||||
{t("bugreport.internalOnlyNote")}
|
||||
</p>
|
||||
@@ -215,7 +215,7 @@ export default function BugReportPage() {
|
||||
<div
|
||||
className={`rounded-lg p-3 text-sm ${
|
||||
result.type === "success"
|
||||
? "bg-dracula-green/10 text-dracula-green"
|
||||
? "bg-success/10 text-success"
|
||||
: "bg-destructive/10 text-destructive"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -1407,7 +1407,7 @@ export function HealthChart({
|
||||
v1.4.19 A2 — hidden on mobile to free up the title row. */}
|
||||
{effectiveCompareBaseline !== "none" && hasComparisonData && (
|
||||
<span
|
||||
className="text-dracula-purple bg-dracula-purple/10 hidden rounded-md border border-current/30 px-1.5 py-0.5 text-[10px] font-medium tracking-wide uppercase sm:inline-flex"
|
||||
className="text-dose-accent bg-dose-accent/10 hidden rounded-md border border-current/30 px-1.5 py-0.5 text-[10px] font-medium tracking-wide uppercase sm:inline-flex"
|
||||
data-slot="chart-compare-caption"
|
||||
>
|
||||
{t(
|
||||
|
||||
@@ -96,7 +96,7 @@ export function ErrorDetails({
|
||||
)}
|
||||
<Button onClick={handleCopy} variant="outline">
|
||||
{copied ? (
|
||||
<CheckCircle2 className="text-dracula-green mr-2 h-4 w-4" />
|
||||
<CheckCircle2 className="text-success mr-2 h-4 w-4" />
|
||||
) : (
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
|
||||
@@ -112,9 +112,11 @@ describe("<DailyBriefing>", () => {
|
||||
|
||||
it("uses tone-specific colour classes per finding", () => {
|
||||
const html = render(<DailyBriefing briefing={baseBriefing} />);
|
||||
expect(html).toContain("bg-dracula-green"); // tone=good bar
|
||||
expect(html).toContain("bg-dracula-orange"); // tone=watch bar
|
||||
expect(html).toContain("bg-dracula-cyan"); // tone=info bar
|
||||
// Routed through the semantic feedback tokens (AA-safe in light mode);
|
||||
// raw `--dracula-*` text primitives have no light-mode override.
|
||||
expect(html).toContain("bg-success"); // tone=good bar
|
||||
expect(html).toContain("bg-warning"); // tone=watch bar
|
||||
expect(html).toContain("bg-info"); // tone=info bar
|
||||
});
|
||||
|
||||
it("renders the 'Generated <time>' caption when updatedAt is supplied", () => {
|
||||
|
||||
@@ -362,7 +362,7 @@ function ChatBubble({
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"border-dracula-purple/30 bg-dracula-purple/12 text-foreground",
|
||||
"border-dose-accent/30 bg-dose-accent/12 text-foreground",
|
||||
// Budget the avatar column (size-8 + gap-2.5 ≈ 2.625rem) out
|
||||
// of the 80% cap so the bubble + avatar together never
|
||||
// overflow a comfortable width on a narrow phone.
|
||||
@@ -460,7 +460,7 @@ function ChatBubble({
|
||||
: ""}
|
||||
</div>
|
||||
{safeError && (
|
||||
<p className="text-dracula-orange/90 text-xs">{safeError}</p>
|
||||
<p className="text-warning/90 text-xs">{safeError}</p>
|
||||
)}
|
||||
{/* v1.12.0 — collapse the whole provenance block ("what was
|
||||
included") behind one disclosure, collapsed by default. The
|
||||
@@ -620,7 +620,7 @@ function CoachMessageFeedback({ messageId }: CoachMessageFeedbackProps) {
|
||||
data-slot="coach-message-feedback-helpful"
|
||||
onClick={() => submit.mutate("helpful")}
|
||||
disabled={submit.isPending}
|
||||
className="text-muted-foreground hover:text-dracula-green focus-visible:ring-ring/50 inline-flex min-h-11 items-center gap-1 rounded px-2 py-1.5 text-xs outline-none focus-visible:ring-2 disabled:opacity-50"
|
||||
className="text-muted-foreground hover:text-success focus-visible:ring-ring/50 inline-flex min-h-11 items-center gap-1 rounded px-2 py-1.5 text-xs outline-none focus-visible:ring-2 disabled:opacity-50"
|
||||
>
|
||||
<ThumbsUp className="size-3" aria-hidden="true" />
|
||||
{t("insights.coach.feedbackHelpful")}
|
||||
@@ -630,7 +630,7 @@ function CoachMessageFeedback({ messageId }: CoachMessageFeedbackProps) {
|
||||
data-slot="coach-message-feedback-unhelpful"
|
||||
onClick={() => submit.mutate("unhelpful")}
|
||||
disabled={submit.isPending}
|
||||
className="text-muted-foreground hover:text-dracula-orange focus-visible:ring-ring/50 inline-flex min-h-11 items-center gap-1 rounded px-2 py-1.5 text-xs outline-none focus-visible:ring-2 disabled:opacity-50"
|
||||
className="text-muted-foreground hover:text-warning focus-visible:ring-ring/50 inline-flex min-h-11 items-center gap-1 rounded px-2 py-1.5 text-xs outline-none focus-visible:ring-2 disabled:opacity-50"
|
||||
>
|
||||
<ThumbsDown className="size-3" aria-hidden="true" />
|
||||
{t("insights.coach.feedbackUnhelpful")}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* v1.4.20 phase D reconcile — shared confidence-band classnames.
|
||||
*
|
||||
* Both `<CorrelationCard>` and `<TrendAnnotation>` paint a discrete
|
||||
* "low / moderate / high" badge with byte-identical Dracula utility
|
||||
* "low / moderate / high" badge with byte-identical semantic utility
|
||||
* classes. Per-component i18n key maps stay local because the copy
|
||||
* differs (correlationRow.* vs trendAnnotation.*); only the classname
|
||||
* table is the load-bearing duplication.
|
||||
@@ -10,7 +10,7 @@
|
||||
export type ConfidenceBand = "low" | "moderate" | "high";
|
||||
|
||||
export const CONFIDENCE_BADGE_CLASS: Record<ConfidenceBand, string> = {
|
||||
high: "border-dracula-green/40 bg-dracula-green/10 text-dracula-green",
|
||||
moderate: "border-dracula-orange/40 bg-dracula-orange/10 text-dracula-orange",
|
||||
high: "border-success/40 bg-success/10 text-success",
|
||||
moderate: "border-warning/40 bg-warning/10 text-warning",
|
||||
low: "border-dracula-comment/40 bg-dracula-comment/10 text-muted-foreground",
|
||||
};
|
||||
@@ -135,15 +135,15 @@ const METRIC_ICON: Record<
|
||||
};
|
||||
|
||||
const TONE_BAR_CLASSNAME: Record<DailyBriefingKeyFinding["tone"], string> = {
|
||||
good: "bg-dracula-green",
|
||||
watch: "bg-dracula-orange",
|
||||
info: "bg-dracula-cyan",
|
||||
good: "bg-success",
|
||||
watch: "bg-warning",
|
||||
info: "bg-info",
|
||||
};
|
||||
|
||||
const TONE_TEXT_CLASSNAME: Record<DailyBriefingKeyFinding["tone"], string> = {
|
||||
good: "text-dracula-green",
|
||||
watch: "text-dracula-orange",
|
||||
info: "text-dracula-cyan",
|
||||
good: "text-success",
|
||||
watch: "text-warning",
|
||||
info: "text-info",
|
||||
};
|
||||
|
||||
function DeltaBadge({
|
||||
@@ -275,7 +275,7 @@ export function DailyBriefing({
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles
|
||||
className="text-dracula-purple h-4 w-4 shrink-0"
|
||||
className="text-dose-accent h-4 w-4 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CardTitle className="text-base font-semibold">
|
||||
|
||||
@@ -92,17 +92,19 @@ const SOURCE_LABEL_KEY: Record<HealthScoreComponentSource, string> = {
|
||||
|
||||
/**
|
||||
* Per-source pill colour vocabulary — mirrors the Coach provenance
|
||||
* `<SourceChips>` accent for `withings` (dracula-cyan) so the user
|
||||
* reads "same provenance grammar across surfaces". Manual rides on
|
||||
* dracula-purple, Apple Health on dracula-pink, mixed on dracula-yellow.
|
||||
* `none` is a muted slate so the empty-state row reads as dimmed
|
||||
* without losing the pill affordance entirely.
|
||||
* `<SourceChips>` accent for `withings` (info/cyan) so the user reads
|
||||
* "same provenance grammar across surfaces". Routed through the
|
||||
* theme-aware semantic tokens (dose-accent / info / warning) which carry
|
||||
* an AA-safe override in light mode; the raw `--dracula-*` text primitives
|
||||
* read illegibly there. The pill always renders its source label, so the
|
||||
* label — not the hue — is the disambiguator. `none` is a muted slate so
|
||||
* the empty-state row reads as dimmed without losing the pill affordance.
|
||||
*/
|
||||
const SOURCE_PILL_CLASS: Record<HealthScoreComponentSource, string> = {
|
||||
manual: "border-dracula-purple/30 text-dracula-purple/90",
|
||||
withings: "border-dracula-cyan/30 text-dracula-cyan/90",
|
||||
appleHealth: "border-dracula-pink/30 text-dracula-pink/90",
|
||||
mixed: "border-dracula-yellow/30 text-dracula-yellow/90",
|
||||
manual: "border-dose-accent/30 text-dose-accent/90",
|
||||
withings: "border-info/30 text-info/90",
|
||||
appleHealth: "border-dose-accent/30 text-dose-accent/90",
|
||||
mixed: "border-warning/30 text-warning/90",
|
||||
none: "border-muted-foreground/30 text-muted-foreground",
|
||||
};
|
||||
|
||||
@@ -119,15 +121,15 @@ const BAND_NUMBER_CLASS: Record<HealthScoreBand, string> = {
|
||||
};
|
||||
|
||||
const BAND_BORDER_CLASS: Record<HealthScoreBand, string> = {
|
||||
green: "border-dracula-green/40",
|
||||
yellow: "border-dracula-orange/40",
|
||||
red: "border-dracula-red/40",
|
||||
green: "border-success/40",
|
||||
yellow: "border-warning/40",
|
||||
red: "border-destructive/40",
|
||||
};
|
||||
|
||||
const BAND_PROGRESS_CLASS: Record<HealthScoreBand, string> = {
|
||||
green: "bg-dracula-green",
|
||||
yellow: "bg-dracula-orange",
|
||||
red: "bg-dracula-red",
|
||||
green: "bg-success",
|
||||
yellow: "bg-warning",
|
||||
red: "bg-destructive",
|
||||
};
|
||||
|
||||
const COMPONENT_LABEL_KEY: Record<
|
||||
@@ -296,7 +298,7 @@ export function HealthScoreCard({
|
||||
{delta !== null && delta > 0 && (
|
||||
<span
|
||||
data-slot="health-score-card-delta-chip"
|
||||
className="bg-dracula-green/15 text-dracula-green rounded-full px-2 py-0.5 text-[10px] font-semibold"
|
||||
className="bg-success/15 text-success rounded-full px-2 py-0.5 text-[10px] font-semibold"
|
||||
>
|
||||
+{delta}
|
||||
</span>
|
||||
@@ -357,13 +359,13 @@ export function HealthScoreCard({
|
||||
<>
|
||||
{delta > 0 && (
|
||||
<ArrowUp
|
||||
className="text-dracula-green h-3 w-3"
|
||||
className="text-success h-3 w-3"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{delta < 0 && (
|
||||
<ArrowDown
|
||||
className="text-dracula-red h-3 w-3"
|
||||
className="text-destructive h-3 w-3"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
@@ -478,7 +480,7 @@ export function HealthScoreCard({
|
||||
<p
|
||||
data-slot="health-score-card-provenance-mixed-banner"
|
||||
role="status"
|
||||
className="border-dracula-yellow/30 text-dracula-yellow/90 bg-dracula-yellow/5 rounded border px-2 py-1 text-[10px] leading-snug"
|
||||
className="border-warning/30 text-warning/90 bg-warning/5 rounded border px-2 py-1 text-[10px] leading-snug"
|
||||
>
|
||||
{t("insights.healthScore.provenance.mixedBanner")}
|
||||
</p>
|
||||
@@ -534,8 +536,8 @@ export function HealthScoreCard({
|
||||
>
|
||||
{row.value === null ? "—" : Math.round(row.value)}
|
||||
</span>
|
||||
{/* weight share — second, narrower bar tinted
|
||||
dracula-cyan to read "provenance grammar"
|
||||
{/* weight share — second, narrower bar tinted with
|
||||
the `info` token to read "provenance grammar"
|
||||
alongside the Coach <SourceChips> accent */}
|
||||
<div
|
||||
className="bg-muted/40 h-1 w-10 shrink-0 overflow-hidden rounded-full"
|
||||
@@ -546,7 +548,7 @@ export function HealthScoreCard({
|
||||
"h-full",
|
||||
isEmpty
|
||||
? "bg-muted"
|
||||
: "bg-dracula-cyan/60",
|
||||
: "bg-info/60",
|
||||
)}
|
||||
style={{
|
||||
width: isEmpty
|
||||
|
||||
@@ -29,12 +29,12 @@ import { ConfidenceMeter } from "./confidence-meter";
|
||||
*/
|
||||
|
||||
const SEVERITY_BADGE_STYLES: Record<string, string> = {
|
||||
info: "bg-dracula-cyan/10 text-dracula-cyan border border-dracula-cyan/25",
|
||||
info: "bg-info/10 text-info border border-info/25",
|
||||
suggestion:
|
||||
"bg-dracula-purple/10 text-dracula-purple border border-dracula-purple/25",
|
||||
"bg-dose-accent/10 text-dose-accent border border-dose-accent/25",
|
||||
important:
|
||||
"bg-dracula-orange/10 text-dracula-orange border border-dracula-orange/25",
|
||||
urgent: "bg-dracula-red/10 text-dracula-red border border-dracula-red/25",
|
||||
"bg-warning/10 text-warning border border-warning/25",
|
||||
urgent: "bg-destructive/10 text-destructive border border-destructive/25",
|
||||
};
|
||||
|
||||
interface RecommendationCardProps {
|
||||
|
||||
@@ -223,7 +223,7 @@ export function RecommendationFeedback({
|
||||
className="text-muted-foreground flex items-center gap-1.5 text-xs"
|
||||
data-slot="rec-feedback-confirmation"
|
||||
>
|
||||
<Check className="text-dracula-green h-3.5 w-3.5" aria-hidden="true" />
|
||||
<Check className="text-success h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span data-feedback-confirmed={verdict}>
|
||||
{t("insights.recommendation.feedbackThanks")}
|
||||
</span>
|
||||
@@ -240,12 +240,12 @@ export function RecommendationFeedback({
|
||||
>
|
||||
{verdict === "up" ? (
|
||||
<ThumbsUp
|
||||
className="text-dracula-green h-3.5 w-3.5"
|
||||
className="text-success h-3.5 w-3.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<ThumbsDown
|
||||
className="text-dracula-orange h-3.5 w-3.5"
|
||||
className="text-warning h-3.5 w-3.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
@@ -269,7 +269,7 @@ export function RecommendationFeedback({
|
||||
title={t("insights.recommendation.feedbackHelpful")}
|
||||
disabled={disabled}
|
||||
onClick={() => submit(true)}
|
||||
className="text-muted-foreground hover:text-dracula-green disabled:hover:text-muted-foreground focus-visible:ring-ring/50 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-60"
|
||||
className="text-muted-foreground hover:text-success disabled:hover:text-muted-foreground focus-visible:ring-ring/50 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-60"
|
||||
>
|
||||
{submittingUp ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" aria-hidden="true" />
|
||||
@@ -284,7 +284,7 @@ export function RecommendationFeedback({
|
||||
title={t("insights.recommendation.feedbackNotHelpful")}
|
||||
disabled={disabled}
|
||||
onClick={() => submit(false)}
|
||||
className="text-muted-foreground hover:text-dracula-orange disabled:hover:text-muted-foreground focus-visible:ring-ring/50 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-60"
|
||||
className="text-muted-foreground hover:text-warning disabled:hover:text-muted-foreground focus-visible:ring-ring/50 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:opacity-60"
|
||||
>
|
||||
{submittingDown ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" aria-hidden="true" />
|
||||
|
||||
@@ -404,7 +404,7 @@ export function AboutSection() {
|
||||
role="alert"
|
||||
className={`mt-2 text-xs ${
|
||||
tourFeedback.type === "success"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -573,7 +573,7 @@ export function AccountSection() {
|
||||
role="alert"
|
||||
className={`text-sm ${
|
||||
saveMsgType === "success"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}`}
|
||||
>
|
||||
@@ -627,7 +627,7 @@ export function AccountSection() {
|
||||
role="alert"
|
||||
className={`mt-2 text-right text-sm ${
|
||||
passkeyMsgType === "success"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}`}
|
||||
>
|
||||
@@ -709,7 +709,7 @@ export function AccountSection() {
|
||||
role="alert"
|
||||
className={`mt-2 text-xs ${
|
||||
tourFeedback.type === "success"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}`}
|
||||
>
|
||||
@@ -778,7 +778,7 @@ export function AccountSection() {
|
||||
role="alert"
|
||||
className={`text-sm ${
|
||||
passwordMsgType === "success"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}`}
|
||||
>
|
||||
@@ -1199,7 +1199,7 @@ function AvatarSection() {
|
||||
<p
|
||||
role="alert"
|
||||
className={`mt-3 text-sm ${
|
||||
msgType === "success" ? "text-dracula-green" : "text-destructive"
|
||||
msgType === "success" ? "text-success" : "text-destructive"
|
||||
}`}
|
||||
>
|
||||
{msg}
|
||||
|
||||
@@ -248,7 +248,7 @@ function ApiTokensCard() {
|
||||
status={
|
||||
<>
|
||||
{activeTokens.length > 0 && (
|
||||
<Badge className="border-dracula-green/30 bg-dracula-green/15 text-dracula-green">
|
||||
<Badge className="border-success/30 bg-success/15 text-success">
|
||||
{t("settings.configured")}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -284,10 +284,10 @@ function ApiTokensCard() {
|
||||
|
||||
{newToken && (
|
||||
<div
|
||||
className="bg-dracula-green/10 rounded-lg p-3 text-sm"
|
||||
className="bg-success/10 rounded-lg p-3 text-sm"
|
||||
data-slot="settings-api-token-created"
|
||||
>
|
||||
<p className="text-dracula-green mb-1 font-medium">
|
||||
<p className="text-success mb-1 font-medium">
|
||||
{t("settings.tokenCreated")}
|
||||
</p>
|
||||
<div className="flex items-start gap-2">
|
||||
@@ -304,7 +304,7 @@ function ApiTokensCard() {
|
||||
data-slot="settings-api-token-copy"
|
||||
>
|
||||
{tokenCopied ? (
|
||||
<Check className="text-dracula-green h-3.5 w-3.5" />
|
||||
<Check className="text-success h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
)}
|
||||
@@ -377,7 +377,7 @@ function ApiTokensCard() {
|
||||
{t("settings.tokenExpired")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-dracula-green/15 text-dracula-green text-xs">
|
||||
<Badge className="bg-success/15 text-success text-xs">
|
||||
{t("settings.tokenActive")}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -465,7 +465,7 @@ function ApiTokensCard() {
|
||||
{t("settings.tokenExpired")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-dracula-green/15 text-dracula-green text-[10px]">
|
||||
<Badge className="bg-success/15 text-success text-[10px]">
|
||||
{t("settings.tokenActive")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -99,20 +99,19 @@ export function IntegrationStatusPill({
|
||||
|
||||
// chipClass only applies to the `connected`, `warning`, and `parked`
|
||||
// branches — `error` falls back to `variant="destructive"` and
|
||||
// `disconnected` to `variant="outline"`. The warning chip uses amber
|
||||
// tokens to signal "connected, but the upstream just returned a
|
||||
// contract-level error a reauth won't fix" — distinct from the red
|
||||
// reconnect pill. The parked chip uses the orange tokens to signal
|
||||
// "we tried to keep going for 24h but the contract mismatch persists
|
||||
// — manual intervention required" — visually heavier than warning
|
||||
// (sustained problem), lighter than error (still recoverable without
|
||||
// re-auth).
|
||||
// `disconnected` to `variant="outline"`. The warning and parked chips
|
||||
// both carry the semantic `warning` tone (an AA-safe amber that clears
|
||||
// contrast in light mode); their distinct labels + icons carry the
|
||||
// meaning — "connected, but the upstream returned a contract-level error
|
||||
// a reauth won't fix" vs "tried to keep going for 24h but the mismatch
|
||||
// persists, manual intervention required" — distinct from the red
|
||||
// reconnect pill (`error`).
|
||||
const connectedChipClass =
|
||||
"border-dracula-green/30 bg-dracula-green/15 text-dracula-green";
|
||||
"border-success/30 bg-success/15 text-success";
|
||||
const warningChipClass =
|
||||
"border-dracula-yellow/30 bg-dracula-yellow/15 text-dracula-yellow";
|
||||
"border-warning/30 bg-warning/15 text-warning";
|
||||
const parkedChipClass =
|
||||
"border-dracula-orange/30 bg-dracula-orange/15 text-dracula-orange";
|
||||
"border-warning/30 bg-warning/15 text-warning";
|
||||
|
||||
let label: string;
|
||||
let icon: React.ReactNode;
|
||||
|
||||
@@ -433,9 +433,9 @@ function WithingsCard({
|
||||
{pillState === "parked" && (
|
||||
<div
|
||||
data-testid="withings-parked-banner"
|
||||
className="border-dracula-orange/30 bg-dracula-orange/10 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm"
|
||||
className="border-warning/30 bg-warning/10 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<span className="text-dracula-orange min-w-0 break-words text-xs">
|
||||
<span className="text-warning min-w-0 break-words text-xs">
|
||||
{t("settings.integrationPill.parkedReconnect")}
|
||||
</span>
|
||||
<Button
|
||||
@@ -468,7 +468,7 @@ function WithingsCard({
|
||||
{resume.isSuccess && resume.data?.wasParked && (
|
||||
<p
|
||||
role="status"
|
||||
className="text-dracula-green text-xs"
|
||||
className="text-success text-xs"
|
||||
data-testid="withings-resume-success"
|
||||
>
|
||||
{t("settings.integrationPill.resumeSuccess")}
|
||||
@@ -553,7 +553,7 @@ function WithingsCard({
|
||||
{credsMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${credsMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${credsMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{credsMsg}
|
||||
</p>
|
||||
@@ -656,7 +656,7 @@ function WithingsCard({
|
||||
{syncMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{syncMsg}
|
||||
</p>
|
||||
@@ -857,9 +857,9 @@ function WhoopCard({
|
||||
{pillState === "parked" && (
|
||||
<div
|
||||
data-testid="whoop-parked-banner"
|
||||
className="border-dracula-orange/30 bg-dracula-orange/10 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm"
|
||||
className="border-warning/30 bg-warning/10 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<span className="text-dracula-orange min-w-0 break-words text-xs">
|
||||
<span className="text-warning min-w-0 break-words text-xs">
|
||||
{t("settings.integrationPill.parkedReconnect")}
|
||||
</span>
|
||||
<Button
|
||||
@@ -892,7 +892,7 @@ function WhoopCard({
|
||||
{resume.isSuccess && resume.data?.wasParked && (
|
||||
<p
|
||||
role="status"
|
||||
className="text-dracula-green text-xs"
|
||||
className="text-success text-xs"
|
||||
data-testid="whoop-resume-success"
|
||||
>
|
||||
{t("settings.integrationPill.resumeSuccess")}
|
||||
@@ -972,7 +972,7 @@ function WhoopCard({
|
||||
{credsMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${credsMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${credsMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{credsMsg}
|
||||
</p>
|
||||
@@ -1074,7 +1074,7 @@ function WhoopCard({
|
||||
{syncMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{syncMsg}
|
||||
</p>
|
||||
@@ -1303,9 +1303,9 @@ function FitbitCard({
|
||||
{pillState === "parked" && (
|
||||
<div
|
||||
data-testid="fitbit-parked-banner"
|
||||
className="border-dracula-orange/30 bg-dracula-orange/10 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm"
|
||||
className="border-warning/30 bg-warning/10 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
<span className="text-dracula-orange min-w-0 break-words text-xs">
|
||||
<span className="text-warning min-w-0 break-words text-xs">
|
||||
{t("settings.integrationPill.parkedReconnect")}
|
||||
</span>
|
||||
<Button
|
||||
@@ -1338,7 +1338,7 @@ function FitbitCard({
|
||||
{resume.isSuccess && resume.data?.wasParked && (
|
||||
<p
|
||||
role="status"
|
||||
className="text-dracula-green text-xs"
|
||||
className="text-success text-xs"
|
||||
data-testid="fitbit-resume-success"
|
||||
>
|
||||
{t("settings.integrationPill.resumeSuccess")}
|
||||
@@ -1418,7 +1418,7 @@ function FitbitCard({
|
||||
{credsMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${credsMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${credsMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{credsMsg}
|
||||
</p>
|
||||
@@ -1520,7 +1520,7 @@ function FitbitCard({
|
||||
{syncMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${syncMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{syncMsg}
|
||||
</p>
|
||||
@@ -1887,7 +1887,7 @@ function MoodLogCard({
|
||||
{msg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${msgType === "error" ? "text-destructive" : "text-dracula-green"}`}
|
||||
className={`text-sm ${msgType === "error" ? "text-destructive" : "text-success"}`}
|
||||
>
|
||||
{msg}
|
||||
</p>
|
||||
|
||||
@@ -149,7 +149,7 @@ export function NtfyCard({ isAuthenticated }: { isAuthenticated: boolean }) {
|
||||
{saveMsg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${saveMsgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${saveMsgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{saveMsg}
|
||||
</p>
|
||||
|
||||
@@ -336,10 +336,10 @@ function ShareLinksCard() {
|
||||
|
||||
{newToken && (
|
||||
<div
|
||||
className="bg-dracula-green/10 space-y-2 rounded-lg p-3 text-sm"
|
||||
className="bg-success/10 space-y-2 rounded-lg p-3 text-sm"
|
||||
data-testid="share-token-reveal"
|
||||
>
|
||||
<p className="text-dracula-green font-medium">
|
||||
<p className="text-success font-medium">
|
||||
{t("settings.sharing.tokenCreated")}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-[11px]">
|
||||
@@ -360,7 +360,7 @@ function ShareLinksCard() {
|
||||
</Button>
|
||||
</div>
|
||||
{copied && (
|
||||
<p className="text-dracula-green text-[11px]">
|
||||
<p className="text-success text-[11px]">
|
||||
{t("settings.sharing.copied")}
|
||||
</p>
|
||||
)}
|
||||
@@ -390,7 +390,7 @@ function ShareLinksCard() {
|
||||
{link.label}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<Badge className="bg-dracula-green/15 text-dracula-green text-[10px]">
|
||||
<Badge className="bg-success/15 text-success text-[10px]">
|
||||
{t("settings.sharing.statusActive")}
|
||||
</Badge>
|
||||
{link.allowFhirApi && (
|
||||
|
||||
@@ -157,7 +157,7 @@ export function TelegramCard({
|
||||
role="alert"
|
||||
className={`text-sm ${
|
||||
msgType === "success"
|
||||
? "text-dracula-green"
|
||||
? "text-success"
|
||||
: "text-destructive"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -216,7 +216,7 @@ export function WebPushCard() {
|
||||
{msg && (
|
||||
<p
|
||||
role="alert"
|
||||
className={`text-sm ${msgType === "success" ? "text-dracula-green" : "text-destructive"}`}
|
||||
className={`text-sm ${msgType === "success" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{msg}
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user