Skip to content
File

Blob: src/client/components/external-link-redirect.tsx

typescript103 lines
1import { AlertTriangle, ArrowUpRight, Copy, Shield } from "lucide-react";
2import { useMemo, useState } from "react";
3import { Link, useSearchParams } from "react-router-dom";
4import { toast } from "@/client/components/toast";
5import { parseExternalLinkTarget } from "@/client/lib/external-link";
6 
7export function ExternalLinkRedirect() {
8 const [searchParams] = useSearchParams();
9 const [copied, setCopied] = useState(false);
10 const target = useMemo(() => parseExternalLinkTarget(searchParams.get("url")), [searchParams]);
11 
12 const handleCopy = async () => {
13 if (!target) {
14 return;
15 }
16 
17 try {
18 await navigator.clipboard.writeText(target.toString());
19 setCopied(true);
20 toast.success("Link copied to clipboard.");
21 window.setTimeout(() => setCopied(false), 1600);
22 } catch {
23 toast.error("Could not copy the link.");
24 }
25 };
26 
27 return (
28 <main className="animate-slide-up mx-auto max-w-2xl pt-2">
29 <section className="overflow-hidden rounded-xl border border-zinc-800/60 bg-zinc-900 shadow-2xl shadow-black/20">
30 <div className="border-b border-zinc-800/60 bg-zinc-900 px-5 py-4 sm:px-6">
31 <div className="flex items-center gap-3">
32 <span className="inline-grid h-11 w-11 place-items-center rounded-lg bg-amber-500/10 text-amber-300 ring-1 ring-inset ring-amber-500/20">
33 <Shield className="h-5 w-5" />
34 </span>
35 <div>
36 <h1 className="text-lg font-semibold tracking-tight text-zinc-100 sm:text-xl">Leaving flamemail</h1>
37 <p className="mt-1 text-sm text-zinc-400">Links in emails open through this safety check first.</p>
38 </div>
39 </div>
40 </div>
41
42 <div className="space-y-6 px-5 py-5 sm:px-6 sm:py-6">
43 {target ? (
44 <>
45 <div className="rounded-lg border border-zinc-800/70 bg-zinc-900 p-4">
46 <span className="block text-xs font-semibold uppercase tracking-[0.2em] text-zinc-500">
47 Destination
48 </span>
49 <strong className="mt-3 block break-all text-sm font-semibold text-zinc-100 sm:text-base">
50 {target.host}
51 </strong>
52 <p className="mt-2 break-all font-mono text-xs leading-relaxed text-zinc-400 sm:text-sm">
53 {target.toString()}
54 </p>
55 </div>
56
57 <div className="flex items-start gap-3 rounded-lg border border-amber-500/20 bg-amber-500/8 p-4 text-sm text-amber-100/90">
58 <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-300" />
59 <p>
60 Email links can be misleading. Check the destination carefully before continuing, especially if the
61 message asks for passwords, codes, or payments.
62 </p>
63 </div>
64
65 <div className="flex flex-col gap-3 sm:flex-row">
66 <a
67 href={target.toString()}
68 rel="noopener noreferrer nofollow"
69 className="inline-flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-accent-400"
70 >
71 Continue to site
72 <ArrowUpRight className="h-4 w-4" />
73 </a>
74 <button
75 type="button"
76 onClick={handleCopy}
77 className="inline-flex items-center justify-center gap-2 rounded-lg border border-zinc-700/70 bg-zinc-800/70 px-4 py-2.5 text-sm font-medium text-zinc-200 transition-colors hover:border-zinc-600 hover:bg-zinc-800"
78 >
79 <Copy className="h-4 w-4" />
80 {copied ? "Copied" : "Copy URL"}
81 </button>
82 <Link
83 to="/create"
84 className="inline-flex items-center justify-center rounded-lg border border-zinc-800/80 bg-zinc-800/40 px-4 py-2.5 text-sm font-medium text-zinc-400 transition-colors hover:border-zinc-700 hover:text-zinc-200"
85 >
86 Back to inboxes
87 </Link>
88 </div>
89 </>
90 ) : (
91 <div className="rounded-lg border border-red-500/20 bg-red-500/8 p-4 text-sm text-red-100/90">
92 This email link is missing a valid destination.{" "}
93 <Link to="/create" className="font-medium text-red-300 underline underline-offset-2 hover:text-red-200">
94 Back to your inboxes
95 </Link>
96 </div>
97 )}
98 </div>
99 </section>
100 </main>
101 );
102}