(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol.
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> );}