Skip to content

feat: Implement Organization Profile Page ui#411

Merged
0xdevcollins merged 2 commits intoboundlessfi:mainfrom
Sandijigs:feat/implement-organization-profile-page-ui
Feb 28, 2026
Merged

feat: Implement Organization Profile Page ui#411
0xdevcollins merged 2 commits intoboundlessfi:mainfrom
Sandijigs:feat/implement-organization-profile-page-ui

Conversation

@Sandijigs
Copy link
Contributor

@Sandijigs Sandijigs commented Feb 26, 2026

Summary

  • Adds a public organization profile page at /org/[id] displaying organization name, logo, description, and key stats
  • Implements responsive layout with gradient banner header, stat cards grid, social links, and about section
  • Includes skeleton loading states and graceful error/not-found handling

Closes #392

Test plan

  • Navigate to /org/{valid-org-id} and verify org name, logo, tagline, stats, and about section display correctly
  • Navigate to /org/invalid-id and verify error state is handled gracefully
  • Resize browser to test mobile (1-col stats), tablet (2-col), and desktop (4-col) responsive breakpoints
  • Throttle network in DevTools to verify skeleton loading state appears
  • Run npm run build to verify no type errors

Summary by CodeRabbit

  • New Features
    • Organization profile pages with dynamic metadata for improved sharing and SEO.
    • Client-side organization profile view showing logo, description and a responsive stats grid (projects, hackathons, bounties, grants).
    • Improved loading experience with a structured skeleton UI while profile data loads.
  • Chores
    • Added a public profile lookup API to fetch organization profile data.

@vercel
Copy link

vercel bot commented Feb 26, 2026

@Sandijigs is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 415a12d and a5dd9d6.

📒 Files selected for processing (3)
  • app/(landing)/org/[id]/org-profile-client.tsx
  • app/(landing)/org/[id]/page.tsx
  • lib/api/organization.ts

📝 Walkthrough

Walkthrough

Adds an organization profile page: server-side metadata generation, a client component that fetches and renders an organization profile with loading/error handling, a Skeleton loading component, and a new API helper and type for fetching organization profiles by slug.

Changes

Cohort / File(s) Summary
Page + routing
app/(landing)/org/[id]/page.tsx
Adds async page component and generateMetadata that fetches profile by slug and builds title/OG/Twitter metadata; renders the client component.
Client UI
app/(landing)/org/[id]/org-profile-client.tsx
New client-side component OrgProfileClient (props: { slug }) that fetches getOrganizationProfile(slug), renders profile UI, shows OrgProfileSkeleton while loading, and handles not-found/error states.
Loading skeleton
app/(landing)/org/[id]/loading.tsx
New OrgProfileLoading component rendering banner, avatar, stats grid, and about-section skeleton placeholders using Skeleton components.
API + types
lib/api/organization.ts
Adds OrganizationProfile type and getOrganizationProfile(slug: string): Promise<OrganizationProfile> which requests /organizations/profile/:slug.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant NextPage as Next.js Page (server)
  participant OrgClient as OrgProfileClient (client)
  participant APIlib as lib/api/getOrganizationProfile
  participant Backend as Backend API (/organizations/profile/:slug)

  Browser->>NextPage: Request /org/[slug]
  NextPage->>APIlib: getOrganizationProfile(slug) for metadata (server)
  APIlib->>Backend: GET /organizations/profile/:slug
  Backend-->>APIlib: 200 + profile JSON
  APIlib-->>NextPage: OrganizationProfile
  NextPage-->>Browser: HTML + client bundle

  Browser->>OrgClient: Hydrated component mounts with slug
  OrgClient->>APIlib: getOrganizationProfile(slug) (client)
  APIlib->>Backend: GET /organizations/profile/:slug
  Backend-->>APIlib: 200 + profile JSON
  APIlib-->>OrgClient: OrganizationProfile
  OrgClient-->>Browser: Renders profile UI (or skeleton while loading)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped to build a profile bright,
Skeletons twinkling in the night,
Slugs fetched gently, names in view,
Logos bounce and stats accrue,
A rabbit cheers — the page takes flight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Implement Organization Profile Page ui' directly and clearly describes the main change - implementing an organization profile page UI component.
Linked Issues check ✅ Passed The PR implements all coding requirements from #392: displays organization name, logo, description, and stats; handles loading states with skeleton UI; provides responsive design across mobile, tablet, and desktop.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the organization profile page as specified in #392; no unrelated or out-of-scope modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@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: 5

🧹 Nitpick comments (4)
app/(landing)/org/[id]/page.tsx (1)

10-12: Use typed const arrow exports for TS/TSX declarations.

Both exported functions currently use function declarations; convert to typed const arrow functions to match the project convention.

As per coding guidelines, **/*.{ts,tsx}: Prefer const arrow functions with explicit type annotations over function declarations.

Also applies to: 42-44

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/page.tsx around lines 10 - 12, The exported functions
are declared using function declarations; convert them to typed const arrow
exports with explicit type annotations to match project conventions—replace the
generateMetadata function (export async function generateMetadata({ params }:
OrgProfilePageProps): Promise<Metadata>) and the other exported function at
lines 42-44 with exported const arrow functions (e.g., export const
generateMetadata: (args: OrgProfilePageProps) => Promise<Metadata> = async ({
params }) => { ... }) keeping the same parameter and return types and behavior
so the signatures remain identical but use const arrow syntax.
app/(landing)/org/[id]/loading.tsx (1)

3-3: Use const arrow component with explicit return type.

Line 3 uses a function declaration; switch to a typed const arrow to match the TS/TSX guideline.

As per coding guidelines, **/*.{ts,tsx}: Prefer const arrow functions with explicit type annotations over function declarations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/loading.tsx at line 3, Replace the function
declaration OrgProfileLoading with a const arrow component and add an explicit
return type to satisfy TS/TSX guidelines: define a named const
(OrgProfileLoading) typed to return JSX.Element or React.ReactElement and export
it as the default export, preserving the existing component body/return value;
update any references to the default export if needed.
app/(landing)/org/[id]/org-profile-client.tsx (2)

267-306: Duplicate skeleton markup should be extracted to one shared component.

This block duplicates the loading UI already defined in app/(landing)/org/[id]/loading.tsx (Line 5-Line 40). Keeping both copies will drift over time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/org-profile-client.tsx around lines 267 - 306,
Duplicate skeleton markup in OrgProfileSkeleton should be extracted into a
single reusable component and used by both org-profile-client.tsx and the
existing loading.tsx; create a new component (e.g., OrgSkeleton or
OrgProfileSkeletonShared) that returns the entire section JSX (banner skeleton,
stats grid skeleton, and about skeleton), export it from a shared module, then
replace the JSX return in the OrgProfileSkeleton function with a simple render
of that new component and import+use the same component inside
app/(landing)/org/[id]/loading.tsx so both files share the exact same markup.

64-64: Align component declarations with TS/TSX style guideline.

Line 64 and Line 267 use function declarations; prefer typed const arrow components.

As per coding guidelines, **/*.{ts,tsx}: Prefer const arrow functions with explicit type annotations over function declarations.

Also applies to: 267-267

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/org-profile-client.tsx at line 64, Convert the
function declaration "export default function OrgProfileClient({ id }:
OrgProfileClientProps) { ... }" to a typed const arrow component, e.g. "const
OrgProfileClient: React.FC<OrgProfileClientProps> = ({ id }) => { ... }; export
default OrgProfileClient;", ensuring you keep the same props type
(OrgProfileClientProps) and export; apply the same change to the other function
component declared later in this file so all components follow the const arrow +
explicit type annotation guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/`(landing)/org/[id]/loading.tsx:
- Line 27: The stats skeleton grid currently uses "grid-cols-2" and renders two
columns on mobile; update the class on the container div in
app/(landing)/org/[id]/loading.tsx (the div with the existing 'mb-8 grid
grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4' classes) to use mobile 1-column and
responsive breakpoints by replacing the column classes with 'grid-cols-1
sm:grid-cols-2 lg:grid-cols-4' so it renders 1 column on mobile, 2 on small
screens, and 4 on large.

In `@app/`(landing)/org/[id]/org-profile-client.tsx:
- Around line 24-28: The OrgStats shape and rendering must be changed to match
the expected profile contract: replace the current interface OrgStats (which has
totalMembers/totalHackathons/totalGrants) with fields projectsCount,
totalHackathons, totalBounties, and totalGrants, and update the UI cards in
org-profile-client.tsx to read and render those new fields instead of “Members”
or deriving projects from org.hackathons.length; import and use the canonical
Types from the Trustless Work docs for the org/profile stats type so the
component (org-profile-client.tsx) consumes the correct properties
(projectsCount, totalHackathons, totalBounties, totalGrants) throughout the
rendering logic.
- Around line 232-233: The stats grid currently uses "grid grid-cols-2 ...
lg:grid-cols-4" which makes mobile show two columns; update both occurrences
that render the stats grid (the JSX mapping over statCards) to use responsive
classes for 1 column on mobile, 2 on tablet, and 4 on desktop by replacing the
class sequence to include "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" (keep
existing gap and other classes intact) so the component rendering statCards and
its duplicate instance follow the required breakpoints.
- Around line 212-221: The icon-only social link anchors in
org-profile-client.tsx (the mapped <a> using key={i}, href={link.url},
title={link.label} and rendering <Icon />) lack an explicit accessible name;
update that anchor to include aria-label={link.label} (or add
visually-hidden/sr-only text inside the anchor) so screen readers announce each
link reliably, keeping the existing href, target, rel and styling intact.
- Around line 78-82: The catch block in org-profile-client.tsx currently masks
real fetch failures by unconditionally calling setOrg(MOCK_ORG) and
setStats(MOCK_STATS); remove that fallback so real errors/invalid IDs surface.
Modify the catch to either (a) set an explicit error/notFound state (e.g.,
setNotFound(true) or setError(err)) or rethrow the error so the parent can
handle it, and only use MOCK_ORG/MOCK_STATS when a clear design-preview flag is
present (check process.env.NODE_ENV or an explicit isDesignPreview prop) before
calling setOrg/setStats; update the catch to reference setOrg, setStats,
MOCK_ORG, MOCK_STATS, and any notFound/error state you add (or rethrow) so the
page preserves proper error/not-found behavior.

---

Nitpick comments:
In `@app/`(landing)/org/[id]/loading.tsx:
- Line 3: Replace the function declaration OrgProfileLoading with a const arrow
component and add an explicit return type to satisfy TS/TSX guidelines: define a
named const (OrgProfileLoading) typed to return JSX.Element or
React.ReactElement and export it as the default export, preserving the existing
component body/return value; update any references to the default export if
needed.

In `@app/`(landing)/org/[id]/org-profile-client.tsx:
- Around line 267-306: Duplicate skeleton markup in OrgProfileSkeleton should be
extracted into a single reusable component and used by both
org-profile-client.tsx and the existing loading.tsx; create a new component
(e.g., OrgSkeleton or OrgProfileSkeletonShared) that returns the entire section
JSX (banner skeleton, stats grid skeleton, and about skeleton), export it from a
shared module, then replace the JSX return in the OrgProfileSkeleton function
with a simple render of that new component and import+use the same component
inside app/(landing)/org/[id]/loading.tsx so both files share the exact same
markup.
- Line 64: Convert the function declaration "export default function
OrgProfileClient({ id }: OrgProfileClientProps) { ... }" to a typed const arrow
component, e.g. "const OrgProfileClient: React.FC<OrgProfileClientProps> = ({ id
}) => { ... }; export default OrgProfileClient;", ensuring you keep the same
props type (OrgProfileClientProps) and export; apply the same change to the
other function component declared later in this file so all components follow
the const arrow + explicit type annotation guideline.

In `@app/`(landing)/org/[id]/page.tsx:
- Around line 10-12: The exported functions are declared using function
declarations; convert them to typed const arrow exports with explicit type
annotations to match project conventions—replace the generateMetadata function
(export async function generateMetadata({ params }: OrgProfilePageProps):
Promise<Metadata>) and the other exported function at lines 42-44 with exported
const arrow functions (e.g., export const generateMetadata: (args:
OrgProfilePageProps) => Promise<Metadata> = async ({ params }) => { ... })
keeping the same parameter and return types and behavior so the signatures
remain identical but use const arrow syntax.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c584ac3 and 415a12d.

📒 Files selected for processing (3)
  • app/(landing)/org/[id]/loading.tsx
  • app/(landing)/org/[id]/org-profile-client.tsx
  • app/(landing)/org/[id]/page.tsx

</div>

{/* Stats grid skeleton */}
<div className='mb-8 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4'>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stats skeleton layout misses the mobile 1-column requirement.

Line 27 starts at grid-cols-2, so mobile renders 2 columns instead of the required 1. Use grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 to match the test plan.

Proposed fix
-      <div className='mb-8 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4'>
+      <div className='mb-8 grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-4'>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/loading.tsx at line 27, The stats skeleton grid
currently uses "grid-cols-2" and renders two columns on mobile; update the class
on the container div in app/(landing)/org/[id]/loading.tsx (the div with the
existing 'mb-8 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4' classes) to use
mobile 1-column and responsive breakpoints by replacing the column classes with
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4' so it renders 1 column on mobile, 2
on small screens, and 4 on large.

Comment on lines 212 to 221
<a
key={i}
href={link.url}
target='_blank'
rel='noopener noreferrer'
title={link.label}
className='flex h-9 w-9 items-center justify-center rounded-lg border border-zinc-700/50 bg-zinc-800/50 text-zinc-400 transition-all hover:border-[#a7f950]/30 hover:bg-[#a7f950]/10 hover:text-[#a7f950]'
>
<Icon className='h-4 w-4' />
</a>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Icon-only social links need explicit accessible names.

Line 212-Line 221 currently relies on title; add aria-label (or visible/sr-only text) so assistive tech can reliably announce each link.

Proposed fix
                       <a
                         key={i}
                         href={link.url}
                         target='_blank'
                         rel='noopener noreferrer'
                         title={link.label}
+                        aria-label={link.label}
                         className='flex h-9 w-9 items-center justify-center rounded-lg border border-zinc-700/50 bg-zinc-800/50 text-zinc-400 transition-all hover:border-[`#a7f950`]/30 hover:bg-[`#a7f950`]/10 hover:text-[`#a7f950`]'
                       >
📝 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.

Suggested change
<a
key={i}
href={link.url}
target='_blank'
rel='noopener noreferrer'
title={link.label}
className='flex h-9 w-9 items-center justify-center rounded-lg border border-zinc-700/50 bg-zinc-800/50 text-zinc-400 transition-all hover:border-[#a7f950]/30 hover:bg-[#a7f950]/10 hover:text-[#a7f950]'
>
<Icon className='h-4 w-4' />
</a>
<a
key={i}
href={link.url}
target='_blank'
rel='noopener noreferrer'
title={link.label}
aria-label={link.label}
className='flex h-9 w-9 items-center justify-center rounded-lg border border-zinc-700/50 bg-zinc-800/50 text-zinc-400 transition-all hover:border-[`#a7f950`]/30 hover:bg-[`#a7f950`]/10 hover:text-[`#a7f950`]'
>
<Icon className='h-4 w-4' />
</a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/org-profile-client.tsx around lines 212 - 221, The
icon-only social link anchors in org-profile-client.tsx (the mapped <a> using
key={i}, href={link.url}, title={link.label} and rendering <Icon />) lack an
explicit accessible name; update that anchor to include aria-label={link.label}
(or add visually-hidden/sr-only text inside the anchor) so screen readers
announce each link reliably, keeping the existing href, target, rel and styling
intact.

Comment on lines +232 to +233
<div className='mb-8 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4'>
{statCards.map((stat, index) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stats grid breakpoints are off for mobile.

Line 232 and Line 291 start from grid-cols-2; the requirement is mobile 1-column, tablet 2-column, desktop 4-column.

Proposed fix
-      <div className='mb-8 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4'>
+      <div className='mb-8 grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-4'>
...
-      <div className='mb-8 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4'>
+      <div className='mb-8 grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-4'>

Also applies to: 291-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/org/[id]/org-profile-client.tsx around lines 232 - 233, The
stats grid currently uses "grid grid-cols-2 ... lg:grid-cols-4" which makes
mobile show two columns; update both occurrences that render the stats grid (the
JSX mapping over statCards) to use responsive classes for 1 column on mobile, 2
on tablet, and 4 on desktop by replacing the class sequence to include
"grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" (keep existing gap and other classes
intact) so the component rendering statCards and its duplicate instance follow
the required breakpoints.

@Benjtalkshow
Copy link
Collaborator

@Sandijigs,

Thank you for your contribution. I appreciate the effort.

However, I advise that you wait until the issue is unblocked. Did you see my comment here?
#392 (comment)

Please wait until the organization public data are returned through a new endpoint. That is why the issue is currently blocked.

Right now, your implementation is based on assumptions because the response format has not been defined yet.

Thanks.

@0xdevcollins 0xdevcollins merged commit c97b3f5 into boundlessfi:main Feb 28, 2026
0 of 2 checks passed
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.

Implement Organization Profile Page ui

3 participants