Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 33 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
# UMSA website
# selamat datang! :malaysia:

React, TypeScript, Express and MongoDB.
this is the repo for wdcc's project umsa 2026 !!

## Setup
## a bit about the project

Use Node 22 and pnpm. Copy `server/.env.example` to `server/.env` and fill in the values.
this is a full-stack react, node, express & mongodb, written in typescript with a vite front end & tailwindcss for ui styling :)

```sh
pnpm install
pnpm dev
```
## getting started

Vite prints the frontend URL. The local API runs on port 5051.
### prerequisites

```sh
pnpm build
pnpm lint
```
- install git [here](https://git-scm.com/downloads)
- install node [here](https://nodejs.org/en/download)

### running the project

## Content management
- git clone
- cd umsa
- npm install -g pnpm
- pnpm install
- pnpm dev

Open `/admin` directly to sign in and edit homepage text, team members, events,
sponsors, gallery photos and FAQs. Image uploads use Cloudinary.
### other scripts

Contact sends email through Resend to `umsa@projects.wdcc.co.nz`.
Set `RESEND_API_KEY` and `CONTACT_FROM_EMAIL` using a verified sender.
Membership is managed through the linked Google Form.
to lint: pnpm lint
to build: pnpm build

See [setup](docs/01-setup.md), [API reference](docs/05-api-reference.md)
and [contributors](docs/contributors.md).
## team members

| name | role | fuel | quote |
| ------------------------ | --------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| hannah santiago | project manager | | |
| alanna santoso | tech lead | jasmine milk tea 70 sugar less ice | _"hi!"_ |
| koutaro yumimi | tech overlord | ethiopian natural | _"studying is doubting your own potential"_ |
| matthew ren | project manager | | |
| nicholars garcia-scholtz | tech exec | | |
| amy yan | designer | | _"meow"_ |
| andrew qiu | designer | charlies tea osmanthus oolong 30 sugar no ice | _"i am just a designer~"_ |
| terrence wu | developer | oolong milk tea 30 sugar little ice | _"hardstuck bronze in league for 6 years (BUT IM GETTING OUT THIS YEAR IM BRONZE 3 44LP NOW)"_ |
| alex lee | developer | pink monster | "DAH" -pinkpanteress |
| eric zheng | developer | dirty matcha latte | "disaster-" |
| jacky chen | developer | purple rice yoghurt | _"i make a lot of typos"_ |
| pruna lee | developer | coconut redbull | _"i love food"_ |
73 changes: 0 additions & 73 deletions client/README.md

This file was deleted.

222 changes: 2 additions & 220 deletions client/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,228 +1,10 @@
import { MEMBERSHIP_FORM_URL } from "../lib/siteLinks";
import NewNavbar from "./NewNavbar";
import Marquee from "./Marquee";
import { useEffect, useState } from "react";
import { Link, NavLink, useLocation } from "react-router-dom";
import { AnimatePresence, motion } from "motion/react";
import { FiChevronDown, FiMenu, FiX } from "react-icons/fi";
import logo from "../assets/brand/umsa-logo.png";

const ABOUT_MENU = [
{ to: "/about", label: "About Us" },
{ to: "/contact", label: "Contact" },
{ to: "/team", label: "Our Team" },
];

const LINKS = [
{ to: "/", label: "Home", end: true },
{ to: "/events", label: "Events" },
{ to: "/sponsors", label: "Sponsors" },
{ to: "/gallery", label: "Photos" },
];

const LINK_BASE =
"font-heading text-[13px] tracking-[0.12em] uppercase transition-colors";

function AboutMenu() {
const [open, setOpen] = useState(false);
const { pathname } = useLocation();
const active = ABOUT_MENU.some((item) => pathname === item.to);

useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [open]);

return (
<div
className="relative"
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onFocus={() => setOpen(true)}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) setOpen(false);
}}
>
<NavLink
to="/about"
onClick={() => setOpen(false)}
aria-expanded={open}
aria-haspopup="menu"
className={`relative flex items-center gap-1 ${LINK_BASE} ${
active
? "text-accent2-primary"
: "text-accent1-primary hover:text-accent2-primary"
}`}
>
About
<FiChevronDown
size={13}
className={`transition-transform ${open ? "rotate-180" : ""}`}
/>
{active && (
<motion.span
layoutId="nav-underline"
className="absolute -bottom-1.5 left-0 h-0.5 w-full bg-accent2-primary"
/>
)}
</NavLink>

<AnimatePresence>
{open && (
<motion.div
role="menu"
initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
transition={{ duration: 0.16, ease: "easeOut" }}
className="absolute top-full left-1/2 z-50 w-44 -translate-x-1/2 rounded-lg
bg-background-secondary py-2 shadow-[0_10px_24px_rgba(0,0,0,0.14)]"
style={{ marginTop: 8 }}
>
{ABOUT_MENU.map((item) => (
<NavLink
key={item.to}
to={item.to}
role="menuitem"
onClick={() => setOpen(false)}
className={({ isActive }) =>
`block px-5 py-2.5 ${LINK_BASE} ${
isActive
? "text-accent2-primary"
: "text-accent1-primary hover:text-accent2-primary"
}`
}
>
{item.label}
</NavLink>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
}

export default function Navbar() {
const [open, setOpen] = useState(false);

const linkClass = ({ isActive }: { isActive: boolean }) =>
`relative ${LINK_BASE} ${
isActive
? "text-accent2-primary"
: "text-accent1-primary hover:text-accent2-primary"
}`;

return (
<header className="sticky top-0 z-50">
<nav className="border-b-2 border-accent1-primary bg-background-primary">
<div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-5 py-2">
<Link to="/" aria-label="UMSA home" className="shrink-0">
<img src={logo} alt="UMSA" className="h-10 w-10 object-contain" />
</Link>

<div className="hidden items-center gap-7 lg:flex">
<NavLink to="/" end className={linkClass}>
{({ isActive }) => (
<>
Home
{isActive && (
<motion.span
layoutId="nav-underline"
className="absolute -bottom-1.5 left-0 h-0.5 w-full bg-accent2-primary"
/>
)}
</>
)}
</NavLink>

<AboutMenu />

{LINKS.filter((l) => l.to !== "/").map((link) => (
<NavLink key={link.to} to={link.to} className={linkClass}>
{({ isActive }) => (
<>
{link.label}
{isActive && (
<motion.span
layoutId="nav-underline"
className="absolute -bottom-1.5 left-0 h-0.5 w-full bg-accent2-primary"
/>
)}
</>
)}
</NavLink>
))}
</div>

<div className="flex items-center gap-3">
<a
href={MEMBERSHIP_FORM_URL}
target="_blank"
rel="noopener noreferrer"
className="hidden rounded-full border-2 border-red-600 bg-accent2-primary px-5 py-1.5
font-heading text-xs tracking-[0.12em] text-white uppercase
shadow-[2px_2px_0_0_var(--color-red-600)] transition-transform
hover:translate-x-[1px] hover:translate-y-[1px] hover:shadow-none sm:block"
>
Sign up
</a>
<button
onClick={() => setOpen((v) => !v)}
className="text-accent1-primary lg:hidden"
aria-label={open ? "Close menu" : "Open menu"}
>
{open ? <FiX size={22} /> : <FiMenu size={22} />}
</button>
</div>
</div>

<AnimatePresence>
{open && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
className="overflow-hidden lg:hidden"
>
<div className="flex flex-col gap-1 px-5 pb-4">
<NavLink
to="/"
end
onClick={() => setOpen(false)}
className={`py-2 ${LINK_BASE} text-accent1-primary`}
>
Home
</NavLink>
{ABOUT_MENU.concat(LINKS.filter((l) => l.to !== "/")).map(
(link) => (
<NavLink
key={link.to}
to={link.to}
onClick={() => setOpen(false)}
className={`py-2 ${LINK_BASE} text-accent1-primary`}
>
{link.label}
</NavLink>
),
)}
<a
href={MEMBERSHIP_FORM_URL}
target="_blank"
rel="noopener noreferrer"
onClick={() => setOpen(false)}
className={`py-2 ${LINK_BASE} text-accent2-primary sm:hidden`}
>
Sign up
</a>
</div>
</motion.div>
)}
</AnimatePresence>
</nav>

<NewNavbar />
<Marquee />
</header>
);
Expand Down
Loading
Loading