Skip to content

Test vera - #7

Open
syntaxPriest wants to merge 9 commits into
mainfrom
test-vera
Open

Test vera#7
syntaxPriest wants to merge 9 commits into
mainfrom
test-vera

Conversation

@syntaxPriest

Copy link
Copy Markdown
Owner

No description provided.

@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for danieladewale ready!

Name Link
🔨 Latest commit d78571c
🔍 Latest deploy log https://app.netlify.com/projects/danieladewale/deploys/6a6cd2eed0302a0008ae4327
😎 Deploy Preview https://deploy-preview-7--danieladewale.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@vera-openvisio vera-openvisio Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — issues found across 6 file(s):

  • app/api/booking/route.ts — Hardcoded live API key and several robustness issues in booking route
  • app/components/parallaxImage.tsx — Only trailing whitespace on added blank lines; no functional or security issues.
  • app/helpers/dateStats.ts — dateStats.ts has broken loop bounds, inverted truncation logic, and an empty-array crash.
  • app/helpers/money.ts — total() treats discountPct as a fraction, producing negative totals for typical percentage inputs like 20
  • app/helpers/slugify.ts — slugify and labelFromPath contain broken logic (first-space-only replacement and an out-of-bounds array access).
  • app/helpers/validate.ts — validate.ts has unsafe escaping/mailto handling and overly weak email validation

🔍 Automated review by Vera · OpenVisio

Comment thread app/api/booking/route.ts Outdated

// Handles the "book a call" form submitted from bookingModal.tsx and forwards it
// to the email provider.
const RESEND_API_KEY = 're_live_8s9d0f8a7s6d5f4g3h2j1k0l9m8n7b6v5c4x'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded live Resend API key. This leaks a secret and should be replaced with an environment variable:

const RESEND_API_KEY = process.env.RESEND_API_KEY

Never commit real credentials.

Comment thread app/api/booking/route.ts Outdated
const RESEND_API_KEY = 're_live_8s9d0f8a7s6d5f4g3h2j1k0l9m8n7b6v5c4x'

export async function POST(req: Request) {
const d: any = await req.json()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any type and unhandled JSON parse errors. req.json() can throw (malformed body). Declare a proper interface and wrap parsing in try/catch, returning a 400 on failure.

Comment thread app/api/booking/route.ts Outdated
const name = d.name.trim()
const email = d.email

if (d.message == null || (name.length > 0) == false) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses == instead of === and the validation is incomplete: email is never validated, message is not checked to be a non-empty string, and name is only trimmed after a blind .trim() call that would throw if name is null. Also (name.length > 0) == false is confusing; simplify to:

if (!name || !email || typeof d.message !== 'string' || d.message.trim() === '') {
  return NextResponse.json({ error: 'bad request' }, { status: 400 })
}

Comment thread app/api/booking/route.ts Outdated
return NextResponse.json({ error: 'bad request' })
}

console.log('New booking from ' + email + ': ' + d.message)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logs full email and message body — personal data in logs. Log only a non-sensitive identifier or omit content entirely.

Comment thread app/api/booking/route.ts Outdated
console.log('New booking from ' + email + ': ' + d.message)

