qualogy-solutions/qualogy-websites

Read-only snapshot · generated 2026-09-30T06:49:10.884Z · act on PRs directly in Bitbucket · ← all repos · auto-merge is off for this repo — reviews still post automatically, merging is manual

🔺 Needs your review (1)

QRAD-287: mobile UI fixes across onze-cases, diensten, homepage and other pages
#33 by Prijanka Ramcharan · QRAD-287 → main · Open in Bitbucket →
medium

Large sprawling PR (146 files, ~2400 lines changed) touching almost every marketing/content page and shared component (hero sections, cases pages, homepage, footer, nav) for mobile UI fixes. No backend, auth, payments, or schema changes are involved, but the sheer breadth means a CSS/layout regression or a broken import (e.g. PullQuote component usage across dozens of files) could visually break many public-facing pages simultaneously.

  • <item>146 files changed across nearly all onze-cases pages, homepage, diensten, footer, nav, and shared UI components — very wide surface area</item> <item>Repeated structural changes (hero markup, spacing, new PullQuote component, image markup) applied consistently across many pages, increasing risk of a systemic layout bug affecting the whole site</item> <item>Includes a new client-side search/filter behavior (FilterWrapper using useSearchParams) which changes interactive behavior, not just styling</item> <item>Purely front-end/UI/content changes — no auth, database, payments, infra, or security-sensitive code touched, which caps this below 'high'</item> <item>Given the scale and cross-cutting nature of the diff, visual regressions could be broad even if each individual change is low-risk, warranting careful review rather than auto-approval</item> </reasons>

Large mechanical mobile-responsiveness PR touching mostly Tailwind classes, hero section layout, and refactoring inline quote blocks into a shared PullQuote component. The diff is very large but repetitive/low-risk in nature (styling adjustments, spacing, hero restructuring). I spotted a couple of correctness concerns worth a second look, but nothing that clearly breaks functionality; flagging as warnings rather than blocking issues, with one moderate one around the mainImage duplicate-check heuristic potentially hiding legitimate images.

apps/qualogy-web/app/[locale]/onze-cases/15-jaar-innovatie-bij-de-bekendste-supermarktketen-van-nederland/page.tsx:121 · warning
The intro image is now conditionally rendered only if `data.intro.mainImage !== data.hero.image`. If mainImage is intentionally the same as the hero image for design reasons (e.g. showing it twice), this will silently drop it. Consider a more explicit flag in the data model rather than an equality heuristic to control whether the intro image is shown.
apps/qualogy-web/app/[locale]/onze-cases/25-jaar-qualogy-tims-reis/page.tsx:55 · info
Hardcoded image path was replaced with `data.article.heroImage`, and the standalone hero image section above was removed. Double check `data.article.heroImage` renders correctly at the new aspect ratio/height (h-[320px] sm:h-[420px] md:h-[480px]) since the image was previously sized dynamically via width=0/height=0/sizes=100vw for full responsiveness; now it's fixed height with object-cover, which may crop differently across breakpoints. Worth a visual check across locales/images site-wide given this same fixed height/crop pattern is applied broadly for this refactor.
apps/qualogy-web/app/[locale]/onze-cases/de-gemeente-schiedam-verstrekt-rvv/page.tsx:74 · info
Two `bg-gradient-to-*` utility classes are combined on the same div (`bg-gradient-to-b ... bg-gradient-to-r ...`); only the last one wins in Tailwind since they set the same CSS property. This pattern repeats in several other files in this PR. Not a functional break, but likely unintended (probably meant to combine into a single diagonal gradient or accept that only one applies) — worth clarifying intent since it's copy-pasted across many hero sections and may confuse future maintainers about which gradient is actually applied of the two directions and stop color sets specified redundantly here (though it does not error, it makes the extra gradient in the class list dead code).

✅ Auto-merged (4)

QRAD-282
#29 by Prijanka Ramcharan · QRAD-282 → main · Open in Bitbucket →
low

Adds a new static blog/case page, a cover image asset, and a new entry in the cases data array. This is purely additive content for a marketing/blog section with no changes to existing logic, APIs, auth, or infrastructure.

  • New file only (page.tsx) with no modification to existing components' behavior
  • New static image asset added
  • Only addition to cases.ts array (new object appended), no existing entries changed
  • No touches to auth, payments, database, infra, or public API contracts
  • Content is isolated to a single non-critical marketing/blog page

Mostly a static content/blog page addition with no logic risk. A couple of minor correctness issues worth noting but nothing blocking.

apps/qualogy-web/app/[locale]/onze-cases/the-art-of-not-waiting/page.tsx:234 · info
Article date is '17 september 2026', a future date. Likely a typo for 2025 or the current year; may confuse readers/SEO if published before that date.
apps/qualogy-web/app/[locale]/onze-cases/the-art-of-not-waiting/page.tsx:8 · info
PageProps only declares locale as 'nl' | 'en', but articleContent objects also include an 'es' key and cases.ts includes 'es' translations elsewhere in the codebase. If the app supports Spanish locale elsewhere, this page will not render Spanish content correctly (falls through to type error or defaults) — worth confirming locale type consistency with the rest of the app.
apps/qualogy-web/app/[locale]/onze-cases/the-art-of-not-waiting/page.tsx:205 · info
Several fields intended for translation (locale === 'nl' ? ... : ...) return identical English text for both branches (e.g. 'Consultancy Blog', breadcrumb 'Home'), and articleContent.intro/etc. also duplicate the same English text under nl/en/es keys. This suggests the Dutch translation is still a placeholder/TODO rather than an intentional decision — worth confirming before merge if the site is meant to be localized for nl users on this page.
apps/qualogy-web/src/data/cases/cases.ts:1004 · info
date '2026-09-17' is in the future; if `date` is used for sorting 'latest' cases, this entry may sort above genuinely recent items and could confuse date-based filtering/display until that date arrives.
QRAD-147: Add Prompt Engineering training landing page
#27 by Prijanka Ramcharan · QRAD-147 → main · Open in Bitbucket →
low

This PR adds a new, self-contained marketing landing page (prompt engineering training) with its own components and static data, plus minor CTA link additions on two existing marketing pages. It's new/isolated frontend content with no auth, payments, schema, infra, or shared-core logic changes, and the new page is explicitly noindexed pending marketing approval.

  • All new files are scoped to a single new route and its dedicated components/data under ai-trainingen/prompt-engineering
  • Changes to existing files (AiOplossingenTraining.tsx, AiTrainingenCTA.tsx) are small, additive UI link insertions, not behavior changes to critical logic
  • No touches to auth, payments, database/migrations, infra/deployment config, or public API contracts
  • New page is marked robots: noindex/nofollow, limiting external exposure while under review
  • Large line count (1236 added) is mostly repetitive presentational JSX/data content, not complex logic
  • Failure mode is limited to a single marketing page rendering incorrectly, not core app functionality

Mostly straightforward content/marketing page addition with new components and data files. No major logic bugs found; a few minor issues worth flagging (hardcoded contact info duplicated inline, `robots: { index: false }` on a real, linked-to page, and a stat-animation edge case), but nothing blocking.

apps/qualogy-web/app/[locale]/ai-trainingen/prompt-engineering/page.tsx:33 · warning
The page is marked `robots: { index: false, follow: false }` as a concept page under review, yet it's already linked from AiOplossingenTraining and AiTrainingenCTA components in this same PR (making it publicly reachable and cross-linked). If this page truly isn't ready for launch, consider not adding public inbound links yet, or remove the noindex once approved so internal links aren't pointing to a deliberately de-indexed page for an extended period.
apps/qualogy-web/components/sections/diensten/ai-trainingen/prompt-engineering/PromptEngineeringStats.tsx:22 · info
`parseInt(stat.value[locale].replace(/\D/g, ""), 10) || 0` will misparse values like "25+" or "4 uur"/"4 hours" fine (extracts leading digits), but for combined values such as "10 jaar" it still works since \D strips all non-digits, concatenating any embedded numbers (e.g. would break for something like "3-4"). Since current data doesn't contain multiple embedded numbers this happens to work, but this parsing is fragile if stats content changes in the future.
apps/qualogy-web/components/sections/diensten/ai-trainingen/prompt-engineering/PromptEngineeringCTA.tsx:48 · info
Contact details (email, phone) are hardcoded here rather than sourced from data/config, duplicating information that likely exists elsewhere in the codebase (e.g. footer/contact components). Consider centralizing to avoid drift if these details change.
apps/qualogy-web/components/sections/diensten/ai-oplossingen/AiOplossingenTraining.tsx:90 · info
The new secondary CTA label ('Bekijk Prompt Engineering' / 'View Prompt Engineering') is hardcoded in the component rather than sourced from the `data` prop/localization data file, unlike the other CTA text which comes from `data.cta[locale]`. This is inconsistent with how the rest of this component sources copy and duplicates the same hardcoded string used in AiTrainingenCTA.tsx.
QRAD-254: Replace default share/OG image
#28 by Prijanka Ramcharan · QRAD-254 → main · Open in Bitbucket →
low

This PR only swaps the default OG/share image asset used in site metadata, adding a new image file and updating a single default path constant. It affects social share previews but no business logic, auth, data, or infra.

  • Change confined to a single default image path in metadata.ts
  • New file is a static image asset, not code
  • No changes to logic, APIs, schemas, or security-sensitive areas
  • Minimal diff size (1 line changed)

Simple, low-risk change swapping the default OG/share image. The new asset is added and referenced correctly. Minor note: the filename contains parentheses and spaces-like special characters, which is generally fine in URLs but worth confirming it's properly encoded when used in meta tags; otherwise no issues found.

apps/qualogy-web/src/lib/metadata.ts:19 · info
The new default image filename contains parentheses, which will need URL-encoding when rendered in og:image/meta tags. Verify that Next.js metadata generation encodes this correctly, or consider renaming the asset to avoid special characters for safety.
QRAD-147: Add Prompt Engineering training landing page
#26 by Prijanka Ramcharan · QRAD-147 → main · Open in Bitbucket →
low

Adds a new, isolated marketing landing page (Prompt Engineering training) with its own components and static data files, plus minor CTA link additions on two existing marketing pages. No changes to auth, payments, DB, infra, or shared business logic; new page is explicitly noindexed while under review.

  • Purely additive: new route, new components, new static content data, no deletions of existing logic (only 9 lines removed, replaced with equivalent+extra markup)
  • Confined to a single Next.js app's marketing/content pages (apps/qualogy-web), no shared libraries, APIs, or infra touched
  • New page is flagged with robots noindex, indicating it's a low-stakes concept page under marketing review
  • Changes to existing components (AiOplossingenTraining, AiTrainingenCTA) are small, additive UI links (new CTA button/section), not altering existing behavior
  • No auth, billing, migrations, secrets, or public API contracts affected
  • Large line count is mostly repetitive presentational JSX and data objects, not complex logic

Overall this is a well-structured, low-risk content/landing-page addition. Data-driven components follow existing conventions, locale typing is consistent, and the new page correctly sets noindex robots for a page under review. A few minor issues worth a human glance, none blocking.

apps/qualogy-web/components/sections/diensten/ai-trainingen/prompt-engineering/PromptEngineeringStats.tsx:24 · warning
parseInt(stat.value[locale].replace(/\D/g, ""), 10) will misparse values that contain multiple numeric groups or a decimal point (e.g. "3.5" or "10 jaar" style strings with digits in units), stripping all non-digits and concatenating them. Given current data ("25+", "10 jaar", "7", "4 uur") it happens to work, but it's fragile if content changes — consider matching the leading number with a regex like /^\d+/ instead of stripping all non-digits.
apps/qualogy-web/components/sections/diensten/ai-trainingen/prompt-engineering/PromptEngineeringStats.tsx:66 · info
getDisplayValue uses originalValue.replace(/^\d+/, "") to derive the suffix, but the animated number is derived from stripping all non-digits (line 24). For a value like "10 jaar" the suffix becomes " jaar" while animatedValue is parsed from "10" — consistent here, but the mismatch in extraction strategy between the two functions could silently break for future data values with numbers embedded mid-string.
apps/qualogy-web/components/sections/diensten/ai-trainingen/prompt-engineering/PromptEngineeringCTA.tsx:47 · info
Phone number and email are hardcoded here (and likely duplicated elsewhere in the CTA components). Consider centralizing contact details in a shared data/config file to avoid drift if they change.
apps/qualogy-web/app/[locale]/ai-trainingen/prompt-engineering/page.tsx:26 · info
robots: { index: false, follow: false } is correctly used to hide this page while under review, but there's no tracking mechanism (e.g. TODO with ticket removal date) to ensure this is flipped back before/at launch — easy to forget once QRAD-147 is approved.

🗄️ Closed elsewhere (merged/declined outside this tool) (5)

QRAD-283
#32 by Prijanka Ramcharan · QRAD-283 → main · Open in Bitbucket →
medium

This PR refactors job/vacancy filtering logic for the careers page (werken-bij), introducing a shared taxonomy normalization module used by both the ATS job pipeline and the Salesforce sync route, and deletes an unused duplicate filter component. It affects how interim/ATS vacancies are categorized and displayed/filterable on a public marketing page, but does not touch auth, payments, DB schema, or infra.

  • Business logic change affecting job listing normalization and filtering behavior on a public-facing careers page
  • Touches a Salesforce sync API route that writes to the database (data normalization for department/city/experience_level), a moderate-sized behavior change to synced data
  • Large diff (226 additions, 536 deletions) mostly due to deleting a redundant component (VacanciesWithFilters.tsx) and consolidating logic into vacancy-taxonomy.ts
  • No changes to authentication, payments, schema/migrations, secrets, or infra config, limiting worst-case impact to incorrect job filtering/display rather than security or data-loss issues
  • Changes are confined to a single feature area (careers/vacancies) within one app, reducing cross-system blast radius

Solid refactor: extracts duplicated normalization logic into a shared vacancy-taxonomy module, applies it consistently across ATS jobs, Salesforce sync, and the werken-bij page, removes a dead/duplicate component, and improves filter counts to reflect cross-filter narrowing. No blocking issues found; a few minor points worth a look.

apps/qualogy-web/src/lib/vacancy-taxonomy.ts:68 · info
normalizeCity falls back to the first non-empty fragment (which may be `location` rather than `city`, since fragments are passed in that order at call sites) when no known city matches. Worth double-checking the fallback picks the most relevant fragment (e.g. prefer `city` over `location` text) rather than whichever was passed first, to avoid odd results like 'Hybrid from Rijswijk' surviving partial regex stripping when city was empty but location wasn't recognized.
apps/qualogy-web/components/sections/careers/WerkenBijContent.tsx:60 · info
selectedJobTypes comparison now runs vacancy.job_type through normalizeJobType while selectedJobTypes stores the normalized checkbox label — this is consistent, but double-check `normalizeJobType` is idempotent for all existing stored job_type values (e.g. already-normalized 'Full Time') so re-normalizing doesn't change casing/spacing unexpectedly and break the includes() check silently for edge-case strings.
apps/qualogy-web/app/[locale]/werken-bij/page.tsx:76 · info
Normalization is now done at read time on every page render/ISR revalidation for interim vacancies, but ATS vacancies use mapAtsJobToVacancy elsewhere with the same normalizers. Consider confirming both paths produce compatible department/city labels so the two vacancy lists merge cleanly into a single consistent filter set (this appears intended, just worth a sanity check given the removed VacanciesWithFilters component suggests some consolidation).
QRAD-51: Security & performance audit hardening
#24 by Prijanka Ramcharan · QRAD-51 → main · Open in Bitbucket →
high

This PR adds a new Content-Security-Policy and other security headers globally via next.config.ts, and changes the rate-limiter API contract (limiter.check now returns an object with .success instead of a boolean) across all public-facing form/API routes (contact, apply, meeting, quote, coffee-call, ATS). If the CSP is too strict or misconfigured, it can silently break scripts, styles, images, or third-party integrations (GTM, Cookiebot, Turnstile, Supabase, Vercel Analytics) site-wide. If the rate-limiter contract change is inconsistent anywhere, requests could be incorrectly blocked or rate limiting could silently no-op, affecting all public lead-gen/application endpoints.

  • Adds global security headers (CSP, HSTS, X-Frame-Options, Permissions-Policy) in next.config.ts affecting every route/page site-wide
  • Changes the rate-limiter's public contract (boolean -> {success}) and updates call sites across 6+ public API routes (contact, apply/ats, apply/hardcoded, meeting, coffee-call, quote) - security-relevant logic touching abuse/spam protection
  • CSP misconfiguration is a classic source of silent breakage for scripts/analytics/embeds (GTM, Cookiebot, Turnstile, YouTube, Supabase) that is easy to miss in review/testing
  • Adds new third-party dependency (@vercel/speed-insights) and lockfile changes across the monorepo
  • Touches multiple independent public-facing API routes simultaneously, increasing scope of potential regression
  • New logging utility (logEvent) replacing console.error across routes - low risk itself but part of a broad, cross-cutting change
  • Large number of files changed (27) spanning config, API routes, new package module, and binary asset updates, increasing chance of an overlooked interaction

Overall a solid, low-risk hardening PR: adds structured logging via a shared logger, CSP/security headers, and Vercel Speed Insights. One notable inconsistency in apply/ats/route.ts where the rate limiter API usage differs from all sibling routes — worth confirming createRateLimiter's actual return type before merge. A few other minor points below.

apps/qualogy-web/app/api/apply/ats/route.ts:12 · warning
This route now calls `await limiter.check(ip)` and checks `.success`, while every other route in this PR (contact, meeting, coffee-call, ai-oplossingen, ai-trainingen, apply/hardcoded) still uses `if (!limiter.check(ip))` (presumably synchronous boolean). Confirm which is the actual current signature of `createRateLimiter().check` — if it's synchronous returning a boolean, this route will incorrectly await a boolean and access `.success` on it (always undefined, so `!rateLimit.success` is always true, blocking every request with a 429). If the API was changed to return a Promise<{success}> only here, the other six routes are now broken instead. This needs to be verified/fixed for correctness.
apps/qualogy-web/next.config.ts:37 · info
`script-src` includes `'unsafe-inline'`, which significantly weakens the CSP's XSS protection (allows inline <script> execution). This is likely needed for GTM/Cookiebot inline snippets, but consider using nonces or hashes for the known inline scripts (e.g. the GTM snippet in layout.tsx) instead of blanket 'unsafe-inline' if feasible in a future iteration.
packages/security/src/logger.ts:16 · info
logEvent logs error messages/meta as-is; callers pass `meta: { error: String(error) } }` which could include stack traces or potentially sensitive data (e.g., request payload details) if errors are constructed with such info. Worth double-checking that no PII/secrets end up in these log lines, since Vercel logs may be retained/searchable.
fix(qualogy-web): update Oracle 23c SQL source links
#30 by Deborah · feature/QRAD-258 → main · Open in Bitbucket →
low

This PR only updates hardcoded href links (and matching e2e test) pointing to external SQL source files on marketing/blog case-study pages, switching from GitHub to Bitbucket URLs. No application logic, routing, or shared components are touched.

  • Change is confined to static content (external link URLs) in isolated marketing pages under onze-cases", "No business logic, auth, data, or infra changes", "Includes a new e2e test verifying the links, reducing risk of typos", "Diff is small and mechanical, repeated across similar files"]}

Straightforward link migration from GitHub to Bitbucket, consistently applied across pages and backed by a new e2e test verifying rendered hrefs. Only issue found is a minor inconsistency in repo-name casing ('oracledatabase23c' vs 'OracleDatabase23c') in two files, which is unlikely to break functionality but worth confirming intentional."

