Skip to content

Conversation

@Ammaar-Alam
Copy link
Member

@Ammaar-Alam Ammaar-Alam commented Nov 7, 2025

Summary

  • Adds a feedback button in the navbar and a modal to submit feedback.
  • Sends a thank‑you acknowledgement to the submitter and a notification to the TigerType team.
  • Uses Office 365 SMTP with OAuth2 (delegated, device‑code seeded via MSAL) — no basic auth, no DB writes.
  • Reorders navbar icons: GitHub button appears before the feedback (bug) button.
  • Thank you to OIT for approving SMTP request :)

TLDR: Feedback button added in the navbar, feel free to report bugs, give suggestions, talk, etc.

Implementation Notes

  • Client
    • Feedback modal UI and wiring:
      • client/src/components/FeedbackModal.jsx
      • client/src/components/FeedbackModal.css
    • Navbar changes (order + modal trigger):
      • client/src/components/Navbar.jsx
      • client/src/components/Navbar.css
  • Server
    • Email pipeline (delegated SMTP OAuth):
      • server/utils/email.js
        • Uses @azure/msal-node PublicClientApplication with a persisted token cache (silent refresh).
        • Sends via nodemailer to smtp.office365.com:587 (STARTTLS) with OAuth2 accessToken.
        • Sets replyTo to the contact email so replies go directly to the user.
    • API endpoint:
      • server/routes/api.js
        • POST /api/feedback validates input, derives user email (contactInfo or @princeton.edu), then calls sendFeedbackEmails in the background
          and returns { success: true } immediately.
    • Scripts
      • server/scripts/seed_smtp_oauth_device_login.js
        • One‑time/occasional “device code” seeding for cs‑[email protected]; writes MSAL cache to server/.smtp_oauth_cache.json and prints the
          same JSON so it can be set as SMTP_OAUTH_CACHE in Heroku.
      • server/scripts/test_feedback_email.js
        • Sends an acknowledgement to the recipient and notifications to team recipients using the live helper.
  • Config / Secrets
  • Dependencies
    • package.json
      • Added @azure/msal-node and nodemailer.

Copilot AI review requested due to automatic review settings November 7, 2025 22:12
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds an in-app feedback system with SMTP OAuth email functionality. Users can submit feedback through a modal in the navbar, which triggers automated email notifications to the team and an acknowledgement to the submitter.

  • Implements Office 365 SMTP with OAuth2 using MSAL device code flow for delegated authentication
  • Adds a feedback modal UI component accessible from a new bug report icon in the navbar
  • Creates feedback API endpoint that validates input and sends emails asynchronously

Reviewed Changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
server/utils/email.js Core email utility using MSAL OAuth and nodemailer for feedback notifications and acknowledgements
server/routes/api.js POST /api/feedback endpoint with input validation and fire-and-forget email sending
server/scripts/seed_smtp_oauth_device_login.js Device code authentication script to seed OAuth token cache
server/scripts/test_feedback_email.js Manual testing script for feedback email flow
client/src/components/FeedbackModal.jsx React modal component for feedback submission form
client/src/components/FeedbackModal.css Styling for feedback modal
client/src/components/Navbar.jsx Adds feedback icon button and modal integration
client/src/components/Navbar.css Styling for feedback icon button
package.json Adds @azure/msal-node and nodemailer dependencies
.env.example Documents new environment variables for SMTP OAuth and feedback emails
.gitignore Excludes OAuth token cache file from version control

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 126 to 127
// quick email regex
const emailRe = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The email regex is defined after it's used in sendFeedbackNotification (lines 118-120). JavaScript hoisting allows this to work, but it reduces code readability. Consider moving the regex definition before the functions that use it.

Copilot uses AI. Check for mistakes.
Comment on lines 118 to 121
if (contactInfo && emailRe.test(contactInfo)) {
const m = contactInfo.match(emailRe);
if (m) replyTo = m[0];
}
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The regex test is performed twice: once with .test() and again with .match(). This is inefficient. Use .match() directly and check if the result is truthy: const m = contactInfo?.match(emailRe); if (m) replyTo = m[0];

