Skip to content

feat: Phase 0 foundations — auth/toast contexts, theme tokens, deps, route skeleton - #1

Open
devin-ai-integration[bot] wants to merge 5 commits into
mainfrom
devin/1782226655-phase-0-foundations
Open

devin-ai-integration[bot] wants to merge 5 commits into
mainfrom
devin/1782226655-phase-0-foundations

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 23, 2026 •

Copy link
Copy Markdown

Summary

Phase 0 establishes the shared foundation every other feature in the web→mobile port depends on, so the remaining work can be built in parallel without colliding on shared files. No user-facing screen is implemented here yet — this is plumbing + a route skeleton.

What's included:

  • Auth (contexts/AuthContext.tsx): RN port of the web AuthContext. Token persisted in AsyncStorage; full method surface (login/register/googleLogin/logout/updateProfile/changePassword/refreshUser). refreshUser only clears the token on an explicit 401/403 — transient/offline/5xx failures keep the session:
    if (response.status === 401 || response.status === 403) { setAuthToken(null); setUser(null); }
  • Toast (contexts/ToastContext.tsx): RN port of the web toast system using Reanimated + lucide icons, type-specific styling, auto-dismiss.
  • components/ProtectedRoute.tsx: auth-gating primitive (spinner while hydrating → redirect to /login if unauthenticated).
  • services/api.ts: added googleLogin endpoint and surfaced HTTP status on ApiResponse so auth can distinguish auth-failure from network-failure.
  • app/_layout.tsx: wraps the app in AuthProvider → ToastProvider and registers the full Stack route skeleton.
  • tailwind.config.js: added brand / glass / surface token scales to match the web theme.
  • Deps: zustand, expo-video.
  • Route skeleton: stub screens for every web route missing on mobile — movies, tv-shows, new-popular, browse, continue-watching, login, signup, watch/[type]/[id] — all registered in the root Stack. Each is an isolated file so subsequent feature PRs can be developed in parallel without touching shared layouts.

Verification: npx tsc --noEmit passes clean. No lint/test scripts exist in this repo.

Link to Devin session: https://app.devin.ai/sessions/a6b2d78a5bc6487cb1ce9c6547791321
Requested by: @simoabid


Open in Devin Review

Port AuthContext (JWT/Bearer) and ToastContext from cineflix-app web.
Add ProtectedRoute primitive, brand/glass/surface theme tokens, and
zustand + expo-video deps. Wire providers into root layout.

Co-Authored-By: Mohamed Amine Abid <ma5741913@gmail.com>
@simoabid simoabid self-assigned this Jun 23, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 7 potential issues.

Open in Devin Review

Comment thread contexts/AuthContext.tsx Resolved
Comment thread components/ProtectedRoute.tsx Resolved
Comment thread contexts/AuthContext.tsx
Comment on lines +59 to +72
useEffect(() => {
const checkAuth = async () => {
try {
const stored = await AsyncStorage.getItem('auth_token');
if (stored) {
setAuthToken(stored);
await refreshUser();
}
} finally {
setIsLoading(false);
}
};
checkAuth();
}, [refreshUser]);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Duplicate token hydration between api.ts module-level read and AuthContext

Both services/api.ts:7 and contexts/AuthContext.tsx:62 independently read auth_token from AsyncStorage. The module-level read at api.ts:7 fires on import and directly assigns to the module variable (authToken = t), while AuthContext reads the same key and calls setAuthToken(stored). In practice there's no race condition because the module import happens before the component mounts, and both AsyncStorage reads resolve to the same value. However, this dual-read pattern is redundant and could become confusing if the storage key ever diverges. The module-level read in api.ts is pre-existing code not changed by this PR, so this is just a maintenance note.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — both reads resolve the same auth_token key and the module import precedes mount, so no race. The AuthContext read stays as the canonical hydration point (it gates the isLoading spinner); the module-level read in api.ts is pre-existing. Left as-is to keep this PR scoped to foundations.

