Write on the margins of the internet. Powered by the AT Protocol.
margin.at
extension
web
atproto
comments
1import React, { useState } from "react";
2import { Button } from "../ui";
3import { ExternalLink, Shield } from "lucide-react";
4import { addSkippedHostname } from "../../store/preferences";
5
6interface ExternalLinkModalProps {
7 isOpen: boolean;
8 onClose: () => void;
9 url: string | null;
10}
11
12export default function ExternalLinkModal({
13 isOpen,
14 onClose,
15 url,
16}: ExternalLinkModalProps) {
17 const [dontAskAgain, setDontAskAgain] = useState(false);
18
19 if (!isOpen || !url) return null;
20
21 const displayUrl = url.split("#:~:text=")[0];
22
23 const handleContinue = () => {
24 if (dontAskAgain && url) {
25 try {
26 const hostname = new URL(url).hostname;
27 addSkippedHostname(hostname);
28 } catch (e) {
29 console.error("Invalid URL", e);
30 }
31 }
32 window.open(url, "_blank", "noopener,noreferrer");
33 onClose();
34 };
35
36 const hostname = (() => {
37 try {
38 return new URL(url).hostname;
39 } catch {
40 return "this site";
41 }
42 })();
43
44 return (
45 <div
46 className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm animate-fade-in"
47 onClick={onClose}
48 >
49 <div
50 className="bg-white dark:bg-surface-900 rounded-xl shadow-2xl max-w-md w-full animate-scale-in ring-1 ring-surface-200 dark:ring-surface-700 overflow-hidden"
51 onClick={(e) => e.stopPropagation()}
52 >
53 <div className="px-6 pt-6 pb-4">
54 <div className="flex items-start gap-3">
55 <div className="w-9 h-9 bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
56 <Shield size={18} />
57 </div>
58 <div className="min-w-0">
59 <h2 className="text-base font-semibold text-surface-900 dark:text-white">
60 Leaving Margin
61 </h2>
62 <p className="text-sm text-surface-500 dark:text-surface-400 mt-1">
63 You're about to visit an external site.
64 </p>
65 </div>
66 </div>
67
68 <div className="mt-4 flex items-center gap-2 bg-surface-50 dark:bg-surface-800/60 border border-surface-200 dark:border-surface-700 rounded-lg px-3 py-2.5">
69 <ExternalLink
70 size={14}
71 className="text-surface-400 dark:text-surface-500 flex-shrink-0"
72 />
73 <span className="text-sm text-surface-700 dark:text-surface-300 break-all line-clamp-2">
74 {displayUrl}
75 </span>
76 </div>
77 </div>
78
79 <div className="px-6 pb-5 pt-2 flex flex-col gap-3">
80 <label className="flex items-center gap-2 cursor-pointer select-none group">
81 <input
82 type="checkbox"
83 checked={dontAskAgain}
84 onChange={(e) => setDontAskAgain(e.target.checked)}
85 className="rounded border-surface-300 dark:border-surface-600 text-primary-600 focus:ring-primary-500 w-3.5 h-3.5 cursor-pointer"
86 />
87 <span className="text-xs text-surface-500 dark:text-surface-400 group-hover:text-surface-600 dark:group-hover:text-surface-300 transition-colors">
88 Always allow links to{" "}
89 <span className="font-medium text-surface-700 dark:text-surface-200">
90 {hostname}
91 </span>
92 </span>
93 </label>
94
95 <div className="flex gap-2">
96 <Button
97 onClick={onClose}
98 variant="ghost"
99 className="flex-1 justify-center"
100 >
101 Cancel
102 </Button>
103 <Button
104 onClick={handleContinue}
105 variant="primary"
106 className="flex-1 justify-center"
107 icon={<ExternalLink size={14} />}
108 >
109 Open Link
110 </Button>
111 </div>
112 </div>
113 </div>
114 </div>
115 );
116}