Skip to content

Commit 8d7f385

Browse files
authored
feat: add share button for comparison results (#218)
* feat: add share result translations * feat: add Arabic share result translations * feat: add share button to result dashboard
1 parent 5691456 commit 8d7f385

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

‎src/features/comparison/components/result-dashboard.tsx‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"use client";
22

3-
import { useMemo } from "react";
3+
import { useMemo, useState, useRef } from "react";
44
import Link from "next/link";
55
import type { Route } from "next";
6-
import { Check, Copy, ExternalLink, Trophy } from "lucide-react";
6+
import { Check, Copy, ExternalLink, Trophy, Share2, XCircle } from "lucide-react";
77
import { useSearchParams } from "next/navigation";
88
import { useClipboardCopy } from "@/hooks";
99
import { Avatar } from "@/components/layout/avatar";
@@ -63,6 +63,34 @@ export function ResultDashboard({
6363
const { t } = useTranslation();
6464
const searchParams = useSearchParams();
6565
const { copied, copy } = useClipboardCopy();
66+
const { copy: copyShare } = useClipboardCopy();
67+
const [shareState, setShareState] = useState<"idle" | "success" | "error">("idle");
68+
const shareTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
69+
70+
const setShareFeedback = (state: "success" | "error") => {
71+
setShareState(state);
72+
if (shareTimerRef.current) clearTimeout(shareTimerRef.current);
73+
shareTimerRef.current = setTimeout(() => setShareState("idle"), 2000);
74+
};
75+
76+
const handleShare = async () => {
77+
const url = window.location.href;
78+
try {
79+
if (navigator.share) {
80+
await navigator.share({ url });
81+
setShareFeedback("success");
82+
} else {
83+
const success = await copyShare(url);
84+
setShareFeedback(success ? "success" : "error");
85+
}
86+
} catch (err: unknown) {
87+
if (err instanceof Error && err.name === "AbortError") {
88+
return;
89+
}
90+
setShareFeedback("error");
91+
}
92+
};
93+
6694
const methodologyHref = useMemo(() => {
6795
const query = searchParams.toString();
6896
return query ? `/scoring-methodology?${query}` : "/scoring-methodology";
@@ -454,7 +482,31 @@ export function ResultDashboard({
454482
</Card>
455483
) : null}
456484

457-
<div className="flex justify-end">
485+
<div className="flex justify-end gap-2">
486+
<Button
487+
variant="secondary"
488+
size="sm"
489+
onClick={handleShare}
490+
className="flex items-center gap-2"
491+
aria-label={t("results.shareAria")}
492+
>
493+
{shareState === "success" ? (
494+
<>
495+
<Check className="h-4 w-4 text-green-500" />
496+
<span className="text-green-500">{t("results.shareSuccess")}</span>
497+
</>
498+
) : shareState === "error" ? (
499+
<>
500+
<XCircle className="h-4 w-4 text-destructive" />
501+
<span className="text-destructive">{t("results.shareError")}</span>
502+
</>
503+
) : (
504+
<>
505+
<Share2 className="h-4 w-4" />
506+
{t("results.share")}
507+
</>
508+
)}
509+
</Button>
458510
<Button
459511
variant="secondary"
460512
size="sm"

‎src/locales/ar.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@
172172
"results.copy": "نسخ النتيجة",
173173
"results.copyAria": "نسخ نتائج المقارنة إلى الحافظة",
174174
"results.pointsLead": "+{points} نقطة",
175+
"results.share": "مشاركة",
176+
"results.shareAria": "مشاركة رابط المقارنة",
177+
"results.shareSuccess": "تمت مشاركة الرابط!",
178+
"results.shareError": "فشلت مشاركة الرابط",
175179
"score.rawLabel": "الدرجة الخام: {value}",
176180
"results.scoreVersion": "إصدار التقييم",
177181
"section.language": "النتيجة اللغوية",

‎src/locales/en.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@
172172
"results.copy": "Copy Result",
173173
"results.copyAria": "Copy comparison results to clipboard",
174174
"results.pointsLead": "+{points} points",
175+
"results.share": "Share",
176+
"results.shareAria": "Share comparison link",
177+
"results.shareSuccess": "Link shared!",
178+
"results.shareError": "Failed to share link",
175179
"score.rawLabel": "Raw: {value}",
176180
"results.scoreVersion": "Score version",
177181
"section.language": "Language-focused Score",

0 commit comments

Comments
 (0)