Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9c9199a
feat(utils): create package
alexanderpaolini Feb 19, 2026
c8456d5
feat(utils): migrate over discord from api
alexanderpaolini Feb 19, 2026
8b191a6
feat(utils): remove discord from api utils
alexanderpaolini Feb 19, 2026
b943dfb
feat(utils): migrate over discord stuff for seed script
alexanderpaolini Feb 19, 2026
a387a2c
feat(utils): migrate over auth to use api as well
alexanderpaolini Feb 19, 2026
fb45249
feat(utils): move over discord from api package
alexanderpaolini Feb 19, 2026
c06e62d
feat(utils): migrate the rest of discord utils
alexanderpaolini Feb 19, 2026
35841f4
feat(utils): move over permissons
alexanderpaolini Feb 19, 2026
ae29578
feat(utils): move over date utils
alexanderpaolini Feb 19, 2026
9f98fd4
Merge branch 'main' into utils/create-utils-package
alexanderpaolini Feb 19, 2026
96c5766
chore(utils): lint and format
alexanderpaolini Feb 19, 2026
4039f62
Merge branch 'utils/create-utils-package' of https://github.com/Knigh…
alexanderpaolini Feb 19, 2026
033d76d
feat(repo): fix pnpm-locl
alexanderpaolini Feb 19, 2026
b9d4ec1
chore(utils): test change
alexanderpaolini Feb 19, 2026
afd5aaa
feat: move sendEmail util to forge/emails
DGoel1602 Feb 19, 2026
509245d
chore: format
DGoel1602 Feb 19, 2026
374ed20
chore(utils): make console an eslint error
alexanderpaolini Feb 19, 2026
0a7fb71
chore(*): upgrade .nvmrc
alexanderpaolini Feb 19, 2026
98a6c58
chore(*): format:fix
alexanderpaolini Feb 19, 2026
d21cf7c
migrate api utils
DVidal1205 Mar 7, 2026
28c6979
Merge main into utils/create-utils-package
DVidal1205 Mar 7, 2026
0ea5c1f
migrate blade utils
DVidal1205 Mar 7, 2026
14439cb
migrate gemiknights utils
DVidal1205 Mar 7, 2026
29d67cd
migrate form client utils
DVidal1205 Mar 7, 2026
013acb7
chore: format
DVidal1205 Mar 7, 2026
39c381e
chore: format (again)
DVidal1205 Mar 7, 2026
8e37f80
chore: remove scripts
DVidal1205 Mar 7, 2026
8801f76
chore: format
DVidal1205 Mar 7, 2026
af2dd92
chore: format and client/server separation
DVidal1205 Mar 7, 2026
87e8e1f
chore: lint and type
DVidal1205 Mar 8, 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
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.16
v25.6.1
147 changes: 147 additions & 0 deletions MIGRATION_STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Utils Package Migration Status

## Overview
This document tracks the progress of migrating utility functions from various locations into the centralized `@forge/utils` package.

## What Has Been Done ✅

### 1. Created `@forge/utils` Package
- Created new package at `packages/utils/`
- Set up package.json with proper dependencies
- Configured TypeScript, ESLint, and build setup

### 2. Migrated Functions to `@forge/utils`

#### Discord Utilities (`packages/utils/src/discord.ts`)
- ✅ `api` - Discord REST API client
- ✅ `addRoleToMember`
- ✅ `removeRoleFromMember`
- ✅ `addMemberToServer`
- ✅ `handleDiscordOAuthCallback`
- ✅ `resolveDiscordUserId`
- ✅ `isDiscordAdmin`
- ✅ `isDiscordMember`
- ✅ `isDiscordVIP`
- ✅ `log` - Discord logging function

#### Permissions (`packages/utils/src/permissions.ts`)
- ✅ `hasPermission`
- ✅ `controlPerms` (with `or` and `and` methods)
- ✅ `isJudgeAdmin`
- ✅ `getJudgeSessionFromCookie`
- ✅ `getPermsAsList`

#### Time Utilities (`packages/utils/src/time.ts`)
- ✅ `formatHourTime`
- ✅ `formatDateRange`

#### Other Utilities
- ✅ `logger` (`packages/utils/src/logger.ts`) - Console logger wrapper
- ✅ `stripe` (`packages/utils/src/stripe.ts`) - Stripe client
- ✅ `env` (`packages/utils/src/env.ts`) - Environment variables

