Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ next-env.d.ts

# Nix
flake.lock
.env*.local
2 changes: 1 addition & 1 deletion app/events/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import styles from './Events.module.css'
type CompleteEventDetails = EventDetails & { end: number, link: string | null }
export type EventFrontmatter = Omit<Frontmatter, 'event_details'> & { event_details: CompleteEventDetails }

async function getEvents() {
export async function getEvents() {
const markdownFiles = await generateUnmodifiedSlugsFromMarkdownFiles('app/events')
const events: Frontmatter[] = []
for (const { slug } of markdownFiles) {
Expand Down
7 changes: 6 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { NavItems } from "@/components/NavItems"
import { HomeLoop } from "@/components/HomeLoop"
import { HomepageBanner } from "@/components/HomepageBanner"
import { getEvents } from "./events/page"

import styles from "./Home.module.css"

export default function Home() {
export default async function Home() {
const events = await getEvents()

return (
<>
<div id={styles.content}>
<HomepageBanner events={events} />
<h1 id={styles.title}>compsigh</h1>
<p id={styles.description}>
compsigh is the social computer science club at USFCA for meeting cool
Expand Down
19 changes: 19 additions & 0 deletions components/HomepageBanner/HomepageBanner.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
div.banner {
/*font-size: 1.2rem;*/

/*a {
display: flex;
white-space: pre;
text-decoration: none;
padding: 4px 8px;
}*/
}

@media (max-width: 700px) {
div.banner {
margin-left: -24px;
/*a {
flex-direction: column;
}*/
}
}
33 changes: 33 additions & 0 deletions components/HomepageBanner/HomepageBanner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { render, screen } from "@testing-library/react"
import { HomepageBanner } from "./HomepageBanner"
import { EventFrontmatter } from "@/app/events/page"

describe("Next Component HomepageBanner", () => {
it("Renders when an event is coming up", () => {
const currentTime = Math.floor(new Date().getTime() / 1000)

const testing_UpComingEvent: EventFrontmatter[] = [
{
title: "DAVE",
description: "register!",
event_details: {
start: currentTime + 60,
end: currentTime + 90,
location: "The Hive",
cover_image: "/events/2025-11-07/deploy25.png",
pictures: [],
link: "https://touch-grass.tech/"
},
slug: "events/2025-11-07/deploy25"
}
]

render(<HomepageBanner events={testing_UpComingEvent} />)

const banner = screen.getByText((content) =>
content.includes("Next event:")
)

expect(banner).toBeInTheDocument()
})
})
63 changes: 63 additions & 0 deletions components/HomepageBanner/HomepageBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client"

// Components
import { LinkBar } from "@/components/LinkBar"

// Functions
import { isValidURL } from "./isValidURL"

// Types
import { EventFrontmatter } from "@/app/events/page"

// Styles
import styles from "./HomepageBanner.module.css"

export function HomepageBanner({ events }: { events: EventFrontmatter[] }) {
const currentTime = Math.floor(new Date().getTime() / 1000)
if (events.length === 0) return <></>

const updatedEvents: EventFrontmatter[] = [
...events,
{
title: "compsigh night v2026.01.20 6 7",
description: "register!",
event_details: {
start: 1770420908,
end: 1770420968,
location: "The Hive",
cover_image: "/events/2025-11-07/deploy25.png",
pictures: [],
link: "https://touch-grass.tech/"
},
slug: "events/2025-11-07/deploy25"
}
]

const upcomingEvents: EventFrontmatter[] = []
for (const event of updatedEvents)
if (currentTime < event.event_details.start) upcomingEvents.push(event)

if (upcomingEvents.length === 0) return <></>
const nearestUpcomingEvent = upcomingEvents[0]
const title = nearestUpcomingEvent.title
const eventLink = nearestUpcomingEvent.event_details.link!

if (!isValidURL(eventLink)) return <></>

return (
<div className={styles.banner}>
{/*<Link href={eventLink} style={{ fontFamily: "var(--font-tronica-mono)" }}>
<span>Next event: </span>
<TextStream duration={1} text={`${title} >`} />
</Link>*/}
<LinkBar
arrowDirection="forward"
alignment="end"
order="text-first"
href={"https://compsigh.club"}
>
Next event: {title}
</LinkBar>
</div>
)
}
1 change: 1 addition & 0 deletions components/HomepageBanner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HomepageBanner } from './HomepageBanner'
89 changes: 89 additions & 0 deletions components/HomepageBanner/isValidURL.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { isValidURL } from "./isValidURL"

interface Test {
name: string
input: string
expected: boolean
}

const tests: Test[] = [
{
name: "Return true for valid URL string",
input: "https://touch-grass.tech/",
expected: true
},
{
name: "File Transfer Protocol",
input: "ftp://example.com",
expected: true
},
{
name: "WebSocket Secure Protocol",
input: "wss://example.com",
expected: true
},
{
name: "Email Protocol",
input: "mailto://johnpork@yahoo.com",
expected: true
},
{
name: "Torrent Protocol",
input:
"magnet:?xt=urn:btih:6D5B5F0E8E6E4F5D8B7A9C1E2D3F4A5B6C7D8E9F&dn=example-file.zip&tr=udp://tracker.example.com:6969/announce",
expected: true
},
{
name: "Missing protocol",
input: "example.com",
expected: false
},
{
name: "Fake protocol",
input: "67://example.com",
expected: false
},
{
name: "Space in domain name",
input: "https://example .com",
expected: false
},
{
name: "Space in domain",
input: "https://exam ple.com/path",
expected: false
},
{
name: "No colon after protocol",
input: "ftp//example.com",
expected: false
},
{
name: "Missing one slash",
input: "https:/example.com",
expected: true
},
{
name: "Extra slash",
input: "https:////////////////example.com",
expected: true
},
{
name: "Non-numeric port",
input: "https://exam[ple].com",
expected: false
},
{
name: "Unencoded spaces in path",
input: "https://tungtungtung.com/path with spaces",
expected: true
}
]

describe("TS function IsValidURL", () => {
tests.forEach((config) => {
it(config.name, () => {
expect(isValidURL(config.input)).toBe(config.expected)
})
})
})
8 changes: 8 additions & 0 deletions components/HomepageBanner/isValidURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function isValidURL(url: string): boolean {
try {
new URL(url)
return true
} catch {
return false
}
}
15 changes: 13 additions & 2 deletions components/LinkBar/LinkBar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,23 @@ div#container {
width: 100%;
height: 64px;
display: flex;
justify-content: flex-end;
align-items: center;
font-family: var(--font-tronica-mono);
}

div#container.previous {
div#container.alignment-start {
justify-content: left;
}

div#container.alignment-end {
justify-content: right;
}

div#container.text-first {
flex-direction: row;
}

div#container.arrow-first {
flex-direction: row-reverse;
}

Expand Down
25 changes: 19 additions & 6 deletions components/LinkBar/LinkBar.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import Link, { type LinkProps } from 'next/link'
import Link, { type LinkProps } from "next/link"

import styles from './LinkBar.module.css'
import styles from "./LinkBar.module.css"

interface LinkBarProps extends LinkProps {
children: React.ReactNode
type?: 'previous' | 'next'
arrowDirection?: "back" | "forward"
alignment?: "start" | "end"
order?: "text-first" | "arrow-first"
}

export function LinkBar({ children, type = 'next', ...props }: LinkBarProps) {
export function LinkBar({
children,
arrowDirection = "forward",
alignment = "end",
order = "text-first",
...props
}: LinkBarProps) {
return (
<div id={styles["link-container"]}>
<Link {...props}>
<div
id={styles.container}
className={type === 'next' ? styles.next : styles.previous}
className={`
${order === "text-first" ? styles["text-first"] : styles["arrow-first"]}
${alignment === "start" ? styles["alignment-start"] : styles["alignment-end"]}
`}
>
<div id={styles.bar} />
<div>{children}</div>
<div id={styles.arrow}>{type === 'next' ? '>' : '<'}</div>
<div id={styles.arrow}>
{arrowDirection === "forward" ? ">" : "<"}
</div>
</div>
</Link>
</div>
Expand Down
14 changes: 12 additions & 2 deletions components/PostWrapper/PostWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,25 @@ function AuthorsAndContent({ content, frontmatter }: PostProps) {
{frontmatter.previous && (
<>
<Spacer size={32} />
<LinkBar type="previous" href={frontmatter.previous.link}>
<LinkBar
arrowDirection="back"
alignment="start"
order="arrow-first"
href={frontmatter.previous.link}
>
{frontmatter.previous.text}
</LinkBar>
</>
)}
{frontmatter.next && (
<>
<Spacer size={32} />
<LinkBar type="next" href={frontmatter.next.link}>
<LinkBar
arrowDirection="forward"
alignment="end"
order="text-first"
href={frontmatter.next.link}
>
{frontmatter.next.text}
</LinkBar>
</>
Expand Down
12 changes: 12 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
dir: './',
})

const config = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}

export default createJestConfig(config)
1 change: 1 addition & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom'
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
"prebuild": "pnpm run lint",
"build": "next build --turbopack",
"start": "next start",
"migrate": "dotenv -e .env.local prisma migrate dev"
"migrate": "dotenv -e .env.local prisma migrate dev",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"dependencies": {
"@prisma/client": "6.5.0",
Expand All @@ -43,12 +46,17 @@
"use-sound": "5.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/howler": "2.2.12",
"@types/jest": "^29.5.0",
"@types/node": "25.0.0",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"eslint": "9.39.1",
"eslint-config-next": "16.0.10",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "3.7.4",
"prisma": "6.5.0",
"typescript": "5.9.3"
Expand Down
Loading