Copilot uses AI. Check for mistakes.
Comment on lines 130 to 133
if (contactInfo && emailRe.test(contactInfo)) {
const m = contactInfo.match(emailRe);
if (m) return m[0];
}
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The regex test is performed twice: once with .test() and again with .match(). This is inefficient. Use .match() directly and check if the result is truthy: const m = contactInfo?.match(emailRe); if (m) return m[0];

Copilot uses AI. Check for mistakes.
const { category = 'feedback', message, contactInfo, pagePath } = req.body || {};

if (!message || typeof message !== 'string' || message.trim().length < 10) {
return res.status(400).json({ error: 'Please include a short description so we can help (10+ characters).' });
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The error message says 'short description' which is confusing since the requirement is actually for a longer message (10+ characters). Consider changing to 'Please include a description with at least 10 characters so we can help.' for clarity.

Suggested change
return res.status(400).json({ error: 'Please include a short description so we can help (10+ characters).' });
return res.status(400).json({ error: 'Please include a description with at least 10 characters so we can help.' });

Copilot uses AI. Check for mistakes.
setPlayerReady,
resetRace
} = useRace();
const { raceState, typingState, setPlayerReady, resetRace } = useRace();
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

Unused variable raceState.

Suggested change
const { raceState, typingState, setPlayerReady, resetRace } = useRace();
const { typingState, setPlayerReady, resetRace } = useRace();

Copilot uses AI. Check for mistakes.
setPlayerReady,
resetRace
} = useRace();
const { raceState, typingState, setPlayerReady, resetRace } = useRace();
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

Unused variable typingState.

Suggested change
const { raceState, typingState, setPlayerReady, resetRace } = useRace();
const { raceState, setPlayerReady, resetRace } = useRace();

Copilot uses AI. Check for mistakes.
setPlayerReady,
resetRace
} = useRace();
const { raceState, typingState, setPlayerReady, resetRace } = useRace();
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

Unused variable setPlayerReady.

Suggested change
const { raceState, typingState, setPlayerReady, resetRace } = useRace();
const { raceState, typingState, resetRace } = useRace();

Copilot uses AI. Check for mistakes.
});

(async () => {
const result = await pca.acquireTokenByDeviceCode({
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

Unused variable result.

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 120 to 149
router.post('/feedback', async (req, res) => {
try {
const { category = 'feedback', message, contactInfo, pagePath } = req.body || {};

if (!message || typeof message !== 'string' || message.trim().length < 10) {
return res.status(400).json({ error: 'Please include a short description so we can help (10+ characters).' });
}

const sanitizedMessage = message.trim().slice(0, 4000);
const sanitizedContact = typeof contactInfo === 'string' ? contactInfo.trim().slice(0, 255) : null;
const sanitizedPagePath = typeof pagePath === 'string' ? pagePath.trim().slice(0, 255) : null;

let netid = null;
if (isAuthenticated(req)) {
if (!req.user && req.session?.userInfo) {
req.user = {
id: req.session.userInfo.userId,
netid: req.session.userInfo.user
};
}
netid = req.user?.netid || req.session?.userInfo?.user || null;
}

const userAgent = req.get('user-agent')?.slice(0, 500) || null;

// fire and forget to avoid hanging if mail provider is slow
sendFeedbackEmails({
category,
message: sanitizedMessage,
contactInfo: sanitizedContact,

Choose a reason for hiding this comment

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

P0 Badge Gate feedback email endpoint to prevent open relay

The new /api/feedback route accepts arbitrary contactInfo from unauthenticated clients and immediately calls sendFeedbackEmails with that value, which sends an acknowledgement email to the derived address (sendFeedbackAcknowledgement in server/utils/email.js). Because there is no authentication, rate limiting, or verification that the email belongs to the submitter, this endpoint can be scripted to send TigerType-branded emails with attacker-controlled content to any address. That effectively turns the feedback API into an open mail relay and risks the SMTP account being abused or blocked. The handler should require a logged-in user, enforce CAPTCHA/rate limits, or suppress acknowledgements for untrusted addresses.

Useful? React with 👍 / 👎.

@Ammaar-Alam
Copy link
Member Author

@codex re-review

Copy link
Member Author

@Ammaar-Alam Ammaar-Alam left a comment

Choose a reason for hiding this comment

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

secured api endpoint

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 14 to 19
// Validate required email configuration
if (!process.env.FEEDBACK_EMAIL_FROM) {
throw new Error('Missing required env var: FEEDBACK_EMAIL_FROM');
}
if (!process.env.FEEDBACK_EMAIL_TO_TEAM) {
throw new Error('Missing required env var: FEEDBACK_EMAIL_TO_TEAM');

Choose a reason for hiding this comment

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

P1 Badge Avoid crashing server when feedback env vars are unset

The new feedback email helper throws during module load if FEEDBACK_EMAIL_FROM or FEEDBACK_EMAIL_TO_TEAM are not defined. Because server/routes/api.js imports this module at the top level, the entire Express app will fail to boot in any environment where those secrets are not configured (including local development and tests) even if the /api/feedback route is never hit. Consider deferring validation until the first send attempt or providing a no‑op implementation when the variables are absent so that the rest of the server can still run.

Useful? React with 👍 / 👎.

Copilot AI review requested due to automatic review settings November 7, 2025 23:10
@Ammaar-Alam
Copy link
Member Author

@codex re-review one more time pls 🥺

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 46 to 52
const pca = new PublicClientApplication({
auth: {
clientId,
authority: `https://login.microsoftonline.com/${tenantId}`
},
cache: { cachePlugin: tokenCachePlugin }
});

Choose a reason for hiding this comment

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

P1 Badge Guard MSAL client creation when SMTP env vars are absent

The helper initializes new PublicClientApplication at module load using AZURE_CLIENT_ID and AZURE_TENANT_ID. If those variables are unset (the common case for local dev or CI that hasn’t seeded OAuth credentials), @azure/msal-node throws a ClientConfigurationError immediately and the server process fails to start, despite the later checks in sendMailGeneric that try to no-op when SMTP isn’t configured. This makes the entire backend depend on optional secrets. Instantiate the MSAL client only after verifying the env vars are present or wrap the constructor in a conditional so the app can boot without SMTP configured.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

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

image

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 137 to 140
router.post('/feedback', async (req, res) => {
try {
const ip = (req.headers['x-forwarded-for'] || req.ip || req.connection?.remoteAddress || '').toString();
if (isRateLimited(ip)) {

Choose a reason for hiding this comment

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

P1 Badge Rate limiter trusts spoofable X-Forwarded-For header

The new feedback endpoint uses (req.headers['x-forwarded-for'] || req.ip …) to identify the caller’s IP before applying the in-memory rate limit. Because the raw X-Forwarded-For header is checked before req.ip and there is no verification that the header comes from a trusted proxy, any client can send arbitrary header values to reset the rate limit on every request and spam the feedback API. This leaves the endpoint effectively unprotected against abuse. Consider relying on req.ip (which honors Express’s trust proxy setting) or explicitly parsing X-Forwarded-For only when the proxy is known to be trusted.

Useful? React with 👍 / 👎.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (isRateLimited(ip)) {
return res.status(429).json({ error: 'Too many feedback submissions. Please try again later.' });
}
const { category = 'feedback', message, contactInfo, pagePath } = req.body || {};
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The category parameter is not validated against a whitelist of allowed values. While the frontend uses a dropdown with specific options ('feedback', 'bug', 'idea', 'other'), a malicious client could send arbitrary values via direct API calls.

Suggestion: Add validation to ensure category is one of the allowed values:

const ALLOWED_CATEGORIES = ['feedback', 'bug', 'idea', 'other'];
const category = ALLOWED_CATEGORIES.includes(req.body.category) ? req.body.category : 'feedback';
Suggested change
const { category = 'feedback', message, contactInfo, pagePath } = req.body || {};
const ALLOWED_CATEGORIES = ['feedback', 'bug', 'idea', 'other'];
const { category: rawCategory = 'feedback', message, contactInfo, pagePath } = req.body || {};
const category = ALLOWED_CATEGORIES.includes(rawCategory) ? rawCategory : 'feedback';

Copilot uses AI. Check for mistakes.
const html = `
<div style="font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:720px;margin:0 auto;padding:32px 24px;color:#333;background:#ffffff;">
<div style=\"margin:0 0 32px 0;text-align:left;\">
<a href=\"${siteUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-block;\">
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The siteUrl variable should be escaped when used in HTML to prevent potential XSS vulnerabilities. Even though this comes from environment variables, it's a best practice to escape all dynamic content in HTML.

Suggestion: Change ${siteUrl} to ${escapeHtml(siteUrl)} on this line and line 205.

Copilot uses AI. Check for mistakes.
}) => {
const fromAddr = from || process.env.FEEDBACK_EMAIL_FROM;

const subject = `[TigerType Feedback] ${category.toUpperCase()} from ${netid || 'anonymous user'}`;
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The email subject line uses unsanitized user input (category and netid) which could potentially be exploited for email header injection attacks. While these values are somewhat controlled (category from a dropdown and netid from authentication), they should be sanitized to prevent newline characters and other potentially dangerous characters.

Suggestion: Sanitize these values or at minimum remove newline characters before using in the subject line. For example:

const subject = `[TigerType Feedback] ${category.replace(/[\r\n]/g, '').toUpperCase()} from ${(netid || 'anonymous user').replace(/[\r\n]/g, '')}`;
Suggested change
const subject = `[TigerType Feedback] ${category.toUpperCase()} from ${netid || 'anonymous user'}`;
const subject = `[TigerType Feedback] ${category.replace(/[\r\n]/g, '').toUpperCase()} from ${(netid || 'anonymous user').replace(/[\r\n]/g, '')}`;

Copilot uses AI. Check for mistakes.
Comment on lines 120 to 135
// simple in-memory rate limiting for feedback (per IP)
const FEEDBACK_LIMIT_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
const FEEDBACK_LIMIT_COUNT = 5; // max 5 submissions per window
const feedbackRate = new Map(); // ip -> { windowStart, count }

function isRateLimited(ip) {
const now = Date.now();
const entry = feedbackRate.get(ip);
if (!entry || (now - entry.windowStart) > FEEDBACK_LIMIT_WINDOW_MS) {
feedbackRate.set(ip, { windowStart: now, count: 1 });
return false;
}
entry.count += 1;
if (entry.count > FEEDBACK_LIMIT_COUNT) return true;
return false;
}
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The in-memory rate limiting will not work correctly in multi-instance deployments (e.g., when horizontally scaled on Heroku). Each instance will maintain its own rate limit map, allowing users to bypass the rate limit by hitting different instances. Consider using a shared store like Redis for rate limiting, or accept that this is a best-effort rate limit for single-instance deployments.

Additionally, the Map will grow indefinitely as IPs are added and never cleaned up, which could lead to a memory leak over time. Consider implementing periodic cleanup of old entries or using a package like express-rate-limit which handles this properly.

Copilot uses AI. Check for mistakes.
Comment on lines +226 to +240
const text = [
'Thanks for your feedback!',
'',
`We received your ${category || 'feedback'} and will look into it shortly.`,
'',
'Summary:',
pagePath ? `• Page: ${pagePath}` : null,
contactInfo ? `• Contact: ${contactInfo}` : null,
`• Submitted: ${submittedLocal} ET`,
'',
safeMessage ? 'Your message:' : null,
safeMessage || null,
'',
'— TigerType Team'
].filter(Boolean).join('\n');
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

[nitpick] The submittedLocal variable is used in the plain text email body without escaping. While this is plain text and not HTML, category, pagePath, and contactInfo could potentially contain control characters or formatting that might cause issues in email clients.

For consistency and safety, consider sanitizing these values to remove control characters, especially newlines that could break the email formatting.

Copilot uses AI. Check for mistakes.
AZURE_TENANT_ID=
AZURE_CLIENT_ID=
SMTP_SENDER=[email protected]
SMTP_OAUTH_CACHE= (optional; JSON blob of MSAL cache for headless servers)
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The SMTP_OAUTH_CACHE environment variable has a comment in the value portion which is not valid .env syntax. Comments should be on their own line or after the value with a # separator.

Suggestion: Change to:

# Optional: JSON blob of MSAL cache for headless servers
SMTP_OAUTH_CACHE=
Suggested change
SMTP_OAUTH_CACHE= (optional; JSON blob of MSAL cache for headless servers)
# Optional: JSON blob of MSAL cache for headless servers
SMTP_OAUTH_CACHE=

Copilot uses AI. Check for mistakes.

router.post('/feedback', async (req, res) => {
try {
const ip = (req.headers['x-forwarded-for'] || req.ip || req.connection?.remoteAddress || '').toString();
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The IP address extraction could result in an empty string when behind certain proxies, which would cause all anonymous requests to share the same rate limit entry. The x-forwarded-for header can also contain multiple IPs (comma-separated), and only the first (client) IP should be used for rate limiting.

Suggestion: Parse the x-forwarded-for header properly and provide a fallback:

const forwardedFor = req.headers['x-forwarded-for'];
const ip = forwardedFor ? forwardedFor.split(',')[0].trim() : (req.ip || req.connection?.remoteAddress || 'unknown');
Suggested change
const ip = (req.headers['x-forwarded-for'] || req.ip || req.connection?.remoteAddress || '').toString();
const forwardedFor = req.headers['x-forwarded-for'];
const ip = forwardedFor
? forwardedFor.split(',')[0].trim()
: (req.ip || req.connection?.remoteAddress || 'unknown');

Copilot uses AI. Check for mistakes.
console.error(' node server/scripts/test_feedback_email.js [email protected] bug');
console.error(' node server/scripts/test_feedback_email.js [email protected] feature "I would like dark mode"');
console.error('');
console.error('Available categories: bug, feature, feedback, question, test');
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The available categories listed in the usage documentation don't match the actual categories defined in the FeedbackModal component. The script lists "bug, feature, feedback, question, test" but the modal uses "feedback, bug, idea, other".

Suggestion: Update the documentation to match:

console.error('Available categories: feedback, bug, idea, other');
Suggested change
console.error('Available categories: bug, feature, feedback, question, test');
console.error('Available categories: feedback, bug, idea, other');

Copilot uses AI. Check for mistakes.
const html = `
<div style=\"font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:640px;margin:0 auto;padding:32px 24px;color:#333;background:#ffffff;\">
<div style=\"margin:0 0 32px 0;text-align:left;\">
<a href=\"${siteUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-block;\">
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The siteUrl variable should be escaped when used in HTML to prevent potential XSS vulnerabilities. Even though this comes from environment variables, it's a best practice to escape all dynamic content in HTML.

Suggestion: Change ${siteUrl} to ${escapeHtml(siteUrl)}.

Suggested change
<a href=\"${siteUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-block;\">
<a href=\"${escapeAttr(siteUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-block;\">

Copilot uses AI. Check for mistakes.
value={contactInfo}
onChange={(event) => setContactInfo(event.target.value)}
disabled={submitting}
placeholder="we'll follow up here if we need more info"
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The placeholder text "we'll follow up here if we need more info" should start with a capital letter for consistency with other UI text in the application.

Suggestion: Change to "We'll follow up here if we need more info"

Suggested change
placeholder="we'll follow up here if we need more info"
placeholder="We'll follow up here if we need more info"

Copilot uses AI. Check for mistakes.
@Ammaar-Alam
Copy link
Member Author

@codex re-re-review last time promise 🙏🏼

Copy link
Member

@joshuamotoaki joshuamotoaki left a comment

Choose a reason for hiding this comment

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

LGTM 🚢 it

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Keep them coming!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Ammaar-Alam Ammaar-Alam merged commit acdf319 into master Nov 7, 2025
@Ammaar-Alam Ammaar-Alam deleted the feat/feedback-rebased branch November 7, 2025 23:37
Ammaar-Alam added a commit that referenced this pull request Dec 5, 2025
consolidating ports into one npm command, fixing conflicting ports, fixing race results screen not showing, fixing cas login button redirect, fixing backend serving static files
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.

3 participants