apps/qualogy-web/app/[locale]/onze-cases/json-relational-duality-view/page.tsx:382 · warning
Inconsistent casing: this URL and lock-free-reservation/page.tsx use lowercase 'oracledatabase23c', while all other pages (and the workspace/org name shown elsewhere) use 'OracleDatabase23c'. Bitbucket repo slugs are typically case-insensitive so this may still resolve, but it's inconsistent and worth double-checking/aligning for consistency.
apps/qualogy-web/app/[locale]/onze-cases/lock-free-reservation/page.tsx:444 · warning
Same lowercase 'oracledatabase23c' inconsistency as in json-relational-duality-view/page.tsx - verify this is intentional and not a typo, since all sibling pages use 'OracleDatabase23c'.
Feature/cleanup hygiene
#4 by Deborah · feature/cleanup-hygiene → main · Open in Bitbucket →
low

Pure cleanup PR: removes unused imports/variables, simplifies two Image components to use `fill` instead of fixed width/height (same visual output), and deletes an empty temp file. No business logic, routing, or data changes.

  • Only touches marketing website pages (qualogy-caribbean-web) and a leftover temp text file", "Changes are unused import removals, unused variable removals, and minor JSX restructuring with no behavioral impact", "Image component change to `fill` layout is a well-established Next.js pattern equivalent to explicit width/height with object-cover", "No changes to auth, payments, database, infra, or public API contracts", "Small, self-contained diff (47 lines added/removed) confined to presentational code</reasons>

Mostly a safe cleanup PR (removing unused imports/variables and a stray temp file), but there's one behavior-changing edit worth flagging: the two Image components switched from fixed width/height to `fill` inside a wrapper div. This is a reasonable pattern but should be verified for regressions (e.g., missing `object-cover`/aspect issues, or loss of intrinsic sizing hints for LCP). Everything else (unused `ArrowRight`/`idx`/`FadeUp`/`PRIVACY_EMAIL` removal, deleted temp file) looks like straightforward, low-risk hygiene cleanup.

apps/qualogy-caribbean-web/app/[locale]/onze-cases/strategische-offshore-partner-voor-informatie-beveiliging/page.tsx:100 · info
Switching from fixed width/height Image to a `fill`-based Image inside a relative wrapper changes how Next.js computes the image's intrinsic aspect ratio and can affect CLS/LCP if the wrapper's height isn't reserved correctly on all breakpoints. Since `className="object-cover object-center rounded-[20px]"` no longer includes `w-full h-[...]` directly on the Image, double check that the wrapper's explicit heights match what was previously produced by the width/height props (they appear to, but worth a visual QA pass).
apps/qualogy-caribbean-web/app/[locale]/onze-cases/succesvolle-offshoring-naar-suriname/page.tsx:100 · info
Same fill-based Image change as in the offshore-partner page; recommend a visual regression check to confirm no layout shift or cropping differences from the previous fixed width/height version.
apps/qualogy-caribbean-web/app/[locale]/privacy-policy/page.tsx:6 · info
Removing the `PRIVACY_EMAIL` constant leaves a blank line; confirm it wasn't used elsewhere in the file (e.g., rendered in the page) — if it was referenced anywhere below, this would be a build error rather than just unused-var cleanup.
Add GTM-NN3W3TC8 container to Meeting and Coffee Call modals
#14 by Deborah · feature/gtm-modal-container → main · Open in Bitbucket →
low

This PR shows no actual file changes or diff content, suggesting it adds a Google Tag Manager container snippet to two specific modals (Meeting and Coffee Call). If implemented as described, impact would be limited to those UI components and analytics tracking, not core business logic.

  • No diff content or changed files were provided, making it impossible to verify actual scope
  • Title suggests a narrow, isolated change to two specific modal components
  • GTM container additions are typically analytics/tracking snippets with low functional risk
  • Does not appear to touch auth, payments, database, or infrastructure
  • However, empty diff is unusual and warrants human verification before merging

The diff is empty (no files changed), so there is nothing to review. Cannot verify that the GTM-NN3W3TC8 container was actually added to the Meeting and Coffee Call modals. Please confirm the correct branch/commit is pushed before merging.

No inline comments.