Comment thread contexts/ToastContext.tsx
Comment on lines +59 to +65
const showToast = useCallback((message: string, type: ToastType = 'info', duration: number = 3000) => {
const id = Math.random().toString(36).substring(2, 9);
setToasts((prev) => [...prev, { id, message, type, duration }]);

if (duration > 0) {
setTimeout(() => hideToast(id), duration);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Toast setTimeout is never cleaned up on manual dismiss

In ToastContext.tsx:63-64, when a toast is shown with a positive duration, a setTimeout is scheduled to auto-dismiss it. If the user manually dismisses the toast via the X button before the timer fires, the timer still runs and calls hideToast(id) again. This is harmless because hideToast uses a filter (prev.filter(...)) which is a no-op when the toast is already removed. Additionally, since ToastProvider is mounted at the root layout and never unmounts, there's no risk of state updates on an unmounted component. Not a bug, but adding timer cleanup via useRef + clearTimeout would be cleaner.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — harmless as noted (filter no-op, provider never unmounts). Leaving as-is for the foundation PR; can add useRef/clearTimeout cleanup if we later allow manual dismissal mid-timer at scale.

Comment thread tailwind.config.js
Comment on lines +34 to +45
glass: {
background: 'rgba(19, 19, 43, 0.7)',
'background-dark': 'rgba(10, 10, 31, 0.85)',
border: 'rgba(255, 255, 255, 0.08)',
hover: 'rgba(255, 255, 255, 0.12)',
},
surface: {
background: '#0A0A1F',
card: '#13132B',
accent: '#1F1F35',
elevated: '#252540',
},

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: New top-level Tailwind color groups (glass, brand, surface) don't conflict with existing netflix.glass keys

The new glass color group at tailwind.config.js:34-39 adds utilities like bg-glass-background, text-glass-border, etc. The pre-existing netflix.glass key (line 23) generates bg-netflix-glass. These are distinct utility names so there's no collision. However, bg-glass by itself won't work (no DEFAULT key in the glass object), which is fine since the intended usage is via sub-keys like glass-background. The naming could cause initial confusion for developers expecting bg-glass to work as a standalone utility.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — no collision with netflix.glass (distinct utility names). The scales mirror web's tailwind.config.js and are used via sub-keys (bg-surface-card, border-glass-border, etc.); there's intentionally no DEFAULT, matching web. No action needed.

Comment thread contexts/AuthContext.tsx Resolved
Comment thread contexts/AuthContext.tsx Resolved
devin-ai-integration Bot and others added 2 commits June 23, 2026 15:07
refreshUser now only clears the token on 401/403 (explicit auth
rejection); network/5xx failures keep the stored token. apiRequest
surfaces HTTP status. Wrap AuthContext value in useMemo.

Co-Authored-By: Mohamed Amine Abid <ma5741913@gmail.com>
Add stub screens for every web route missing on mobile (movies, tv-shows,
new-popular, browse, continue-watching, login, signup, watch/[type]/[id])
and register them in the root Stack. This lets each feature be built in an
isolated file without touching shared layouts, enabling safe parallel work.

Co-Authored-By: Mohamed Amine Abid <ma5741913@gmail.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat: Phase 0 foundations — auth/toast contexts, theme tokens, deps feat: Phase 0 foundations — auth/toast contexts, theme tokens, deps, route skeleton Jun 23, 2026
devin-ai-integration Bot and others added 2 commits June 23, 2026 15:16
Co-Authored-By: Mohamed Amine Abid <ma5741913@gmail.com>
Co-Authored-By: Mohamed Amine Abid <ma5741913@gmail.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

Open in Devin Review

Comment thread services/api.ts
Comment on lines 38 to 39
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Network error' };

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 apiRequest catch block loses HTTP status, preventing token cleanup on non-JSON 401/403 responses

The PR adds status to ApiResponse and uses it in refreshUser (contexts/AuthContext.tsx:52) to detect 401/403 and clear invalid tokens. However, when response.json() at services/api.ts:35 throws (e.g., a 401/403 from a reverse proxy returning an HTML error page instead of JSON), execution falls to the catch block at services/api.ts:38-39, which returns { success: false, error: ... } without the status field. The response object (which holds .status) is scoped inside the try block and is inaccessible from catch. As a result, refreshUser cannot detect auth rejection, the stale token is never removed from AsyncStorage, and every subsequent app launch wastes a network request trying to validate the dead token.

Prompt for agents
In services/api.ts, the apiRequest function's catch block (lines 38-39) does not propagate the HTTP status code when response.json() throws. This happens because the response variable is declared inside the try block and is inaccessible in catch.

The fix is to capture the HTTP status before calling response.json(), so it's available in the catch block. For example, declare a variable like let httpStatus: number | undefined before the try block, assign it after the fetch (httpStatus = response.status), and include it in the catch return: { success: false, error: ..., status: httpStatus }.

This ensures that refreshUser in contexts/AuthContext.tsx can detect 401/403 errors even when the response body is not valid JSON (e.g., HTML error pages from reverse proxies).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread contexts/AuthContext.tsx
Comment on lines +56 to +58
} catch {
// Network error — keep the token and current user state intact.
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Dead catch block in refreshUser — apiRequest never rejects

The refreshUser function wraps authApi.getMe() in a try-catch (contexts/AuthContext.tsx:56-58) with a comment saying "Network error — keep the token and current user state intact." However, authApi.getMe() calls apiRequest() which has its own internal try-catch (services/api.ts:23-40) and always returns a resolved promise — it never throws. This means the catch block in refreshUser is dead code. The actual network error handling works correctly regardless (the response will have success: false without a status, so the token is preserved), but the misleading comment could confuse future developers into thinking this catch is providing meaningful error handling.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread services/api.ts
const data = await response.json();
if (!response.ok) return { success: false, error: data.error || `HTTP error ${response.status}` };
if (!response.ok) return { success: false, error: data.error || `HTTP error ${response.status}`, status: response.status };
return data;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: apiRequest returns raw backend JSON on success — no status field guaranteed

On the success path (services/api.ts:37), apiRequest returns data (the raw parsed JSON from the server) directly, not a constructed ApiResponse<T> object. This means the status field added to ApiResponse<T> is only reliably present on error responses (line 36). If any caller checks response.status after a successful response, they'd get whatever the backend happened to include (or undefined). Currently refreshUser only checks status on the failure path (after response.success is false), so this isn't a bug today — but the asymmetric contract is worth being aware of for future callers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant