Skip to content

Add team pages#1161

Merged
Kitenite merged 5 commits into
mainfrom
team-pages
Feb 3, 2026
Merged

Add team pages#1161
Kitenite merged 5 commits into
mainfrom
team-pages

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Feb 3, 2026

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • New Features
    • Added a Team section: header/footer links, team listing with member cards, and individual profile pages with bios and social links.
  • SEO
    • Team pages added to the sitemap with metadata and Open Graph/Twitter info.
  • Content
    • Added/updated bios and an avatar image for team members.
  • Style
    • Added a new display font to the site layout.
  • Chores
    • Redirect /about → /team (permanent).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 3, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a Team section to the marketing app: header/footer links to /team, a team listing page, individual team member pages with JSON‑LD and metadata, sitemap entries for /team and each member, a TeamMemberCard component and barrel export, content front‑matter updates, a redirect from /about → /team, and a new font in layout.

Changes

Cohort / File(s) Summary
Navigation
apps/marketing/src/app/components/Header/Header.tsx, apps/marketing/src/app/components/Footer/Footer.tsx
Inserted an "About" / "Team" nav item pointing to /team (Header label added to NAV_LINKS; Footer link added).
Sitemap
apps/marketing/src/app/sitemap.ts
Added static /team entry and dynamic per-person sitemap entries using getAllPeople with monthly frequency and set priorities.
Team Listing Page
apps/marketing/src/app/team/page.tsx
New Team index page: fetches getAllPeople, renders responsive grid of members (avatar/initials, role, bio, social links) and exports metadata.
Team Member Detail Page
apps/marketing/src/app/team/[id]/page.tsx
New dynamic member page with generateStaticParams, generateMetadata, JSON‑LD Person and breadcrumbs, MDX bio rendering, avatar fallback, related members grid, and 404 handling.
Team Member Card Component
apps/marketing/src/app/team/components/TeamMemberCard/TeamMemberCard.tsx, apps/marketing/src/app/team/components/TeamMemberCard/index.ts
Added TeamMemberCard component and barrel export; clickable card linking to /team/{id} with avatar/initials, name, role, optional bio, and social icons.
Content Updates
apps/marketing/content/people/satya.mdx, apps/marketing/content/people/avi.mdx, apps/marketing/content/people/kiet.mdx
Added/updated front‑matter bio fields for multiple people and changed Satya's avatar path to /team/satya.jpeg.
Layout / Fonts
apps/marketing/src/app/layout.tsx
Imported Micro_5 Google font, exposed CSS variable and added it to root HTML classNames.
Config / Redirects
apps/marketing/next.config.ts
Added redirect: source /about → destination /team (permanent: true).

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant NextJS as Next.js Server
  participant PeopleLib as getPersonById / getAllPeople
  participant Renderer as TeamMember React Server Component

  Browser->>NextJS: GET /team or /team/{id}
  NextJS->>PeopleLib: fetch people data (getAllPeople / getPersonById)
  PeopleLib-->>NextJS: people / person data (or null)
  alt person found
    NextJS->>Renderer: render page with person + allPeople
    Renderer-->>NextJS: HTML with JSON-LD, metadata, and links
    NextJS-->>Browser: 200 HTML
  else not found
    NextJS-->>Browser: 404
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code with whiskers keen,

New faces bloom on the team page screen,
Cards and breadcrumbs, metadata bright,
A tiny hop for each SEO light,
Cheers from a rabbit, bounding in delight.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely blank; all template sections contain only placeholder comments with no actual content filled in. Provide concrete details: describe what was added (team pages, routing, components), explain the rationale, list related issues, verify testing was performed, and add any relevant context.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change—adding team pages across multiple components, including new team listing and member detail pages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch team-pages

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 3, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Fly.io Electric (Fly.io) View App
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/marketing/content/people/satya.mdx`:
- Around line 1-8: The avatar field in this markdown frontmatter uses satya.webp
while other team member files (e.g., avi.mdx, kiet.mdx) use .jpg, so standardize
the avatar format: choose the target extension (e.g., .webp or .jpg) and update
the avatar value in this file's frontmatter (avatar: /team/satya.webp → avatar:
/team/satya.jpg or vice versa) and ensure the actual image asset in the team
directory is renamed/converted to match; verify other team member files use the
same extension so all avatar references are consistent.

In `@apps/marketing/src/app/team/`[id]/page.tsx:
- Around line 60-64: The JSON-LD injection uses dangerouslySetInnerHTML with
JSON.stringify(schema) which doesn't escape characters like <, >, &; change the
rendering to produce an escaped JSON string before passing to
dangerouslySetInnerHTML (escape <, >, & in the JSON output) or replace
JSON.stringify(schema) with a safe serializer such as serialize-javascript;
update the script tag that references schema (the dangerouslySetInnerHTML usage
in page.tsx) to use the escaped/serialized string so closing tags or
script-breakout vectors cannot be injected.
🧹 Nitpick comments (2)
apps/marketing/src/lib/people.ts (1)

48-51: Consider logging when the people directory is missing.

For consistency with the error logging in getPersonById, consider adding a log statement when the directory doesn't exist. This aids debugging when content is expected but the directory is misconfigured.

♻️ Optional: Add debug logging for missing directory
 export function getAllPeople(): Person[] {
 	if (!fs.existsSync(peopleDirectory)) {
+		console.warn("[people/getAllPeople] Directory not found:", peopleDirectory);
 		return [];
 	}
apps/marketing/src/app/team/page.tsx (1)

29-41: Extract the vertical guide background into named constants (and reuse across team pages).
The inline gradient embeds repeated magic numbers and is duplicated in the team member page, making it harder to tune. Consider hoisting the offsets/color into constants and referencing a single background string.

♻️ Suggested refactor
+const GUIDE_OFFSET_PX = 384;
+const GUIDE_LINE_PX = 1;
+const GUIDE_COLOR = "rgba(255,255,255,0.06)";
+const VERTICAL_GUIDE_BACKGROUND = `linear-gradient(
+  to right,
+  transparent 0%,
+  transparent calc(50% - ${GUIDE_OFFSET_PX}px),
+  ${GUIDE_COLOR} calc(50% - ${GUIDE_OFFSET_PX}px),
+  ${GUIDE_COLOR} calc(50% - ${GUIDE_OFFSET_PX - GUIDE_LINE_PX}px),
+  transparent calc(50% - ${GUIDE_OFFSET_PX - GUIDE_LINE_PX}px),
+  transparent calc(50% + ${GUIDE_OFFSET_PX - GUIDE_LINE_PX}px),
+  ${GUIDE_COLOR} calc(50% + ${GUIDE_OFFSET_PX - GUIDE_LINE_PX}px),
+  ${GUIDE_COLOR} calc(50% + ${GUIDE_OFFSET_PX}px),
+  transparent calc(50% + ${GUIDE_OFFSET_PX}px)
+)`;
+
 export default function TeamPage() {
 	const people = getAllPeople();
 
 	return (
 		<main className="relative min-h-screen">
 			{/* Vertical guide lines */}
 			<div
 				className="absolute inset-0 pointer-events-none"
 				style={{
-					backgroundImage: `
-						linear-gradient(to right, transparent 0%, transparent calc(50% - 384px), rgba(255,255,255,0.06) calc(50% - 384px), rgba(255,255,255,0.06) calc(50% - 383px), transparent calc(50% - 383px), transparent calc(50% + 383px), rgba(255,255,255,0.06) calc(50% + 383px), rgba(255,255,255,0.06) calc(50% + 384px), transparent calc(50% + 384px))
-					`,
+					backgroundImage: VERTICAL_GUIDE_BACKGROUND,
 				}}
 			/>
As per coding guidelines: Avoid magic numbers by extracting them to named constants at module top.

Comment on lines +1 to +8
---
name: Satya Patel
role: Cofounder
avatar: /team/satya.webp
twitter: saddle_paddle
github: saddlepaddle
linkedin: saddlepaddle
---
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the directory structure
echo "=== Checking for team directory ==="
find . -type d -name team | head -20

echo -e "\n=== Looking for avatar files ==="
fd -t f . | grep -i "team.*\.(jpg|jpeg|webp|png)$" | head -20

echo -e "\n=== Checking apps/marketing/public/team directory ==="
ls -la apps/marketing/public/team/ 2>/dev/null || echo "Directory not found"

echo -e "\n=== Checking satya.mdx ==="
find . -name "satya.mdx" -exec cat {} \;

echo -e "\n=== Checking avi.mdx ==="
find . -name "avi.mdx" -exec cat {} \;

echo -e "\n=== Checking kiet.mdx ==="
find . -name "kiet.mdx" -exec cat {} \;

Repository: superset-sh/superset

Length of output: 1057


Consider using a consistent avatar file format across team members.

Team avatars use inconsistent formats: satya.webp while avi.mdx and kiet.mdx use .jpg. Consider standardizing on a single format (e.g., all .jpg or all .webp) for consistency and easier maintenance of the team directory.

🤖 Prompt for AI Agents
In `@apps/marketing/content/people/satya.mdx` around lines 1 - 8, The avatar field
in this markdown frontmatter uses satya.webp while other team member files
(e.g., avi.mdx, kiet.mdx) use .jpg, so standardize the avatar format: choose the
target extension (e.g., .webp or .jpg) and update the avatar value in this
file's frontmatter (avatar: /team/satya.webp → avatar: /team/satya.jpg or vice
versa) and ensure the actual image asset in the team directory is
renamed/converted to match; verify other team member files use the same
extension so all avatar references are consistent.

Comment on lines +60 to +64
<script
type="application/ld+json"
// biome-ignore lint/security/noDangerouslySetInnerHtml: Safe for JSON-LD structured data
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

JSON.stringify escape less-than character < security risk JSON-LD

💡 Result:

JSON.stringify() is not an HTML-escaping function. It escapes JSON-required characters (quotes, backslashes, control chars), but it does not reliably protect you when you embed the resulting JSON directly into an HTML <script> element (including JSON-LD: <script type="application/ld+json">…</script>).

Why < / </script> matters (XSS risk)

Inside an HTML <script> element, the HTML parser can terminate the element when it encounters </script>—even if that text appears “inside” a JavaScript/JSON string. OWASP explicitly calls out that a </script> closing tag will close the script block because the HTML parser runs before the JS parser. [1] This is exactly how JSON-in-script XSS happens (e.g., attacker-controlled text contains </script><script>…). [2]

So the risk is not “JSON-LD is unsafe”; it’s: embedding untrusted data into a <script> tag without HTML-safe escaping is unsafe.

What to do

When emitting JSON/JSON-LD into a <script> tag, post-process the serialized JSON to escape characters that can trigger HTML/script parsing issues—at minimum < (commonly also >, &, '). This widely used pattern prevents </script> from ever appearing as raw text by turning < into \u003c. [2] Many template engines do exactly this (e.g., Jinja’s tojson). [2]

Safe-ish JS pattern

const jsonLd = JSON.stringify(data)
  .replace(/</g, "\\u003c")
  .replace(/>/g, "\\u003e")
  .replace(/&/g, "\\u0026")
  .replace(/'/g, "\\u0027");

(Then place jsonLd between the <script type="application/ld+json"> tags.)

Alternative

Use a library designed to make JSON safe for embedding in <script> tags; OWASP JSON Sanitizer, for example, guarantees the output won’t contain the substring </script (case-insensitive). [3]

When it’s a real problem

  • Problem: any JSON-LD fields include untrusted content (user profiles, reviews, comments, product Q&A, etc.).
  • Usually fine: all values are fully server-controlled and never include attacker input.

Sources: OWASP XSS Prevention Cheat Sheet (script-tag termination) [1]; examples and recommended escaping (<\u003c) [2]; OWASP JSON Sanitizer guarantee about </script [3].

[1] (docs.devnetexperttraining.com)
[2] (stackoverflow.com)
[3] (owasp.org)

Citations:


🌐 Web query:

React dangerouslySetInnerHTML JSON-LD script type application/ld+json XSS prevention

💡 Result:

To emit JSON-LD in React you typically do use a <script type="application/ld+json"> with dangerouslySetInnerHTML, but you must ensure the string cannot break out of the script tag and become executable HTML/JS.

Key safety rules

  • Never inject untrusted/user-controlled strings into dangerouslySetInnerHTML unless you first sanitize/validate them (React’s normal escaping is bypassed). [1][2]
  • Generate JSON-LD as an object, then JSON.stringify it (don’t build JSON with string concatenation). [2]
  • Escape < in the JSON output so an attacker can’t inject </script> via a value and terminate the tag (a common breakout). This is a widely used pattern (including in Next.js docs). [2]
  • Add defense-in-depth: CSP to reduce XSS impact if something slips through. [4]
  • Google explicitly supports JSON-LD in a <script> tag and can read it even when injected via JS (so this approach is compatible with SEO requirements). [3]

Recommended React snippet (safe-by-construction pattern)

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "Product",
  name: product.name,
  description: product.description,
  // keep values trusted/validated
};

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
  }}
/>

This uses JSON.stringify plus < escaping to prevent </script>-style breakouts. [2]

If any fields might be user-controlled

  • Treat them as untrusted input and apply strict validation/allow-listing (preferred), or sanitization where appropriate, because dangerouslySetInnerHTML reintroduces XSS risk. [1][5]

Sources: React’s warning about dangerouslySetInnerHTML and XSS risk [1], Next.js JSON-LD implementation pattern with < escaping [2], Google structured data/JSON-LD guidance [3], OWASP CSP guidance [4], OWASP XSS prevention guidance [5].


🌐 Web query:

JSON-LD script injection prevention best practices React

💡 Result:

Best practices to prevent JSON-LD <script> injection (React)

  1. Treat JSON-LD data as untrusted input

    • Don’t place user-controlled strings directly into JSON-LD without validation/normalization (same principle as any XSS defense). Avoid using dangerouslySetInnerHTML for anything except this narrowly-scoped JSON-LD use case. (React’s protection is bypassed when you inject raw HTML/script.) [5]
  2. Escape characters that can break out of the <script> tag

    • A classic payload is </script> inside a JSON string, which can terminate the tag and allow HTML/script injection.
    • At minimum, escape sequences that could form a closing tag, e.g. replace </ with <\/ before embedding. [4]
    • Next.js explicitly warns that JSON.stringify alone doesn’t prevent XSS in JSON-LD and suggests scrubbing < (e.g. to \u003c) or using a safer serializer. [1]
  3. Prefer a serializer designed for embedding in <script>

    • Use serialize-javascript (in JSON mode if applicable). It automatically escapes HTML characters and line terminators so </script> becomes unicode-escaped. [6]
    • Pin to a patched version and monitor advisories: serialize-javascript had an XSS issue affecting >= 6.0.0 < 6.0.2, patched in 6.0.2. [7]
    • Do not enable the library’s “unsafe” mode/options that disable escaping. [6]
  4. Add a strict Content Security Policy (CSP) as a backstop

    • Use nonce- or hash-based script-src (avoid relying only on domain allowlists). [3]
    • If you must use an inline JSON-LD <script>, allow it via a nonce (or hash), not 'unsafe-inline'. [2][3]

Minimal React example (safe embedding pattern)

import serialize from "serialize-javascript";

const JsonLd = ({ data, nonce }) => (
  <script
    type="application/ld+json"
    nonce={nonce}
    dangerouslySetInnerHTML={{ __html: serialize(data, { isJSON: true }) }}
  />
);

(Uses escaping designed for <script> embedding [6] and works well with nonce-based CSP [2][3].)


Harden JSON‑LD injection against potential script breakout.

JSON.stringify doesn't escape <, so if any person fields ever become user-controlled, </script> could break out of the tag. Currently low-risk since this data is repo-controlled, but add escaping as a defensive measure for future-proofing.

🛡️ Suggested hardening
-	const schema = {
+	const schema = {
 		"@context": "https://schema.org",
 		"@type": "Person",
 		name: person.name,
 		jobTitle: person.role,
 		url,
 		worksFor: {
 			"@type": "Organization",
 			name: COMPANY.NAME,
 			url: COMPANY.MARKETING_URL,
 		},
 		...(person.avatar && {
 			image: `${COMPANY.MARKETING_URL}${person.avatar}`,
 		}),
 		...(sameAs.length > 0 && { sameAs }),
 	};
+	const jsonLd = JSON.stringify(schema)
+		.replace(/</g, "\\u003c")
+		.replace(/>/g, "\\u003e")
+		.replace(/&/g, "\\u0026");
 
 	return (
 		<script
 			type="application/ld+json"
 			// biome-ignore lint/security/noDangerouslySetInnerHtml: Safe for JSON-LD structured data
-			dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
+			dangerouslySetInnerHTML={{ __html: jsonLd }}
 		/>
 	);

Escape <, >, & to block HTML/script breakout vectors. If untrusted content enters person fields, consider using a dedicated library like serialize-javascript instead.

🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 62-62: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
In `@apps/marketing/src/app/team/`[id]/page.tsx around lines 60 - 64, The JSON-LD
injection uses dangerouslySetInnerHTML with JSON.stringify(schema) which doesn't
escape characters like <, >, &; change the rendering to produce an escaped JSON
string before passing to dangerouslySetInnerHTML (escape <, >, & in the JSON
output) or replace JSON.stringify(schema) with a safe serializer such as
serialize-javascript; update the script tag that references schema (the
dangerouslySetInnerHTML usage in page.tsx) to use the escaped/serialized string
so closing tags or script-breakout vectors cannot be injected.

Resolve conflict in sitemap.ts by keeping both team pages and comparison pages.
- Add bios for Satya, Kiet, and Avi with links to their previous companies
- Support HTML rendering in bios for clickable company links
- Add permanent redirect from /about to /team
@Kitenite Kitenite merged commit 37c2d98 into main Feb 3, 2026
10 of 12 checks passed
@Kitenite Kitenite deleted the team-pages branch February 3, 2026 21:36
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.

2 participants