diff --git a/.gitignore b/.gitignore index 5a6f8656..d6aeb5e4 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ yarn-error.log* # vscode .vscode .env*.local +.env.* \ No newline at end of file diff --git a/apps/infrastructure-migrator/driver.ts b/apps/infrastructure-migrator/driver.ts deleted file mode 100644 index 60eca43d..00000000 --- a/apps/infrastructure-migrator/driver.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { sql } from "@vercel/postgres"; -import { drizzle as pgDrizzle } from "drizzle-orm/vercel-postgres"; -import { drizzle } from "drizzle-orm/libsql"; -import * as pgSchema from "./schema"; -import { createClient } from "@libsql/client"; -export * from "drizzle-orm"; -import dotenv from "dotenv"; -import * as schema from "db/schema"; -import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; -import c, { staticUploads } from "config"; -import { eq } from "drizzle-orm"; - -dotenv.config({ - path: "../../.env", -}); - -export const S3 = new S3Client({ - region: "auto", - endpoint: `https://${process.env.CLOUDFLARE_ACCOUNT_ID!}.r2.cloudflarestorage.com`, - credentials: { - accessKeyId: process.env.R2_ACCESS_KEY_ID!, - secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!, - }, -}); - -const dbPostgres = pgDrizzle(sql, { schema: pgSchema }); -const turso = createClient({ - url: process.env.TURSO_DATABASE_URL!, - authToken: process.env.TURSO_AUTH_TOKEN, -}); - -const db = drizzle(turso, { schema }); - -const allUsersPromise = dbPostgres.query.users.findMany(); -const allDataPromise = dbPostgres.query.data.findMany(); -const allEventsPromise = dbPostgres.query.events.findMany(); -const allCheckinsPromise = dbPostgres.query.checkins.findMany(); -const allEventCategoriesPromise = dbPostgres.query.eventCategories.findMany(); -const allSemestersPromise = dbPostgres.query.semesters.findMany(); -const allEventsToCategoriesPromise = - dbPostgres.query.eventsToCategories.findMany(); - -async function migratePostgresSqLite(runDB = false) { - console.log("Starting Migration 🚀"); - console.log("Fetching Postgres Data 🐘"); - const [ - allUsers, - allData, - allEvents, - allCheckins, - allEventCategories, - allSemesters, - allEventsToCategories, - ] = await Promise.all([ - allUsersPromise, - allDataPromise, - allEventsPromise, - allCheckinsPromise, - allEventCategoriesPromise, - allSemestersPromise, - allEventsToCategoriesPromise, - ]); - // console.log("Postgres data fetched 📦"); - - // console.log("Migrating Users 👥"); - // await db.insert(schema.users).values(allUsers); - // console.log("Migrated Users ✅"); - - // console.log("Migrating User Data 📊"); - // await db.insert(schema.data).values(allData); - // console.log("Migrated User Data ✅"); - - // console.log("Migrating Event Categories 💬"); - // await db.insert(schema.eventCategories).values(allEventCategories); - // console.log("Migrated Event Categories ✅"); - - // console.log("Migrating Semesters 📚"); - // await db.insert(schema.semesters).values(allSemesters); - // console.log("Migrated Semesters ✅"); - - // console.log("Migrating Events 📝"); - // await db.insert(schema.events).values( - // allEvents.map((event) => ({ - // ...event, - // updatedAt: event.updatedAt.getTime(), - // })), - // ); - // console.log("Migrated Events ✅"); - - // console.log("Migrating Events to Categories 📝"); - // await db.insert(schema.eventsToCategories).values(allEventsToCategories); - // console.log("Migrated Events to Categories ✅"); - - // console.log("Migrating Checkins 📝"); - // await db.insert(schema.checkins).values(allCheckins); - // console.log("Migrated Checkins ✅"); - - console.log("Migrating Vercel Blob Files To R2"); - - const resumeData = await db.query.data.findMany({ - columns: { resume: true, userID: true }, - }); - - for (let resumeEntry of resumeData) { - const { resume: resumeUrlAsString, userID } = resumeEntry; - if ( - resumeUrlAsString === null || - !resumeUrlAsString.length || - resumeUrlAsString.startsWith("/api") - ) - continue; - console.log("Migrating resume for user", userID, resumeUrlAsString); - const resumeUrl = new URL(resumeUrlAsString); - const resumeFetchResponse = await fetch(resumeUrl); - - if (!resumeFetchResponse.ok) { - console.log("resume fetch failed"); - continue; - } - const resumeBlob = await resumeFetchResponse.blob(); - - let key = decodeURIComponent(resumeUrl.pathname); - // if the first character is a slash, remove it - if (key.charAt(0) === "/") { - key = key.slice(1); - } - - const buffer = await resumeBlob.arrayBuffer(); - - const cmd = new PutObjectCommand({ - Key: key, - Bucket: staticUploads.bucketName, - ContentType: "application/pdf", - ///@ts-expect-error - Body: buffer, - }); - - await S3.send(cmd); - - // New url to correspond to an api route - const newResumeUrl = `/api/upload/resume/view?key=${key}`; - - await db - .update(schema.data) - .set({ resume: newResumeUrl.toString() }) - .where(eq(schema.data.userID, userID)); - } - - console.log("Migrated Vercel Blob Files To R2"); - - return process.exit(0); -} - -migratePostgresSqLite().catch((e) => { - console.error(e); - process.exit(1); -}); diff --git a/apps/infrastructure-migrator/package.json b/apps/infrastructure-migrator/package.json deleted file mode 100644 index 9ddd440d..00000000 --- a/apps/infrastructure-migrator/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "infrastructure-migrator", - "version": "1.0.0", - "description": "Helper to migrate from postgres to sqlite", - "main": "driver.ts", - "scripts": { - "migrate": "node -r esbuild-register driver.ts" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@aws-sdk/client-s3": "^3.758.0", - "@libsql/client": "^0.14.0", - "@vercel/postgres": "^0.10.0", - "config": "workspace:*", - "db": "workspace:*", - "dotenv": "^16.3.1", - "drizzle-orm": "^0.38.4", - "esbuild-register": "^3.4.2", - "web": "workspace:*" - }, - "devDependencies": { - "drizzle-kit": "^0.30.5" - } -} diff --git a/apps/infrastructure-migrator/schema.ts b/apps/infrastructure-migrator/schema.ts deleted file mode 100644 index 239a54fb..00000000 --- a/apps/infrastructure-migrator/schema.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { - text, - varchar, - boolean, - timestamp, - integer, - pgEnum, - primaryKey, - pgTable, - serial, - date, -} from "drizzle-orm/pg-core"; -import { relations } from "drizzle-orm"; -import c from "config"; - -// pieces of this schema need to be revamped as a lot of them are lazily set as text instead of varchar with a hard limit -/* USERS */ - -export const userRoles = pgEnum("user_roles", c.memberRoles); - -export const users = pgTable("users", { - userID: serial("user_id").primaryKey(), - clerkID: varchar("clerk_id", { length: 255 }).unique(), - firstName: varchar("first_name", { length: 255 }).notNull(), - lastName: varchar("last_name", { length: 255 }).notNull(), - email: varchar({ length: 255 }).notNull().unique(), - role: userRoles().default("member").notNull(), - joinDate: timestamp("join_date").defaultNow().notNull(), - universityID: varchar("university_id", { length: 255 }).notNull().unique(), -}); - -export const usersRelations = relations(users, ({ one, many }) => ({ - data: one(data, { fields: [users.userID], references: [data.userID] }), - checkins: many(checkins), -})); - -export const data = pgTable("data", { - userID: integer("user_id") - .primaryKey() - .references(() => users.userID, { onDelete: "cascade" }), - major: varchar({ length: 255 }).notNull(), - classification: varchar({ length: 255 }).notNull(), - graduationMonth: integer("graduation_month").notNull(), - graduationYear: integer("graduation_year").notNull(), - birthday: timestamp("birthday"), - gender: varchar({ length: 255 }).array().notNull(), - ethnicity: varchar({ length: 255 }).array().notNull(), - resume: varchar({ length: 255 }), - shirtType: varchar("shirt_type", { length: 255 }).notNull(), - shirtSize: varchar("shirt_size", { length: 255 }).notNull(), - interestedEventTypes: varchar("interested_event_types", { length: 255 }) - .array() - .notNull(), -}); - -/* EVENTS */ -export const eventCategories = pgTable("event_categories", { - id: varchar("id", { length: 8 }).primaryKey(), - name: varchar({ length: 255 }).notNull().unique(), - color: varchar({ length: 255 }).notNull(), -}); - -export const eventCategoriesRelations = relations( - eventCategories, - ({ many }) => ({ - eventsToCategories: many(eventsToCategories), - }), -); - -export const events = pgTable("events", { - id: varchar({ length: 100 }).primaryKey(), - name: varchar({ length: 100 }).notNull(), - description: text("description").notNull(), - thumbnailUrl: varchar("thumbnail_url", { length: 255 }) - .default(c.thumbnails.default) - .notNull(), - start: timestamp("start").notNull(), - end: timestamp("end").notNull(), - checkinStart: timestamp("checkin_start").notNull(), - checkinEnd: timestamp("checkin_end").notNull(), - location: varchar({ length: 255 }).notNull(), - isUserCheckinable: boolean("is_user_checkinable").notNull().default(true), - isHidden: boolean("is_hidden").notNull().default(false), - points: integer("points").notNull().default(1), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at").defaultNow().notNull(), - semesterID: integer("semester_id").references(() => semesters.semesterID, { - onDelete: "set null", - }), -}); - -export const eventsRelations = relations(events, ({ many, one }) => ({ - eventsToCategories: many(eventsToCategories), - checkins: many(checkins), - semester: one(semesters, { - fields: [events.semesterID], - references: [semesters.semesterID], - }), -})); - -export const eventsToCategories = pgTable("events_to_categories", { - eventID: varchar("event_id", { length: 100 }) - .notNull() - .references(() => events.id, { onDelete: "cascade" }), - categoryID: varchar("category_id", { length: 100 }) - .notNull() - .references(() => eventCategories.id, { onDelete: "cascade" }), -}); - -export const eventsToCategoriesRelations = relations( - eventsToCategories, - ({ one }) => ({ - category: one(eventCategories, { - fields: [eventsToCategories.categoryID], - references: [eventCategories.id], - }), - event: one(events, { - fields: [eventsToCategories.eventID], - references: [events.id], - }), - }), -); - -export const checkins = pgTable( - "checkins", - { - eventID: varchar("event_id", { length: 100 }) - .references(() => events.id, { onDelete: "cascade" }) - .notNull(), - userID: integer("user_id") - .references(() => users.userID, { onDelete: "cascade" }) - .notNull(), - time: timestamp("time").defaultNow().notNull(), - rating: integer("rating"), - adminID: integer("admin_id"), - feedback: varchar({ length: 2000 }), - }, - (table) => { - return [ - { - id: primaryKey({ columns: [table.eventID, table.userID] }), - }, - ]; - }, -); - -export const checkinRelations = relations(checkins, ({ one }) => ({ - author: one(users, { - fields: [checkins.userID], - references: [users.userID], - }), - event: one(events, { - fields: [checkins.eventID], - references: [events.id], - }), -})); - -export const semesters = pgTable("semesters", { - semesterID: serial("semester_id").primaryKey(), - name: varchar("name", { length: 255 }).notNull().unique(), - startDate: timestamp("start_date").notNull(), - endDate: timestamp("end_date").notNull(), - pointsRequired: integer("points_required").notNull(), - isCurrent: boolean("is_current").notNull().default(false), -}); - -export const semestersRelations = relations(semesters, ({ many }) => ({ - events: many(events), -})); diff --git a/apps/mover/package.json b/apps/mover/package.json deleted file mode 100644 index 18ef7551..00000000 --- a/apps/mover/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "mover", - "version": "1.0.0", - "description": "", - "main": "index.ts", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "move": "bun run --env-file=../../.env ./script.ts" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "bun-promptx": "^0.2.0", - "db": "workspace:*", - "nanoid": "^5.0.6", - "web": "workspace:^", - "zod": "^3.23.8" - } -} diff --git a/apps/mover/script.ts b/apps/mover/script.ts deleted file mode 100644 index c7f84bae..00000000 --- a/apps/mover/script.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { createPrompt } from "bun-promptx"; -import { z } from "zod"; -import { db } from "db"; -import { data, users } from "db/schema"; - -async function move() { - const url = createPrompt("Enter Get All Users URL: "); - if (url.error || !url.value) { - console.error(url.error); - return process.exit(1); - } - - const username = createPrompt("Enter admin username: "); - if (username.error || !username.value) { - console.error(username.error); - return process.exit(1); - } - const password = createPrompt("Enter password: ", { echoMode: "password" }); - if (password.error || !password.value) { - console.error(password.error); - return process.exit(1); - } - - const req = await fetch(url.value, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - apikey: username.value + password.value, - }), - }); - - const res = await req.json(); - - const allMembers = res.allMembers; - - const memberValidator = z.object({ - id: z.string(), - email: z.string().email(), - name: z.string(), - joinDate: z.string().pipe(z.coerce.date()), - data: z.object({ - memberID: z.string(), - major: z.string(), - classification: z.string(), - graduationDate: z.string(), - shirtIsUnisex: z.boolean(), - shirtSize: z.string(), - Birthday: z.string().optional().nullable(), - isInACM: z.boolean(), - isInACMW: z.boolean(), - isInRC: z.boolean(), - isInICPC: z.boolean(), - isInCIC: z.boolean(), - isBlackorAA: z.boolean(), - isAsian: z.boolean(), - isNAorAN: z.boolean(), - isNHorPI: z.boolean(), - isHispanicorLatinx: z.boolean(), - isWhite: z.boolean(), - isMale: z.boolean(), - isFemale: z.boolean(), - isNonBinary: z.boolean(), - isTransgender: z.boolean(), - isIntersex: z.boolean(), - doesNotIdentify: z.boolean(), - otherIdentity: z.string().optional().nullable(), - address: z.string().optional().nullable(), - }), - extendedMemberData: z.string(), - lastSeen: z.string().optional().nullable(), - }); - - for (const member of allMembers) { - const validatedMember = memberValidator.safeParse(member); - if (validatedMember.success) { - console.log( - "Processing member: ", - validatedMember.data.email + " | " + validatedMember.data.name, - ); - const m = validatedMember.data; - await db.transaction(async (tx) => { - const newUserRecordResult = await tx - .insert(users) - .values({ - email: m.email, - joinDate: m.joinDate, - universityID: m.id, - firstName: m.name - .split(" ") - .slice(0, -1) - .join(" ") - .trim(), - lastName: m.name - .split(" ") - [m.name.split(" ").length - 1].trim(), - }) - .returning({ id: users.userID }); - if ( - newUserRecordResult.length !== 1 || - !newUserRecordResult[0] - ) { - await tx.rollback(); - console.error( - `User creation failed for user (${m.id} / ${m.email})`, - ); - return; - } - const newUserRecord = newUserRecordResult[0]; - const gender = [ - m.data.isMale ? "Male" : null, - m.data.isFemale ? "Female" : null, - m.data.isNonBinary ? "Non-binary" : null, - m.data.isTransgender ? "Transgender" : null, - m.data.isIntersex ? "Intersex" : null, - m.data.doesNotIdentify ? "Prefer not to say" : null, - m.data.otherIdentity ? "Other" : null, - ].filter((x) => x !== null) as string[]; - - const ethnicity = [ - m.data.isBlackorAA ? "Black or African American" : null, - m.data.isAsian ? "Asian" : null, - m.data.isNAorAN ? "American Indian or Alaska Native" : null, - m.data.isNHorPI - ? "Native Hawaiian or Other Pacific Islander" - : null, - m.data.isHispanicorLatinx ? "Hispanic or Latino" : null, - m.data.isWhite ? "White" : null, - ].filter((x) => x !== null) as string[]; - - let gradYear = 0; - let gradMonth = 0; - - if (m.data.graduationDate.includes("-")) { - gradYear = parseInt(m.data.graduationDate.split("-")[0]); - gradMonth = parseInt(m.data.graduationDate.split("-")[1]); - } else if (m.data.graduationDate.includes("/")) { - gradYear = parseInt(m.data.graduationDate.split("/")[0]); - gradMonth = parseInt(m.data.graduationDate.split("/")[1]); - } - - await tx.insert(data).values({ - userID: newUserRecord.id, - major: m.data.major, - - classification: m.data.classification, - graduationMonth: gradMonth, - graduationYear: gradYear, - birthday: m.data.Birthday - ? new Date(m.data.Birthday) - : null, - gender: gender, - ethnicity: ethnicity, - resume: null, - shirtType: m.data.shirtIsUnisex ? "Unisex" : "Women's", - shirtSize: m.data.shirtSize, - interestedEventTypes: [], - }); - }); - } else { - console.error("Member is invalid: ", member.id); - console.log("due to error ", validatedMember.error); - } - } - - return process.exit(0); -} - -move(); diff --git a/apps/web/components.json b/apps/web/components.json index ce76ad3f..d249920f 100644 --- a/apps/web/components.json +++ b/apps/web/components.json @@ -1,6 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", + "style": "new-york", "rsc": true, "tsx": true, "tailwind": { diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 40c3d680..1b3be084 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index 7790ced1..c903e2fb 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "pnpm with-env next dev", + "dev": "pnpm with-env next dev --turbopack", "build": "pnpm with-env next build", "start": "pnpm with-env next start", "lint": "next lint", @@ -21,18 +21,31 @@ "@internationalized/date": "^3.5.4", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-alert-dialog": "^1.0.5", + "@radix-ui/react-aspect-ratio": "^1.1.2", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-context-menu": "^2.2.6", "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-hover-card": "^1.1.6", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-menubar": "^1.1.6", + "@radix-ui/react-navigation-menu": "^1.2.5", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-progress": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-scroll-area": "^1.0.5", - "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-slider": "^1.2.3", + "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-toggle": "^1.1.2", + "@radix-ui/react-toggle-group": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.17.3", @@ -40,31 +53,41 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "colorthief": "^2.6.0", "config": "workspace:*", "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", - "db": "workspace:*", "dotenv": "^16.3.1", "dotenv-cli": "^7.4.1", + "embla-carousel-react": "^8.5.2", + "fast-fuzzy": "^1.12.0", + "input-otp": "^1.4.2", "jiti": "^1.21.0", "lucide-react": "^0.377.0", + "mapbox-gl": "^3.11.0", + "motion": "^12.6.2", "nanoid": "^5.0.6", - "next": "14.2.25", + "next": "15.2.4", "next-safe-action": "^7.10.2", "next-themes": "^0.3.0", - "react": "^18", + "nuqs": "^2.4.1", + "react": "19.1.0", "react-aria": "^3.33.1", "react-colorful": "^5.6.1", "react-day-picker": "^8.10.1", - "react-dom": "^18", + "react-dom": "19.1.0", "react-hook-form": "^7.51.3", + "react-map-gl": "^8.0.2", "react-qr-code": "^2.0.15", + "react-resizable-panels": "^2.1.7", "react-stately": "^3.31.1", "recharts": "^2.12.7", "sonner": "^1.4.41", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", + "tailwindcss-motion": "^1.1.0", "use-debounce": "^10.0.1", + "vaul": "^1.1.2", "zod": "^3.23.8" }, "devDependencies": { @@ -72,11 +95,17 @@ "@cloudflare/workers-types": "^4.20250313.0", "@tailwindcss/line-clamp": "^0.4.4", "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", + "@types/react": "19.1.0", + "@types/react-dom": "19.1.1", "autoprefixer": "^10.0.1", "postcss": "^8", - "tailwindcss": "^3.3.0", + "tailwindcss": "^3.4.16", "typescript": "^5" + }, + "pnpm": { + "overrides": { + "@types/react": "19.1.0", + "@types/react-dom": "19.1.1" + } } } diff --git a/apps/web/public/img/landing/noise.png b/apps/web/public/img/landing/noise.png new file mode 100644 index 00000000..b86c36c8 Binary files /dev/null and b/apps/web/public/img/landing/noise.png differ diff --git a/apps/web/public/img/logos/acm-blue.png b/apps/web/public/img/logos/acm-blue.png new file mode 100644 index 00000000..a83db3df Binary files /dev/null and b/apps/web/public/img/logos/acm-blue.png differ diff --git a/apps/web/public/img/logos/acm-white.png b/apps/web/public/img/logos/acm-white.png new file mode 100644 index 00000000..5b1555d2 Binary files /dev/null and b/apps/web/public/img/logos/acm-white.png differ diff --git a/apps/web/public/img/logos/discord_logo.webp b/apps/web/public/img/logos/discord_logo.webp new file mode 100644 index 00000000..5860cda2 Binary files /dev/null and b/apps/web/public/img/logos/discord_logo.webp differ diff --git a/apps/web/public/img/logos/hackathons/cq-blue.png b/apps/web/public/img/logos/hackathons/cq-blue.png new file mode 100644 index 00000000..a4cd1be7 Binary files /dev/null and b/apps/web/public/img/logos/hackathons/cq-blue.png differ diff --git a/apps/web/public/img/logos/hackathons/rd-blue.png b/apps/web/public/img/logos/hackathons/rd-blue.png new file mode 100644 index 00000000..0a265ced Binary files /dev/null and b/apps/web/public/img/logos/hackathons/rd-blue.png differ diff --git a/apps/web/public/img/logos/hackathons/rh-blue.png b/apps/web/public/img/logos/hackathons/rh-blue.png new file mode 100644 index 00000000..4620b1dd Binary files /dev/null and b/apps/web/public/img/logos/hackathons/rh-blue.png differ diff --git a/apps/web/public/img/logos/sponsor/dell.png b/apps/web/public/img/logos/sponsor/dell.png new file mode 100644 index 00000000..01fe0881 Binary files /dev/null and b/apps/web/public/img/logos/sponsor/dell.png differ diff --git a/apps/web/public/img/logos/sponsor/heb.png b/apps/web/public/img/logos/sponsor/heb.png new file mode 100644 index 00000000..0d8bdd5f Binary files /dev/null and b/apps/web/public/img/logos/sponsor/heb.png differ diff --git a/apps/web/public/img/logos/sponsor/utsacs.png b/apps/web/public/img/logos/sponsor/utsacs.png new file mode 100644 index 00000000..04672b3e Binary files /dev/null and b/apps/web/public/img/logos/sponsor/utsacs.png differ diff --git a/apps/web/public/img/logos/suborgs/acmw-blue.png b/apps/web/public/img/logos/suborgs/acmw-blue.png new file mode 100644 index 00000000..30f5c64e Binary files /dev/null and b/apps/web/public/img/logos/suborgs/acmw-blue.png differ diff --git a/apps/web/public/img/logos/suborgs/cnc-blue.png b/apps/web/public/img/logos/suborgs/cnc-blue.png new file mode 100644 index 00000000..91e90938 Binary files /dev/null and b/apps/web/public/img/logos/suborgs/cnc-blue.png differ diff --git a/apps/web/public/img/logos/suborgs/icpc-blue.png b/apps/web/public/img/logos/suborgs/icpc-blue.png new file mode 100644 index 00000000..bfdbfc1b Binary files /dev/null and b/apps/web/public/img/logos/suborgs/icpc-blue.png differ diff --git a/apps/web/public/img/logos/suborgs/icpc-color.png b/apps/web/public/img/logos/suborgs/icpc-color.png new file mode 100644 index 00000000..8ee44c6f Binary files /dev/null and b/apps/web/public/img/logos/suborgs/icpc-color.png differ diff --git a/apps/web/public/img/logos/suborgs/rc-blue.png b/apps/web/public/img/logos/suborgs/rc-blue.png new file mode 100644 index 00000000..dca1e3c1 Binary files /dev/null and b/apps/web/public/img/logos/suborgs/rc-blue.png differ diff --git a/apps/web/public/img/officer_photos/acm_general/brooke-lane.jpg b/apps/web/public/img/officer_photos/acm_general/brooke-lane.jpg new file mode 100755 index 00000000..cde96cf7 Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/brooke-lane.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/eric-lee.jpg b/apps/web/public/img/officer_photos/acm_general/eric-lee.jpg new file mode 100755 index 00000000..a859da50 Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/eric-lee.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/james-haddock.jpg b/apps/web/public/img/officer_photos/acm_general/james-haddock.jpg new file mode 100755 index 00000000..4076bc1b Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/james-haddock.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/jonathan-dekoning.png b/apps/web/public/img/officer_photos/acm_general/jonathan-dekoning.png new file mode 100755 index 00000000..f066b26c Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/jonathan-dekoning.png differ diff --git a/apps/web/public/img/officer_photos/acm_general/josh-silva.jpg b/apps/web/public/img/officer_photos/acm_general/josh-silva.jpg new file mode 100755 index 00000000..111b001e Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/josh-silva.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/juleah-cephus.jpg b/apps/web/public/img/officer_photos/acm_general/juleah-cephus.jpg new file mode 100755 index 00000000..674661cf Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/juleah-cephus.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/lyly-vo.jpg b/apps/web/public/img/officer_photos/acm_general/lyly-vo.jpg new file mode 100755 index 00000000..c07985e6 Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/lyly-vo.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/miguel-oseguera.jpg b/apps/web/public/img/officer_photos/acm_general/miguel-oseguera.jpg new file mode 100755 index 00000000..6bd90574 Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/miguel-oseguera.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_general/vivian-tran.jpg b/apps/web/public/img/officer_photos/acm_general/vivian-tran.jpg new file mode 100755 index 00000000..f61f5487 Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_general/vivian-tran.jpg differ diff --git a/apps/web/public/img/officer_photos/acm_w/reese-sylvester.jpg b/apps/web/public/img/officer_photos/acm_w/reese-sylvester.jpg new file mode 100755 index 00000000..6d3df0f0 Binary files /dev/null and b/apps/web/public/img/officer_photos/acm_w/reese-sylvester.jpg differ diff --git a/apps/web/public/img/officer_photos/code_quantum/rahul-paul.jpg b/apps/web/public/img/officer_photos/code_quantum/rahul-paul.jpg new file mode 100755 index 00000000..60e09be9 Binary files /dev/null and b/apps/web/public/img/officer_photos/code_quantum/rahul-paul.jpg differ diff --git a/apps/web/public/img/officer_photos/coding_in_color/judith-infante.jpg b/apps/web/public/img/officer_photos/coding_in_color/judith-infante.jpg new file mode 100755 index 00000000..7f0adc4f Binary files /dev/null and b/apps/web/public/img/officer_photos/coding_in_color/judith-infante.jpg differ diff --git a/apps/web/public/img/officer_photos/rowdy_creators/yash-verma.png b/apps/web/public/img/officer_photos/rowdy_creators/yash-verma.png new file mode 100755 index 00000000..50a50a1f Binary files /dev/null and b/apps/web/public/img/officer_photos/rowdy_creators/yash-verma.png differ diff --git a/apps/web/public/img/officer_photos/rowdy_cybercon/samantha-vivanco.jpg b/apps/web/public/img/officer_photos/rowdy_cybercon/samantha-vivanco.jpg new file mode 100755 index 00000000..d16cdbe5 Binary files /dev/null and b/apps/web/public/img/officer_photos/rowdy_cybercon/samantha-vivanco.jpg differ diff --git a/apps/web/public/img/officer_photos/rowdy_hacks/zander-brysch.jpg b/apps/web/public/img/officer_photos/rowdy_hacks/zander-brysch.jpg new file mode 100755 index 00000000..4291aa90 Binary files /dev/null and b/apps/web/public/img/officer_photos/rowdy_hacks/zander-brysch.jpg differ diff --git a/apps/web/public/img/other/map-pin.png b/apps/web/public/img/other/map-pin.png new file mode 100644 index 00000000..c35925fd Binary files /dev/null and b/apps/web/public/img/other/map-pin.png differ diff --git a/apps/web/public/img/photos/bag.png b/apps/web/public/img/photos/bag.png new file mode 100644 index 00000000..6a8535ee Binary files /dev/null and b/apps/web/public/img/photos/bag.png differ diff --git a/apps/web/public/img/photos/birdsup.png b/apps/web/public/img/photos/birdsup.png new file mode 100644 index 00000000..48a2a3ae Binary files /dev/null and b/apps/web/public/img/photos/birdsup.png differ diff --git a/apps/web/public/img/photos/dinochess.jpg b/apps/web/public/img/photos/dinochess.jpg new file mode 100644 index 00000000..1580235e Binary files /dev/null and b/apps/web/public/img/photos/dinochess.jpg differ diff --git a/apps/web/public/img/photos/dinogreen.jpg b/apps/web/public/img/photos/dinogreen.jpg new file mode 100644 index 00000000..09af6ad6 Binary files /dev/null and b/apps/web/public/img/photos/dinogreen.jpg differ diff --git a/apps/web/public/img/photos/rhshirt.jpg b/apps/web/public/img/photos/rhshirt.jpg new file mode 100644 index 00000000..2a134d54 Binary files /dev/null and b/apps/web/public/img/photos/rhshirt.jpg differ diff --git a/apps/web/public/img/photos/shock.jpg b/apps/web/public/img/photos/shock.jpg new file mode 100644 index 00000000..b539784d Binary files /dev/null and b/apps/web/public/img/photos/shock.jpg differ diff --git a/apps/web/public/img/photos/walrus.jpg b/apps/web/public/img/photos/walrus.jpg new file mode 100644 index 00000000..6446c9c6 Binary files /dev/null and b/apps/web/public/img/photos/walrus.jpg differ diff --git a/apps/web/public/img/thumbnails/default.png b/apps/web/public/img/thumbnails/default.png index 0d982295..f81c9759 100644 Binary files a/apps/web/public/img/thumbnails/default.png and b/apps/web/public/img/thumbnails/default.png differ diff --git a/apps/web/src/actions/categories.ts b/apps/web/src/actions/categories.ts deleted file mode 100644 index 4953de5e..00000000 --- a/apps/web/src/actions/categories.ts +++ /dev/null @@ -1,82 +0,0 @@ -"use server"; -import { adminAction } from "@/lib/safe-action"; -import { db, eq } from "db"; -import { createEventCategorySchema, eventCategorySchema } from "db/zod"; -import { customAlphabet } from "nanoid"; -import { LOWER_ALPHANUM_CUSTOM_ALPHABET } from "@/lib/constants"; -import c from "config"; -import { revalidatePath } from "next/cache"; -import { eventCategories } from "db/schema"; -import { UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE } from "@/lib/constants"; -import z from "zod"; - -const deleteEventCategorySchema = z.string().length(c.events.categoryIDLength); - -const nanoid = customAlphabet( - LOWER_ALPHANUM_CUSTOM_ALPHABET, - c.events.categoryIDLength, -); - -export const createEventCategory = adminAction - .schema(createEventCategorySchema) - .action(async ({ parsedInput }) => { - try { - await db.insert(eventCategories).values({ - ...parsedInput, - id: nanoid(), - }); - } catch (e) { - ///@ts-expect-error could not find the type of the error and the status code is the next most accurate way of telling an issue - if (e.code === UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE) { - return { - success: false, - message: "category_exists", - }; - } - throw e; - } - // revalidatePath("/admin/categories"); - return { - success: true, - message: "category_created", - }; - }); - -export const updateEventCategory = adminAction - .schema(eventCategorySchema) - .action(async ({ parsedInput }) => { - const { id: categoryID, ...inputs } = parsedInput; - try { - await db - .update(eventCategories) - .set(inputs) - .where(eq(eventCategories.id, categoryID)); - } catch (e) { - ///@ts-expect-error could not find the type of the error and the status code is the next most accurate way of telling an issue - if (e.code === UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE) { - return { - success: false, - message: "category_exists", - }; - } - throw e; - } - // revalidatePath("/admin/categories"); - return { - success: true, - message: "category_updated", - }; - }); - -export const deleteEventCategory = adminAction - .schema(deleteEventCategorySchema) - .action(async ({ parsedInput: categoryID }) => { - await db - .delete(eventCategories) - .where(eq(eventCategories.id, categoryID)); - // revalidatePath("/admin/categories"); - return { - success: true, - message: "category_deleted", - }; - }); diff --git a/apps/web/src/actions/checkin.ts b/apps/web/src/actions/checkin.ts deleted file mode 100644 index 5e4edf1b..00000000 --- a/apps/web/src/actions/checkin.ts +++ /dev/null @@ -1,74 +0,0 @@ -"use server"; - -import { userAction, adminAction } from "@/lib/safe-action"; -import { userCheckinSchemaFormified } from "db/zod"; -import { UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE } from "@/lib/constants/"; -import { checkInUserClient, checkInUserList } from "@/lib/queries/checkins"; -import { adminCheckinSchema, universityIDSplitter } from "db/zod"; -import { CheckinResult } from "@/lib/types/events"; -import { revalidatePath } from "next/cache"; -import { headers } from "next/headers"; - -const { ALREADY_CHECKED_IN, SUCCESS, FAILED, SOME_FAILED } = CheckinResult; - -export const checkInUserAction = userAction - .schema(userCheckinSchemaFormified) - .action(async ({ parsedInput }) => { - try { - await checkInUserClient(parsedInput); - } catch (e) { - ///@ts-expect-error could not find the type of the error and the status code is the next most accurate way of telling an issue - if (e.code === UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE) { - return { - success: false, - code: ALREADY_CHECKED_IN, - }; - } - throw e; - } - return { - success: true, - code: SUCCESS, - }; - }); - -export const adminCheckin = adminAction - .schema(adminCheckinSchema) - .action(async ({ ctx, parsedInput }) => { - const { universityIDs, eventID } = parsedInput; - const { userID: adminID } = ctx; - try { - const currentPath = headers().get("referer") ?? ""; - - const idList = universityIDSplitter.parse(universityIDs); - const failedIDs = await checkInUserList(eventID, idList, adminID); - - // revalidatePath(currentPath); - if (failedIDs.length == 0) { - return { - success: true, - code: SUCCESS, - }; - } else if (failedIDs.length < idList.length) { - return { - success: false, - code: SOME_FAILED, - failedIDs, - }; - } else if (failedIDs.length == idList.length) { - return { - success: false, - code: FAILED, - }; - } - return { - success: false, - code: FAILED, - }; - } catch (e) { - return { - success: false, - code: FAILED, - }; - } - }); diff --git a/apps/web/src/actions/events/createNewEvent.ts b/apps/web/src/actions/events/createNewEvent.ts deleted file mode 100644 index f8518f39..00000000 --- a/apps/web/src/actions/events/createNewEvent.ts +++ /dev/null @@ -1,70 +0,0 @@ -"use server"; - -import { db } from "db"; -import { customAlphabet } from "nanoid"; -import { insertEventSchemaFormified } from "db/zod"; -import { adminAction } from "@/lib/safe-action"; -import { events, eventsToCategories } from "db/schema"; -import c from "config"; -import { revalidatePath } from "next/cache"; -import { LOWER_ALPHANUM_CUSTOM_ALPHABET } from "@/lib/constants"; - -const nanoid = customAlphabet( - LOWER_ALPHANUM_CUSTOM_ALPHABET, - c.events.idLength, -); - -export const createEvent = adminAction - .schema(insertEventSchemaFormified) - .action(async ({ parsedInput }) => { - let res = { - success: true, - code: "success", - eventID: "", - }; - const { categories, ...e } = parsedInput; - await db.transaction(async (tx) => { - const eventIDs = await tx - .insert(events) - .values({ ...e, id: nanoid() }) - .returning({ eventID: events.id }); - - if (eventIDs.length === 0) { - res = { - success: false, - code: "insert_event_failed", - eventID: "", - }; - tx.rollback(); - return; - } - - const { eventID } = eventIDs[0]; - - const vals = categories.map((cat: string) => ({ - eventID, - categoryID: cat, - })); - - const events_to_categories = await tx - .insert(eventsToCategories) - .values(vals) - .returning(); - - if (events_to_categories.length === 0) { - res = { - success: false, - code: "events_to_categories_failed", - eventID: "", - }; - tx.rollback(); - return; - } - - res.eventID = eventID; - }); - // revalidatePath("/admin/events"); - // revalidatePath("/events"); - - return res; - }); diff --git a/apps/web/src/actions/events/delete.ts b/apps/web/src/actions/events/delete.ts deleted file mode 100644 index 130a8fb1..00000000 --- a/apps/web/src/actions/events/delete.ts +++ /dev/null @@ -1,14 +0,0 @@ -"use server"; -import { adminAction } from "@/lib/safe-action"; -import { db, eq } from "db"; -import { deleteEventSchema } from "db/zod"; -import { events } from "db/schema"; -import { revalidatePath } from "next/cache"; -import { redirect } from "next/navigation"; - -export const deleteEventAction = adminAction - .schema(deleteEventSchema) - .action(async ({ parsedInput: id }) => { - await db.delete(events).where(eq(events.id, id)); - // redirect("/admin/events"); - }); diff --git a/apps/web/src/actions/events/update.ts b/apps/web/src/actions/events/update.ts deleted file mode 100644 index 78e52c34..00000000 --- a/apps/web/src/actions/events/update.ts +++ /dev/null @@ -1,66 +0,0 @@ -"use server"; - -import { and, db, eq, inArray, sql } from "db"; -import { updateEventSchema } from "db/zod"; -import { adminAction } from "@/lib/safe-action"; -import { events, eventsToCategories } from "db/schema"; - -export const updateEvent = adminAction - .schema(updateEventSchema) - .action(async ({ parsedInput }) => { - let res = { - success: true, - code: "success", - }; - const { eventID, oldCategories, categories, ...e } = parsedInput; - await db.transaction(async (tx) => { - const ids = await tx - .update(events) - .set({ ...e }) - .where(eq(events.id, eventID)) - .returning({ eventID: events.id }); - - if (ids.length === 0) { - res = { - success: false, - code: "update_event_failed", - }; - tx.rollback(); - } - - //find new categories - const newCategories: string[] = categories.filter( - (item: string) => !oldCategories.includes(item), - ); - - //find deleting categories - const deletingCategories: string[] = oldCategories.filter( - (item: string) => !categories.includes(item), - ); - - const insertVal = newCategories.map((cat) => ({ - eventID, - categoryID: cat, - })); - - if (insertVal.length !== 0) { - await tx.insert(eventsToCategories).values(insertVal); - } - - await tx - .delete(eventsToCategories) - .where( - and( - inArray( - eventsToCategories.categoryID, - deletingCategories, - ), - eq(eventsToCategories.eventID, eventID), - ), - ); - }); - // VACUUM is handled by Libsql according to: https://discord.com/channels/933071162680958986/1200296371484368956 - // await db.run(sql`PRAGMA VACUUM`); - - return res; - }); diff --git a/apps/web/src/actions/member.ts b/apps/web/src/actions/member.ts deleted file mode 100644 index 566678d7..00000000 --- a/apps/web/src/actions/member.ts +++ /dev/null @@ -1,33 +0,0 @@ -"use server"; -import { executiveAction } from "@/lib/safe-action"; -import z from "zod"; -import { db, eq } from "db"; -import { headers } from "next/headers"; -import { revalidatePath } from "next/cache"; -import { users } from "db/schema"; -import c from "config"; - -export const updateMemberRole = executiveAction - .schema( - z.object({ - userID: z.number().positive(), - role: z.enum(c.memberRoles), - }), - ) - .action(async ({ parsedInput }) => { - const link = headers().get("referer") ?? ""; - const { userID, role } = parsedInput; - await db - .update(users) - .set({ - role, - }) - .where(eq(users.userID, userID)); - - // check if we need to do this - // revalidatePath(link); - - return { - success: true, - }; - }); diff --git a/apps/web/src/actions/register/migrate.ts b/apps/web/src/actions/register/migrate.ts deleted file mode 100644 index fe30b2af..00000000 --- a/apps/web/src/actions/register/migrate.ts +++ /dev/null @@ -1,79 +0,0 @@ -"use server"; - -import { authenticatedAction } from "@/lib/safe-action"; -import { z } from "zod"; -import { db } from "db"; -import { and, eq, isNull } from "db/drizzle"; -import { users, data } from "db/schema"; -import { currentUser } from "@clerk/nextjs/server"; -import c from "config"; - -export const doPortalLookupCheck = authenticatedAction - .schema( - z.object({ universityID: z.string().min(1), email: z.string().min(1) }), - ) - .action(async ({ parsedInput: { email, universityID } }) => { - const lookup = await db - .select() - .from(users) - .where( - and( - eq(users.email, email.toLowerCase()), - isNull(users.clerkID), - eq(users.universityID, universityID.toLowerCase()), - ), - ) - .limit(1); - - if (lookup[0]) { - return { - success: true, - name: lookup[0].firstName + " " + lookup[0].lastName, - }; - } else { - return { - success: false, - name: null, - }; - } - }); - -export const doPortalLink = authenticatedAction - .schema( - z.object({ universityID: z.string().min(1), email: z.string().min(1) }), - ) - .action( - async ({ ctx: { clerkID }, parsedInput: { email, universityID } }) => { - const lookup = await db - .select() - .from(users) - .where( - and( - eq(users.email, email.toLowerCase()), - isNull(users.clerkID), - eq(users.universityID, universityID.toLowerCase()), - ), - ) - .limit(1); - const currUser = await currentUser(); - if (!currUser) { - return { - success: false, - }; - } - const userEmail = currUser.emailAddresses[0].emailAddress; - if (lookup[0]) { - await db - .update(users) - .set({ clerkID, email: userEmail }) - .where(eq(users.userID, lookup[0].userID)); - return { - success: true, - }; - } else { - return { - success: false, - }; - } - }, - ); diff --git a/apps/web/src/actions/register/new.ts b/apps/web/src/actions/register/new.ts deleted file mode 100644 index 83aba7c3..00000000 --- a/apps/web/src/actions/register/new.ts +++ /dev/null @@ -1,72 +0,0 @@ -"use server"; - -import { authenticatedAction } from "@/lib/safe-action"; -import { insertUserWithDataSchemaFormified } from "db/zod"; -import { db } from "db"; -import { eq, or } from "db/drizzle"; -import { users, data } from "db/schema"; - -export const createRegistration = authenticatedAction - .schema(insertUserWithDataSchemaFormified) - .action(async ({ parsedInput: registerFormInputs, ctx: { clerkID } }) => { - const { data: dataSchemaInputs, ...usersSchemaInputs } = - registerFormInputs; - const lowerCasedEmail = registerFormInputs.email.toLowerCase(); - const lowerCasedUniversityID = - registerFormInputs.universityID.toLowerCase(); - const existingUser = await db - .select() - .from(users) - .where( - or( - eq(users.email, lowerCasedEmail), - eq(users.clerkID, clerkID), - eq(users.universityID, lowerCasedUniversityID), - ), - ) - .limit(1); - - if (existingUser.length > 0) { - const foundUser = existingUser[0]; - if (foundUser.clerkID == clerkID) { - return { - success: false, - code: "user_already_exists", - }; - } else if (foundUser.email == lowerCasedEmail) { - return { - success: false, - code: "email_already_exists", - }; - } else if (foundUser.universityID == lowerCasedUniversityID) { - return { - success: false, - code: "university_id_already_exists", - }; - } - } - - await db.transaction(async (tx) => { - const res = await tx - .insert(users) - .values({ - ...usersSchemaInputs, - email: lowerCasedEmail, - universityID: lowerCasedUniversityID, - clerkID, - }) - .returning({ userID: users.userID }); - const userID = res[0].userID; - await tx.insert(data).values({ - ...dataSchemaInputs, - userID, - // this is a placeholder for now. This isn't super important - interestedEventTypes: [], - }); - }); - - return { - success: true, - code: "success", - }; - }); diff --git a/apps/web/src/actions/semester.ts b/apps/web/src/actions/semester.ts deleted file mode 100644 index 771cc861..00000000 --- a/apps/web/src/actions/semester.ts +++ /dev/null @@ -1,131 +0,0 @@ -"use server"; -import { db, eq } from "db"; -import { semesters } from "db/schema"; -import { executiveAction } from "@/lib/safe-action"; -import { - createSemesterSchema, - toggleCurrentSemesterSchema, - updateSemesterSchema, -} from "db/zod"; -import { UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE } from "@/lib/constants"; -import { - SEMESTER_DATE_RANGE_EXISTS, - SEMESTER_NAME_EXISTS, -} from "@/lib/constants/semesters"; -import { - resetCurrentSemesters, - getExistingSemester, -} from "@/lib/queries/semesters"; -import z from "zod"; -import { revalidatePath } from "next/cache"; - -export const createNewSemester = executiveAction - .schema(createSemesterSchema) - .action(async ({ parsedInput }) => { - const { startDate, endDate } = parsedInput; - - const existing = await getExistingSemester(startDate, endDate); - - if (existing) { - return { - success: false, - code: SEMESTER_DATE_RANGE_EXISTS, - semesterName: existing.name, - }; - } - - try { - const res = await db - .insert(semesters) - .values(parsedInput) - .returning({ semesterID: semesters.semesterID }); - if (parsedInput.isCurrent) { - await resetCurrentSemesters(res[0].semesterID); - } - } catch (e) { - /// @ts-expect-error could not find the type of the error and the status code is the next most accurate way of telling an issue - if (e.code === UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE) { - console.log(e); - return { - success: false, - code: SEMESTER_NAME_EXISTS, - semesterName: parsedInput.name, - }; - } - console.log(e); - throw e; - } - // revalidatePath("/admin/semesters"); - return { - success: true, - }; - }); - -export const updateSemester = executiveAction - .schema(updateSemesterSchema) - .action(async ({ parsedInput: dbInputs }) => { - const { startDate, endDate, semesterID } = dbInputs; - const existing = await getExistingSemester(startDate, endDate); - if (existing && existing.semesterID !== semesterID) { - return { - success: false, - code: SEMESTER_DATE_RANGE_EXISTS, - semesterName: existing.name, - }; - } - - try { - await db - .update(semesters) - .set(dbInputs) - .where(eq(semesters.semesterID, dbInputs.semesterID)); - if (dbInputs.isCurrent) { - await resetCurrentSemesters(semesterID); - } - } catch (e) { - /// @ts-expect-error could not find the type of the error and the status code is the next most accurate way of telling an issue - if (e.code === UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE) { - return { - success: false, - code: SEMESTER_NAME_EXISTS, - semesterName: dbInputs.name, - }; - } - throw e; - } - // revalidatePath("/admin/semesters"); - return { - success: true, - }; - }); - -export const toggleCurrentSemester = executiveAction - .schema(toggleCurrentSemesterSchema) - .action(async ({ parsedInput }) => { - const { semesterID, isCurrent } = parsedInput; - if (isCurrent) { - await resetCurrentSemesters(semesterID); - } - await db - .update(semesters) - .set({ - isCurrent, - }) - .where(eq(semesters.semesterID, semesterID)); - - // revalidatePath("/admin/semesters"); - - return { - success: true, - }; - }); - -export const deleteSemester = executiveAction - .schema(z.number().int()) - .action(async ({ parsedInput: semesterID }) => { - await db.delete(semesters).where(eq(semesters.semesterID, semesterID)); - // revalidatePath("/admin/semesters"); - return { - success: true, - }; - }); diff --git a/apps/web/src/actions/settings/edit.ts b/apps/web/src/actions/settings/edit.ts deleted file mode 100644 index 8e02e9e4..00000000 --- a/apps/web/src/actions/settings/edit.ts +++ /dev/null @@ -1,126 +0,0 @@ -"use server"; - -import { userAction } from "@/lib/safe-action"; -import { db, eq } from "db"; -import { data, users } from "db/schema"; -import { revalidatePath } from "next/cache"; -import { del } from "@/lib/server/file-upload"; -import { - editAccountSettingsSchema, - editAcademicSettingsSchema, - editClubSettingsSchema, - editResumeActionSchema, -} from "@/validators/settings"; - -export const editAccountSettings = userAction - .schema(editAccountSettingsSchema) - .action( - async ({ - parsedInput: { firstName, lastName, ethnicity, gender, birthday }, - ctx: { userID, clerkID }, - }) => { - try { - await db.transaction(async (tx) => { - await tx - .update(users) - .set({ firstName, lastName }) - .where(eq(users.clerkID, clerkID)); - - await tx - .update(data) - .set({ ethnicity, gender, birthday }) - .where(eq(data.userID, userID)); - }); - } catch (error) { - console.error(error); - return { - success: false, - error: "Failed to save account settings", - }; - } - - // revalidatePath("/settings"); - return { success: true }; - }, - ); - -export const editAcademicSettings = userAction - .schema(editAcademicSettingsSchema) - .action( - async ({ - parsedInput: { - major, - classification, - graduationYear, - graduationMonth, - }, - ctx: { userID }, - }) => { - try { - await db - .update(data) - .set({ - major, - classification, - graduationYear, - graduationMonth, - }) - .where(eq(data.userID, userID)); - } catch (error) { - console.error(error); - return { - success: false, - error: "Failed to save academic settings", - }; - } - - // revalidatePath("/settings"); - return { success: true }; - }, - ); - -export const editResumeUrl = userAction - .schema(editResumeActionSchema) - .action(async ({ ctx: { userID }, parsedInput: { resume, oldResume } }) => { - try { - await db - .update(data) - .set({ resume }) - .where(eq(data.userID, userID)); - - if (oldResume) await del(oldResume); - - // revalidatePath("/settings"); - return { success: true }; - } catch (error) { - // Failed to update user data to new resume. Delete the new resume from the blob and make the user try again. - console.error(error); - await del(resume); - return { - success: false, - error: "Failed to finalize resume upload.", - }; - } - }); - -export const editClubSettings = userAction - .schema(editClubSettingsSchema) - .action( - async ({ ctx: { userID }, parsedInput: { shirtSize, shirtType } }) => { - try { - await db - .update(data) - .set({ shirtSize, shirtType }) - .where(eq(data.userID, userID)); - } catch (error) { - console.error(error); - return { - success: false, - error: "Failed to update user settings", - }; - } - - // revalidatePath("/settings"); - return { success: true }; - }, - ); diff --git a/apps/web/src/app/(marketing)/client.tsx b/apps/web/src/app/(marketing)/client.tsx new file mode 100644 index 00000000..3bb2125b --- /dev/null +++ b/apps/web/src/app/(marketing)/client.tsx @@ -0,0 +1,125 @@ +"use client"; + +import Image from "next/image"; +import React, { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "motion/react"; + +export function WeArePhotoGrid() { + // Ensure we have at least 4 photos for the grid, can be more for cycling + const photos = [ + "bag.png", + "birdsup.png", + "dinochess.jpg", + "shock.jpg", + "walrus.jpg", + "dinogreen.jpg", + "rhshirt.jpg", + ]; + const photoPrefix = "/img/photos/"; + const totalPhotos = photos.length; + const gridSize = 4; // Still conceptually 4 items + const idleSeconds = 6; + + // State holds the index of the photo currently displayed in each grid cell + const [currentIndices, setCurrentIndices] = useState(() => { + // Initialize with unique non-repeating indices + const initialIndices: number[] = []; + for (let i = 0; i < gridSize; i++) { + let nextIndex = i % totalPhotos; + // Check for duplicates + while (initialIndices.includes(nextIndex)) { + nextIndex = (nextIndex + 1) % totalPhotos; + } + initialIndices.push(nextIndex); + } + return initialIndices; + }); + + // Animation variants for different directions + const variants = [ + { initial: { y: "100%" }, exit: { x: "100%" } }, // Cell0 (top-left): in from bottom, exit to right + { initial: { x: "-100%" }, exit: { y: "100%" } }, // Cell1 (top-right): in from left, exit downward + { initial: { x: "100%" }, exit: { y: "-100%" } }, // Cell2 (bottom-left): in from right, exit upward + { initial: { y: "-100%" }, exit: { x: "-100%" } }, // Cell3 (bottom-right): in from top, exit to left + ]; + + useEffect(() => { + const interval = setInterval(() => { + setCurrentIndices((prevIndices) => { + // Start with the basic rotation pattern + const nextIndices = [ + (prevIndices[2] + 1) % totalPhotos, // cell0 gets next photo after bottom-left + (prevIndices[0] + 1) % totalPhotos, // cell1 gets next photo after top-left + (prevIndices[3] + 1) % totalPhotos, // cell2 gets next photo after bottom-right + (prevIndices[1] + 1) % totalPhotos, // cell3 gets next photo after top-right + ]; + + // Ensure no duplicates and no unmoved photos + for (let i = 0; i < gridSize; i++) { + // Fix duplicate photos + for (let j = 0; j < i; j++) { + if (nextIndices[i] === nextIndices[j]) { + nextIndices[i] = (nextIndices[i] + 1) % totalPhotos; + // Check again for duplicates after incrementing + j = -1; // Restart the inner loop + } + } + + // Fix unmoved photos + if (nextIndices[i] === prevIndices[i]) { + nextIndices[i] = (nextIndices[i] + 1) % totalPhotos; + + // After changing, check for duplicates again + for (let j = 0; j < i; j++) { + if (nextIndices[i] === nextIndices[j]) { + nextIndices[i] = + (nextIndices[i] + 1) % totalPhotos; + j = -1; // Restart the inner loop + } + } + } + } + + return nextIndices; + }); + }, idleSeconds * 1000); // Change image every 3 seconds + + // Clear interval on component unmount + return () => clearInterval(interval); + }, []); + + // Return a fragment containing the four individual animated divs + return ( + + {[0, 1, 2, 3].map((i) => ( +
+ + +
+ {`ACM +
+
+
+
+ ))} +
+ ); +} diff --git a/apps/web/src/app/(marketing)/layout.tsx b/apps/web/src/app/(marketing)/layout.tsx new file mode 100644 index 00000000..070883e7 --- /dev/null +++ b/apps/web/src/app/(marketing)/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "ACM UTSA", + description: + "The premiere organization for students interested in computer science and technology at UTSA", +}; + +export default function MarketingLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/apps/web/src/app/(marketing)/page.tsx b/apps/web/src/app/(marketing)/page.tsx new file mode 100644 index 00000000..6ef51f55 --- /dev/null +++ b/apps/web/src/app/(marketing)/page.tsx @@ -0,0 +1,440 @@ +import Globe from "@/components/landing/globe"; +import { HeroNav } from "@/components/shared/navbar"; +import Image from "next/image"; +import { WeArePhotoGrid } from "./client"; +import { Suspense, cloneElement, Fragment } from "react"; +import { + Code as CodeIcon, + Plus as PlusIcon, + Users as UsersIcon, + Equal as EqualIcon, + Braces as BracesIcon, + Zap as ZapIcon, + Code2 as Code2Icon, + Users2 as Users2Icon, + Lightbulb as LightbulbIcon, + Share2 as Share2Icon, + Scaling as ScalingIcon, + Laptop as LaptopIcon, + Network as NetworkIcon, + Building as BuildingIcon, + Map as MapIcon, + MapPin, + Calendar, + HeartHandshake, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import Footer from "@/components/shared/footer"; +// import { getNextEvent, getUpcomingEvents } from "@/lib/queries/events"; +import { SPONSORS } from "@/site.config"; +import { EventType } from "@/lib/types/events"; + +export default function Page() { + return ( + <> +
+ +
+

+ acm utsa +

+

+ The premiere organization for students interested in + computer science and technology at UTSA +

+
+ +
+ +
+
+
+
+
+
+

+ We are the Association for Computing Machinery + at{" "} + + UTSA + +

+
+ ACM Blue Logo +

+ Fig. 1 +

+
+
+ Loading...
}> + + +
+
+
+
+ + + + + ACM Blue Logo +
+
+

+ Mission +

+

+ At + + } + > + ACM + + , our mission is to empower students passionate + about technology and + }> + computer science + + by fostering a vibrant + }>community + that supports innovation, collaboration, and + growth. +

+

+ Fig. 2 +

+
+
+
+
+
+

1000+

+

Members

+

+ Fig. 3 +

+
+
+

4

+

+ Sub-organizations +

+

+ Fig. 4 +

+
+
+

3

+

+ Annual Hackathons +

+

+ Fig. 5 +

+
+
+
+
+

+ acm is for{" "} + everyone. +

+

+ {"Membership is 100% free, and always will be :)"} +

+
+

Fig. 6

+ + Become a Member {">"} + +
+
+
+
+

+ Not sure yet or just want to learn more? Read more + below! +

+
+
+
+
+

+ Suborgs +

+

+ Along with core ACM Events, we are home to + numerous sub-organizations which focus on + specific areas of interest and communities. +

+
+
+ ACM Logo +
+
+ ACM Logo +
+
+ ACM Logo +
+
+ ACM Logo +
+
+
+
+
+

+ Hackathons +

+

+ ACM hosts 3 annual hackathons, welcoming + students of all skill levels from across the + country to innovate and build. +

+
+
+ ACM Logo +
+
+ ACM Logo +
+
+ ACM Logo +
+
+ +
+
+
+ + + +
+
+
+

+ Sponsors +

+

+ We are able to operate at no cost to our members + through the generous support of our sponsors +

+
+ {SPONSORS.map((sponsor, index) => ( + 0 ? "border-l-2" : "" + }`} + > + {`${sponsor.name} + + ))} + +
+ + + Become a Sponsor + +
+ +
+
+
+