Something went wrong. Try again.
(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol.
margin.at
extension web atproto comments
Something went wrong. Try again.
7.8 kB · 256 lines
JSX
at ui-refactor
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257import { useState, useEffect } from "react";import { X, ChevronRight, Loader2, AlertCircle } from "lucide-react";import { BlackskyIcon, NorthskyIcon, BlueskyIcon, TopphieIcon } from "./Icons";import { startSignup } from "../api/client";import logo from "../assets/logo.svg";
const RECOMMENDED_PROVIDER = { id: "margin", name: "Margin", service: "https://pds.margin.at", Icon: null, description: "Hosted by Margin, the easiest way to get started", isMargin: true,};
const OTHER_PROVIDERS = [ { id: "bluesky", name: "Bluesky", service: "https://bsky.social", Icon: BlueskyIcon, description: "The main network", }, { id: "blacksky", name: "Blacksky", service: "https://blacksky.app", Icon: BlackskyIcon, description: "For the Culture. A safe space for Black users and allies", }, { id: "northsky", name: "Northsky", service: "https://northsky.social", Icon: NorthskyIcon, description: "A Canadian-based worker-owned cooperative", }, { id: "topphie", name: "Topphie", service: "https://tophhie.social", Icon: TopphieIcon, description: "A welcoming and friendly community", }, { id: "altq", name: "AltQ", service: "https://altq.net", Icon: null, description: "An independent, self-hosted PDS instance", }, { id: "custom", name: "Custom", service: "", custom: true, Icon: null, description: "Connect to your own or another custom PDS", },];
export default function SignUpModal({ onClose }) { const [showOtherProviders, setShowOtherProviders] = useState(false); const [showCustomInput, setShowCustomInput] = useState(false); const [customService, setCustomService] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null);
useEffect(() => { document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = "unset"; }; }, []);
const handleProviderSelect = async (provider) => { if (provider.custom) { setShowCustomInput(true); return; }
setLoading(true); setError(null);
try { const result = await startSignup(provider.service); if (result.authorizationUrl) { window.location.href = result.authorizationUrl; } } catch (err) { console.error(err); setError("Could not connect to this provider. Please try again."); setLoading(false); } };
const handleCustomSubmit = async (e) => { e.preventDefault(); if (!customService.trim()) return;
setLoading(true); setError(null);
let serviceUrl = customService.trim(); if (!serviceUrl.startsWith("http")) { serviceUrl = `https://${serviceUrl}`; }
try { const result = await startSignup(serviceUrl); if (result.authorizationUrl) { window.location.href = result.authorizationUrl; } } catch (err) { console.error(err); setError("Could not connect to this PDS. Please check the URL."); setLoading(false); } };
return ( <div className="modal-overlay"> <div className="modal-content signup-modal"> <button className="modal-close" onClick={onClose}> <X size={20} /> </button>
{loading ? ( <div className="signup-step" style={{ textAlign: "center" }}> <Loader2 size={32} className="spinner" /> <p style={{ marginTop: "1rem", color: "var(--text-secondary)" }}> Connecting to provider... </p> </div> ) : showCustomInput ? ( <div className="signup-step"> <h2>Custom Provider</h2> <form onSubmit={handleCustomSubmit}> <div className="form-group"> <label>PDS address (e.g. pds.example.com)</label> <input type="text" value={customService} onChange={(e) => setCustomService(e.target.value)} placeholder="pds.example.com" autoFocus /> </div>
{error && ( <div className="error-message"> <AlertCircle size={16} /> {error} </div> )}
<div className="modal-actions"> <button type="button" className="btn-secondary" onClick={() => { setShowCustomInput(false); setError(null); }} > Back </button> <button type="submit" className="btn-primary" disabled={!customService.trim()} > Continue </button> </div> </form> </div> ) : ( <div className="signup-step"> <h2>Create your account</h2> <p className="signup-subtitle"> Margin uses the AT Protocol — the same decentralized network that powers Bluesky. Your account will be hosted on a server of your choice. </p>
{error && ( <div className="error-message" style={{ marginBottom: "1rem" }}> <AlertCircle size={16} /> {error} </div> )}
<div className="signup-recommended"> <div className="signup-recommended-badge">Recommended</div> <button className="provider-card provider-card-featured" onClick={() => handleProviderSelect(RECOMMENDED_PROVIDER)} > <div className="provider-icon"> <img src={logo} alt="Margin" style={{ width: 24, height: 24 }} /> </div> <div className="provider-info"> <h3>{RECOMMENDED_PROVIDER.name}</h3> <span>{RECOMMENDED_PROVIDER.description}</span> </div> <ChevronRight size={16} className="provider-arrow" /> </button> </div>
<button type="button" className="signup-toggle-others" onClick={() => setShowOtherProviders(!showOtherProviders)} > {showOtherProviders ? "Hide other options" : "More options"} <ChevronRight size={14} className={`toggle-chevron ${showOtherProviders ? "open" : ""}`} /> </button>
{showOtherProviders && ( <div className="provider-grid"> {OTHER_PROVIDERS.map((p) => ( <button key={p.id} className="provider-card" onClick={() => handleProviderSelect(p)} > <div className={`provider-icon ${p.wide ? "wide" : ""}`}> {p.Icon ? ( <p.Icon size={32} /> ) : ( <span className="provider-initial">{p.name[0]}</span> )} </div> <div className="provider-info"> <h3>{p.name}</h3> <span>{p.description}</span> </div> <ChevronRight size={16} className="provider-arrow" /> </button> ))} </div> )} </div> )} </div> </div> );}