Skip to content

Commit d7b8f54

Browse files
authored
Merge pull request #36 from compsoc-edinburgh/feat/new-look-for-minutes-and-news-page
feat: refresh news and minutes page design
2 parents 2a63467 + 13418d8 commit d7b8f54

30 files changed

+1502
-501
lines changed

app/events/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ export default function Events() {
88
return (
99
<div className="max-w-5xl mx-auto">
1010
<div className="pt-4">
11-
<Heading title="Full event calendar"></Heading>
11+
<Heading title="Full event calendar" />
1212
<div className="mt-6 font-space-mono">
1313
Aside from the large events, CompSoc and its SIGs run a variety of
1414
events each week. Explore our full event calendar below and find out
1515
what&apos;s coming up!
1616
</div>
17-
<EventsCalendar></EventsCalendar>
17+
<EventsCalendar />
1818

1919
<div className="mt-10">
20-
<Heading title="Flagship Events"></Heading>
20+
<Heading title="Flagship Events" />
2121
<div className="mt-6 pb-6 font-space-mono">
2222
These are big events we run every year without fail. Consider
2323
joining us for one of these!
2424
</div>
25-
<EventCard></EventCard>
25+
<EventCard />
2626
</div>
2727
</div>
2828
</div>

app/minutes/MinutesList.tsx

Lines changed: 239 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,156 @@
11
'use client'
22

3-
import SearchPosts from '@/components/SearchPosts'
43
import { prefix } from '@/utils/prefix'
5-
import Image from 'next/image'
64
import Link from 'next/link'
75
import { useEffect, useState } from 'react'
6+
import { Search, Filter, X } from 'iconoir-react'
7+
import Heading from '@/components/heading'
8+
9+
const SearchBar = ({
10+
searchQuery,
11+
setSearchQuery,
12+
onFilterChange,
13+
yearFilter,
14+
availableYears,
15+
}: {
16+
searchQuery: string
17+
setSearchQuery: (query: string) => void
18+
onFilterChange: (year: string) => void
19+
yearFilter: string
20+
availableYears: string[]
21+
}) => {
22+
const [showFilters, setShowFilters] = useState(false)
823

9-
const MinuteFile = ({ slug, intro }: { slug: string; intro: string }) => {
1024
return (
11-
<Link href={`${prefix}/minutes/${slug}`}>
12-
<div className="border border-border p-4 rounded-sm bg-foreground">
13-
<h2 className="text-xl font-space-mono">{slug}</h2>
14-
<p
15-
style={{
16-
display: '-webkit-box',
17-
WebkitLineClamp: 5,
18-
WebkitBoxOrient: 'vertical',
19-
overflow: 'hidden',
20-
position: 'relative',
21-
}}
22-
>
23-
<span className="opacity-70">{intro}</span>
24-
<span
25-
style={{
26-
position: 'absolute',
27-
bottom: 0,
28-
left: 0,
29-
right: 0,
30-
height: '10em',
31-
background:
32-
'linear-gradient(to bottom, rgba(255,255,255,0) 0%, #353535 100%)',
33-
}}
25+
<div className="mb-8">
26+
<div className="relative flex flex-col sm:flex-row gap-4 items-center">
27+
<div className="relative flex-1 max-w-md">
28+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-zinc-400 w-5 h-5" />
29+
<input
30+
type="text"
31+
placeholder="Search minutes, topics, or dates..."
32+
className="w-full pl-10 pr-4 py-3 bg-foreground border border-border focus:outline-none focus:ring-2 focus:ring-csred/50 focus:border-csred transition-all duration-200 font-space-mono text-sm"
33+
value={searchQuery}
34+
onChange={(e) => setSearchQuery(e.target.value)}
3435
/>
35-
</p>
36+
{searchQuery && (
37+
<button
38+
onClick={() => setSearchQuery('')}
39+
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-zinc-400 hover:text-white transition-colors"
40+
>
41+
<X className="w-4 h-4" />
42+
</button>
43+
)}
44+
</div>
45+
46+
<button
47+
onClick={() => setShowFilters(!showFilters)}
48+
className="flex items-center gap-2 px-4 py-3 bg-foreground border border-border hover:bg-border/50 transition-colors font-space-mono text-sm"
49+
>
50+
<Filter className="w-4 h-4" />
51+
Filters
52+
{yearFilter && (
53+
<span className="bg-csred text-white px-2 py-1 rounded-full text-xs">
54+
{yearFilter}
55+
</span>
56+
)}
57+
</button>
3658
</div>
37-
</Link>
59+
60+
{showFilters && (
61+
<div className="mt-4 p-4 bg-foreground border border-border ">
62+
<div className="flex flex-wrap gap-2">
63+
<button
64+
onClick={() => onFilterChange('')}
65+
className={`px-3 py-2 text-sm font-space-mono transition-colors ${
66+
!yearFilter
67+
? 'bg-csred text-white'
68+
: 'bg-border text-zinc-300 hover:bg-border/70'
69+
}`}
70+
>
71+
All Years
72+
</button>
73+
{availableYears.map((year) => (
74+
<button
75+
key={year}
76+
onClick={() => onFilterChange(year)}
77+
className={`px-3 py-2 text-sm font-space-mono transition-colors ${
78+
yearFilter === year
79+
? 'bg-csred text-white'
80+
: 'bg-border text-zinc-300 hover:bg-border/70'
81+
}`}
82+
>
83+
{year}
84+
</button>
85+
))}
86+
</div>
87+
</div>
88+
)}
89+
</div>
90+
)
91+
}
92+
93+
const ListView = ({
94+
filteredPosts,
95+
initialPostsToShow,
96+
setInitialPostsToShow,
97+
}: {
98+
filteredPosts: {
99+
slug: string
100+
content: string
101+
date: string
102+
title: string
103+
}[]
104+
initialPostsToShow: number
105+
setInitialPostsToShow: (count: number) => void
106+
}) => {
107+
return (
108+
<div>
109+
{filteredPosts.slice(0, initialPostsToShow).map((post, index) => (
110+
<Link key={post.slug} href={`${prefix}/minutes/${post.slug}`}>
111+
<div className="group mt-4 border border-border p-4 bg-foreground hover:bg-border/30 transition-all duration-200 hover:scale-[1.01]">
112+
<div className="flex items-center justify-between">
113+
<div className="flex-1">
114+
<h3 className="text-lg font-tomorrow text-white group-hover:text-csred transition-colors">
115+
{new Date(post.date).toLocaleDateString('en-GB', {
116+
day: 'numeric',
117+
month: 'short',
118+
year: 'numeric',
119+
})}
120+
</h3>
121+
<span className="text-sm text-zinc-400 font-space-mono">
122+
{new Date(post.date).toLocaleDateString('en-GB', {
123+
weekday: 'long',
124+
})}
125+
</span>
126+
</div>
127+
</div>
128+
<p
129+
className="text-sm opacity-70 leading-relaxed mt-2"
130+
style={{
131+
display: '-webkit-box',
132+
WebkitLineClamp: 2,
133+
WebkitBoxOrient: 'vertical',
134+
overflow: 'hidden',
135+
}}
136+
>
137+
{post.content.slice(0, 180)}...
138+
</p>
139+
</div>
140+
</Link>
141+
))}
142+
143+
{initialPostsToShow < filteredPosts.length && (
144+
<div className="text-center mt-6">
145+
<button
146+
onClick={() => setInitialPostsToShow(Infinity)}
147+
className="bg-csred hover:bg-csred/80 text-white font-space-mono px-6 py-2 transition-all duration-200 hover:scale-105"
148+
>
149+
Show all {filteredPosts.length - initialPostsToShow} more minutes
150+
</button>
151+
</div>
152+
)}
153+
</div>
38154
)
39155
}
40156

@@ -44,64 +160,114 @@ const MinutesList = ({
44160
posts: {
45161
slug: string
46162
content: string
163+
date: string
164+
title: string
47165
}[]
48166
}) => {
49167
const [searchQuery, setSearchQuery] = useState('')
50168
const [filteredPosts, setFilteredPosts] = useState(posts)
51-
const [initialPostsToShow, setInitialPostsToShow] = useState(16)
169+
const [initialPostsToShow, setInitialPostsToShow] = useState(20)
170+
const [yearFilter, setYearFilter] = useState('')
171+
172+
const availableYears = Array.from(
173+
new Set(posts.map((post) => post.date.split('-')[0]))
174+
)
175+
.sort()
176+
.reverse()
52177

53178
useEffect(() => {
54-
setFilteredPosts(
55-
posts.filter((post) =>
56-
post.content.toLowerCase().includes(searchQuery.toLowerCase())
179+
let filtered = posts
180+
181+
if (searchQuery) {
182+
filtered = filtered.filter(
183+
(post) =>
184+
post.content.toLowerCase().includes(searchQuery.toLowerCase()) ||
185+
post.slug.toLowerCase().includes(searchQuery.toLowerCase()) ||
186+
post.date.includes(searchQuery)
57187
)
58-
)
59-
}, [searchQuery])
188+
}
189+
190+
if (yearFilter) {
191+
filtered = filtered.filter((post) => post.date.startsWith(yearFilter))
192+
}
193+
194+
setFilteredPosts(filtered)
195+
}, [searchQuery, yearFilter, posts])
60196

61197
return (
62-
<>
63-
<div className="max-w-5xl mx-auto">
64-
<div className="mb-10 mt-4">
65-
<h1 className="font-tomorrow text-3xl">Minutes</h1>
66-
</div>
67-
<p className="text-md mb-10">
68-
Welcome to the CompSoc Meeting Minutes Archive. Here, you will find
69-
detailed records of all committee meetings dating back to 2016.
70-
Explore these documents to gain insights into the decisions,
71-
discussions, and developments that have shaped our society.
72-
</p>
73-
74-
<SearchPosts setSearchQuery={setSearchQuery} />
75-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 lg:grid-cols-4">
76-
{filteredPosts.slice(0, initialPostsToShow).map((post) => (
77-
<MinuteFile key={post.slug} slug={post.slug} intro={post.content} />
78-
))}
198+
<div className="max-w-5xl mx-auto">
199+
<div className="mb-10 mt-4">
200+
<Heading title="Meeting Minutes" />
201+
</div>
202+
<p className="mt-6 font-space-mono mb-10">
203+
Explore our comprehensive archive of committee meetings dating back to
204+
2016. Discover the decisions, discussions, and developments that shaped
205+
CompSoc.
206+
</p>
207+
208+
<SearchBar
209+
searchQuery={searchQuery}
210+
setSearchQuery={setSearchQuery}
211+
onFilterChange={setYearFilter}
212+
yearFilter={yearFilter}
213+
availableYears={availableYears}
214+
/>
215+
216+
{searchQuery || yearFilter ? (
217+
<div className="mt-8">
218+
<div className="flex items-center justify-between mb-6">
219+
<h3 className="text-xl font-tomorrow">
220+
{searchQuery ? 'Search Results' : `${yearFilter} Minutes`}
221+
<span className="ml-2 text-sm font-space-mono text-zinc-400">
222+
({filteredPosts.length} found)
223+
</span>
224+
</h3>
225+
{(searchQuery || yearFilter) && (
226+
<button
227+
onClick={() => {
228+
setSearchQuery('')
229+
setYearFilter('')
230+
}}
231+
className="text-sm text-csred hover:text-csred/80 font-space-mono"
232+
>
233+
Clear filters
234+
</button>
235+
)}
236+
</div>
237+
<ListView
238+
filteredPosts={filteredPosts}
239+
initialPostsToShow={initialPostsToShow}
240+
setInitialPostsToShow={setInitialPostsToShow}
241+
/>
79242
</div>
80-
{initialPostsToShow < filteredPosts.length && (
243+
) : (
244+
<ListView
245+
filteredPosts={filteredPosts}
246+
initialPostsToShow={initialPostsToShow}
247+
setInitialPostsToShow={setInitialPostsToShow}
248+
/>
249+
)}
250+
251+
{filteredPosts.length === 0 && (searchQuery || yearFilter) && (
252+
<div className="flex flex-col items-center justify-center py-16">
253+
<div className="text-6xl mb-4">🔍</div>
254+
<h3 className="text-xl font-tomorrow mb-2">No minutes found</h3>
255+
<p className="text-zinc-400 text-center max-w-md">
256+
Try adjusting your search terms or filters to find what you&apos;re
257+
looking for.
258+
</p>
81259
<button
82-
onClick={() => setInitialPostsToShow(Infinity)}
83-
className="text-primary font-space-mono bg-foreground px-3 py-2 rounded-sm m-auto block border border-border my-4"
260+
onClick={() => {
261+
setSearchQuery('')
262+
setYearFilter('')
263+
}}
264+
className="mt-4 px-6 py-2 bg-csred text-white hover:bg-csred/80 transition-colors font-space-mono"
84265
>
85-
Show all
266+
Clear all filters
86267
</button>
87-
)}
88-
{filteredPosts.length === 0 && (
89-
<div className="flex flex-col items-center justify-center space-y-4 mt-8 h-32">
90-
<Image
91-
src={`${prefix}/sad-mug.png`}
92-
alt="No results"
93-
width={150}
94-
height={150}
95-
className="m-auto my-0"
96-
/>
97-
98-
<p className="text-center font-space-mono text-lg mt-4">
99-
Nothing to see here {':('}
100-
</p>
101-
</div>
102-
)}
103-
</div>
104-
</>
268+
</div>
269+
)}
270+
</div>
105271
)
106272
}
107273

0 commit comments

Comments
 (0)