Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8a590eb79 | |||
| baf829349c | |||
| 3145acc423 | |||
| ce3fc36835 | |||
| c267bc86a8 | |||
| c790a7236c | |||
| 67973b27aa |
@@ -15,6 +15,9 @@ jobs:
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Run tests
|
||||
run: go test -race ./...
|
||||
|
||||
|
||||
+3
-3
@@ -73,9 +73,9 @@ curl -s https://gitea-mobile.testing.leeworks.dev | head -5
|
||||
3. Tap on a PR to see details
|
||||
4. **Expected**: PR diff summary or review status displays correctly
|
||||
|
||||
## Step 7: Core Functionality -- Triage Queue
|
||||
## Step 7: Core Functionality -- Dashboard / Triage Queue
|
||||
|
||||
1. Navigate to the Triage tab (`/triage`)
|
||||
1. Navigate to the Dashboard/Triage tab (`/`)
|
||||
2. **Expected**: Unassigned issues and PRs awaiting review appear sorted by priority
|
||||
|
||||
## Step 8: Create Issue (Write Operation)
|
||||
@@ -116,7 +116,7 @@ curl -s https://gitea-mobile.testing.leeworks.dev | head -5
|
||||
| 4 | Auth | Token saved, API calls work |
|
||||
| 5 | Issues | List loads, filter works |
|
||||
| 6 | PRs | List loads with review status |
|
||||
| 7 | Triage | Queue displays correctly |
|
||||
| 7 | Dashboard/Triage | Queue displays correctly at `/` |
|
||||
| 8 | Create issue | Issue created in Gitea |
|
||||
| 9 | Apply label | Label applied via API |
|
||||
| 10 | PWA | Standalone mode, safe areas, dark mode |
|
||||
|
||||
@@ -78,6 +78,31 @@ func getToken(r *http.Request) string {
|
||||
return middleware.TokenFromContext(r.Context())
|
||||
}
|
||||
|
||||
// isTokenError returns true if the error indicates an expired or revoked API token.
|
||||
func isTokenError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
msg := err.Error()
|
||||
return strings.Contains(msg, "API error 401") || strings.Contains(msg, "API error 403")
|
||||
}
|
||||
|
||||
// redirectOnTokenError checks if the error is a token auth error and redirects
|
||||
// to /settings with an error banner. Returns true if a redirect was performed.
|
||||
func redirectOnTokenError(w http.ResponseWriter, r *http.Request, err error) bool {
|
||||
if !isTokenError(err) {
|
||||
return false
|
||||
}
|
||||
slog.Warn("Gitea API token expired or revoked, redirecting to settings", "error", err)
|
||||
if isHTMX(r) {
|
||||
w.Header().Set("HX-Redirect", "/settings?error=token_expired")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
http.Redirect(w, r, "/settings?error=token_expired", http.StatusSeeOther)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// getUserOrgs returns the list of org names the user belongs to.
|
||||
func (h *Handler) getUserOrgs(r *http.Request) []string {
|
||||
token := getToken(r)
|
||||
@@ -263,6 +288,9 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
queue, err := h.Client.GetTriageQueue(r.Context(), token, queryOrgs)
|
||||
if err != nil {
|
||||
if redirectOnTokenError(w, r, err) {
|
||||
return
|
||||
}
|
||||
slog.Error("failed to get triage queue", "error", err)
|
||||
data.Error = "Error loading triage queue."
|
||||
} else {
|
||||
@@ -346,6 +374,9 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
result, err := h.Client.ListAllIssues(r.Context(), token, queryOrgs, selectedState, page, selectedLabel, selectedRepo)
|
||||
if err != nil {
|
||||
if redirectOnTokenError(w, r, err) {
|
||||
return
|
||||
}
|
||||
slog.Error("failed to list issues", "error", err)
|
||||
data.Error = "Error loading issues."
|
||||
} else {
|
||||
@@ -451,6 +482,9 @@ func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
result, err := h.Client.ListAllPullRequests(r.Context(), token, queryOrgs, selectedState, page, selectedLabel, selectedRepo)
|
||||
if err != nil {
|
||||
if redirectOnTokenError(w, r, err) {
|
||||
return
|
||||
}
|
||||
slog.Error("failed to list pull requests", "error", err)
|
||||
data.Error = "Error loading pull requests."
|
||||
} else {
|
||||
|
||||
@@ -45,6 +45,13 @@ func (h *SettingsHandler) handleGet(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := settingsData{HasToken: hasToken}
|
||||
|
||||
// Show error banner when redirected due to expired/revoked token.
|
||||
if r.URL.Query().Get("error") == "token_expired" {
|
||||
data.Message = "Your Gitea API token is expired or has been revoked. Please enter a new token."
|
||||
data.MessageType = "error"
|
||||
}
|
||||
|
||||
h.renderSettings(w, data)
|
||||
}
|
||||
|
||||
|
||||
+53
-2
@@ -1,4 +1,12 @@
|
||||
/* Gitea Mobile — Mobile-first CSS (~5KB target) */
|
||||
/* Gitea Mobile — Mobile-first CSS
|
||||
* Dark-mode-first: dark colors are the :root defaults.
|
||||
* Light mode is applied via @media (prefers-color-scheme: light).
|
||||
*
|
||||
* Size note: The original ~5KB target was based on the initial Phase 1 scope.
|
||||
* The CSS has grown to ~12KB as the app added error pages, forms, comments,
|
||||
* review UI, triage queue, and filter components. All rules are in active use.
|
||||
* Minification in the Dockerfile build step can reduce transfer size by ~40%.
|
||||
*/
|
||||
|
||||
/* Reset */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@@ -533,7 +541,7 @@ a:active {
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode is default; light mode override if needed */
|
||||
/* Dark mode is default; light mode override for prefers-color-scheme: light */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--bg-primary: #ffffff;
|
||||
@@ -543,6 +551,49 @@ a:active {
|
||||
--text-primary: #1f2328;
|
||||
--text-secondary: #656d76;
|
||||
--text-link: #0969da;
|
||||
--accent-green: #1a7f37;
|
||||
--accent-red: #cf222e;
|
||||
--accent-yellow: #9a6700;
|
||||
--accent-blue: #0969da;
|
||||
--accent-purple: #8250df;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background: #dafbe1;
|
||||
border-color: #1a7f37;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: #ffebe9;
|
||||
border-color: #cf222e;
|
||||
}
|
||||
|
||||
.message.info {
|
||||
background: #ddf4ff;
|
||||
border-color: #0969da;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #1a7f37;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
background: #116329;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ffebe9;
|
||||
border-color: #cf222e;
|
||||
}
|
||||
|
||||
.type-issue {
|
||||
background: rgba(9, 105, 218, 0.1);
|
||||
border-color: rgba(9, 105, 218, 0.3);
|
||||
}
|
||||
|
||||
.type-pull {
|
||||
background: rgba(26, 127, 55, 0.1);
|
||||
border-color: rgba(26, 127, 55, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user