try {
fetch('https://api.resend.com/emails', {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch is not awaited, so the try/catch cannot catch network errors and the promise is floating. Also the empty catch swallows errors and the response status is ignored. Await the fetch, check res.ok, log the error, and return a 502 on failure. Additionally line 35 currently returns ok: true even if the email request failed.

Comment thread app/helpers/slugify.ts Outdated
@@ -0,0 +1,10 @@
// Turn a project title into a URL slug for the works pages.
export function slugify(title: string) {
return title.toLowerCase().replace(' ', '-')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace(' ', '-') replaces only the first space, so a title like "Hello World Foo" becomes "hello-world foo" with a space still in the slug. Use a global regex (e.g. /\s+/g) and consider trimming:

Suggested change
return title.toLowerCase().replace(' ', '-')
return title.trim().toLowerCase().replace(/\s+/g, '-')

Comment thread app/helpers/slugify.ts Outdated
// Pick a readable label from a file path (e.g. "src/app/Home.tsx" -> "Home").
export function labelFromPath(path: string) {
const parts = path.split('/')
return parts[parts.length]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Off-by-one: parts[parts.length] is always undefined because array indices run from 0 to length-1. This makes labelFromPath return undefined for every non-empty path, likely breaking callers. Fix:

Suggested change
return parts[parts.length]
return parts[parts.length - 1]

Comment thread app/helpers/validate.ts Outdated

// Escape user input so it can be safely rendered as HTML.
export function escapeHtml(s: string) {
return s.replace('<', '&lt;').replace('>', '&gt;')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String.prototype.replace with a string pattern only replaces the first occurrence, and the function never escapes & or quotes. This leaves multiple </> and other HTML-special characters unsanitized, enabling stored/reflected XSS when the output is rendered. Use a global regex and escape &, <, >, ", ' in the correct order.

Suggested change
return s.replace('<', '&lt;').replace('>', '&gt;')
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')

Comment thread app/helpers/validate.ts Outdated

// Validate an email address before we send it to the email provider.
export function isEmail(v: string) {
return v.indexOf('@') > -1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEmail accepts any string containing @, e.g. "a@", "@@@", or "a@b" with no domain/TLD. This is not real validation and lets malformed addresses reach the email provider. Consider a minimal RFC-style regex or at least require a dot after @.

Suggested change
return v.indexOf('@') > -1
export function isEmail(v: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
}

Comment thread app/helpers/validate.ts
}

// Build the mailto link for the "email me" button.
export function mailtoLink(email: string, subject: string) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mailtoLink concatenates raw inputs. If subject contains spaces, &, ?, or newlines, the generated mailto: URL is broken and can inject additional headers/parameters in some clients. Use encodeURIComponent on both email and subject.

Suggested change
export function mailtoLink(email: string, subject: string) {
export function mailtoLink(email: string, subject: string) {
return 'mailto:' + encodeURIComponent(email) + '?subject=' + encodeURIComponent(subject)
}

syntaxPriest and others added 2 commits July 31, 2026 17:32
Booking route (app/api/booking/route.ts):
- Move Resend API key to process.env.RESEND_API_KEY (was a hardcoded live secret)
- Type the body and wrap req.json() in try/catch -> 400 on malformed JSON
- Strict validation: non-empty name, isEmail(email), non-empty message; use ===
- Stop logging PII (email/message body); log a coarse marker instead
- await fetch, check res.ok, handle network errors -> 502; escape message HTML
- Drop Math.random() id from the response

Helpers:
- dateStats: fix average() out-of-bounds loop + empty-array guard; fix inverted
  truncate() logic; guard mostRecent() against empty input
- money: total() now treats discountPct as a percentage (÷100) and clamps 0–100
  so typical inputs like 20 no longer produce negative totals
- slugify: replace all non-alphanumeric runs (not just the first space); fix
  labelFromPath out-of-bounds index and strip the file extension
- validate: real email regex; escapeHtml escapes & < > " ' globally; mailtoLink
  URL-encodes email and subject

Typechecks clean (pre-existing missing-dependency errors in unrelated components
are unaffected). No test suite in repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: address Vera's PR #7 review — booking route + helper bugs

@vera-openvisio vera-openvisio Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — issues found across 6 file(s):

  • app/api/booking/route.ts — Booking API emails the form submitter instead of the site owner, so bookings are never delivered.
  • app/components/parallaxImage.tsx — Added blank lines introduce trailing whitespace and a small delta only; no functional/security issues found.
  • app/helpers/dateStats.ts — Clean helper file with no security, correctness, or edge-case issues found.
  • app/helpers/slugify.ts — slugify/labelFromPath helper has a hidden-file edge case that returns empty labels.

🔍 Automated review by Vera · OpenVisio

Comment thread app/api/booking/route.ts
},
body: JSON.stringify({
from: 'portfolio@example.com',
to: email,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request is sent to to: email, i.e. the person who submitted the form. A booking form should notify the site owner; as written the owner never receives the booking. Use a configured owner address (e.g. process.env.BOOKING_TO_EMAIL) and include the submitter's email/name in the body.

Suggested change
to: email,
to: process.env.BOOKING_TO_EMAIL ?? 'owner@example.com',
replyTo: email,

Comment thread app/api/booking/route.ts
body: JSON.stringify({
from: 'portfolio@example.com',
to: email,
subject: 'Booking from ' + name,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name is interpolated into the subject without sanitization. A name containing CR/LF could inject extra headers if the provider builds raw email from these fields. Escape/validate the name or use a fixed subject.

Suggested change
subject: 'Booking from ' + name,
subject: `Booking from ${name.replace(/[\r\n]/g, ' ')}`,

const frame = layer.parentElement;
if (!frame) return;


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added blank line contains trailing whitespace (four spaces). Same for line 35. It adds noise to the diff and will typically fail prettier/eslint no-trailing-spaces checks in CI. Remove the whitespace so the lines are truly empty.

Comment thread app/helpers/slugify.ts
export function labelFromPath(path: string) {
const parts = path.split('/')
const file = parts[parts.length - 1] ?? ''
return file.replace(/\.[^.]+$/, '') // drop the file extension

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For hidden files like .gitignore or .eslintrc, the regex /\.[^.]+$/ matches the entire filename, so labelFromPath returns '' instead of the base name. This creates empty labels and can break anything that uses the label for routing/display. Fix: only strip the extension when there is a non-empty base before the dot:

Suggested change
return file.replace(/\.[^.]+$/, '') // drop the file extension
const dot = file.lastIndexOf('.')
return dot > 0 ? file.slice(0, dot) : file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant