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
2 changes: 1 addition & 1 deletion .github/workflows/prod-build-deploy-email-worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Deploy Email Worker to Production
on:
push:
branches:
- main
- master
paths:
- 'apps/api/**'
- 'infra/docker-compose.api.yml'
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ api:

storage:
docker compose up postgres redis

backend:
docker compose up api email_worker asynqmon
4 changes: 2 additions & 2 deletions apps/api/cmd/email_worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func main() {
Queues: map[string]int{
"email": 1,
},
TaskCheckInterval: 60 * time.Second,
DelayedTaskCheckInterval: 2 * time.Minute,
TaskCheckInterval: 5 * time.Second,
DelayedTaskCheckInterval: time.Minute,
HealthCheckInterval: 2 * time.Minute,
JanitorInterval: time.Hour,
JanitorBatchSize: 100,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/internal/services/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
}

// Submitting application is an atomic operation
err = s.txm.WithTx(ctx, func(tx pgx.Tx) error {

Check failure on line 132 in apps/api/internal/services/application.go

View workflow job for this annotation

GitHub Actions / API Lint

ineffectual assignment to err (ineffassign)
txAppRepo := s.appRepo.NewTx(tx)

err := txAppRepo.SubmitApplication(ctx, data, userId, eventId)
Expand Down Expand Up @@ -159,9 +159,9 @@
taskInfo, err := s.emailService.QueueSendConfirmationEmail(data.PreferredEmail, data.FirstName)
s.logger.Info().Str("TaskID", taskInfo.ID).Str("Task Queue", taskInfo.Queue).Str("Task Type", taskInfo.Type).Msg("Queued SendConfirmationEmail task!")

// Non-blocking error
if err != nil {
s.logger.Err(err).Msg(err.Error())
return err
}

return nil
Expand Down
19 changes: 3 additions & 16 deletions apps/web/src/components/AppShell/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import { useToggleState } from "react-stately";
import { Button as RACButton } from "react-aria-components";
import TablerMenu2 from "~icons/tabler/menu-2";
import IconX from "~icons/tabler/x";
import { Logo } from "../Logo";
import { auth } from "@/lib/authClient";
import { Profile } from "./Profile";
import { MobileProfile } from "@/components/AppShell/MobileProfile";
import { Link, useLocation, useRouter } from "@tanstack/react-router";
import { Link, useLocation } from "@tanstack/react-router";
import TablerArrowRight from "~icons/tabler/arrow-right";
import TablerArrowLeft from "~icons/tabler/arrow-left";

Expand Down Expand Up @@ -52,7 +51,6 @@ const AppShellBase: FC<PropsWithChildren> = ({ children }) => {
);
const { data } = auth.useUser();
const pathname = useLocation({ select: (loc) => loc.pathname });
const router = useRouter();

const isAdminPortal = pathname.startsWith("/admin");

Expand Down Expand Up @@ -83,15 +81,7 @@ const AppShellBase: FC<PropsWithChildren> = ({ children }) => {
)}
</RACButton>
<div className="flex justify-between w-full items-center">
<div>
<div className="flex items-center gap-2 ml-3">
<Logo
onClick={() => router.navigate({ to: "/portal" })}
className="py-2 cursor-pointer"
/>
</div>
{header}
</div>
{header}
<MobileProfile
name={user.name}
role={user.role}
Expand All @@ -108,10 +98,7 @@ const AppShellBase: FC<PropsWithChildren> = ({ children }) => {
{navbar && (
<aside className="w-64 h-full px-2 py-3 border-r bg-surface border-neutral-200 dark:border-neutral-800 hidden md:block">
<nav className="flex flex-col gap-2 h-full">
<Logo
onClick={() => router.navigate({ to: "/portal" })}
className="py-2 cursor-pointer mb-3"
/>
{header}
<div className="flex flex-col justify-between h-full">
<div>{navbar}</div>
<div className="flex flex-col gap-5">
Expand Down
42 changes: 35 additions & 7 deletions apps/web/src/components/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,56 @@ import { useTheme } from "./ThemeProvider";
interface LogoProps {
className?: string;
onClick?: () => void;
logo?: string;
label?: string;
hideOnMobile?: boolean;
hideLabelOnMobile?: boolean;
hideLogoOnMobile?: boolean;
}

export function Logo({ className, onClick }: LogoProps) {
export function Logo({
className,
onClick,
label,
logo,
hideOnMobile,
hideLabelOnMobile,
hideLogoOnMobile,
}: LogoProps) {
const { theme } = useTheme();

return (
<div
onClick={onClick}
className={cn("flex items-center gap-2 ml-2", className)}
className={cn(
"flex items-center gap-2",
className,
hideOnMobile && "hidden md:flex",
)}
>
<div className="w-13">
<div className={cn("w-13", hideLogoOnMobile && "hidden md:flex")}>
<img
src={
theme === "dark"
? "/assets/SwampHacks_Logo_Light.png"
: "/assets/SwampHacks_Logo_Dark.png"
logo
? logo
: theme === "dark" // Default logos if no logo prop is provided
? "/assets/SwampHacks_Logo_Light.png"
: "/assets/SwampHacks_Logo_Dark.png"
}
alt="SwampHacks Logo"
/>
</div>

<h1 className="text-xl font-bold">SwampHacks</h1>
{label && (
<h1
className={cn(
"text-xl font-bold",
hideLabelOnMobile && "hidden md:flex",
)}
>
{label}
</h1>
)}
</div>
);
}
32 changes: 25 additions & 7 deletions apps/web/src/features/Application/components/ApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { api } from "@/lib/ky";
import { Spinner } from "@/components/ui/Spinner";
import { useApplication } from "@/features/Application/hooks/useApplication";
import { format, parseISO } from "date-fns";
import { formatDistanceToNowStrict, parseISO } from "date-fns";

// TODO: can we put these in the assets folder?
import Cloud from "./cloud.svg?react";
Expand Down Expand Up @@ -39,20 +39,34 @@
const application = useApplication(eventId);

const [isSaving, setIsSaving] = useState(false);
const [lastSavedAt, setLastSavedAt] = useState<string | undefined>(undefined);
const [lastSavedAt, setLastSavedAt] = useState<Date | undefined>(undefined);
const [savedText, setSavedText] = useState<string | undefined>("");

// Update saved text every second. Restart interval when lastSavedAt changes.
useEffect(() => {
const id = setInterval(() => {
if (lastSavedAt) {
setSavedText(
`Saved ${formatDistanceToNowStrict(lastSavedAt, { addSuffix: true })}`,
);
}
}, 1000);

return () => clearInterval(id);
}, [lastSavedAt]);

useEffect(() => {
if (!application || application.isLoading) return;

if (application.data?.saved_at) {
const parsed = parseISO(application.data.saved_at);
setLastSavedAt(format(parsed, "yyyy-MM-dd h:mm a"));
setLastSavedAt(parsed);
} else {
setLastSavedAt(undefined);
}
}, [application?.data?.saved_at, application?.isLoading]);

Check warning on line 67 in apps/web/src/features/Application/components/ApplicationForm.tsx

View workflow job for this annotation

GitHub Actions / Web Lint & Format

React Hook useEffect has a missing dependency: 'application'. Either include it or remove the dependency array

const onSubmit = useCallback(async (data: Record<string, any>) => {

Check warning on line 69 in apps/web/src/features/Application/components/ApplicationForm.tsx

View workflow job for this annotation

GitHub Actions / Web Lint & Format

Unexpected any. Specify a different type
setIsSubmitting(true);

const formData = new FormData();
Expand All @@ -72,7 +86,7 @@
});

if (!res.ok) {
const resBody: any = await res.json();

Check warning on line 89 in apps/web/src/features/Application/components/ApplicationForm.tsx

View workflow job for this annotation

GitHub Actions / Web Lint & Format

Unexpected any. Specify a different type

showToast({
title: "Submission Error",
Expand All @@ -87,7 +101,7 @@
}

setIsSubmitting(false);
}, []);

Check warning on line 104 in apps/web/src/features/Application/components/ApplicationForm.tsx

View workflow job for this annotation

GitHub Actions / Web Lint & Format

React Hook useCallback has missing dependencies: 'eventId' and 'fieldsMeta'. Either include them or remove the dependency array

const onNewAttachments = useCallback((newFiles: Record<string, File[]>) => {
for (const field in newFiles) {
Expand All @@ -110,7 +124,13 @@
json: formValues,
});

setLastSavedAt(format(new Date(), "yyyy-MM-dd h:mm a"));
const now = new Date();

setLastSavedAt(now);
// Set text immediately to avoid text flash
setSavedText(
`Saved ${formatDistanceToNowStrict(now, { addSuffix: true })}`,
);
setIsSaving(false);
},
[isSubmitted, isSubmitting],
Expand All @@ -128,9 +148,7 @@
const saveStatus = (
<>
{isSaving && !isSubmitted && <span>Autosaving...</span>}
{!isSaving && lastSavedAt && !isSubmitted && (
<span>Last saved at: {lastSavedAt}</span>
)}
{!isSaving && savedText && !isSubmitted && <span>{savedText}</span>}
</>
);

Expand Down
20 changes: 11 additions & 9 deletions apps/web/src/features/Dashboard/components/ApplicantAppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { Link, useLocation } from "@tanstack/react-router";
import { useLocation, useRouter } from "@tanstack/react-router";
import { AppShell } from "@/components/AppShell/AppShell";
import { NavLink } from "@/components/AppShell/NavLink";
import TablerProgress from "~icons/tabler/progress";
import TablerTransformPointBottomLeft from "~icons/tabler/transform-point-bottom-left";
import { type PropsWithChildren } from "react";
import { Logo } from "@/components/Logo";

interface DashboardAppShellProps {
eventId: string;
eventName?: string;
}

export default function ApplicantAppShell({
eventId,
children,
eventName,
}: PropsWithChildren<DashboardAppShellProps>) {
const router = useRouter();
const pathname = useLocation({ select: (loc) => loc.pathname });

const applicationStatusActive =
Expand All @@ -23,14 +27,12 @@ export default function ApplicantAppShell({
return (
<AppShell>
<AppShell.Header>
<div className="w-full px-4 flex flex-row justify-between h-full items-center">
<h1 className="text-2xl font-bold">Applicant Dashboard</h1>
<Link
to="/portal"
className="text-blue-500 underline underline-offset-4"
>
Back to portal
</Link>
<div className="items-center gap-2 ml-3 flex">
<Logo
onClick={() => router.navigate({ to: "/portal" })}
className="py-2 cursor-pointer"
label={eventName || "Event Portal"}
/>
</div>
</AppShell.Header>

Expand Down
23 changes: 12 additions & 11 deletions apps/web/src/features/Dashboard/components/AttendeeAppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { Link, useLocation } from "@tanstack/react-router";
import { useLocation, useRouter } from "@tanstack/react-router";
import { AppShell } from "@/components/AppShell/AppShell";
import { NavLink } from "@/components/AppShell/NavLink";
import TablerLayoutCollage from "~icons/tabler/layout-collage";
import TablerUsersGroup from "~icons/tabler/users-group";
import TablerCalendar from "~icons/tabler/calendar";
import { type PropsWithChildren } from "react";
import { Logo } from "@/components/Logo";

interface DashboardAppShellProps {
eventId: string;
eventName?: string;
}

export default function AttendeeAppShell({
eventId,
children,
eventName,
}: PropsWithChildren<DashboardAppShellProps>) {
const router = useRouter();

const pathname = useLocation({ select: (loc) => loc.pathname });

const dashboardOverviewActive = /^\/events\/[^/]+\/dashboard\/?$/.test(
Expand All @@ -28,16 +33,12 @@ export default function AttendeeAppShell({
return (
<AppShell>
<AppShell.Header>
<div className="w-full px-4 flex flex-row justify-between h-full items-center">
{/* This needs to be replaced with the hackathon name */}
<h1 className="text-2xl font-bold">SwampHacks</h1>

<Link
to="/portal"
className="text-blue-500 underline underline-offset-4"
>
Back to portal
</Link>
<div className="items-center gap-2 ml-3 flex">
<Logo
onClick={() => router.navigate({ to: "/portal" })}
className="py-2 cursor-pointer"
label={eventName || "Event Portal"}
/>
</div>
</AppShell.Header>

Expand Down
19 changes: 10 additions & 9 deletions apps/web/src/features/Dashboard/components/StaffAppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link, useLocation } from "@tanstack/react-router";
import { useLocation, useRouter } from "@tanstack/react-router";
import { AppShell } from "@/components/AppShell/AppShell";
import { NavLink } from "@/components/AppShell/NavLink";
import TablerLayoutDashboard from "~icons/tabler/layout-dashboard";
Expand All @@ -15,6 +15,7 @@ import TablerTicket from "~icons/tabler/ticket";
import TablerAdjustmentsHorizontal from "~icons/tabler/adjustments-horizontal";
import TablerShieldHalfFilled from "~icons/tabler/shield-half-filled";
import { type PropsWithChildren } from "react";
import { Logo } from "@/components/Logo";

interface DashboardAppShellProps {
eventId: string;
Expand All @@ -26,7 +27,9 @@ export default function StaffDashboardShell({
eventRole,
children,
}: PropsWithChildren<DashboardAppShellProps>) {
const router = useRouter();
const pathname = useLocation({ select: (loc) => loc.pathname });

const dashboardOverviewActive = /^\/events\/[^/]+\/dashboard\/?$/.test(
pathname,
);
Expand Down Expand Up @@ -56,14 +59,12 @@ export default function StaffDashboardShell({
return (
<AppShell>
<AppShell.Header>
<div className="w-full px-4 flex flex-row justify-between h-full items-center">
<h1 className="text-2xl font-bold">Staff Dashboard</h1>
<Link
to="/portal"
className="text-blue-500 underline underline-offset-4"
>
Back to portal
</Link>
<div className="items-center gap-2 ml-3 flex">
<Logo
onClick={() => router.navigate({ to: "/portal" })}
className="py-2 cursor-pointer"
label={eventRole === "admin" ? "Admin Portal" : "Staff Portal"}
/>
</div>
</AppShell.Header>

Expand Down
27 changes: 16 additions & 11 deletions apps/web/src/features/Event/hooks/useEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ import { useQuery } from "@tanstack/react-query";
import { getEventById } from "../api/getEvent";
import { EventSchema } from "../schemas/event";

export function useEvent(eventId: string) {
const fetchEvent = async () => {
const data = await getEventById(eventId);

console.log("Fetched event data:", data);

return EventSchema.parse(data);
};
export const fetchEvent = async (eventId: string) => {
const data = await getEventById(eventId);

const eventQueryKey = ["event", eventId] as const;
return EventSchema.parse(data);
};

export function useEvent(eventId: string) {
return useQuery({
queryKey: eventQueryKey,
queryFn: fetchEvent,
queryKey: getEventQueryKey(eventId),
queryFn: () => fetchEvent(eventId),
staleTime: 1000 * 60 * 5, // 5 minutes,
});
}

/**
*
* @param eventId - The ID of the event
* @returns A tuple representing the query key for the event
*/
export function getEventQueryKey(eventId: string) {
return ["event", eventId] as const;
}
Loading
Loading