### 3. Updated Imports Across Codebase
- ✅ All API package routers now import from `@forge/utils`
- ✅ Auth package updated to use `@forge/utils`
- ✅ Email package updated to use `@forge/utils`
- ✅ DB scripts updated to use `@forge/utils`
- ✅ No remaining imports from old `../utils` path in API package

### 4. Email Package Migration
- ✅ Moved `sendEmail` function to `@forge/email` package
- ✅ Updated email package to use `@forge/utils` logger

## What's Left To Do ⚠️

### 1. Duplicate Functions (High Priority)

#### `formatDateRange` - NAMING CONFLICT ⚠️
- **Location 1**: `apps/blade/src/lib/utils.ts:29`
- Formats date ranges: "Jan 1 - Jan 15, 2024" (dates only)
- Uses `toLocaleDateString` with month/day/year
- **Location 2**: `packages/utils/src/time.ts:36`
- Formats time ranges: "9:00am - 5:00pm" (times only)
- Uses `formatHourTime` helper
- **Status**: These are DIFFERENT functions with the same name!
- **Action Required**:
- Rename one of them to avoid confusion
- Recommended: Rename utils version to `formatTimeRange` (more accurate)
- Or: Rename blade version to `formatDateRangeOnly` or similar
- These serve different purposes and both should exist

#### `getPermsAsList`
- **Location 1**: `apps/blade/src/lib/utils.ts:120`
- **Location 2**: `packages/utils/src/permissions.ts:95`
- **Status**: Function exists in both places
- **Used in**:
- `apps/blade/src/app/_components/admin/roles/roleedit.tsx`
- `apps/blade/src/app/_components/admin/roles/roletable.tsx`
- `apps/blade/src/app/_components/navigation/session-navbar.tsx`
- **Action Required**:
- Update all imports in blade app to use `@forge/utils`
- Remove duplicate definition from `apps/blade/src/lib/utils.ts`

### 2. Remaining Functions in Old `packages/api/src/utils.ts`

The following functions are still in the old utils file and may need to be migrated or kept:

- `gmail` - Google Gmail API client (may stay in API package)
- `calendar` - Google Calendar API client (may stay in API package)
- `generateJsonSchema` - Form schema generation (form-specific, may stay)
- `regenerateMediaUrls` - Form media URL regeneration (form-specific, may stay)
- `CreateFormSchema` - Form schema type (form-specific, may stay)
- `createForm` - Form creation function (form-specific, may stay)

