(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor login page

scanash00 d9406cd5 01e5086e

+192 -176
-3
web/src/components/RightSidebar.jsx
··· 124 124 <Link to="/url" className="right-link"> 125 125 Browse by URL 126 126 </Link> 127 - <Link to="/highlights" className="right-link"> 128 - Public Highlights 129 - </Link> 130 127 </nav> 131 128 </div> 132 129 )}
+116 -125
web/src/css/login.css
··· 3 3 flex-direction: column; 4 4 align-items: center; 5 5 justify-content: center; 6 - min-height: 70vh; 7 - padding: 60px 20px; 6 + min-height: 80vh; 7 + padding: 40px 20px; 8 8 width: 100%; 9 - max-width: 500px; 10 - margin: 0 auto; 11 9 } 12 10 13 - @media (max-width: 600px) { 14 - .login-page { 15 - padding: 40px 16px; 16 - } 11 + .login-header-group { 12 + display: flex; 13 + flex-direction: row; 14 + align-items: center; 15 + justify-content: center; 16 + gap: 24px; 17 + margin-bottom: 48px; 18 + width: auto; 19 + } 17 20 18 - .login-at-logo { 19 - font-size: 4rem; 20 - } 21 - 22 - .login-brand-name { 23 - font-size: 1.25rem; 24 - } 25 - 26 - .login-brand-icon { 27 - width: 40px; 28 - height: 40px; 29 - font-size: 1.5rem; 30 - } 21 + .login-logo-img { 22 + width: 60px; 23 + height: 60px; 24 + object-fit: contain; 25 + display: block; 31 26 } 32 27 33 - .login-at-logo { 34 - font-size: 5rem; 35 - font-weight: 800; 36 - color: var(--accent); 37 - margin-bottom: 24px; 28 + .login-x { 29 + font-size: 2rem; 30 + color: var(--text-tertiary); 31 + font-weight: 300; 38 32 line-height: 1; 33 + padding-bottom: 4px; 39 34 } 40 35 41 - .login-logo-img { 42 - width: 80px; 43 - height: 80px; 44 - margin-bottom: 24px; 45 - object-fit: contain; 36 + .login-atproto-icon { 37 + color: #3b83f6 !important; 38 + display: flex; 39 + align-items: center; 40 + justify-content: center; 46 41 } 47 42 48 43 .login-heading { 49 44 font-size: 1.5rem; 50 - font-weight: 600; 45 + font-weight: 700; 51 46 margin-bottom: 32px; 52 47 display: flex; 53 48 align-items: center; 54 - gap: 10px; 49 + justify-content: center; 50 + gap: 8px; 55 51 text-align: center; 56 - line-height: 1.4; 52 + line-height: 1.3; 53 + color: var(--text-primary); 57 54 } 58 55 59 56 .login-help-btn { ··· 73 70 } 74 71 75 72 .login-help-text { 76 - background: var(--bg-elevated); 73 + background: var(--bg-tertiary); 77 74 border: 1px solid var(--border); 78 75 border-radius: var(--radius-md); 79 - padding: 16px 20px; 76 + padding: 16px; 80 77 margin-bottom: 24px; 81 - font-size: 0.95rem; 78 + font-size: 0.9rem; 82 79 color: var(--text-secondary); 83 - line-height: 1.6; 80 + line-height: 1.5; 84 81 text-align: center; 82 + width: 100%; 85 83 } 86 84 87 85 .login-help-text code { 88 - background: var(--bg-tertiary); 89 - padding: 2px 8px; 86 + background: rgba(255, 255, 255, 0.05); 87 + padding: 2px 6px; 90 88 border-radius: var(--radius-sm); 91 - font-size: 0.9rem; 89 + font-size: 0.85rem; 90 + font-family: var(--font-mono); 92 91 } 93 92 94 93 .login-form { 95 94 display: flex; 96 95 flex-direction: column; 97 - gap: 16px; 96 + gap: 20px; 98 97 width: 100%; 99 98 } 100 99 ··· 105 104 .login-input { 106 105 width: 100%; 107 106 padding: 14px 16px; 108 - background: var(--bg-elevated); 107 + background: var(--bg-secondary); 109 108 border: 1px solid var(--border); 110 109 border-radius: var(--radius-md); 111 110 color: var(--text-primary); 112 111 font-size: 1rem; 113 - transition: 114 - border-color 0.15s, 115 - box-shadow 0.15s; 112 + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); 113 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 116 114 } 117 115 118 116 .login-input:focus { 119 117 outline: none; 120 118 border-color: var(--accent); 121 - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); 119 + box-shadow: 0 0 0 4px var(--accent-subtle); 120 + background: var(--bg-primary); 122 121 } 123 122 124 123 .login-input::placeholder { ··· 127 126 128 127 .login-suggestions { 129 128 position: absolute; 130 - top: calc(100% + 4px); 129 + top: calc(100% + 8px); 131 130 left: 0; 132 131 right: 0; 133 - background: var(--bg-card); 132 + background: var(--bg-elevated); 134 133 border: 1px solid var(--border); 135 134 border-radius: var(--radius-md); 136 135 box-shadow: var(--shadow-lg); 137 136 overflow: hidden; 138 137 z-index: 100; 138 + max-height: 300px; 139 + overflow-y: auto; 139 140 } 140 141 141 142 .login-suggestion { ··· 149 150 cursor: pointer; 150 151 text-align: left; 151 152 transition: background 0.1s; 153 + border-bottom: 1px solid var(--border); 154 + } 155 + 156 + .login-suggestion:last-child { 157 + border-bottom: none; 152 158 } 153 159 154 160 .login-suggestion:hover, 155 161 .login-suggestion.selected { 156 - background: var(--bg-elevated); 162 + background: var(--bg-tertiary); 157 163 } 158 164 159 165 .login-suggestion-avatar { 160 - width: 40px; 161 - height: 40px; 166 + width: 36px; 167 + height: 36px; 162 168 border-radius: var(--radius-full); 163 169 background: linear-gradient(135deg, var(--accent), #a855f7); 164 170 display: flex; ··· 166 172 justify-content: center; 167 173 flex-shrink: 0; 168 174 overflow: hidden; 169 - font-size: 0.875rem; 175 + font-size: 0.8rem; 170 176 font-weight: 600; 171 177 color: white; 172 178 } ··· 181 187 display: flex; 182 188 flex-direction: column; 183 189 min-width: 0; 190 + gap: 2px; 184 191 } 185 192 186 193 .login-suggestion-name { 187 194 font-weight: 600; 195 + font-size: 0.95rem; 188 196 color: var(--text-primary); 189 197 white-space: nowrap; 190 198 overflow: hidden; ··· 192 200 } 193 201 194 202 .login-suggestion-handle { 195 - font-size: 0.875rem; 203 + font-size: 0.85rem; 196 204 color: var(--text-secondary); 197 205 white-space: nowrap; 198 206 overflow: hidden; ··· 202 210 .login-error { 203 211 padding: 12px 16px; 204 212 background: rgba(239, 68, 68, 0.1); 205 - border: 1px solid rgba(239, 68, 68, 0.3); 213 + border: 1px solid rgba(239, 68, 68, 0.2); 206 214 border-radius: var(--radius-md); 207 - color: #ef4444; 215 + color: var(--error); 208 216 font-size: 0.875rem; 217 + text-align: center; 209 218 } 210 219 211 - .login-legal { 212 - font-size: 0.75rem; 213 - color: var(--text-tertiary); 214 - line-height: 1.5; 215 - margin-top: 16px; 216 - } 217 - 218 - .login-brand { 219 - display: flex; 220 - align-items: center; 221 - justify-content: center; 222 - gap: 12px; 223 - margin-bottom: 24px; 224 - } 225 - 226 - .login-brand-icon { 227 - width: 48px; 228 - height: 48px; 229 - background: linear-gradient(135deg, var(--accent), #a855f7); 230 - border-radius: var(--radius-lg); 231 - display: flex; 232 - align-items: center; 233 - justify-content: center; 234 - font-size: 1.75rem; 235 - font-weight: 800; 236 - color: white; 237 - } 238 - 239 - .login-brand-name { 240 - font-size: 1.75rem; 241 - font-weight: 700; 242 - } 243 - 244 - .login-avatar { 245 - width: 72px; 246 - height: 72px; 247 - border-radius: var(--radius-full); 248 - background: linear-gradient(135deg, var(--accent), #a855f7); 249 - display: flex; 250 - align-items: center; 251 - justify-content: center; 252 - margin: 0 auto 16px; 253 - font-weight: 700; 254 - font-size: 1.5rem; 255 - color: white; 256 - overflow: hidden; 257 - } 258 - 259 - .login-avatar img { 220 + .login-submit { 221 + padding: 14px 24px; 222 + font-size: 1rem; 223 + font-weight: 600; 260 224 width: 100%; 261 - height: 100%; 262 - object-fit: cover; 225 + justify-content: center; 263 226 } 264 227 265 228 .login-avatar-large { 266 - width: 100px; 267 - height: 100px; 229 + width: 80px; 230 + height: 80px; 268 231 border-radius: var(--radius-full); 269 232 background: linear-gradient(135deg, var(--accent), #a855f7); 270 233 display: flex; ··· 275 238 font-size: 2rem; 276 239 color: white; 277 240 overflow: hidden; 241 + box-shadow: var(--shadow-md); 278 242 } 279 243 280 244 .login-avatar-large img { ··· 284 248 } 285 249 286 250 .login-welcome { 287 - font-size: 1.5rem; 251 + font-size: 1.25rem; 288 252 font-weight: 600; 289 253 margin-bottom: 32px; 290 254 text-align: center; 291 - } 292 - 293 - .login-welcome-name { 294 - font-size: 1.25rem; 295 - font-weight: 600; 296 - margin-bottom: 24px; 255 + color: var(--text-primary); 297 256 } 298 257 299 258 .login-actions { ··· 303 262 width: 100%; 304 263 } 305 264 306 - .login-btn { 307 - width: 100%; 308 - padding: 14px 24px; 309 - font-size: 1rem; 310 - font-weight: 600; 265 + .morph-container { 266 + display: inline-block; 267 + color: var(--text-primary); 268 + font-weight: 700; 269 + transition: 270 + opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), 271 + transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), 272 + filter 0.4s cubic-bezier(0.4, 0, 0.2, 1); 273 + white-space: nowrap; 274 + vertical-align: bottom; 311 275 } 312 276 313 - .login-submit { 314 - padding: 18px 32px; 315 - font-size: 1.1rem; 316 - font-weight: 600; 277 + .morph-out { 278 + opacity: 0; 279 + transform: translateY(8px) scale(0.95); 280 + filter: blur(4px); 281 + } 282 + 283 + .morph-in { 284 + opacity: 1; 285 + transform: translateY(0) scale(1); 286 + filter: blur(0); 287 + } 288 + 289 + .login-legal { 290 + margin-top: 24px; 291 + font-size: 0.85rem; 292 + color: var(--text-tertiary); 293 + text-align: center; 294 + line-height: 1.5; 295 + } 296 + 297 + .login-legal a { 298 + color: var(--accent); 299 + text-decoration: underline; 300 + text-decoration-color: var(--accent); 301 + text-underline-offset: 4px; 302 + font-weight: 500; 303 + } 304 + 305 + .login-legal a:hover { 306 + text-decoration-thickness: 2px; 307 + opacity: 0.8; 317 308 }
+76 -48
web/src/pages/Login.jsx
··· 2 2 import { Link } from "react-router-dom"; 3 3 import { useAuth } from "../context/AuthContext"; 4 4 import { searchActors, startLogin } from "../api/client"; 5 - import { HelpCircle } from "lucide-react"; 5 + import { AtSign } from "lucide-react"; 6 6 import logo from "../assets/logo.svg"; 7 7 8 8 export default function Login() { ··· 12 12 const [showInviteInput, setShowInviteInput] = useState(false); 13 13 const [suggestions, setSuggestions] = useState([]); 14 14 const [showSuggestions, setShowSuggestions] = useState(false); 15 - const [showHelp, setShowHelp] = useState(false); 16 15 const [loading, setLoading] = useState(false); 17 16 const [error, setError] = useState(null); 18 17 const [selectedIndex, setSelectedIndex] = useState(-1); 19 18 const inputRef = useRef(null); 20 19 const inviteRef = useRef(null); 21 20 const suggestionsRef = useRef(null); 21 + 22 + const [providerIndex, setProviderIndex] = useState(0); 23 + const [morphClass, setMorphClass] = useState("morph-in"); 24 + const providers = [ 25 + "AT Protocol", 26 + "Bluesky", 27 + "Blacksky", 28 + "Tangled", 29 + "selfhosted.social", 30 + "Northsky", 31 + "witchcraft.systems", 32 + "topphie.social", 33 + "altq.net", 34 + ]; 35 + 36 + useEffect(() => { 37 + const cycleText = () => { 38 + setMorphClass("morph-out"); 39 + 40 + setTimeout(() => { 41 + setProviderIndex((prev) => (prev + 1) % providers.length); 42 + setMorphClass("morph-in"); 43 + }, 400); 44 + }; 45 + 46 + const interval = setInterval(cycleText, 3000); 47 + return () => clearInterval(interval); 48 + }, [providers.length]); 22 49 23 50 const isSelectionRef = useRef(false); 24 51 ··· 58 85 return () => document.removeEventListener("mousedown", handleClickOutside); 59 86 }, []); 60 87 88 + if (isAuthenticated) { 89 + return ( 90 + <div className="login-page"> 91 + <div className="login-avatar-large"> 92 + {user?.avatar ? ( 93 + <img src={user.avatar} alt={user.displayName || user.handle} /> 94 + ) : ( 95 + <span> 96 + {(user?.displayName || user?.handle || "??") 97 + .substring(0, 2) 98 + .toUpperCase()} 99 + </span> 100 + )} 101 + </div> 102 + <h1 className="login-welcome"> 103 + Welcome back, {user?.displayName || user?.handle} 104 + </h1> 105 + <div className="login-actions"> 106 + <Link to={`/profile/${user?.did}`} className="btn btn-primary"> 107 + View Profile 108 + </Link> 109 + <button onClick={logout} className="btn btn-ghost"> 110 + Sign out 111 + </button> 112 + </div> 113 + </div> 114 + ); 115 + } 116 + 61 117 const handleKeyDown = (e) => { 62 118 if (!showSuggestions || suggestions.length === 0) return; 63 119 ··· 113 169 } 114 170 }; 115 171 116 - if (isAuthenticated) { 117 - return ( 118 - <div className="login-page"> 119 - <div className="login-avatar-large"> 120 - {user?.avatar ? ( 121 - <img src={user.avatar} alt={user.displayName || user.handle} /> 122 - ) : ( 123 - <span> 124 - {(user?.displayName || user?.handle || "??") 125 - .substring(0, 2) 126 - .toUpperCase()} 127 - </span> 128 - )} 129 - </div> 130 - <h1 className="login-welcome"> 131 - Welcome back, {user?.displayName || user?.handle} 132 - </h1> 133 - <div className="login-actions"> 134 - <Link to={`/profile/${user?.did}`} className="btn btn-primary"> 135 - View Profile 136 - </Link> 137 - <button onClick={logout} className="btn btn-ghost"> 138 - Sign out 139 - </button> 140 - </div> 141 - </div> 142 - ); 143 - } 144 - 145 172 return ( 146 173 <div className="login-page"> 147 - <img src={logo} alt="Margin Logo" className="login-logo-img" /> 174 + <div className="login-header-group"> 175 + <img src={logo} alt="Margin Logo" className="login-logo-img" /> 176 + <span className="login-x">X</span> 177 + <div className="login-atproto-icon"> 178 + <AtSign size={64} strokeWidth={2.4} /> 179 + </div> 180 + </div> 148 181 149 182 <h1 className="login-heading"> 150 - Use the AT Protocol to login to Margin 151 - <button 152 - className="login-help-btn" 153 - onClick={() => setShowHelp(!showHelp)} 154 - type="button" 155 - > 156 - <HelpCircle size={20} /> 157 - </button> 183 + Sign in with your{" "} 184 + <span className={`morph-container ${morphClass}`}> 185 + {providers[providerIndex]} 186 + </span>{" "} 187 + handle 158 188 </h1> 159 - 160 - {showHelp && ( 161 - <p className="login-help-text"> 162 - The AT Protocol is an open, decentralized network for social apps. 163 - Your handle looks like <code>name.bsky.social</code> or your own 164 - domain. 165 - </p> 166 - )} 167 189 168 190 <form onSubmit={handleSubmit} className="login-form"> 169 191 <div className="login-input-wrapper"> ··· 263 285 ? "Submit Code" 264 286 : "Continue"} 265 287 </button> 288 + 289 + <p className="login-legal"> 290 + By signing in, you agree to our{" "} 291 + <Link to="/terms">Terms of Service</Link> and{" "} 292 + <Link to="/privacy">Privacy Policy</Link>. 293 + </p> 266 294 </form> 267 295 </div> 268 296 );