Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
45d6c89
vibe save point
kllarena07 Jan 23, 2026
ca649b4
feat: proper filter bar ordering
kllarena07 Jan 23, 2026
6e46ccb
feat: more project data
kllarena07 Jan 23, 2026
33a2cd3
feat: better satured colors
kllarena07 Jan 23, 2026
7879fe5
feat: turn filter into dropdown
kllarena07 Jan 23, 2026
b13c777
fix: y-combinator -> y combinator
kllarena07 Jan 23, 2026
b95ee02
refactor: better project data
kllarena07 Jan 23, 2026
5f7e3de
feat: remove project badges
kllarena07 Jan 23, 2026
d58e6d7
save
kllarena07 Jan 28, 2026
fc87c65
Merge branch 'main' into kieran/add-projects
kllarena07 Feb 7, 2026
0175ecc
feat: upgrade pnpm version
kllarena07 Feb 7, 2026
e875cca
feat: pull project data from notion
kllarena07 Feb 9, 2026
7f65b1b
refactor: reorder cohorts and prioritize funded projects
kllarena07 Feb 9, 2026
64d1340
refactor: server side filtering for projects
kllarena07 Feb 9, 2026
1fd6626
feat: load page skeleton and then data
kllarena07 Feb 9, 2026
7ad6384
refactor: remove clear all filters button
kllarena07 Feb 9, 2026
1ba1a18
refactor: use tanstack query
kllarena07 Feb 9, 2026
ee45824
feat: add searching
kllarena07 Feb 9, 2026
f2547c4
chore: delete unused page
kllarena07 Feb 9, 2026
b00018e
feat: rename project-directory to projects
kllarena07 Feb 9, 2026
93371de
chore: remove tags for funding
kllarena07 Feb 9, 2026
c76fe07
feat: better cohort badges
kllarena07 Feb 9, 2026
b00613e
refactor: spinner on data load when a filter is applied
kllarena07 Feb 9, 2026
ee8e172
feat: provide proper founder connects and hide company website if n/a
kllarena07 Feb 9, 2026
e574869
refactor: hide founders section if none are present
kllarena07 Feb 9, 2026
18e069e
feat: weighted ordering of funded projects
kllarena07 Feb 9, 2026
b647b68
feat: better search bar
kllarena07 Feb 10, 2026
0ccee82
feat: improved left side filtering ui
kllarena07 Feb 10, 2026
2d5d42e
feat: ensure consistent border rounding
kllarena07 Feb 10, 2026
6179e31
feat: better badging for projects
kllarena07 Feb 10, 2026
66c8f4a
refactor: set cohort ordering to descending for filter bar
kllarena07 Feb 10, 2026
2bb3e14
refactor: data is obtained before build process
kllarena07 Feb 10, 2026
72cf91e
refactor: change font for filter side
kllarena07 Feb 10, 2026
60925fa
chore: lighter checkbox borders for filtering
kllarena07 Feb 10, 2026
be3790c
feat: add back clear all filters button
kllarena07 Feb 10, 2026
6228baf
chore: update project data
kllarena07 Feb 10, 2026
e445e1b
refactor: ycomb glaze is unbeatable
kllarena07 Feb 10, 2026
c91094d
chore: add color for w24
kllarena07 Feb 10, 2026
345839c
chore: delete .DS_Store
kllarena07 Feb 12, 2026
c4959ce
Merge branch 'main' into kieran/add-projects
kllarena07 Feb 16, 2026
98e8529
feat: download images from notion to use for projects in build process
kllarena07 Feb 16, 2026
07bb8aa
feat: new embedder logo
kllarena07 Feb 16, 2026
8ee91c2
Merge branch 'main' into kieran/add-projects
kllarena07 Feb 16, 2026
91ecfea
feat: get founder pfps
kllarena07 Feb 16, 2026
2ef20df
chore: trigger netlify build
kllarena07 Feb 16, 2026
827b2f8
chore: restore README
kllarena07 Feb 17, 2026
641d18b
fix: serve data over import and not fs
kllarena07 Feb 17, 2026
bea5207
maybe it filtering works now
kllarena07 Feb 17, 2026
3f1888c
refactor: moved all the data into the public dir
kllarena07 Feb 17, 2026
d5ed71b
feat: testing in preview
kllarena07 Feb 17, 2026
358fd3c
maybe this time
kllarena07 Feb 17, 2026
908ad7e
refactor: client is now client-sided
kllarena07 Feb 17, 2026
f43b653
chore: documentation on project data build process
kllarena07 Feb 18, 2026
536771b
fix: add url sanitization for notion lniks
kllarena07 Feb 18, 2026
b72f06a
refactor: use empty li pfp image for missing pfps
kllarena07 Feb 19, 2026
3626d18
chore: specify platform/community instructions for projects in README
kllarena07 Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 103 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# V1 at Michigan
# V1 at Michigan

This is the code repository for the website. Serverless frontend built with Next.js + Tailwind CSS. Hosted on Netlify.

## Tech Stack
Expand All @@ -15,33 +15,104 @@ This is the code repository for the website. Serverless frontend built with Next
- **Package Manager**: pnpm

## How to work on this Project

1. First clone the repository: `https://github.com/V1Michigan/website-v2.git`.
2. Make sure you enter the repository through terminal `cd <FilePath>/website-v2`.
3. Install dependencies: `pnpm install`. **We DO NOT use npm for this.**
4. Begin Devving: `pnpm dev` or `pnpm run dev`, whatever pleases you. This will allow you to access a dev version of the site @ `localhost:3000` that will update automatically as you save files.
5. Create a new branch for your NEW feature: `git checkout -b [FEATURE_NAME]`. If you want to go to an existing feature: `git checkout [FEATURE_NAME]`.
6. Ask another team member for the latest `.env` file.
7. When you finish a part of your feature and wish to push the changes to the remote repository:
8. `git add [changed_files]` (replace `changed_files` with the actual file names you changed)
9. `git commit -m "meaningful commit message goes here"`
10. `git push --set-upstream origin [FEATURE_NAME]` (the next time you push to this branch you can just say `git push`).

## Linting and Code Quality

We use ESLint + Prettier to lint our code and enforce consistent style. To make this process smoother, we've set up:

1. **Pre-commit hooks**: Automatically lint and format your code when you commit changes. This uses Husky and lint-staged to ensure code quality without disrupting your workflow.

2. **Non-blocking CI**: Our GitHub Actions workflow checks for linting issues but won't block builds due to formatting problems.

For manual linting:

- Use `pnpm lint` to check for linting/style errors
- Use `pnpm lint --fix` to automatically fix (some) linting issues