**Decision Needed**: These are form-specific utilities. Should they:
1. Stay in API package (recommended - they're domain-specific)
2. Move to a separate `@forge/forms` package
3. Move to `@forge/utils` (not recommended - too domain-specific)

### 3. Other App-Specific Utils

#### `apps/blade/src/lib/utils.ts`
Contains app-specific utilities that should likely stay:
- `formatDateTime` - Blade-specific date formatting
- `getFormattedDate` - Blade-specific date formatting
- `getTagColor` - Event tag color mapping (Blade-specific)
- `getClassTeam` - Hackathon class team mapping (Blade-specific)
- `extractProcedures` - tRPC procedure extraction (Blade-specific)

**Status**: These are app-specific and should remain in the blade app.

## Migration Statistics

- **Old utils.ts exports**: 6 items (mostly form-specific)
- **New @forge/utils exports**: 21 items
- **Files importing from old utils**: 0 ✅
- **Files importing from new utils**: 23 ✅
- **Duplicate utility functions**: 2 ⚠️

## Next Steps

1. **Immediate Actions**:
- [ ] **Resolve naming conflict**: Rename `formatDateRange` in `@forge/utils` to `formatTimeRange` (or rename blade version)
- [ ] Update `apps/blade/src/lib/utils.ts` to import `getPermsAsList` from `@forge/utils`
- [ ] Update all blade app files using `getPermsAsList` to import from `@forge/utils`
- [ ] Remove duplicate `getPermsAsList` definition from `apps/blade/src/lib/utils.ts`

2. **Verification**:
- [ ] Test all affected components after migration
- [ ] Run static analysis again to confirm no duplicates remain
- [ ] Verify both date/time formatting functions work correctly after renaming

3. **Documentation**:
- [ ] Update any documentation referencing old utils paths
- [ ] Document which utilities belong in `@forge/utils` vs app-specific utils

## Running Analysis

To re-run the analysis scripts:

```bash
# Find duplicate functions and code blocks
npx tsx scripts/analyze-duplicates.ts

# Find utils migration status
npx tsx scripts/analyze-utils-migration.ts
```
1 change: 1 addition & 0 deletions apps/blade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@forge/db": "workspace:*",
"@forge/email": "workspace:^",
"@forge/ui": "workspace:*",
"@forge/utils": "workspace:*",
"@forge/validators": "workspace:*",
"@react-email/render": "1.1.0",
"@stripe/react-stripe-js": "^3.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@forge/ui/dialog";

import { formatDateTime, getTagColor } from "~/lib/utils";
import { events, time } from "@forge/utils";

export function EventDetailsButton({
event,
Expand All @@ -41,12 +40,14 @@ export function EventDetailsButton({
<div>
<div className="flex flex-row justify-normal gap-4 pb-2 text-left">
<DialogTitle>{event.name}</DialogTitle>
<Badge className={`${getTagColor(event.tag)} whitespace-nowrap`}>
<Badge
className={`${events.getTagColor(event.tag)} whitespace-nowrap`}
>
{event.tag}
</Badge>
{event.hackathonName && (
<Badge
className={`${getTagColor(event.tag)} whitespace-nowrap`}
className={`${events.getTagColor(event.tag)} whitespace-nowrap`}
>
{event.hackathonName}
</Badge>
Expand All @@ -63,14 +64,14 @@ export function EventDetailsButton({
<div className="flex flex-col items-start">
<span className="text-sm font-medium text-gray-600">Start</span>
<span className="mt-1 font-medium">
{formatDateTime(event.start_datetime)}
{time.formatDateTime(event.start_datetime)}
</span>
</div>

<div className="flex flex-col items-start">
<span className="text-sm font-medium text-gray-600">End</span>
<span className="mt-1 font-medium">
{formatDateTime(event.end_datetime)}
{time.formatDateTime(event.end_datetime)}
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
TableHeader,
TableRow,
} from "@forge/ui/table";
import { time } from "@forge/utils";

import SortButton from "~/app/_components/shared/SortButton";
import { getFormattedDate } from "~/lib/utils";
import { api } from "~/trpc/react";
import { CreateEventButton } from "./create-event";
import { DeleteEventButton } from "./delete-event";
Expand Down Expand Up @@ -188,7 +188,7 @@ export function EventsTable() {
<TableCell className="text-center">{event.tag}</TableCell>

<TableCell className="text-center">
{getFormattedDate(event.start_datetime)}
{time.getFormattedDate(event.start_datetime)}
</TableCell>

<TableCell>{event.location}</TableCell>
Expand Down Expand Up @@ -244,7 +244,7 @@ export function EventsTable() {
<TableCell className="text-center">{event.tag}</TableCell>

<TableCell className="text-center">
{getFormattedDate(event.start_datetime)}
{time.getFormattedDate(event.start_datetime)}
</TableCell>

<TableCell>{event.location}</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function Attendees({ eventId }: { eventId: string }) {
}

invalidateAttendees().catch((error) => {
// TODO: look into not using the console
// eslint-disable-next-line no-console
console.error(
"Error invalidating members in gathering attendees: ",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function MemberProfileButton({
}

invalidateMembers().catch((error) => {
// TODO: why are we logging to the browser console
// eslint-disable-next-line no-console
console.error("Error invalidating members in member profile: ", error);
});
Expand Down
7 changes: 4 additions & 3 deletions apps/blade/src/app/_components/admin/club/members/scanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import {
} from "@forge/ui/form";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@forge/ui/tabs";
import { toast } from "@forge/ui/toast";
import { hackathons } from "@forge/utils";

import ToggleButton from "~/app/_components/admin/hackathon/hackers/toggle-button";
import { getClassTeam } from "~/lib/utils";
import { api } from "~/trpc/react";

const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => {
Expand Down Expand Up @@ -337,8 +337,9 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => {
<div
className="text-2xl"
style={{
color: getClassTeam(assignedClass as HackerClass).teamColor,
textShadow: `0 0 10px ${getClassTeam(assignedClass as HackerClass).teamColor}, 0 0 20px ${getClassTeam(assignedClass as HackerClass).teamColor}`,
color: hackathons.getClassTeam(assignedClass as HackerClass)
.teamColor,
textShadow: `0 0 10px ${hackathons.getClassTeam(assignedClass as HackerClass).teamColor}, 0 0 20px ${hackathons.getClassTeam(assignedClass as HackerClass).teamColor}`,
}}
>
{assignedClass}
Expand Down
4 changes: 3 additions & 1 deletion apps/blade/src/app/_components/admin/forms/editor/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { CSS } from "@dnd-kit/utilities";
import { ArrowLeft, CogIcon, Loader2, Plus, Save, Users } from "lucide-react";

import type { FORMS } from "@forge/consts";
import type { trpc } from "@forge/utils";
import { Button } from "@forge/ui/button";
import { Card } from "@forge/ui/card";
import { Checkbox } from "@forge/ui/checkbox";
Expand Down Expand Up @@ -52,13 +53,14 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@forge/ui/tabs";
import { Textarea } from "@forge/ui/textarea";

import type { MatchingType } from "./linker";
import type { ProcedureMeta } from "~/lib/utils";
import { InstructionEditCard } from "~/app/_components/forms/shared/instruction-edit-card";
import { QuestionEditCard } from "~/app/_components/forms/shared/question-edit-card";
import { api } from "~/trpc/react";
import { ConnectionViewer } from "./con-viewer";
import ListMatcher from "./linker";

type ProcedureMeta = trpc.ProcedureMeta;

type FormQuestion = z.infer<typeof FORMS.QuestionValidator>;
type FormInstruction = z.infer<typeof FORMS.InstructionValidator>;
type UIQuestion = FormQuestion & { id: string };
Expand Down
4 changes: 3 additions & 1 deletion apps/blade/src/app/_components/admin/forms/editor/linker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState } from "react";
import { Loader2 } from "lucide-react";
import { z } from "zod";

import type { trpc } from "@forge/utils";
import { Button } from "@forge/ui/button";
import { Input } from "@forge/ui/input";
import { Label } from "@forge/ui/label";
Expand All @@ -16,9 +17,10 @@ import {
} from "@forge/ui/select";
import { toast } from "@forge/ui/toast";

import type { ProcedureMeta } from "~/lib/utils";
import { api } from "~/trpc/react";

type ProcedureMeta = trpc.ProcedureMeta;

const matchingSchema = z.object({
proc: z.string().optional(),
form: z.string().optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@forge/ui/dialog";

import { formatDateTime, getTagColor } from "~/lib/utils";
import { events, time } from "@forge/utils";

export function EventDetailsButton({
event,
Expand All @@ -41,12 +40,14 @@ export function EventDetailsButton({
<div>
<div className="flex flex-row justify-normal gap-4 pb-2 text-left">
<DialogTitle>{event.name}</DialogTitle>
<Badge className={`${getTagColor(event.tag)} whitespace-nowrap`}>
<Badge
className={`${events.getTagColor(event.tag)} whitespace-nowrap`}
>
{event.tag}
</Badge>
{event.hackathonName && (
<Badge
className={`${getTagColor(event.tag)} whitespace-nowrap`}
className={`${events.getTagColor(event.tag)} whitespace-nowrap`}
>
{event.hackathonName}
</Badge>
Expand All @@ -63,14 +64,14 @@ export function EventDetailsButton({
<div className="flex flex-col items-start">
<span className="text-sm font-medium text-gray-600">Start</span>
<span className="mt-1 font-medium">
{formatDateTime(event.start_datetime)}
{time.formatDateTime(event.start_datetime)}
</span>
</div>

<div className="flex flex-col items-start">
<span className="text-sm font-medium text-gray-600">End</span>
<span className="mt-1 font-medium">
{formatDateTime(event.end_datetime)}
{time.formatDateTime(event.end_datetime)}
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
TableHeader,
TableRow,
} from "@forge/ui/table";
import { time } from "@forge/utils";

import SortButton from "~/app/_components/shared/SortButton";
import { getFormattedDate } from "~/lib/utils";
import { api } from "~/trpc/react";
import { CreateEventButton } from "./create-event";
import { DeleteEventButton } from "./delete-event";
Expand Down Expand Up @@ -194,7 +194,7 @@ export function EventsTable() {
</TableCell>

<TableCell className="text-center">
{getFormattedDate(event.start_datetime)}
{time.getFormattedDate(event.start_datetime)}
</TableCell>

<TableCell>{event.location}</TableCell>
Expand Down Expand Up @@ -251,7 +251,7 @@ export function EventsTable() {
</TableCell>

<TableCell className="text-center">
{getFormattedDate(event.start_datetime)}
{time.getFormattedDate(event.start_datetime)}
</TableCell>

<TableCell>{event.location}</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function Attendees({ eventId }: { eventId: string }) {
}

invalidateAttendees().catch((error) => {
// TODO: look into not logging into the console
// eslint-disable-next-line no-console
console.error(
"Error invalidating members in gathering attendees: ",
Expand Down
Loading