[US-15.2 / OBT-250] Public request page for languages and projects - #36
[US-15.2 / OBT-250] Public request page for languages and projects#36levigtri wants to merge 7 commits into
Conversation
Types mirror the new unauthenticated backend endpoints under /api/public: slim language options for the combobox and creation request payloads carrying requester name/email and the reCAPTCHA token. publicAPI reuses the existing axios instance since public endpoints never return 401.
New /request route outside the authenticated shell. Visitors provide name and email and submit either a language request (blocked when the 3-char code already exists in the system) or a project request whose language combobox lists languages already registered, via the public endpoints. ReCaptcha renders the v2 checkbox on demand, injecting the Google script only on this page; the site key comes from VITE_RECAPTCHA_SITE_KEY with Google's test key as dev fallback.
Gives the /request page an entry point for visitors who land on the login screen without an account.
Without VITE_RECAPTCHA_SITE_KEY the captcha widget is not rendered and the token is omitted from the payload, matching the backend behavior when RECAPTCHA_SECRET_KEY is unset — the public endpoints then rely on per-IP rate limiting alone. Forms surface 429 responses with a dedicated toast.
The project form's language field becomes a searchable combobox (type to filter by name or code) with a pinned 'Create a new language' option that reveals inline name/code fields, mirroring the authenticated flow. Duplicate checks now cover BOTH name and code, case-insensitively, with inline errors on the language tab and on the new-language fields; API error toasts surface the backend detail for 409/400 and handle 422 explicitly.
Visitors with stale tokens are no longer hard-redirected from public paths to /login when the session restore 401s. The reCAPTCHA loader reports script failures (onerror + poll timeout) instead of silently dead-ending the form. A failed languages fetch shows an explicit error with a Retry button instead of an empty-state message, and tab switches keep form state and captcha via forceMount + hidden.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: QUIET Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
[US-15.2 / OBT-250] Public request page for languages and projects
Summary
Adds a public page at
/request(no authentication, outside the AppShell) where a visitor can request the creation of a Language or a Project by providing only their name and email plus the same fields as the authenticated create dialogs. Submissions go to the new unauthenticated/api/publicendpoints (backend PR shemaobt/shema-api#102) and are stored as pending requests for admin review. Abuse protection: the backend rate-limits by IP, and a Google reCAPTCHA v2 checkbox is rendered whenVITE_RECAPTCHA_SITE_KEYis set — unset, the captcha is disabled and the app runs with rate limiting alone (matching the backend's optionalRECAPTCHA_SECRET_KEY). The project form's language field is a searchable combobox (type to filter by name or code) listing the languages already registered, with a pinned "Create a new language" option that reveals inline name/code fields — mirroring the authenticated flow. Duplicate checks cover both name and code, case-insensitively ("English/eng" ≡ "english/ENG"), client-side with inline errors and server-side via 409. The page follows the LoginPage visual pattern and Shema design tokens, reusing the existing ui primitives.Changes
src/types/publicRequest.ts(+ re-export insrc/types/index.ts) —PublicLanguageOption,PublicLanguageRequestCreate,PublicProjectRequestCreate,PublicRequestResponse, mirroring the backend schemas.src/services/api.ts— newpublicAPInamespace:listLanguages,requestLanguage,requestProjectagainst/api/public/*. Reuses the single axios instance (public endpoints never return 401, so the refresh/redirect interceptor cannot fire for anonymous visitors).src/components/common/ReCaptcha.tsx— reusable reCAPTCHA v2 checkbox. Injects the Googleapi.jsscript (explicit render) only when the component mounts, so no other page loads it. Site key fromVITE_RECAPTCHA_SITE_KEY; when unset, exportsRECAPTCHA_ENABLED = falseand renders nothing — forms then submit without a token. Emits the token viaonChange; expiry/error clear it. Remount with a newkeyto reset after a failed submit (tokens are single-use).src/components/pages/publicRequest/index.tsx— the page: requester name + email (shared), Project/Language tabs, success state with "Submit another request", footer link back to/login. Fetches the public languages list once for both tabs.src/components/pages/publicRequest/LanguageCombobox.tsx— searchable combobox built on the existing Popover + Input primitives: type to filter languages by name or code, pinned "Create a new language" entry, check indicator on the selected item.src/components/pages/publicRequest/LanguageRequestForm.tsx— name + 3-letter ISO 639-3 code; blocks submission with inline errors when the name or the code already exists (case-insensitive); toasts surface the backenddetailfor 409/400 and handle 429/422 explicitly.src/components/pages/publicRequest/ProjectRequestForm.tsx— name, optional description, and the language combobox; choosing "Create a new language" reveals inline name/code fields with the same case-insensitive duplicate checks; failed languages fetch shows an error with a Retry button.src/App.tsx— top-level/requestroute, sibling of/login, outside the AppShell (no auth guard).src/components/pages/LoginPage.tsx— discreet "Request a new project or language" link in the footer.src/vite-env.d.ts/.env.example— typing and documentation forVITE_RECAPTCHA_SITE_KEY./requestto/loginby the axios 401 interceptor (public-path guard inapi.ts); the reCAPTCHA loader detects script failures (onerror + poll timeout) and shows a recoverable message; tab switches preserve form state and the solved captcha (forceMount+data-[state=inactive]:hidden).Type of Change
Testing
npm run typecheck,npm run lint(0 errors; only the pre-existing ProjectsPage warning) andnpm run buildpass./requestin a private window (no tokens in localStorage) — the page renders and never redirects to/login.VITE_RECAPTCHA_SITE_KEY: no captcha widget appears and submissions succeed; rapid repeated submissions surface the 429 toast.