Conversation
* feat: add vite build * theme nicosup added --------- Co-authored-by: RV <ruvasqm@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthroughyo frfr this PR is 🔥: removes the old Vite/Express client/server, adds a Next.js App Router app with i18n ([locale]) layouts, theme system, SSR data fetching, a contact POST API with Turnstile + Discord webhook, new theme packages (kayron, nicosup), i18n dictionaries, and updated configs (.gitignore, biome, PostCSS, Next config, package.json). Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Page as Next.js [locale]/layout
participant Dict as getDictionary
participant GitHub as api.github.com
participant FS as public/data
participant Theme as themeServer
User->>Page: Request /[locale]
Page->>Dict: load locale JSON
par concurrent SSR
Page->>GitHub: fetch repos (optional token)
Page->>FS: read testimonials.json
Page->>Theme: load theme components (by cookie or random)
end
Page-->>User: SSR HTML with content + initial data
sequenceDiagram
autonumber
actor User
participant UI as BaseLayout ContactForm
participant API as POST /api/contact
participant Turnstile as Cloudflare siteverify
participant Discord as Discord Webhook
User->>UI: Submit form (name,email,subject,message,turnstileToken)
UI->>API: JSON payload
API->>API: Validate + sanitize + length checks
alt Dev bypass token
API-->>Turnstile: Skip verification
else Verify token
API->>Turnstile: Verify secret + response
Turnstile-->>API: success/failure
end
alt Discord webhook configured
API->>Discord: POST embed
Discord-->>API: 2xx/err
end
alt Any failure
API-->>UI: 4xx/5xx with errors[]
else Success
API-->>UI: 200 {"message":"ok"}
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 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 |
| await themeComponentLoaders[availableThemes[0].id](); | ||
| return defaultThemeModule.default; | ||
| } | ||
| const module = await loader(); |
Check failure
Code scanning / CodeQL
Unvalidated dynamic method call High
This autofix suggestion was applied.
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 months ago
To safely invoke a dynamically looked-up method based on user-controlled data, we must:
- Ensure the property is an own property of the object (not inherited).
- Validate that the property value is a function before invoking it.
Best-practice fix in this situation:
- Use
Object.prototype.hasOwnProperty.call(themeComponentLoaders, themeId)to confirm the property exists directly on the object. - Use
typeof loader === 'function'to confirm it's callable. - Only invoke if both checks pass; otherwise, fall back to the default theme loader.
Required changes:
- Edit
src/lib/themeServer.tsin functionloadThemeComponentsServer, specifically lines 15-27. - No changes to imports or external definitions are necessary.
- The check for "not found" (current lines 16-19) should be replaced with stricter validation (own property and typeof function).
| @@ -13,11 +13,14 @@ | ||
| themeId: ThemeName, | ||
| ): Promise<ThemeComponents> { | ||
| const loader = themeComponentLoaders[themeId]; | ||
| if (!loader) { | ||
| if ( | ||
| !Object.prototype.hasOwnProperty.call(themeComponentLoaders, themeId) || | ||
| typeof loader !== "function" | ||
| ) { | ||
| console.error( | ||
| `Theme components for "${themeId}" not found. Falling back to default.`, | ||
| `Theme components for "${themeId}" not found or invalid. Falling back to default.`, | ||
| ); | ||
| // Fallback to a default theme if the requested one is not found | ||
| // Fallback to a default theme if the requested one is not found or invalid | ||
| const defaultThemeModule = | ||
| await themeComponentLoaders[availableThemes[0].id](); | ||
| return defaultThemeModule.default; |
…od call Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 25
♻️ Duplicate comments (1)
src/lib/themeServer.ts (1)
15-30: Harden dynamic loader + catch import failsYou validate the key (nice), but a failed import still 500s. Add whitelist check and try/catch to fall back cleanly. This also calms CodeQL’s “unvalidated dynamic method call”.
Apply this diff:
export async function loadThemeComponentsServer( themeId: ThemeName, ): Promise<ThemeComponents> { - const loader = themeComponentLoaders[themeId]; - if ( - !Object.prototype.hasOwnProperty.call(themeComponentLoaders, themeId) || - typeof loader !== "function" - ) { + const allowed = new Set(availableThemes.map((t) => t.id as ThemeName)); + const loader = themeComponentLoaders[themeId]; + if (!allowed.has(themeId) || typeof loader !== "function") { console.error( `Theme components for "${themeId}" not found or invalid. Falling back to default.`, ); - // Fallback to a default theme if the requested one is not found or invalid - const defaultThemeModule = - await themeComponentLoaders[availableThemes[0].id](); - return defaultThemeModule.default; + const fallback = await themeComponentLoaders[availableThemes[0].id](); + return fallback.default; } - const module = await loader(); - return module.default; + try { + const module = await loader(); + return module.default; + } catch (err) { + console.error(`Failed to load theme "${themeId}":`, err); + const fallback = await themeComponentLoaders[availableThemes[0].id](); + return fallback.default; + } }
🧹 Nitpick comments (53)
next.config.ts (1)
7-15: SEO/UX sanity on root redirect — permanent 301 is a strong take.301 from "/" → "/es" is okay if Spanish is the hard default, but search/analytics might prefer i18n alternates/canonicals instead of a hard 301. Consider revisiting once i18n is fully wired.
.gitignore (1)
33-35: Save.env.examplefrom the shadow realm.Right now
.env*will also ignore your sample env. Keep the template tracked.# env files (can opt-in for committing if needed) -.env* +.env* +!.env.examplesrc/components/LanguageSwitcher.tsx (5)
10-28: Type the icon props — tiny papercut.TSX file but
GlobeIconprops are implicit any. Give it a quick type.-const GlobeIcon = ({ className = "" }) => ( +const GlobeIcon = ({ className = "" }: { className?: string }) => (
36-39: Locales hardcoded — keep it DRY with a central type/source.If you already have a
Localeunion or config, import it so this doesn’t drift.-interface LanguageSwitcherProps { - currentLocale: string; -} +type Locale = "en" | "es"; // or import from src/lib/types +interface LanguageSwitcherProps { + currentLocale: Locale; +} -const locales = [ +const locales: { code: Locale; name: string }[] = [ { code: "en", name: "EN" }, { code: "es", name: "ES" }, ];
41-45: Path rewrite viasubstring(3)is brittle — make it segment-aware.Assumes 2‑char locale forever; any tweak = instant bozo behavior. Use segments.
- const handleLocaleChange = (newLocale: string) => { - const newPathname = `/${newLocale}${pathname.substring(3)}`; - router.push(newPathname); - setIsOpen(false); // Close dropdown after selection - }; + const handleLocaleChange = (newLocale: Locale) => { + const parts = pathname.split("/"); + // Ensure leading slash => ["", "en", ...] + if (parts.length > 1 && parts[1].length > 0) { + parts[1] = newLocale; + } else { + parts[1] = newLocale; + } + const newPathname = parts.join("/") || `/${newLocale}`; + router.push(newPathname); + setIsOpen(false); + };
57-71: Outside-click handler: mousedown won’t fire on touch — gopointerdown.Mobile users deserve love too, no cap.
- document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("pointerdown", handleClickOutside as EventListener); return () => { - document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("pointerdown", handleClickOutside as EventListener); };
88-112: ARIA polish — reflect selection state.Since exactly one locale is active,
menuitemradio+aria-checkedslaps.- <div className="py-1" role="none"> + <div className="py-1" role="menu"> {locales.map((locale) => ( <button key={locale.code} onClick={() => handleLocaleChange(locale.code)} className={`block w-full text-left px-4 py-2 text-sm ${ currentLocale === locale.code ? "bg-primary text-primary-foreground" : "text-text-base hover:bg-muted" }`} - role="menuitem" + role="menuitemradio" + aria-checked={currentLocale === locale.code} > {locale.name} </button> ))} </div>src/app/[locale]/page.tsx (1)
6-10: Add metadata/alternates for i18n SEO — tiny upgrade, big vibes.Since layout renders UI, this page can still export metadata for language alternates.
-export default async function Page() { +import type { Metadata } from "next"; + +export const metadata: Metadata = { + alternates: { + languages: { + en: "/en", + es: "/es", + }, + }, +}; + +export default async function Page() { // You might fetch data specific to *this* page if it were not a pure landing page, // but for a landing page where BaseLayout renders everything, this can be minimal. return null; // Or <></> }README.md (2)
19-21: Docs path is capping — point to the i18n page file, not app/page.tsxProject uses src/app/[locale]/… with a redirect to /es. Update the edit hint accordingly.
Apply:
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +You can start editing the localized page by modifying `src/app/[locale]/page.tsx` (e.g., `src/app/es/page.tsx`). The page auto‑updates as you edit the file. Note: "/" redirects permanently to "/es".
32-36: Missing env setup section (Turnstile + Discord webhook) — add a quickstartDev onboarding vibes will be smoother with explicit env vars.
Apply:
## Deploy on Vercel @@ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. + +## Environment Variables + +Create `.env.local`: + +``` +TURNSTILE_SITE_KEY=... +TURNSTILE_SECRET_KEY=... +DISCORD_WEBHOOK_URL=... +``` + +Then: + +```bash +npm i +npm run dev +```src/themes/kayron/Layout.tsx (1)
5-7: If no hooks/state, drop 'use client' for free SSR perfMake it a server component unless you truly need client APIs.
Apply:
-"use client"; +// server component: no client-only features neededsrc/themes/kayron/Hero.tsx (2)
1-2: Duplicate "use client" — extra line is pure copeDrop one.
Apply:
-"use client"; "use client";
41-48: External CTA UX nit — open in new tab safely (if external)If ctaLink points off‑site (Discord), consider target/rel and no prefetch.
Apply:
- <Link - href={ctaLink} + <Link + href={ctaLink} + prefetch={false} + target={/^https?:\/\//.test(ctaLink) ? "_blank" : undefined} + rel={/^https?:\/\//.test(ctaLink) ? "noopener noreferrer" : undefined}src/app/globals.css (2)
23-27: Font token not used — swap to var(--font-sans)Match the font variables you defined.
Apply:
body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"); }
52-58: Optional: guard view‑transition rules with @supportsPrevents noisy CSS warnings on browsers that don’t vibe with the pseudo‑elements.
Apply:
-::view-transition-old(root) { - animation: 0.3s ease-out forwards slide-out-fade; -} - -::view-transition-new(root) { - animation: 0.3s ease-out forwards slide-in-fade; -} +@supports (view-transition-name: none) { + ::view-transition-old(root) { animation: 0.3s ease-out forwards slide-out-fade; } + ::view-transition-new(root) { animation: 0.3s ease-out forwards slide-in-fade; } +}src/themes/nicosup/AboutSection.tsx (2)
25-25: Tailwind class cap —hover:-translate-y-1/8prob not realLooks like a no-op unless you’ve custom plugin. Swap to a valid utility.
Apply this diff:
- className=" border border-foreground rounded-xl p-8 text-center hover:shadow-xl hover:shadow-emerald-300/50 hover:-translate-y-1/8 transition-shadow transition-transform duration-300" // Simpler hover effect + className="border border-foreground rounded-xl p-8 text-center hover:shadow-xl hover:shadow-emerald-300/50 hover:-translate-y-1 transform transition-shadow transition-transform duration-300" // Simpler hover effect
24-24: React keys need smoke — titles can dupIf two features share the same title, React re-use goes goofy. Use a stable id or title+index.
Apply this diff if no id exists:
- key={feature.title} + key={`${feature.title}-${index}`}src/themes/kayron/AboutSection.tsx (1)
23-23: Same key sauce hereTitle-only keys can collide. Prefer a stable id or suffix with index.
- key={feature.title} + key={`${feature.title}-${index}`}src/themes/kayron/Footer.tsx (3)
20-25: Decorative logo should be silentMark wrapper as aria-hidden to avoid double announcing brand.
- <div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-blue-600 rounded-xl flex items-center justify-center"> + <div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-blue-600 rounded-xl flex items-center justify-center" aria-hidden="true">
29-37: External socials: open in new tab like the navbarNavbar opens GH/Discord in new tab; mirror that here + noopener.
- <Link + <Link key={link.label} href={link.href} - className="text-gray-400 hover:text-purple-400 transition-colors" + className="text-gray-400 hover:text-purple-400 transition-colors" + target="_blank" + rel="noopener noreferrer" aria-label={`Síguenos en ${link.label}`} >
31-31: Key by label can clashIf two links share a label (e.g., “Website”), collision. Consider
key={link.href}.- key={link.label} + key={link.href}src/components/ThemeSwitcher.tsx (3)
70-76: A11y W — wire up aria-controlsTie button to menu for assistive tech.
- <button + <button type="button" className="inline-flex justify-center items-center rounded-md text-text-base hover:bg-background-card focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors" id="theme-menu-button" aria-expanded={isOpen} + aria-controls="theme-menu" aria-haspopup="true" onClick={handleToggle} onKeyDown={handleKeyDown} >
84-90: Give the menu an id and we gucciMinor a11y polish.
- <div + <div + id="theme-menu" className="origin-top-right absolute right-0 mt-2 w-36 rounded-md shadow-lg bg-background-card ring-1 ring-border ring-opacity-50 focus:outline-none z-50" role="menu" aria-orientation="vertical" aria-labelledby="theme-menu-button" >
83-108: Keyboard UX could hit harderArrow key navigation/roving tabindex would slap; consider Headless UI or radix for menu semantics. Non-blocker.
src/themes/kayron/Navbar.tsx (3)
86-90: Don’t toggle on desktop link clicks — just closeClicking a nav link flips state; on desktop that can briefly switch to the mobile overlay. Close instead of toggle.
- onClick={toggleMenu} // Close menu on link click + onClick={() => setIsMenuOpen(false)} // Close menu on link click
67-74: Hook up aria-controls to the menu containerSmall a11y win; pair the button with the collapsible panel id.
- <button + <button type="button" className="p-2 rounded-lg glass-card" aria-label="Abrir menú de navegación" aria-expanded={isMenuOpen} + aria-controls="primary-navigation" data-testid="button-mobile-menu" onClick={toggleMenu} >
79-80: Give the collapsible a stable idSo the button can target it.
- <div + <div + id="primary-navigation" className={`md:flex items-center space-x-8 ${isMenuOpen ? "flex flex-col absolute top-full left-0 w-full bg-black/90 py-4 space-x-0 space-y-4" : "hidden"}`} >src/themes/kayron/ProjectsSection.tsx (2)
33-34: Key uniqueness againTitle-only keys might collide. If you’ve got an id/slug, use it; else combine with link or index.
- key={project.title} + key={`${project.title}-${project.projectLink}`}
57-63: External project links — open new tab?If these go off-site, mirror navbar behavior with target+rel. If internal, ignore.
- <Link + <Link href={project.projectLink} - className="inline-flex items-center text-purple-400 hover:text-purple-300 font-semibold transition-colors group-hover:translate-x-2 transform duration-300" + className="inline-flex items-center text-purple-400 hover:text-purple-300 font-semibold transition-colors group-hover:translate-x-2 transform duration-300" + target="_blank" + rel="noopener noreferrer" >src/themes/kayron/TestimonialSection.tsx (1)
19-21: Clamp rating + add a11y text (prevents RangeError, improves SR experience) — yo frfrUntrusted data could send negative/huge ratings; Array.from with negative length blows up. Also add SR text for rating.
- const starIcons = Array.from({ length: rating }, (_, i) => ( + const safeRating = Math.max(0, Math.min(5, Math.floor(rating ?? 0))); + const starIcons = Array.from({ length: safeRating }, (_, i) => ( <div key={i} className="w-4 h-4 bg-yellow-400 rounded"></div> ));- <div className="flex items-center mt-1"> - <div className="flex space-x-0.5">{starIcons}</div> - </div> + <div className="flex items-center mt-1"> + <div className="flex space-x-0.5">{starIcons}</div> + <span className="sr-only">Rating: {safeRating} out of 5</span> + </div>Also applies to: 39-40
src/themes/nicosup/ContactForm.tsx (1)
5-7: Type-only React import needed (you reference React.ChangeEvent) — tiny but importantPrevents TS “Cannot find namespace 'React'” in some setups.
import "./style.css"; import type { FC } from "react"; +import type React from "react"; import { SendIcon } from "@/components/BaseLayout"; // Ensure SendIcon is importedsrc/themes/kayron/ContactForm.tsx (4)
1-3: Duplicate "use client" directive — drop one, keep vibes clean frfrTwo identical directives; one is enough.
Apply:
-"use client"; -// theme-modules/kayron/ContactForm.tsx -"use client"; +"use client"; +// theme-modules/kayron/ContactForm.tsx
18-22: Use functional state update to avoid stale writes (small but W)Prevents race conditions on rapid input.
- setFormData({ ...formData, [e.target.name]: e.target.value }); + setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
56-67: Dead branch: subject handling in name/email grid is midThis grid only renders name/email; the subject type check here never hits.
- type={field.type === "subject" ? "text" : field.type} // Handle 'subject' type for input + type={field.type}
56-67: Bonus UX: add autocomplete tokens for name/email/subject/messageMakes the form feel snappier, no cap.
Example pattern (apply similarly to other inputs):
- placeholder={field.placeholder} + placeholder={field.placeholder} + autoComplete={ + field.name === "email" + ? "email" + : field.name === "name" + ? "name" + : field.name === "subject" + ? "subject" + : "off" + }Also applies to: 80-90, 103-114
src/components/ThemeProvider.tsx (3)
44-71: Add deps to useCallback, keep it stable frInclude startTransitionThemeLoad to satisfy hooks lint.
- const loadAndSetTheme = useCallback( + const loadAndSetTheme = useCallback( (themeId: ThemeName) => { startTransitionThemeLoad(async () => { // ... }); }, - [], // initialThemeComponents is not needed here + [startTransitionThemeLoad], );
82-89: Early return when theme unchanged (tiny render win)Skip setState if same theme.
- const setTheme = (theme: ThemeName) => { - if (availableThemes.some((t) => t.id === theme)) { - setCurrentTheme(theme); - if (theme !== currentTheme) { - loadAndSetTheme(theme); - } - } - }; + const setTheme = (theme: ThemeName) => { + if (!availableThemes.some((t) => t.id === theme)) return; + if (theme === currentTheme) return; + setCurrentTheme(theme); + loadAndSetTheme(theme); + };
75-79: Cookie hardening (optional): add Secure in prodSafer cookie when served over HTTPS.
- document.cookie = `app-theme=${currentTheme};path=/;max-age=31536000;SameSite=Lax`; + document.cookie = `app-theme=${currentTheme};path=/;max-age=31536000;SameSite=Lax;${location.protocol === "https:" ? "Secure" : ""}`;src/themes/kayron/style.css (2)
378-385: Keyframes glow usesbrightness()with no valueNeeds a factor like
brightness(1); empty calls get yeeted.- filter: brightness(); + filter: brightness(1);
39-85: Nit: giant token block is legit, but consider grouping/commentsLong var slabs are a lot to scroll. Group by semantic (bg, text, border, charts) and keep a short index comment.
src/app/[locale]/layout.tsx (2)
165-170:cookies()isn’t async — drop the awaitTiny cleanup for SSR cookie read.
- const themeCookie = (await cookies()).get("app-theme")?.value; + const themeCookie = cookies().get("app-theme")?.value;
25-36: Unused icon imports — trim for signal, not noise
SendIcon,ExternalLinkIcon,TwitterIcon,LinkedInIconaren’t used here.- SendIcon, - ExternalLinkIcon, - TwitterIcon, GitHubIcon, - LinkedInIcon, DiscordIcon,src/components/BaseLayout.tsx (2)
4-8: Drop unused useMemo import (tiny tidy, still a W)Keep imports lean.
-import { - useCallback, - useMemo, - unstable_ViewTransition as ViewTransition, -} from "react"; +import { useCallback, unstable_ViewTransition as ViewTransition } from "react";
46-60: SVG<text>uses invaliduser-selectattributeReact will pass it through but it does nothing; use style prop.
- <text + <text x="210" y="104" fontFamily="'Segoe UI', Arial, sans-serif" fontSize="192px" fontWeight="900" fill="#FFFFFF" textAnchor="middle" dominantBaseline="middle" - user-select="none" + style={{ userSelect: "none" }} id="text302" >src/lib/types.ts (9)
2-2: Unused import, yeet it frfr.
FCisn’t used.-import type { FC, ReactNode } from "react"; +import type { ReactNode } from "react";
114-114: Use ReactNode consistently, no cap.Avoid
React.ReactNodewhenReactNodeis already imported.- children: React.ReactNode; + children: ReactNode;
58-64: Nulls are mid; prefer optionals.Cleaner ergonomics with
?over| nullfor presentational bits.- icon: ReactNode | null; // Can be null now, assigned default in BaseLayout + icon?: ReactNode; // Default assigned in BaseLayout - demoLink: string | null; + demoLink?: string;
37-43: CTA icon shouldn’t be hard‑required.Make
ctaIconoptional so themes without an icon don’t have to passnull.- ctaIcon: ReactNode; + ctaIcon?: ReactNode;
66-71: Duped error sources — pick one truthy vibe.You’ve got
initial*Erroron layout anderror?on section props. That’s two channels for the same state → drift risk.Option: keep errors only at the layout level and derive section
errorprops there, or drop layout errors and let sections own them. Either way, one source of truth.Also applies to: 90-93, 117-121
24-35: Lock props immutability with ReadonlyArray where it slaps.These arrays are props, not state. Make them readonly to prevent accidental mutation.
Examples (apply similarly elsewhere):
- navLinks: NavLink[]; + navLinks: ReadonlyArray<NavLink>; - features: FeatureCard[]; + features: ReadonlyArray<FeatureCard>; - projects: ProjectCard[]; + projects: ReadonlyArray<ProjectCard>; - testimonials: TestimonialCardProps[]; + testimonials: ReadonlyArray<TestimonialCardProps>; - socialLinks: SocialLink[]; + socialLinks: ReadonlyArray<SocialLink>; - availableThemes: { id: ThemeName; name: string }[]; + availableThemes: ReadonlyArray<{ id: ThemeName; name: string }>;Also applies to: 51-55, 66-70, 81-88, 105-111, 145-146
116-116: Locale union — make it source‑of‑truth based, ong.Hardcoding
"en" | "es"can drift. Consider deriving from aconst LOCALES = [...] as const.export const LOCALES = ["en", "es"] as const; export type Locale = typeof LOCALES[number];Then use
locale: Locale;.
24-35: Separate data vs slots — don't pass ReactNode from dictionary/server props.src/lib/types.ts currently types fields as ReactNode (logoSvg/icon/ctaIcon) and app/[locale]/layout.tsx assigns JSX to FOOTER_CONTENT.logoSvg which is then passed into client theme components (themes//Navbar.tsx, themes//Footer.tsx); this risks server→client serialization / i18n breakage.
- Action: keep dictionary-backed values as plain strings/URLs, add explicit slot props for JSX (e.g. logoSlot?: ReactNode) and update types + layout accordingly.
yo frfr this pr is 🔥
10-16: Avoid using "subject" as ContactFormField.type — use 'text'|'email'|'textarea' or add a separate variant. yo frfr this pr is 🔥
Found: src/app/[locale]/layout.tsx:260 sets type: "subject" — HTML has no input[type="subject"]; keep the type union for real HTML input kinds and express intent via name === "subject" or a dedicated variant field.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
package-lock.jsonis excluded by!**/package-lock.jsonpublic/file.svgis excluded by!**/*.svgpublic/globe.svgis excluded by!**/*.svgpublic/next.svgis excluded by!**/*.svgpublic/vercel.svgis excluded by!**/*.svgpublic/window.svgis excluded by!**/*.svgsrc/app/favicon.icois excluded by!**/*.ico
📒 Files selected for processing (107)
.github/dependabot.yml(0 hunks).gitignore(1 hunks)README.md(1 hunks)biome.json(1 hunks)client/index.html(0 hunks)client/src/App.tsx(0 hunks)client/src/components/CommunitySection.tsx(0 hunks)client/src/components/CorporateSection.tsx(0 hunks)client/src/components/Footer.tsx(0 hunks)client/src/components/LoginModal.tsx(0 hunks)client/src/components/Navigation.tsx(0 hunks)client/src/components/ui/accordion.tsx(0 hunks)client/src/components/ui/alert-dialog.tsx(0 hunks)client/src/components/ui/alert.tsx(0 hunks)client/src/components/ui/aspect-ratio.tsx(0 hunks)client/src/components/ui/avatar.tsx(0 hunks)client/src/components/ui/badge.tsx(0 hunks)client/src/components/ui/breadcrumb.tsx(0 hunks)client/src/components/ui/button.tsx(0 hunks)client/src/components/ui/calendar.tsx(0 hunks)client/src/components/ui/card.tsx(0 hunks)client/src/components/ui/carousel.tsx(0 hunks)client/src/components/ui/chart.tsx(0 hunks)client/src/components/ui/checkbox.tsx(0 hunks)client/src/components/ui/collapsible.tsx(0 hunks)client/src/components/ui/command.tsx(0 hunks)client/src/components/ui/context-menu.tsx(0 hunks)client/src/components/ui/dialog.tsx(0 hunks)client/src/components/ui/drawer.tsx(0 hunks)client/src/components/ui/dropdown-menu.tsx(0 hunks)client/src/components/ui/form.tsx(0 hunks)client/src/components/ui/hover-card.tsx(0 hunks)client/src/components/ui/input-otp.tsx(0 hunks)client/src/components/ui/input.tsx(0 hunks)client/src/components/ui/label.tsx(0 hunks)client/src/components/ui/menubar.tsx(0 hunks)client/src/components/ui/navigation-menu.tsx(0 hunks)client/src/components/ui/pagination.tsx(0 hunks)client/src/components/ui/popover.tsx(0 hunks)client/src/components/ui/progress.tsx(0 hunks)client/src/components/ui/radio-group.tsx(0 hunks)client/src/components/ui/resizable.tsx(0 hunks)client/src/components/ui/scroll-area.tsx(0 hunks)client/src/components/ui/select.tsx(0 hunks)client/src/components/ui/separator.tsx(0 hunks)client/src/components/ui/sheet.tsx(0 hunks)client/src/components/ui/sidebar.tsx(0 hunks)client/src/components/ui/skeleton.tsx(0 hunks)client/src/components/ui/slider.tsx(0 hunks)client/src/components/ui/switch.tsx(0 hunks)client/src/components/ui/table.tsx(0 hunks)client/src/components/ui/tabs.tsx(0 hunks)client/src/components/ui/textarea.tsx(0 hunks)client/src/components/ui/toast.tsx(0 hunks)client/src/components/ui/toaster.tsx(0 hunks)client/src/components/ui/toggle-group.tsx(0 hunks)client/src/components/ui/toggle.tsx(0 hunks)client/src/components/ui/tooltip.tsx(0 hunks)client/src/hooks/use-mobile.tsx(0 hunks)client/src/hooks/use-toast.ts(0 hunks)client/src/hooks/useFirebaseAuth.ts(0 hunks)client/src/index.css(0 hunks)client/src/lib/firebase.ts(0 hunks)client/src/lib/queryClient.ts(0 hunks)client/src/lib/utils.ts(0 hunks)client/src/main.tsx(0 hunks)client/src/pages/Home.tsx(0 hunks)client/src/pages/not-found.tsx(0 hunks)components.json(0 hunks)drizzle.config.ts(0 hunks)next.config.ts(1 hunks)package.json(1 hunks)postcss.config.mjs(1 hunks)public/data/en.json(1 hunks)public/data/es.json(1 hunks)public/data/testimonials.json(1 hunks)server/index.ts(0 hunks)server/routes.ts(0 hunks)server/storage.ts(0 hunks)server/vite.ts(0 hunks)shared/schema.ts(0 hunks)src/app/[locale]/layout.tsx(1 hunks)src/app/[locale]/page.tsx(1 hunks)src/app/api/contact/route.ts(1 hunks)src/app/globals.css(1 hunks)src/app/layout.tsx(1 hunks)src/app/page.tsx(1 hunks)src/components/BaseLayout.tsx(1 hunks)src/components/LanguageSwitcher.tsx(1 hunks)src/components/ThemeProvider.tsx(1 hunks)src/components/ThemeSwitcher.tsx(1 hunks)src/lib/dictionaries.ts(1 hunks)src/lib/themeConfig.ts(1 hunks)src/lib/themeServer.ts(1 hunks)src/lib/types.ts(1 hunks)src/themes/kayron/AboutSection.tsx(1 hunks)src/themes/kayron/ContactForm.tsx(1 hunks)src/themes/kayron/Footer.tsx(1 hunks)src/themes/kayron/Hero.tsx(1 hunks)src/themes/kayron/Layout.tsx(1 hunks)src/themes/kayron/Navbar.tsx(1 hunks)src/themes/kayron/ProjectsSection.tsx(1 hunks)src/themes/kayron/TestimonialSection.tsx(1 hunks)src/themes/kayron/index.ts(1 hunks)src/themes/kayron/style.css(1 hunks)src/themes/nicosup/AboutSection.tsx(1 hunks)src/themes/nicosup/ContactForm.tsx(1 hunks)
⛔ Files not processed due to max files limit (20)
- src/themes/nicosup/Footer.tsx
- src/themes/nicosup/Hero.tsx
- src/themes/nicosup/Layout.tsx
- src/themes/nicosup/Navbar.tsx
- src/themes/nicosup/ProjectsSection.tsx
- src/themes/nicosup/TestimonialSection.tsx
- src/themes/nicosup/index.ts
- src/themes/nicosup/style.css
- src/themes/pix/AboutSection.tsx
- src/themes/pix/ContactForm.tsx
- src/themes/pix/Footer.tsx
- src/themes/pix/Hero.tsx
- src/themes/pix/Layout.tsx
- src/themes/pix/Navbar.tsx
- src/themes/pix/ProjectsSection.tsx
- src/themes/pix/TestimonialSection.tsx
- src/themes/pix/index.ts
- src/themes/pix/style.css
- tsconfig.json
- vite.config.ts
💤 Files with no reviewable changes (72)
- server/routes.ts
- client/index.html
- client/src/components/ui/textarea.tsx
- client/src/components/ui/switch.tsx
- client/src/main.tsx
- client/src/components/CorporateSection.tsx
- client/src/components/ui/avatar.tsx
- client/src/components/ui/skeleton.tsx
- client/src/components/ui/label.tsx
- client/src/lib/utils.ts
- .github/dependabot.yml
- client/src/components/ui/toaster.tsx
- client/src/pages/not-found.tsx
- components.json
- server/index.ts
- client/src/components/ui/toggle-group.tsx
- client/src/components/ui/tooltip.tsx
- client/src/components/Navigation.tsx
- client/src/components/ui/input.tsx
- client/src/components/CommunitySection.tsx
- client/src/components/ui/calendar.tsx
- client/src/components/ui/radio-group.tsx
- server/vite.ts
- client/src/lib/firebase.ts
- client/src/App.tsx
- shared/schema.ts
- client/src/lib/queryClient.ts
- client/src/components/ui/aspect-ratio.tsx
- client/src/components/ui/card.tsx
- client/src/components/ui/badge.tsx
- client/src/components/ui/hover-card.tsx
- server/storage.ts
- client/src/components/ui/toast.tsx
- client/src/components/ui/slider.tsx
- client/src/components/ui/accordion.tsx
- client/src/components/ui/breadcrumb.tsx
- client/src/components/ui/dialog.tsx
- client/src/hooks/useFirebaseAuth.ts
- client/src/components/ui/checkbox.tsx
- client/src/components/Footer.tsx
- client/src/components/ui/command.tsx
- client/src/pages/Home.tsx
- client/src/hooks/use-mobile.tsx
- client/src/components/ui/input-otp.tsx
- client/src/components/ui/alert.tsx
- client/src/components/ui/table.tsx
- client/src/components/ui/chart.tsx
- client/src/components/ui/alert-dialog.tsx
- client/src/components/ui/carousel.tsx
- client/src/components/ui/scroll-area.tsx
- drizzle.config.ts
- client/src/components/ui/sheet.tsx
- client/src/components/ui/form.tsx
- client/src/components/ui/toggle.tsx
- client/src/components/ui/tabs.tsx
- client/src/components/LoginModal.tsx
- client/src/components/ui/progress.tsx
- client/src/components/ui/resizable.tsx
- client/src/components/ui/separator.tsx
- client/src/components/ui/button.tsx
- client/src/components/ui/collapsible.tsx
- client/src/components/ui/popover.tsx
- client/src/components/ui/context-menu.tsx
- client/src/components/ui/drawer.tsx
- client/src/components/ui/select.tsx
- client/src/components/ui/dropdown-menu.tsx
- client/src/components/ui/menubar.tsx
- client/src/hooks/use-toast.ts
- client/src/components/ui/navigation-menu.tsx
- client/src/index.css
- client/src/components/ui/sidebar.tsx
- client/src/components/ui/pagination.tsx
🧰 Additional context used
🧬 Code graph analysis (17)
src/themes/nicosup/AboutSection.tsx (1)
src/lib/types.ts (1)
AboutSectionProps(51-55)
src/themes/kayron/AboutSection.tsx (1)
src/lib/types.ts (1)
AboutSectionProps(51-55)
src/themes/kayron/Footer.tsx (1)
src/lib/types.ts (1)
FooterProps(105-111)
src/themes/kayron/Hero.tsx (1)
src/lib/types.ts (1)
HeroProps(37-43)
src/themes/kayron/index.ts (1)
src/lib/types.ts (1)
ThemeComponents(130-139)
src/themes/kayron/Navbar.tsx (2)
src/lib/types.ts (1)
NavbarProps(24-35)src/components/BaseLayout.tsx (2)
GitHubIcon(218-223)DiscordIcon(232-237)
src/lib/themeServer.ts (2)
src/lib/types.ts (2)
ThemeName(150-150)ThemeComponents(130-139)src/lib/themeConfig.ts (2)
availableThemes(5-9)themeComponentLoaders(11-18)
src/components/ThemeSwitcher.tsx (2)
src/components/ThemeProvider.tsx (1)
useTheme(106-112)src/lib/themeConfig.ts (1)
availableThemes(5-9)
src/themes/nicosup/ContactForm.tsx (2)
src/lib/types.ts (1)
ContactFormProps(95-103)src/components/BaseLayout.tsx (1)
SendIcon(168-185)
src/components/ThemeProvider.tsx (2)
src/lib/types.ts (3)
ThemeContextType(142-148)ThemeName(150-150)ThemeComponents(130-139)src/lib/themeConfig.ts (2)
themeComponentLoaders(11-18)availableThemes(5-9)
src/lib/themeConfig.ts (1)
src/lib/types.ts (2)
ThemeComponents(130-139)ThemeName(150-150)
src/components/BaseLayout.tsx (4)
src/lib/types.ts (1)
BaseLayoutProps(113-128)src/components/ThemeProvider.tsx (1)
useTheme(106-112)src/components/ThemeSwitcher.tsx (1)
ThemeSwitcher(32-111)src/components/LanguageSwitcher.tsx (1)
LanguageSwitcher(30-115)
src/themes/kayron/ProjectsSection.tsx (2)
src/lib/types.ts (1)
ProjectsSectionProps(66-71)src/components/BaseLayout.tsx (1)
ExternalLinkIcon(187-209)
src/themes/kayron/ContactForm.tsx (1)
src/lib/types.ts (1)
ContactFormProps(95-103)
src/lib/types.ts (1)
src/lib/dictionaries.ts (1)
Dictionary(25-25)
src/app/[locale]/layout.tsx (6)
src/lib/dictionaries.ts (1)
getDictionary(17-23)src/lib/types.ts (11)
ProjectCard(57-64)repo(73-79)TestimonialCardProps(81-88)ThemeName(150-150)NavLink(5-8)HeroProps(37-43)AboutSectionProps(51-55)ProjectsSectionProps(66-71)TestimonialSectionProps(90-93)ContactFormProps(95-103)FooterProps(105-111)src/lib/themeConfig.ts (1)
availableThemes(5-9)src/lib/themeServer.ts (2)
getRandomThemeId(6-9)loadThemeComponentsServer(12-30)src/components/BaseLayout.tsx (8)
MessageCircleIcon(63-83)CircleCheckBigIcon(105-126)UsersIcon(128-147)BookOpenIcon(149-166)CodeIcon(15-62)GitHubIcon(218-223)DiscordIcon(232-237)BaseLayout(240-339)src/components/ThemeProvider.tsx (1)
ThemeProvider(33-104)
src/themes/kayron/TestimonialSection.tsx (2)
src/lib/types.ts (2)
TestimonialCardProps(81-88)TestimonialSectionProps(90-93)src/components/BaseLayout.tsx (1)
MessageCircleIcon(63-83)
🪛 GitHub Check: CodeQL
src/lib/themeServer.ts
[failure] 28-28: Unvalidated dynamic method call
Invocation of method with user-controlled name may dispatch to unexpected target and cause an exception.
🔇 Additional comments (16)
src/app/page.tsx (1)
1-3: Stub page is chill — W placeholder.Keeping root minimal while redirecting "/" → "/es" is fine. No blockers here.
postcss.config.mjs (1)
1-5: Tailwind v4 PostCSS plug — looks valid, ship it.Config is lean and matches TW v4 vibes. All good.
biome.json (2)
21-25: Biome domains — nice, on-brand for Next/React.Enabling
"domains": { "next": "recommended", "react": "recommended" }is a W. No notes.
9-11: Include negations are honored by Biome here — no change needed.
biome v2.1.2 ran;biome checkscanned 54 files and did not include node_modules, .next, dist, or build, so the negated globs in biome.json are respected — yo frfr this pr is 🔥package.json (1)
12-18: Sanity-check Next 15.5.0 + Node — yo frfr this pr is 🔥
- Node on environment: v24.3.0 — satisfies Next 15.5.0 engines (node: '^18.18.0 || ^19.8.0 || >= 20.0.0').
- package.json sets react/react-dom = "experimental" while Next peerDeps expect ^18.2.0 or ^19.x — verify the lockfile resolves "experimental" to a compatible React or pin explicit versions.
- Ensure lockfile (package-lock.json / pnpm-lock.yaml / yarn.lock) is updated and CI installs/tests on Node v24.x pass.
Location: package.json lines 12-18 (also applies to 29-31).
next.config.ts (1)
4-6: Confirmed — experimental.viewTransition (singular) is correctDocs and community threads confirm the key is experimental.viewTransition; your config already uses that — yo frfr this pr is 🔥
public/data/es.json (1)
1-82: Confirmed — placeholders wired to rendering codedictionary.brand.copyright is .replace("{year}", new Date().getFullYear().toString()) and .replace("{brandName}", brandName) in src/app/[locale]/layout.tsx (lines 281–282). yo frfr this pr is 🔥
src/themes/kayron/index.ts (1)
11-20: Approve: Theme wiring looks clean — ship it
All mapped components exist under src/themes/kayron (Layout, Navbar, Hero, AboutSection, ProjectsSection, TestimonialSection, ContactForm, Footer).
yo frfr this pr is 🔥src/app/globals.css (1)
1-14: yo frfr this pr is 🔥 — Tailwind v4 detected; confirm toolchain & configsrc/lib/dictionaries.ts (1)
1-4: Edge/runtime check — node:fs used; confirm no Edge routes or switch to JSON importsrc/lib/dictionaries.ts imports { promises as fs } from "node:fs" (lines 1–4). Repo-wide search found no occurrences of export const runtime = "edge". If any route runs on the Edge runtime this will 500 — ensure affected routes use the Node runtime or load the dictionaries via static JSON import instead. yo frfr this pr is 🔥
src/themes/nicosup/AboutSection.tsx (1)
12-20: UI vibes are clean afLayout/semantics look solid; headings + copy hierarchy land well. Ship it.
src/themes/kayron/AboutSection.tsx (1)
24-31: Animation delay via index — chill but be awareUsing index for delay is fine for cosmetics; if list order changes, timing shuffles. All good for now.
src/lib/themeServer.ts (1)
6-9: RNG theme picker is low-stakes, vibes goodNo issues; server-only util is fine.
src/themes/kayron/ProjectsSection.tsx (1)
16-24: Header + empty/error states hit cleanLogic branches are crisp; copy lands. Ship it.
src/components/BaseLayout.tsx (1)
254-292: Assumes non-null themeComponents — aligned with provider, just flaggingGiven ThemeProvider guarantees non-null, destructuring is valid. If you later allow null in context, add a guard here.
src/components/ThemeProvider.tsx (1)
16-22: Consolidate ThemeContextType nullability (themeComponents)Local interface marks themeComponents as non-null while src/lib/types allows null — unify to a single source of truth to avoid downstream TS drift. I couldn't locate src/lib/types/other ThemeContextType declarations in the sandbox; verify which declaration is canonical and either move ThemeContextType to src/lib/types with non-null themeComponents and update imports, or relax the local type to allow null.
yo frfr this pr is 🔥
| "testimonials": { | ||
| "loading": "Loading testimonials...", | ||
| "error": "Error loading testimonials." | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add testimonials heading/subheading to enable i18n (un-hardcode UI) — chef’s kiss
Lets the Kayron section pull from the dictionary.
"testimonials": {
+ "heading": "Testimonials",
+ "subheading": "What our community is saying.",
"loading": "Loading testimonials...",
"error": "Error loading testimonials."
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "testimonials": { | |
| "loading": "Loading testimonials...", | |
| "error": "Error loading testimonials." | |
| }, | |
| "testimonials": { | |
| "heading": "Testimonials", | |
| "subheading": "What our community is saying.", | |
| "loading": "Loading testimonials...", | |
| "error": "Error loading testimonials." | |
| }, |
🤖 Prompt for AI Agents
In public/data/en.json around lines 43 to 46, the testimonials section currently
only includes loading and error messages; add i18n keys for the testimonials
heading and subheading so the UI can pull those strings instead of hardcoding
them. Add entries such as "heading" and "subheading" (or "title" and "subtitle"
depending on existing conventions) with appropriate English copy, and ensure any
components reading the dictionary are updated to use these new keys.
| [ | ||
| { | ||
| "quote": "Escuadrón 404 transformó mi carrera como desarrollador. La comunidad es increíble, siempre hay alguien dispuesto a ayudar y los proyectos colaborativos me han permitido crecer exponencialmente.", | ||
| "authorName": "María González", | ||
| "authorRole": "Senior Full Stack Developer", | ||
| "authorAvatar": null, | ||
| "rating": 5 | ||
| }, | ||
| { | ||
| "quote": "Desde que me uní a Escuadrón 404, he encontrado un ambiente de aprendizaje inigualable. Las mentorías son de gran valor y la camaradería es fantástica.", | ||
| "authorName": "Carlos Pérez", | ||
| "authorRole": "Junior Software Engineer", | ||
| "authorAvatar": null, | ||
| "rating": 4 | ||
| }, | ||
| { | ||
| "quote": "Los eventos tech organizados por Escuadrón 404 son siempre relevantes y muy bien organizados. Es una excelente manera de mantenerse actualizado y conectar con otros profesionales.", | ||
| "authorName": "Ana López", | ||
| "authorRole": "Data Scientist", | ||
| "authorAvatar": null, | ||
| "rating": 5 | ||
| } | ||
| ] |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add stable ids for React keys — keep lists crispy, no diff thrash
IDs also help future edits/analytics.
[
{
+ "id": "maria-gonzalez",
"quote": "Escuadrón 404 transformó mi carrera como desarrollador. La comunidad es increíble, siempre hay alguien dispuesto a ayudar y los proyectos colaborativos me han permitido crecer exponencialmente.",
"authorName": "María González",
"authorRole": "Senior Full Stack Developer",
"authorAvatar": null,
"rating": 5
},
{
+ "id": "carlos-perez",
"quote": "Desde que me uní a Escuadrón 404, he encontrado un ambiente de aprendizaje inigualable. Las mentorías son de gran valor y la camaradería es fantástica.",
"authorName": "Carlos Pérez",
"authorRole": "Junior Software Engineer",
"authorAvatar": null,
"rating": 4
},
{
+ "id": "ana-lopez",
"quote": "Los eventos tech organizados por Escuadrón 404 son siempre relevantes y muy bien organizados. Es una excelente manera de mantenerse actualizado y conectar con otros profesionales.",
"authorName": "Ana López",
"authorRole": "Data Scientist",
"authorAvatar": null,
"rating": 5
}
]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| [ | |
| { | |
| "quote": "Escuadrón 404 transformó mi carrera como desarrollador. La comunidad es increíble, siempre hay alguien dispuesto a ayudar y los proyectos colaborativos me han permitido crecer exponencialmente.", | |
| "authorName": "María González", | |
| "authorRole": "Senior Full Stack Developer", | |
| "authorAvatar": null, | |
| "rating": 5 | |
| }, | |
| { | |
| "quote": "Desde que me uní a Escuadrón 404, he encontrado un ambiente de aprendizaje inigualable. Las mentorías son de gran valor y la camaradería es fantástica.", | |
| "authorName": "Carlos Pérez", | |
| "authorRole": "Junior Software Engineer", | |
| "authorAvatar": null, | |
| "rating": 4 | |
| }, | |
| { | |
| "quote": "Los eventos tech organizados por Escuadrón 404 son siempre relevantes y muy bien organizados. Es una excelente manera de mantenerse actualizado y conectar con otros profesionales.", | |
| "authorName": "Ana López", | |
| "authorRole": "Data Scientist", | |
| "authorAvatar": null, | |
| "rating": 5 | |
| } | |
| ] | |
| [ | |
| { | |
| "id": "maria-gonzalez", | |
| "quote": "Escuadrón 404 transformó mi carrera como desarrollador. La comunidad es increíble, siempre hay alguien dispuesto a ayudar y los proyectos colaborativos me han permitido crecer exponencialmente.", | |
| "authorName": "María González", | |
| "authorRole": "Senior Full Stack Developer", | |
| "authorAvatar": null, | |
| "rating": 5 | |
| }, | |
| { | |
| "id": "carlos-perez", | |
| "quote": "Desde que me uní a Escuadrón 404, he encontrado un ambiente de aprendizaje inigualable. Las mentorías son de gran valor y la camaradería es fantástica.", | |
| "authorName": "Carlos Pérez", | |
| "authorRole": "Junior Software Engineer", | |
| "authorAvatar": null, | |
| "rating": 4 | |
| }, | |
| { | |
| "id": "ana-lopez", | |
| "quote": "Los eventos tech organizados por Escuadrón 404 son siempre relevantes y muy bien organizados. Es una excelente manera de mantenerse actualizado y conectar con otros profesionales.", | |
| "authorName": "Ana López", | |
| "authorRole": "Data Scientist", | |
| "authorAvatar": null, | |
| "rating": 5 | |
| } | |
| ] |
🤖 Prompt for AI Agents
public/data/testimonials.json lines 1-23: The JSON array of testimonial objects
lacks stable id fields for React list keys; add an "id" property to each
testimonial (use stable values—UUID strings, deterministic slugs, or numeric
ids) so React can key list items consistently and avoid re-render/diff thrash;
update each object to include a unique "id" field (e.g., "id":
"maría-gonzalez-1" or a UUID) and ensure any code that renders these
testimonials uses that "id" as the key.
| export async function generateMetadata(props: { | ||
| params: Promise<{ locale: string }>; | ||
| }): Promise<Metadata> { | ||
| const params = await props.params; | ||
| const { locale } = params; | ||
| const dictionary = await getDictionary(locale as "en" | "es"); | ||
| return { | ||
| title: `${dictionary.brand.name} - ${dictionary.brand.tagline}`, | ||
| description: dictionary.hero.subtitle, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Next params aren’t Promises — fix types and usage
Typing params as Promise is cap; use plain object.
-export async function generateMetadata(props: {
- params: Promise<{ locale: string }>;
-}): Promise<Metadata> {
- const params = await props.params;
- const { locale } = params;
+export async function generateMetadata({
+ params,
+}: {
+ params: { locale: string };
+}): Promise<Metadata> {
+ const { locale } = params;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function generateMetadata(props: { | |
| params: Promise<{ locale: string }>; | |
| }): Promise<Metadata> { | |
| const params = await props.params; | |
| const { locale } = params; | |
| const dictionary = await getDictionary(locale as "en" | "es"); | |
| return { | |
| title: `${dictionary.brand.name} - ${dictionary.brand.tagline}`, | |
| description: dictionary.hero.subtitle, | |
| }; | |
| } | |
| export async function generateMetadata({ | |
| params, | |
| }: { | |
| params: { locale: string }; | |
| }): Promise<Metadata> { | |
| const { locale } = params; | |
| const dictionary = await getDictionary(locale as "en" | "es"); | |
| return { | |
| title: `${dictionary.brand.name} - ${dictionary.brand.tagline}`, | |
| description: dictionary.hero.subtitle, | |
| }; | |
| } |
🤖 Prompt for AI Agents
In src/app/[locale]/layout.tsx around lines 44 to 54, props.params is
incorrectly typed as a Promise and awaited; change the function signature to
accept props: { params: { locale: string } }, remove the await when accessing
params (use const { locale } = props.params), and keep the dictionary fetch as
await getDictionary(locale as "en" | "es"); update any related types accordingly
so params is treated as a plain object rather than a Promise.
| {testimonials.map((testimonial, index) => ( | ||
| <TestimonialCard key={index} {...testimonial} /> | ||
| ))} |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid index as key — use stable key from data, plz — vibing but keep it solid
Index keys cause janky reorders. Use authorName (or add id).
- {testimonials.map((testimonial, index) => (
- <TestimonialCard key={index} {...testimonial} />
+ {testimonials.map((t, index) => (
+ <TestimonialCard key={`${t.authorName}-${index}`} {...t} />
))}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/themes/kayron/TestimonialSection.tsx around lines 70 to 72, the map
currently uses the array index as the React key which leads to unstable
reorders; change the key to a stable unique identifier from the testimonial
object (prefer testimonial.id, otherwise testimonial.authorName if guaranteed
unique) and update the data source to include an id field if needed; ensure the
key prop on <TestimonialCard> uses that stable property so React can track items
correctly.
| const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "poop"; // Use a safer default or error | ||
| const [formData, setFormData] = useState<Record<string, string>>({}); | ||
| const [turnstileToken, setTurnstileToken] = useState<string | null>( | ||
| isDev ? "development_bypass_token" : null, | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Don’t ship “poop” default; wrong var checked — vibes off + logic bug
The default is for site key but you compare it against the token later.
- const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "poop"; // Use a safer default or error
+ const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY ?? "";
+ const siteKeyMissing = !isDev && !turnstileSiteKey;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "poop"; // Use a safer default or error | |
| const [formData, setFormData] = useState<Record<string, string>>({}); | |
| const [turnstileToken, setTurnstileToken] = useState<string | null>( | |
| isDev ? "development_bypass_token" : null, | |
| ); | |
| const turnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY ?? ""; | |
| const siteKeyMissing = !isDev && !turnstileSiteKey; | |
| const [formData, setFormData] = useState<Record<string, string>>({}); | |
| const [turnstileToken, setTurnstileToken] = useState<string | null>( | |
| isDev ? "development_bypass_token" : null, | |
| ); |
🤖 Prompt for AI Agents
In src/themes/nicosup/ContactForm.tsx around lines 17 to 21, the Turnstile site
key default is an inappropriate placeholder ("poop") and later code compares the
wrong variable (token vs site key); replace the silly default with a safe
fallback (empty string or undefined) or throw/configure to fail fast, and update
any checks to compare the correct variable (use turnstileSiteKey when validating
presence of the site key, and only use turnstileToken for token-specific logic);
ensure development bypass token remains assigned only in dev and that runtime
checks use the proper variables so the logic is correct and vibes are fixed.
| const handleSubmit = async (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| if (!turnstileToken || (turnstileToken === "poop" && !isDev)) { | ||
| alert("CAPTCHA site key is missing or not configured for production."); | ||
| return; | ||
| } | ||
| if ( | ||
| !turnstileToken || | ||
| (turnstileToken === "development_bypass_token" && !isDev) | ||
| ) { | ||
| alert("Please complete the CAPTCHA."); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
CAPTCHA gating: check site key presence and token separately
Compare against siteKey, not token, and keep prod/dev behavior clean.
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- if (!turnstileToken || (turnstileToken === "poop" && !isDev)) {
- alert("CAPTCHA site key is missing or not configured for production.");
- return;
- }
- if (
- !turnstileToken ||
- (turnstileToken === "development_bypass_token" && !isDev)
- ) {
- alert("Please complete the CAPTCHA.");
- return;
- }
+ if (siteKeyMissing) {
+ alert("CAPTCHA site key is not configured.");
+ return;
+ }
+ if (!isDev && !turnstileToken) {
+ alert("Please complete the CAPTCHA.");
+ return;
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/themes/nicosup/ContactForm.tsx around lines 30 to 43, the current CAPTCHA
checks incorrectly inspect the token for site-key presence and mix
production/dev behavior; change the first check to verify the siteKey exists and
when in production (isDev false) alert and return if missing, and change the
second check to verify the turnstileToken itself (allowing a development bypass
token only when isDev is true) so production rejects the bypass token and
prompts the user to complete CAPTCHA.
| // Basic form validation: check if all required fields are filled | ||
| const isFormValid = fields.every( | ||
| (field) => !field.required || (formData[field.name]?.trim() ?? "") !== "", | ||
| ); | ||
| const canSubmit = isFormValid && (turnstileToken !== null || isDev); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Submission enablement should also respect siteKey presence
Disable submit when site key missing in prod.
- const canSubmit = isFormValid && (turnstileToken !== null || isDev);
+ const canSubmit = isFormValid && !siteKeyMissing && (turnstileToken !== null || isDev);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Basic form validation: check if all required fields are filled | |
| const isFormValid = fields.every( | |
| (field) => !field.required || (formData[field.name]?.trim() ?? "") !== "", | |
| ); | |
| const canSubmit = isFormValid && (turnstileToken !== null || isDev); | |
| // Basic form validation: check if all required fields are filled | |
| const isFormValid = fields.every( | |
| (field) => !field.required || (formData[field.name]?.trim() ?? "") !== "", | |
| ); | |
| const canSubmit = isFormValid && !siteKeyMissing && (turnstileToken !== null || isDev); |
🤖 Prompt for AI Agents
In src/themes/nicosup/ContactForm.tsx around lines 55 to 60, the canSubmit check
only considers form validity and turnstile token but doesn't ensure a siteKey
exists in production; update canSubmit to also require a non-empty siteKey when
not in dev (e.g., include a condition like isDev || (siteKey && siteKey.trim()
!== "")), so submissions are disabled if siteKey is missing in prod.
| <div className="mt-4"> | ||
| {isDev ? ( | ||
| <p className="text-muted-foreground text-sm"> | ||
| CAPTCHA bypassed in development mode. | ||
| </p> | ||
| ) : ( | ||
| <Turnstile | ||
| siteKey={turnstileSiteKey} | ||
| onSuccess={(token: string) => setTurnstileToken(token)} | ||
| options={{ theme: "light" }} // Light theme for Pix | ||
| /> | ||
| )} | ||
| </div> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Render fallback when site key missing (prod) — fail loud, not weird
Show a clear message instead of a broken widget.
<div className="mt-4">
{isDev ? (
<p className="text-muted-foreground text-sm">
CAPTCHA bypassed in development mode.
</p>
) : (
- <Turnstile
- siteKey={turnstileSiteKey}
- onSuccess={(token: string) => setTurnstileToken(token)}
- options={{ theme: "light" }} // Light theme for Pix
- />
+ <>
+ {siteKeyMissing ? (
+ <p className="text-red-500 text-sm">
+ CAPTCHA misconfigured. Please contact the site admin.
+ </p>
+ ) : (
+ <Turnstile
+ siteKey={turnstileSiteKey}
+ onSuccess={(token: string) => setTurnstileToken(token)}
+ options={{ theme: "light" }}
+ />
+ )}
+ </>
)}
</div>Committable suggestion skipped: line range outside the PR's diff.
Description
We are moving to Nextjs and implementing what was discussed with @plaingabriel about including themes for each willing member of the community.
Related Issues
None
Changes
nextjsHow to Test
Check out this branch.
Run
npm install && npm run devVerify that the switchers work and everything looks neat
Checklist
Before you merge, ensure you have completed the following.
[ ] I have read the contribution guidelines and followed them.
[ ] My code follows the project's style guidelines.
[ ] I have performed a self-review of my code.
[ ] I have added comments to my code, particularly in hard-to-understand areas.
[ ] I have added tests that prove my fix is effective or my feature works.
[ ] New and existing unit tests pass locally with my changes.
[ ] I have updated the documentation where necessary.
[ ] I have run a local build and confirmed it runs without errors.
[ ] I have removed any unnecessary console.log() statements.
Summary by CodeRabbit
New Features
Refactor
Documentation
Chores