feat(landing): polish sprint - feedback widget, waitlist modal, theme fixes#232
Conversation
- Add FeedbackWidget with Discord webhook integration - Floating button in bottom-right, opens modal - Emoji mood selector + text message + email capture - Rich Discord embeds with color coding by mood - Add loading skeleton for DashboardStats - Update .env.example with VITE_DISCORD_FEEDBACK_WEBHOOK
- WaitlistModal with email capture - Posts to same Discord webhook with different embed style - 30% early bird discount messaging - Success animation on submit - Pro plan button now opens modal instead of dead /waitlist link
- Badge: 'One-click GitHub import' instead of 'Free for open source' - Subtext: 'Import any GitHub repo in one click' - Primary CTA: 'Import from GitHub' with GitHub icon - Footer: 'Connect GitHub in seconds • Free for 3 repos'
REMOVED: - Twitter link (no account yet) - Changelog, About, Blog, Careers (pages don't exist) - Privacy, Terms (legal pages don't exist) KEPT: - Features, Pricing (anchor links that work) - Docs (route exists) - GitHub (works) - MCP Setup (route exists) Also linked 'Devanshu' to devanshu.dev portfolio
Updated branding in: - Landing: Navbar, Footer, Features, FAQ, Pricing - Dashboard: TopNav, CommandPalette - Auth: LoginForm - Components: FeedbackWidget, WaitlistModal - Pages: DocsHomePage, MCPSetupPage, Playground Also updated logo from 'CI' to 'OCI' everywhere
- Force white text with inline style (browser was overriding) - Lighten animated placeholder for better contrast - Add caret-white for cursor visibility
- Add VITE_DISCORD_FEEDBACK_WEBHOOK to docker-compose build args - Add ARG/ENV to frontend Dockerfile - Webhook now baked in during Docker build
- Enterprise button opens modal instead of mailto link - Purple themed modal for Enterprise vs blue for Pro - Different Discord embed: '🏢 Enterprise Inquiry' (purple) - Custom messaging for enterprise inquiries - All leads now go to Discord for easy tracking
Light mode: white background, dark text Dark mode: dark background, light text - Background: bg-white / dark:bg-zinc-900 - Text: text-foreground (theme variable) - Border: border-zinc-200 / dark:border-zinc-800 - Placeholder: zinc-400 / dark:zinc-500 - Disabled button: zinc-100 / dark:zinc-800
Bug: Clicking Search mid-animation searched partial text like 'authen' Fix: Added autoTyping prop to HeroSearch, button disabled during animation - User can only search after manually typing - Pressing Enter during animation does nothing - Once user interacts, isTyping=false and search works normally
|
@DevanshuNEU is attempting to deploy a commit to the Dev's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds a feedback collection flow (frontend widget + waitlist modal), backend endpoints that post feedback and waitlist signups to a Discord webhook, an in-project rate limiter service, dashboard loading skeletons, and widespread frontend branding updates renaming "CodeIntel" to "OpenCodeIntel". Changes
Sequence DiagramsequenceDiagram
participant User
participant Frontend as FeedbackWidget (Frontend)
participant API as Backend (/api/v1/feedback)
participant Discord as Discord Webhook (External)
User->>Frontend: open modal, select mood, enter message/email
User->>Frontend: submit feedback
Frontend->>API: POST /feedback {mood,message,email}
API->>API: validate payload, apply rate_limit
API->>API: build Discord embed
API->>Discord: POST embed to webhook
alt Discord success
Discord-->>API: 200 OK
API-->>Frontend: 200 OK
Frontend->>User: show success state
else Discord failure or missing webhook
Discord--xAPI: error
API--xFrontend: 5xx/4xx
Frontend->>User: show error message
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@frontend/Dockerfile`:
- Around line 10-16: Remove the client-side exposure of the
VITE_DISCORD_FEEDBACK_WEBHOOK variable: delete the ARG/ENV lines that inject
VITE_DISCORD_FEEDBACK_WEBHOOK into the frontend build and stop reading
VITE_DISCORD_FEEDBACK_WEBHOOK from the client. Instead, store the webhook URL as
a server-side secret and implement a backend endpoint (e.g., POST /api/feedback
or a server function sendFeedback) that validates/rate-limits/authenticates
requests and forwards feedback to the Discord webhook; update the frontend to
call that endpoint rather than using VITE_DISCORD_FEEDBACK_WEBHOOK.
In `@frontend/src/components/landing/HeroSearch.tsx`:
- Around line 92-97: The submit handler (submit) currently allows Enter to
trigger onSubmit even when a search is in-flight; update the guard to match the
button disabled logic by including the searching check used in canSearch.
Concretely, change submit to check the same composite condition (value.trim(),
!autoTyping, and !searching) before calling onSubmit (or simply reuse canSearch)
so Enter cannot fire duplicate searches while searching is true.
In `@frontend/src/components/landing/WaitlistModal.tsx`:
- Around line 86-91: The early return "if (!isOpen) return null" prevents
AnimatePresence from seeing unmounts; remove that early return and always render
AnimatePresence, then render the modal markup conditionally inside it (e.g.,
{isOpen && <...>}), so the child components inside AnimatePresence (the modal
container/motion elements used in WaitlistModal and the Icon variable/Building2
or Rocket usage) can mount and unmount and trigger their exit animations; ensure
the motion elements keep their existing exit props and a stable key if needed.
In `@frontend/src/pages/DocsHomePage.tsx`:
- Line 149: Fix the typo in the GitHub URLs in the DocsHomePage component by
replacing the duplicated "OpenOpenCodeIntel" organization with the correct
"OpenCodeIntel" in the href attributes (change occurrences of
"https://github.com/OpenOpenCodeIntel/opencodeintel" and
"https://github.com/OpenOpenCodeIntel/opencodeintel/issues" to
"https://github.com/OpenCodeIntel/opencodeintel" and
".../opencodeintel/issues"); locate the JSX anchor elements in DocsHomePage.tsx
(the href attributes in the relevant <a> tags) and update both links to match
the organization used in Footer.tsx.
In `@frontend/src/pages/MCPSetupPage.tsx`:
- Line 408: The GitHub issues link in the JSX anchor inside MCPSetupPage (the
string "https://github.com/OpenOpenCodeIntel/opencodeintel/issues" used in the
<a> element) contains a duplicated organization name; update the href to the
correct organization URL (e.g.,
"https://github.com/OpenCodeIntel/opencodeintel/issues") so the link points to
the real repo issues page and ensure target="_blank" and rel remain unchanged.
- Around line 133-134: Update the incorrect GitHub organization name
"OpenOpenCodeIntel" to "OpenCodeIntel" in MCPSetupPage.tsx: fix the git clone
command inside the CodeBlock that currently shows `git clone
https://github.com/OpenOpenCodeIntel/opencodeintel.git` and also update the
GitHub issues URL near the later link (around the section referencing
issues/PRs) so both use `OpenCodeIntel/opencodeintel`; search for those exact
strings and replace them accordingly.
🧹 Nitpick comments (5)
frontend/.env.example (1)
14-15: Add a note that Vite env vars are public.
This helps prevent treating the webhook URL as a secret.📝 Suggested clarification
# Feedback Discord Webhook (optional) +# Note: VITE_* vars are embedded in the client bundle. Do not put secrets here. VITE_DISCORD_FEEDBACK_WEBHOOK=docker-compose.yml (1)
5-5: Consider: Container names still use "codeintel-" prefix.The container names (
codeintel-redis,codeintel-backend,codeintel-frontend) and network name (codeintel-network) haven't been updated to match the "OpenCodeIntel" rebranding applied elsewhere. This is a minor inconsistency you may want to address for consistency.frontend/src/components/FeedbackWidget.tsx (1)
29-72: Consider adding user-facing error feedback on submission failure.When the Discord webhook call fails, the error is logged to the console but the user receives no indication that their feedback wasn't sent. The modal closes normally after the timeout regardless of success/failure.
💡 Suggested improvement for error handling
const handleSubmit = async () => { if (!mood && !message.trim()) return setSending(true) try { const moodInfo = moods.find(m => m.value === mood) const embed = { // ... embed config } if (DISCORD_WEBHOOK) { - await fetch(DISCORD_WEBHOOK, { + const response = await fetch(DISCORD_WEBHOOK, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ embeds: [embed] }), }) + if (!response.ok) { + throw new Error('Failed to send') + } } else { console.log('Feedback (no webhook configured):', { mood, message, email: email || userEmail }) } setSent(true) setTimeout(() => { setIsOpen(false) setSent(false) setMood(null) setMessage('') setEmail('') }, 2000) } catch (error) { console.error('Failed to send feedback:', error) + // Optionally show error state to user + // For now, still close modal but consider adding error UI } finally { setSending(false) } }frontend/src/components/dashboard/DashboardLayout.tsx (1)
15-15: Consider updating the storage key to match the rebrand.The storage key still uses
codeintel-sidebar-collapsedwhile the app has been rebranded to OpenCodeIntel. This is a minor inconsistency that could be addressed for consistency, though changing it would reset existing users' sidebar preferences.frontend/src/components/landing/Pricing.tsx (1)
133-159: Consider extracting the duplicated className logic.The CTA styling (lines 138-142 and 151-155) is duplicated between the button and anchor elements. This could be extracted to a variable for DRY compliance.
♻️ Optional refactor
+ const ctaClassName = `block w-full py-3 px-4 rounded-lg text-center text-sm font-medium transition-all ${ + plan.popular + ? 'bg-accent text-white hover:bg-accent/90 shadow-lg shadow-accent/20' + : 'border border-border text-foreground hover:bg-muted/50' + }` + {(plan.ctaHref === '/waitlist' || plan.ctaHref === '/contact') ? ( <motion.button onClick={() => handleWaitlistClick(plan.name, plan.price === 'Custom' ? 'Custom pricing' : `${plan.price}${plan.period}`)} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} - className={`block w-full py-3 px-4 rounded-lg text-center text-sm font-medium transition-all ${ - plan.popular - ? 'bg-accent text-white hover:bg-accent/90 shadow-lg shadow-accent/20' - : 'border border-border text-foreground hover:bg-muted/50' - }`} + className={ctaClassName} >
Submit handler now reuses canSearch which includes !searching check. Previously Enter could fire onSubmit even while a search was in flight.
Fixed typo in: - DocsHomePage.tsx: GitHub and Issues links - MCPSetupPage.tsx: git clone command and issues link
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@frontend/src/components/FeedbackWidget.tsx`:
- Around line 57-61: The catch block in FeedbackWidget currently only logs
errors and leaves the modal without any user-visible feedback; add an error
state (e.g., const [error, setError] = useState<string | null>(null)) in the
FeedbackWidget component, set that state inside the catch
(setError(error?.message || 'Failed to send feedback')), and update the JSX to
render a visible error message area (similar to WaitlistModal) that displays the
error and optionally a retry hint; keep the existing setSending(false) in the
finally block so the spinner state is cleared.
🧹 Nitpick comments (4)
frontend/src/components/landing/WaitlistModal.tsx (1)
6-6: Consider importingAPI_URLfrom the shared config.This duplicates the API URL definition that exists in
frontend/src/config/api.ts. Using the shared config would ensure consistency if the URL construction logic changes.♻️ Proposed refactor
-const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' +import { API_URL } from '@/config/api'Note: Verify the export name matches your config file's API URL export (it may be
BASE_URLbased on the relevant snippet showingAPI_URLis constructed withAPI_PREFIX).frontend/src/components/FeedbackWidget.tsx (1)
7-7: Same API_URL duplication as in WaitlistModal.Consider extracting this to a shared config to maintain consistency.
backend/routes/feedback.py (2)
49-50: Consider logging the exception before returning False.Swallowing exceptions silently makes debugging difficult when Discord posts fail. At minimum, log the error for observability.
🔍 Proposed fix
+import logging + +logger = logging.getLogger(__name__) async def post_to_discord(embed: dict) -> bool: """Post an embed to Discord webhook.""" if not DISCORD_WEBHOOK_URL: return False try: async with httpx.AsyncClient() as client: response = await client.post( DISCORD_WEBHOOK_URL, json={"embeds": [embed]}, timeout=10.0 ) return response.status_code == 204 - except Exception: + except Exception as e: + logger.error(f"Failed to post to Discord: {e}") return False
69-69: Usedatetime.now(timezone.utc)instead of deprecateddatetime.utcnow().
datetime.utcnow()is deprecated in Python 3.12+ because it returns a naive datetime. For forward compatibility, use timezone-aware UTC:♻️ Proposed fix
-from datetime import datetime +from datetime import datetime, timezone # ... in embed construction: - "timestamp": datetime.utcnow().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(),
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@frontend/src/components/landing/WaitlistModal.tsx`:
- Around line 1-2: Remove the duplicate React import in WaitlistModal.tsx:
there's two identical lines importing useState; keep a single import statement
(import { useState } from 'react') and delete the redundant one so the symbol is
declared only once and the build error is resolved.
🧹 Nitpick comments (1)
frontend/src/components/landing/WaitlistModal.tsx (1)
44-64: Clear the auto-close timer on close/unmount.The success
setTimeoutcan fire after the modal is closed/unmounted, causing stale state updates. Consider storing the timer id and clearing it inhandleCloseand on unmount.♻️ Proposed refactor
export function WaitlistModal({ isOpen, onClose, planName = 'Pro', planPrice = '$19/mo' }: WaitlistModalProps) { const [email, setEmail] = useState('') const [sending, setSending] = useState(false) const [sent, setSent] = useState(false) const [error, setError] = useState('') + const closeTimerRef = useRef<number | null>(null) + + useEffect(() => { + return () => { + if (closeTimerRef.current) { + clearTimeout(closeTimerRef.current) + closeTimerRef.current = null + } + } + }, []) @@ setSent(true) - setTimeout(() => { + if (closeTimerRef.current) clearTimeout(closeTimerRef.current) + closeTimerRef.current = window.setTimeout(() => { onClose() setSent(false) setEmail('') }, 2500) @@ const handleClose = () => { if (!sending) { + if (closeTimerRef.current) { + clearTimeout(closeTimerRef.current) + closeTimerRef.current = null + } onClose() setEmail('') setError('') setSent(false) } }Add
useEffect/useRefto the React import (and consolidate the duplicate import per the prior comment).
Tests were failing because feedback.py imports rate_limit decorator which didn't exist. Added in-memory rate limiting decorator.
- Added error state with user-visible error message - Clear error on modal open and after successful submit - Match pattern used in WaitlistModal
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@backend/services/rate_limiter.py`:
- Around line 15-58: The decorator's in-memory store (_rate_limit_store) never
removes inactive client keys, causing unbounded growth; update the rate_limit
wrapper to evict stale keys by: after filtering timestamps for the current key
(using window_start), delete _rate_limit_store[key] if its list becomes empty,
and add a light global sweep mechanism (module-level variables like
_last_cleanup and _cleanup_interval) that runs occasionally (e.g., if now -
_last_cleanup > _cleanup_interval) to iterate _rate_limit_store and remove any
entries whose newest/last timestamp is older than window_start; reference
symbols: _rate_limit_store, rate_limit, wrapper, key, requests_per_minute.
- Around line 27-41: The wrapper coroutine currently falls back to a shared
"unknown" bucket and uses request.client.host which can be the proxy IP; update
wrapper to require a FastAPI/Starlette Request instance (raise a clear error if
missing) and derive client_id exclusively from request.client.host (relying on
server proxy header configuration) instead of defaulting to "unknown"; document
that the deployment must run Uvicorn/FastAPI with trusted proxy headers (e.g.,
--proxy-headers and --forwarded-allow-ips) so request.client.host reflects the
real client IP and avoid collapsing all callers into one rate-limiter bucket.
…owth Every 60s, evict keys whose newest timestamp is stale (>60s old). Prevents unbounded memory growth from inactive client IPs.
- rate_limit decorator now requires Request parameter (raises 500 if missing) - Routes updated: request: Request, body: PydanticModel pattern - Added proxy-headers flag to Dockerfile for X-Forwarded-For support - Added FORWARDED_ALLOW_IPS env var to docker-compose - Documented proxy header configuration in decorator docstring Behind reverse proxy, this ensures rate limiting uses real client IP instead of proxy IP (which would bucket all users together).
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@backend/Dockerfile`:
- Around line 32-34: The Dockerfile enables proxy header processing via the
uvicorn command (CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port",
"8000", "--proxy-headers"]) but the docker-compose.yml currently sets
FORWARDED_ALLOW_IPS="*", which trusts all X-Forwarded-For headers; update
docker-compose.yml to set FORWARDED_ALLOW_IPS to the specific proxy IP or CIDR
(e.g., your reverse proxy's address like 10.0.0.0/8 or the load balancer IP)
instead of "*", so the uvicorn --proxy-headers behavior only trusts headers from
your known proxy and prevents client IP spoofing and rate-limit bypass.
In `@backend/routes/feedback.py`:
- Around line 72-73: The feedback route currently appends body.email directly
into Discord embeds (embed["fields"].append({"name": "User", "value":
body.email, ...})) which leaks PII; update backend/routes/feedback.py so you do
not send raw emails to Discord unless an explicit opt-in/config flag (e.g.,
ENABLE_SHARE_EMAILS) is enabled and the user has recorded consent—otherwise omit
the field or send a masked/placeholder value and log consent server-side; apply
the same change to the other occurrences that append the "User" field with
body.email (the other embed["fields"].append(...) calls referenced in the
review).
🧹 Nitpick comments (5)
frontend/src/components/landing/WaitlistModal.tsx (1)
6-6: Consider importingAPI_URLfrom shared config.A shared
API_URLis exported fromfrontend/src/config/api.ts. Using the shared constant ensures consistency across the codebase and centralizes environment variable handling.♻️ Suggested change
-const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' +import { API_URL } from '@/config/api'Note: Verify that
API_URLfrom the config doesn't include the/api/v1prefix already, and adjust the fetch URL accordingly.backend/services/rate_limiter.py (2)
15-18: In-memory store lacks thread safety for multi-worker deployments.The module-level
_rate_limit_storeand_last_cleanupare not thread-safe. Withuvicornrunning multiple workers (or threads via--workersflag), concurrent modifications could cause race conditions or lost updates.The docstring mentions "single-instance deployments," which is appropriate. Consider adding a note that this also assumes single-worker mode, or use a
threading.Lockif multi-worker support is needed.
51-52: Fallback to "unknown" still allows shared bucket abuse.When
request.clientisNone, the client ID becomes"unknown", creating a shared rate-limit bucket. While rare, this could happen in certain proxy misconfigurations. Consider logging a warning when this occurs to aid debugging.🔍 Suggested enhancement
# Get client IP (relies on Uvicorn proxy-headers config for real IP) - client_id = request.client.host if request.client else "unknown" + if request.client: + client_id = request.client.host + else: + client_id = "unknown" + # Consider logging: logger.warning("Rate limiter: request.client is None, using shared bucket")backend/routes/feedback.py (2)
49-50: Avoid bareexceptclause; catch specific exceptions.The bare
exceptcatches all exceptions includingKeyboardInterruptandSystemExit. Catch specific exceptions likehttpx.RequestErrororhttpx.TimeoutExceptionfor cleaner error handling.♻️ Suggested fix
- except Exception: + except (httpx.RequestError, httpx.TimeoutException) as e: + # Optionally log the error for debugging return False
48-48: Discord webhooks may return 200 or other 2xx status codes.While 204 is common for Discord webhooks, checking for any successful 2xx response is more robust.
♻️ Suggested fix
- return response.status_code == 204 + return response.is_success # or: 200 <= response.status_code < 300
Polish Sprint - Pre-Launch Prep
Final polish before LinkedIn soft launch. Adds user feedback capture, waitlist signups, and landing page improvements.
Features
Feedback Widget
Waitlist Modal
Landing Page Updates
Bug Fixes
Cleanup
Files Changed
New Components
FeedbackWidget.tsx- Feedback capture modalWaitlistModal.tsx- Pro/Enterprise waitlist signupModified
Hero.tsx- Updated copy, pass autoTyping propHeroSearch.tsx- Theme-aware styling, disable during auto-typeFooter.tsx- Cleaned dead links, rebrandedPricing.tsx- Integrated waitlist modalDashboardStats.tsx- Added loading skeletonDashboardHome.tsx- Show skeleton during loadDashboardLayout.tsx- Added FeedbackWidgetConfig
docker-compose.yml- Added webhook build argfrontend/Dockerfile- Added webhook ARG/ENV.env.example- Documented webhook variableTesting
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.