A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go

make navbar a component

evan.jarrett.net 9af56daa 55afa99e

verified
Changed files
+55 -50
pkg
+1
pkg/appview/config.go
··· 251 251 // Auto-detect from HTTP addr 252 252 if httpAddr[0] == ':' { 253 253 // Just a port, assume localhost 254 + // Use "127.0.0.1" per RFC 8252 (OAuth servers reject "localhost") 254 255 return fmt.Sprintf("http://127.0.0.1%s", httpAddr) 255 256 } 256 257
+5
pkg/appview/templates/components/nav-brand.html
··· 1 + {{ define "nav-brand" }} 2 + <div class="nav-brand"> 3 + <a href="/"><span class="at-protocol">at://</span>Container Registry</a> 4 + </div> 5 + {{ end }}
+7
pkg/appview/templates/components/nav-search.html
··· 1 + {{ define "nav-search" }} 2 + <div class="nav-search"> 3 + <form action="/search" method="get"> 4 + <input type="text" name="q" placeholder="Search images..." value="{{ .Query }}" /> 5 + </form> 6 + </div> 7 + {{ end }}
+3
pkg/appview/templates/components/nav-theme-toggle.html
··· 1 + {{ define "nav-theme-toggle" }} 2 + <button id="theme-toggle" onclick="toggleTheme()" class="btn-link theme-toggle-btn" aria-label="Toggle theme"></button> 3 + {{ end }}
+27
pkg/appview/templates/components/nav-user.html
··· 1 + {{ define "nav-user" }} 2 + {{ if .User }} 3 + <div class="user-dropdown"> 4 + <button class="user-menu-btn" id="user-menu-btn" aria-expanded="false" aria-haspopup="true"> 5 + {{ if .User.Avatar }} 6 + <img src="{{ .User.Avatar }}" alt="{{ .User.Handle }}" class="user-avatar"> 7 + {{ else }} 8 + <div class="user-avatar-placeholder">{{ firstChar .User.Handle }}</div> 9 + {{ end }} 10 + <span class="user-handle">@{{ .User.Handle }}</span> 11 + <svg class="dropdown-arrow" width="12" height="12" viewBox="0 0 12 12" fill="currentColor"> 12 + <path d="M6 9L1 4h10z"/> 13 + </svg> 14 + </button> 15 + <div class="dropdown-menu" id="user-dropdown-menu" hidden> 16 + <a href="/u/{{ .User.Handle }}" class="dropdown-item">Your Repositories</a> 17 + <a href="/settings" class="dropdown-item">Settings</a> 18 + <hr class="dropdown-divider"> 19 + <form action="/auth/logout" method="POST"> 20 + <button type="submit" class="dropdown-item logout-btn">Logout</button> 21 + </form> 22 + </div> 23 + </div> 24 + {{ else }} 25 + <a href="/auth/oauth/login?return_to=/" class="btn-primary">Login</a> 26 + {{ end }} 27 + {{ end }}
+11 -34
pkg/appview/templates/components/nav.html
··· 1 1 {{ define "nav" }} 2 2 <nav class="navbar"> 3 - <div class="nav-brand"> 4 - <a href="/"><span class="at-protocol">at://</span>Container Registry</a> 3 + {{ template "nav-brand" }} 4 + {{ template "nav-search" . }} 5 + <div class="nav-links"> 6 + {{ template "nav-theme-toggle" }} 7 + {{ template "nav-user" . }} 5 8 </div> 9 + </nav> 10 + {{ end }} 6 11 7 - <div class="nav-search"> 8 - <form action="/search" method="get"> 9 - <input type="text" name="q" placeholder="Search images..." value="{{ .Query }}" /> 10 - </form> 11 - </div> 12 - 12 + {{ define "nav-simple" }} 13 + <nav class="navbar"> 14 + {{ template "nav-brand" }} 13 15 <div class="nav-links"> 14 - <button id="theme-toggle" onclick="toggleTheme()" class="btn-link theme-toggle-btn" aria-label="Toggle theme"></button> 15 - {{ if .User }} 16 - <div class="user-dropdown"> 17 - <button class="user-menu-btn" id="user-menu-btn" aria-expanded="false" aria-haspopup="true"> 18 - {{ if .User.Avatar }} 19 - <img src="{{ .User.Avatar }}" alt="{{ .User.Handle }}" class="user-avatar"> 20 - {{ else }} 21 - <div class="user-avatar-placeholder">{{ firstChar .User.Handle }}</div> 22 - {{ end }} 23 - <span class="user-handle">@{{ .User.Handle }}</span> 24 - <svg class="dropdown-arrow" width="12" height="12" viewBox="0 0 12 12" fill="currentColor"> 25 - <path d="M6 9L1 4h10z"/> 26 - </svg> 27 - </button> 28 - <div class="dropdown-menu" id="user-dropdown-menu" hidden> 29 - <a href="/u/{{ .User.Handle }}" class="dropdown-item">Your Repositories</a> 30 - <a href="/settings" class="dropdown-item">Settings</a> 31 - <hr class="dropdown-divider"> 32 - <form action="/auth/logout" method="POST"> 33 - <button type="submit" class="dropdown-item logout-btn">Logout</button> 34 - </form> 35 - </div> 36 - </div> 37 - {{ else }} 38 - <a href="/auth/oauth/login?return_to=/" class="btn-primary">Login</a> 39 - {{ end }} 16 + {{ template "nav-theme-toggle" }} 40 17 </div> 41 18 </nav> 42 19 {{ end }}
+1 -5
pkg/appview/templates/pages/login.html
··· 6 6 {{ template "head" . }} 7 7 </head> 8 8 <body> 9 - <nav class="navbar"> 10 - <div class="nav-brand"> 11 - <a href="/">ATCR</a> 12 - </div> 13 - </nav> 9 + {{ template "nav-simple" . }} 14 10 15 11 <main class="container"> 16 12 <div class="login-page">
-11
pkg/auth/oauth/client.go
··· 62 62 } else { 63 63 config = oauth.NewLocalhostConfig(redirectURI, scopes) 64 64 65 - // Append client_name to localhost client ID query string 66 - if clientName != "" { 67 - u, err := url.Parse(config.ClientID) 68 - if err == nil { 69 - q := u.Query() 70 - q.Set("client_name", clientName) 71 - u.RawQuery = q.Encode() 72 - config.ClientID = u.String() 73 - } 74 - } 75 - 76 65 slog.Info("Using public OAuth client (localhost development)") 77 66 } 78 67