We recommend using an [ESLint plugin for your editor](https://eslint.org/docs/user-guide/integrations) to lint and format your code as you write it.

- VSCode: [extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint), [tutorial](https://www.digitalocean.com/community/tutorials/linting-and-formatting-with-eslint-in-vs-code)

See the [CONTRIBUTING.md](./CONTRIBUTING.md) file for more detailed information about code standards and workflow.

1. First clone the repository: `https://github.com/V1Michigan/website-v2.git`.
2. Make sure you enter the repository through terminal `cd <FilePath>/website-v2`.
3. Install dependencies: `pnpm install`. **We DO NOT use npm for this.**
4. Begin Devving: `pnpm dev` or `pnpm run dev`, whatever pleases you. This will allow you to access a dev version of the site @ `localhost:3000` that will update automatically as you save files.
5. Create a new branch for your NEW feature: `git checkout -b [FEATURE_NAME]`. If you want to go to an existing feature: `git checkout [FEATURE_NAME]`.
6. Ask another team member for the latest `.env` file.
7. When you finish a part of your feature and wish to push the changes to the remote repository:
8. `git add [changed_files]` (replace `changed_files` with the actual file names you changed)
9. `git commit -m "meaningful commit message goes here"`
10. `git push --set-upstream origin [FEATURE_NAME]` (the next time you push to this branch you can just say `git push`).

## Linting and Code Quality

We use ESLint + Prettier to lint our code and enforce consistent style. To make this process smoother, we've set up:

1. **Pre-commit hooks**: Automatically lint and format your code when you commit changes. This uses Husky and lint-staged to ensure code quality without disrupting your workflow.

2. **Non-blocking CI**: Our GitHub Actions workflow checks for linting issues but won't block builds due to formatting problems.

For manual linting:

- Use `pnpm lint` to check for linting/style errors
- Use `pnpm lint --fix` to automatically fix (some) linting issues

We recommend using an [ESLint plugin for your editor](https://eslint.org/docs/user-guide/integrations) to lint and format your code as you write it.

- VSCode: [extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint), [tutorial](https://www.digitalocean.com/community/tutorials/linting-and-formatting-with-eslint-in-vs-code)

## Project Data Structure

Projects are stored in [Notion](https://notion.so) and built into static JSON data at build time.

### Data Flow

```
Notion DataSource → build script → projects-data.json → API → Components
```

1. **Data Source**: Project information is stored as a Notion DataSource (configured via `NOTION_DATA_SOURCE_ID`)
2. **Build Process**: The build script fetches data from Notion, downloads images, and generates static JSON
3. **Serving**: An API route serves the pre-built JSON data
4. **Consumption**: Components use React Query to fetch and display project data

### Building Project Data

To build or update project data:

```bash
# Build only project data (for development/testing)
pnpm build:projects

# Build project data as part of full production build
pnpm build
```

### Deployment Notes

⚠️ **Important**: The projects page uses static JSON data that is built at deploy time. Any changes made to the Notion database will **not appear on the live site** until the Platform Team triggers a redeploy of the Netlify website.

To edit projects in the Notion database, visit:
**[V1 Projects Notion Database](https://www.notion.so/v1team/30082307c07d80e3a38bd1f180684f93?v=30082307c07d8076bea6000c63bc3738&source=copy_link)**

### Required Environment Variables

These must be set in your `.env.local` file:
- `NOTION_API_KEY` - Your Notion integration API key
- `NOTION_DATA_SOURCE_ID` - The ID of the Notion DataSource containing project information

### Key Files

- `scripts/build-projects-data.ts` - Build script that fetches from Notion and generates JSON
- `lib/notion.ts` - Data transformation, filtering, and sorting logic
- `public/projects-data.json` - Generated project data (do not edit manually)
- `app/api/projects/route.ts` - API route serving project data
- `hooks/useProjects.ts` - React hook for fetching projects
- `types/project.ts` - TypeScript interfaces for Project type
- `app/projects/` - Project directory page and components

### Asset Management

The build process automatically downloads and optimizes:
- **Company logos**: Stored in `public/projects/[company-name].jpg`
- **Founder profile pictures**: Stored in `public/founders/[company-name]/[founder-name].jpg`

All images are resized to 256x256px and converted to JPEG format using Sharp.

### Data Features

Projects can be filtered by:
- **Funding sources**: VC firms, accelerators (e.g., Y Combinator, Techstars)
- **Cohorts**: Product Studio cohorts by semester
- **Categories**: Industry tags (e.g., AI/ML, Developer Tools, B2B)
- **Search**: By company name or project title

Projects are sorted by:
1. Funding status (funded projects first)
2. Investor prestige score
3. Cohort order (newest first)

See the [CONTRIBUTING.md](./CONTRIBUTING.md) file for more detailed information about code standards and workflow.
Binary file removed app/.DS_Store
Binary file not shown.
41 changes: 41 additions & 0 deletions app/api/projects/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NextResponse, NextRequest } from "next/server"
import type { Project } from "@/types/project"
import { filterProjects } from "@/lib/notion"

export async function GET(_request: NextRequest) {
try {
const response = await fetch(`${_request.nextUrl.origin}/projects-data.json`)
if (!response.ok) {
throw new Error("Failed to fetch projects data")
}

const data = await response.json()
const allProjects = data.projects as Project[]
const cachedFilterOptions = data.filterOptions

return NextResponse.json({
projects: allProjects,
filterOptions: cachedFilterOptions,
totalProjects: allProjects.length,
filteredCount: allProjects.length
}, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
}
})
} catch (error) {
console.error("Error in projects API:", {
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
name: error instanceof Error ? error.name : typeof error
})

return NextResponse.json({
error: "Failed to process projects data",
message: error instanceof Error ? error.message : String(error),
type: error instanceof Error ? error.name : typeof error,
}, {
status: 500
})
}
}
5 changes: 0 additions & 5 deletions app/project-directory/[id]/page.tsx

This file was deleted.

49 changes: 0 additions & 49 deletions app/project-directory/page.tsx

This file was deleted.

107 changes: 107 additions & 0 deletions app/projects/components/FilterPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { memo } from "react"

interface FilterPanelProps {
filters: {
searchQuery: string
fundingSources: string[]
cohorts: string[]
categories: string[]
}
filterOptions: {
fundingSources: string[]
cohorts: string[]
categories: string[]
}
onSearchChange: (_query: string) => void
onToggleFunding: (_source: string) => void
onToggleCohort: (_cohort: string) => void
onToggleCategory: (_category: string) => void
isLoading?: boolean
showSearch?: boolean
}

function FilterPanel({
filters,
filterOptions,
onSearchChange,
onToggleFunding,
onToggleCohort,
onToggleCategory,
isLoading = false,
showSearch = true,
}: FilterPanelProps) {
return (
<div className="h-full overflow-y-auto p-6">
{/* Funding Sources */}
<div className="mb-6">
<h3 className="mb-3 font-instrument text-xl font-semibold text-gray-900">Funding</h3>
<div className="space-y-2">
{filterOptions.fundingSources.map((source) => (
<label
key={source}
className={`flex items-center gap-2 rounded-md p-2 ${isLoading ? '' : 'cursor-pointer hover:bg-gray-50'}`}
>
<input
type="checkbox"
checked={filters.fundingSources.includes(source)}
onChange={() => onToggleFunding(source)}
className="h-4 w-4 rounded border-gray-100 text-blue-600 focus:ring-blue-500"
disabled={isLoading}
/>
<span className="text-sm text-gray-700">{source}</span>
</label>
))}
</div>
</div>

{/* Product Studio Cohorts */}
<div className="mb-6">
<h3 className="mb-3 font-instrument text-xl font-semibold text-gray-900">Cohorts</h3>
<div className="space-y-2">
{filterOptions.cohorts.map((cohort) => {
const displayName = cohort.replace(" Product Studio Cohort", "")
return (
<label
key={cohort}
className={`flex items-center gap-2 rounded-md p-2 ${isLoading ? '' : 'cursor-pointer hover:bg-gray-50'}`}
>
<input
type="checkbox"
checked={filters.cohorts.includes(cohort)}
onChange={() => onToggleCohort(cohort)}
className="h-4 w-4 rounded border-gray-100 text-purple-600 focus:ring-purple-500"
disabled={isLoading}
/>
<span className="text-sm text-gray-700">{displayName}</span>
</label>
)
})}
</div>
</div>

{/* Categories */}
<div className="mb-6">
<h3 className="mb-3 font-instrument text-xl font-semibold text-gray-900">Categories</h3>
<div className="space-y-2">
{filterOptions.categories.map((category) => (
<label
key={category}
className={`flex items-center gap-2 rounded-md p-2 ${isLoading ? '' : 'cursor-pointer hover:bg-gray-50'}`}
>
<input
type="checkbox"
checked={filters.categories.includes(category)}
onChange={() => onToggleCategory(category)}
className="h-4 w-4 rounded border-gray-100 text-gray-600 focus:ring-gray-500"
disabled={isLoading}
/>
<span className="text-sm text-gray-700">{category}</span>
</label>
))}
</div>
</div>
</div>
)
}

export default memo(FilterPanel)
Loading