feat: [tools] CV optimizer + lettre de motivation (free taste + credit tier)#19
feat: [tools] CV optimizer + lettre de motivation (free taste + credit tier)#19AirKyzzZ wants to merge 18 commits into
Conversation
…RT-aware playwright config
…el, recruiterName cap
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughAdds CV optimizer and cover-letter tools: prompt schemas, AI model wiring and mocks, server actions with quota/auth handling, marketing pages and public analyzer, dashboard forms, translations, tests, Playwright config, sitemap updates, and E2E checks. ChangesCV Optimizer & Lettre Writer
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
web/src/app/[locale]/dashboard/lettre/_components/lettre-form.tsx (1)
63-68: 💤 Low valueConsider adding error handling for clipboard write.
navigator.clipboard.writeTextcan fail if clipboard permissions are denied or if the API is unavailable. While rare in modern HTTPS contexts, handling the error would improve UX by showing feedback when copy fails.♻️ Proposed enhancement
async function onCopy() { if (!result) return; - await navigator.clipboard.writeText(`${result.subject}\n\n${result.body}`); - setCopied(true); - setTimeout(() => setCopied(false), 2000); + try { + await navigator.clipboard.writeText(`${result.subject}\n\n${result.body}`); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Optionally set an error state to show "Copy failed" feedback + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/app/`[locale]/dashboard/lettre/_components/lettre-form.tsx around lines 63 - 68, Wrap the navigator.clipboard.writeText call in a try/catch inside onCopy: attempt the write, setCopied(true) and schedule reset only on success, and in the catch set an error state (e.g., setCopyError or setCopied(false) and setCopyErrorMessage) so the UI can show a failure message; keep onCopy as the single place handling copy and update any relevant UI to display the error when that state is set.web/src/app/[locale]/(marketing)/outils/_components/cv-analyzer.tsx (1)
28-48: ⚡ Quick winPremature analytics tracking spans both tool components.
Both
CvAnalyzerandToolGeneratortrackEVENTS.ToolGeneratedimmediately on form submit (before their respective async server actions complete). This means the event fires even when the action fails due to rate limiting, invalid input, or server errors. The event name implies successful generation, making analytics data misleading.Consider one of these approaches across both components:
- Track the event only in the success path (after successful response)
- Rename to
EVENTS.ToolGenerationAttemptedorEVENTS.ToolSubmitted- Emit separate events for attempts vs. successes
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-analyzer.tsx around lines 28 - 48, The submit handler currently calls track(EVENTS.ToolGenerated, { tool: TOOL }) immediately in onSubmit, which logs success even when analyzeCvPublic fails; to fix, remove that initial track call and instead call track(EVENTS.ToolGenerated, { tool: TOOL }) only inside the success branch where res.ok is true (right after setAnalysis(res.analysis)), or alternatively emit a distinct event (e.g., EVENTS.ToolGenerationAttempted) at submit and keep EVENTS.ToolGenerated for the success path; update references in onSubmit, analyzeCvPublic success branch, and any related tests to reflect the chosen approach while leaving setAnalysis and setErrorKind logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/e2e/shelf-tools.spec.ts`:
- Around line 4-8: The test named "hub lists five tools" in the test block
(test("hub lists five tools", async ({ page }) => { ... })) only asserts two
links; either rename the test to reflect the actual assertions (e.g., "hub lists
CV and lettre tools") or add expectations for the other three expected tool
links by adding additional expect(page.getByRole("link", { name: /.../
})).toBeVisible() calls after the existing ones (keep the existing
page.goto("/fr/outils") and match the visible link names for the three missing
tools).
In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-tool-page.tsx:
- Around line 6-32: CV_TOOL currently hardcodes French strings so the page
ignores locale; replace the hardcoded values in the CV_TOOL object with
translation lookups using the existing next-intl translations (use the
getTranslations("tools") result and t(...) calls) — e.g. map metaTitle,
metaDescription, h1, intro and each faq.q/a to translation keys like
"cv.metaTitle", "cv.metaDescription", "cv.h1", "cv.intro", "cv.faq.0.q",
"cv.faq.0.a", etc.; update the component that imports/uses CV_TOOL to build or
hydrate CV_TOOL from t(...) after awaiting getTranslations("tools") so content
is locale-aware and remove the French literals from the file.
In `@web/src/app/`[locale]/(marketing)/outils/_components/lead-capture.tsx:
- Around line 14-23: The onCapture handler lacks error handling when
captureToolLead returns ok: false; add a local error state (e.g., captureError)
and set it when res.ok is false inside onCapture so the UI can show a failure
message and optionally allow retry; update the JSX that currently uses
setCaptureSent to also render the error message when captureError is set and
clear captureError when the user modifies the email or retries. Target symbols:
onCapture, startCapture, captureToolLead, setCaptureSent, track, email, tool.
---
Nitpick comments:
In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-analyzer.tsx:
- Around line 28-48: The submit handler currently calls
track(EVENTS.ToolGenerated, { tool: TOOL }) immediately in onSubmit, which logs
success even when analyzeCvPublic fails; to fix, remove that initial track call
and instead call track(EVENTS.ToolGenerated, { tool: TOOL }) only inside the
success branch where res.ok is true (right after setAnalysis(res.analysis)), or
alternatively emit a distinct event (e.g., EVENTS.ToolGenerationAttempted) at
submit and keep EVENTS.ToolGenerated for the success path; update references in
onSubmit, analyzeCvPublic success branch, and any related tests to reflect the
chosen approach while leaving setAnalysis and setErrorKind logic intact.
In `@web/src/app/`[locale]/dashboard/lettre/_components/lettre-form.tsx:
- Around line 63-68: Wrap the navigator.clipboard.writeText call in a try/catch
inside onCopy: attempt the write, setCopied(true) and schedule reset only on
success, and in the catch set an error state (e.g., setCopyError or
setCopied(false) and setCopyErrorMessage) so the UI can show a failure message;
keep onCopy as the single place handling copy and update any relevant UI to
display the error when that state is set.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 93617891-5c8e-417c-938e-72463e1514de
📒 Files selected for processing (34)
web/e2e/shelf-tools.spec.tsweb/messages/de.jsonweb/messages/en.jsonweb/messages/es.jsonweb/messages/fr.jsonweb/playwright.config.tsweb/src/app/[locale]/(marketing)/outils/_components/cv-analyzer.tsxweb/src/app/[locale]/(marketing)/outils/_components/cv-tool-page.tsxweb/src/app/[locale]/(marketing)/outils/_components/lead-capture.tsxweb/src/app/[locale]/(marketing)/outils/_components/tool-generator.tsxweb/src/app/[locale]/(marketing)/outils/_content.tsweb/src/app/[locale]/(marketing)/outils/lettre-de-motivation/page.tsxweb/src/app/[locale]/(marketing)/outils/optimiser-son-cv/page.tsxweb/src/app/[locale]/(marketing)/outils/page.tsxweb/src/app/[locale]/dashboard/_components/sidebar.tsxweb/src/app/[locale]/dashboard/cv-optimizer/_components/cv-optimizer-form.tsxweb/src/app/[locale]/dashboard/cv-optimizer/page.tsxweb/src/app/[locale]/dashboard/lettre/_components/lettre-form.tsxweb/src/app/[locale]/dashboard/lettre/page.tsxweb/src/app/sitemap.tsweb/src/lib/analytics/events.tsweb/src/server/actions/cv-optimizer.test.tsweb/src/server/actions/cv-optimizer.tsweb/src/server/actions/lettre-writer.test.tsweb/src/server/actions/lettre-writer.tsweb/src/server/integrations/ai/index.tsweb/src/server/integrations/ai/openrouter.test.tsweb/src/server/integrations/ai/openrouter.tsweb/src/server/integrations/ai/prompts/cv-optimizer.test.tsweb/src/server/integrations/ai/prompts/cv-optimizer.tsweb/src/server/integrations/ai/prompts/lettre-full.test.tsweb/src/server/integrations/ai/prompts/lettre-full.tsweb/src/server/integrations/ai/prompts/tool-generator.test.tsweb/src/server/integrations/ai/prompts/tool-generator.ts
| export const CV_TOOL = { | ||
| slug: "optimiser-son-cv", | ||
| metaTitle: "Optimiser son CV pour une offre : analyse gratuite · Cheatjob", | ||
| metaDescription: | ||
| "Colle ton CV et l'offre visée : score d'adéquation, points forts, lacunes, mots-clés manquants et conseils concrets. Gratuit, sans inscription.", | ||
| h1: "Optimise ton CV pour l'offre que tu vises", | ||
| intro: | ||
| "Un CV n'est pas bon dans l'absolu, il est bon pour une offre. Colle ton CV et l'annonce : on te dit ce qui colle, ce qui manque et ce qu'il faut réécrire, en trente secondes.", | ||
| faq: [ | ||
| { | ||
| q: "Comment adapter son CV à une offre d'emploi ?", | ||
| a: "Tu reprends les mots de l'offre, tu remontes les expériences qui y répondent, tu coupes le reste. Un CV adapté se lit comme une réponse à l'annonce, pas comme un historique exhaustif.", | ||
| }, | ||
| { | ||
| q: "C'est quoi les mots-clés ATS ?", | ||
| a: "Les termes que les logiciels de tri et les recruteurs cherchent en premier : intitulés de poste, compétences, outils. S'ils sont dans l'offre mais pas dans ton CV, tu passes sous le radar.", | ||
| }, | ||
| { | ||
| q: "Quelle longueur pour un CV de jeune diplômé ?", | ||
| a: "Une page. Pas par dogme : parce qu'un recruteur y passe trente secondes et qu'une page bien hiérarchisée se scanne mieux que deux pages remplies.", | ||
| }, | ||
| { | ||
| q: "Faut-il chiffrer ses expériences sur un CV ?", | ||
| a: "Oui, dès que c'est possible. « Pilotage de 3 projets » dit quelque chose, « participation à des projets » ne dit rien. Les chiffres rendent une expérience vérifiable et mémorisable.", | ||
| }, | ||
| ], | ||
| } as const; |
There was a problem hiding this comment.
Hardcoded French content breaks multi-locale support.
The CV_TOOL constant contains French-only strings for metaTitle, metaDescription, h1, intro, and all FAQ entries. The PR summary states "adds strings for four locales" and the layer depends on expanded message catalogs, yet this component will render French content regardless of the locale parameter.
You should migrate these strings to use next-intl translations, similar to how the component already uses await getTranslations("tools") on line 35.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/app/`[locale]/(marketing)/outils/_components/cv-tool-page.tsx around
lines 6 - 32, CV_TOOL currently hardcodes French strings so the page ignores
locale; replace the hardcoded values in the CV_TOOL object with translation
lookups using the existing next-intl translations (use the
getTranslations("tools") result and t(...) calls) — e.g. map metaTitle,
metaDescription, h1, intro and each faq.q/a to translation keys like
"cv.metaTitle", "cv.metaDescription", "cv.h1", "cv.intro", "cv.faq.0.q",
"cv.faq.0.a", etc.; update the component that imports/uses CV_TOOL to build or
hydrate CV_TOOL from t(...) after awaiting getTranslations("tools") so content
is locale-aware and remove the French literals from the file.
Fixes Applied SuccessfullyFixed 6 file(s) based on 2 of 3 CodeRabbit feedback item(s). Applied:
Deferred (intentional): the hardcoded French content in Commit: |
Closes #9
First slice of the provider-orchestration plan: a CV optimizer and a lettre de motivation writer, each with a free public taste on
/outils(IP rate-limited, Haiku) and a 1-credit dashboard tier (Sonnet). The lettre rides the existing tool machinery as a fourthToolMode, the CV analyzer is its own slice since it returns a structured analysis instead of an email. Both premium actions ground inprofiles.cv_extractedand keep the Phase 4 billing invariant: a failed generation never costs a credit, and theconsume_quotaresult is now acted on so a lost race returns quota_exceeded instead of a free result.Also extracts
LeadCaptureout of the tool generator, adds the two pages to the hub and sitemap, two PostHog events, strings in all four locales, and ashelf-toolsPlaywright smoke. The playwright config now honorsPORTso e2e can run next to another local dev server.154 unit tests, typecheck, build and the 4 e2e all green.
Summary by CodeRabbit
New Features
Tests