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
61 changes: 48 additions & 13 deletions src/components/features/evaluator/response.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ export function Response({ label, type, response, userId }: ResponseProps) {
<Input value={response as string} readOnly />
) : type === "booleanMap" ? (
<BooleanMap value={response as Record<string, boolean>} />
) : type === "resume" ? (
<ResumeField href={resumeLink ?? (response as string)} loading={loading} />
) : loading ? (
<Skeleton />
) : (
<LinkField href={resumeLink ?? (response as string)} />
<LinkField href={response as string} />
)}
</div>
);
Expand All @@ -64,31 +66,64 @@ const BooleanMap = ({ value }: { value?: Record<string, boolean> }) => {
);
};

const ResumeField = ({ href, loading }: { href?: string; loading: boolean }) => {
if (loading) return <Skeleton />;

const value = href ?? "";
if (!value.trim()) {
return <Input value="" readOnly placeholder="No response" />;
}

const handleClipboard = () => {
navigator.clipboard.writeText(value);
toast("Copied link to clipboard!");
};

return (
<div className="flex items-center gap-2">
<Badge>Uploaded</Badge>
<a
href={value}
target="_blank"
rel="noreferrer noopener"
className={cn(buttonVariants({ variant: "outline" }), "gap-2")}
>
View resume
<ExternalLink />
</a>
<Button size="icon" variant="outline" onClick={handleClipboard}>
<Link />
</Button>
</div>
);
};

const LinkField = ({ href }: { href?: string }) => {
const value = href ?? "";
const isUrl = /^https?:\/\//i.test(value.trim());

const handleClipboard = () => {
if (!href) return;
navigator.clipboard.writeText(href);
if (!value) return;
navigator.clipboard.writeText(value);
toast("Copied link to clipboard!");
};

const hasLink = Boolean(href);
if (!isUrl) {
return <Input value={value} readOnly placeholder={value ? undefined : "No response"} />;
}

return (
<div className="relative flex items-center gap-2">
<a
href={hasLink ? href : undefined}
href={value}
target="_blank"
rel="noreferrer noopener"
className={cn(
buttonVariants({ variant: "outline" }),
"flex-grow justify-between",
!hasLink && "pointer-events-none cursor-not-allowed opacity-50",
)}
className={cn(buttonVariants({ variant: "outline" }), "flex-grow justify-between")}
>
<div className="flex-1 truncate">{hasLink ? "Open link in new tab" : "No response"}</div>
{hasLink && <ExternalLink />}
<div className="flex-1 truncate">Open link in new tab</div>
<ExternalLink />
</a>
<Button size="icon" variant="outline" onClick={handleClipboard} disabled={!hasLink}>
<Button size="icon" variant="outline" onClick={handleClipboard}>
<Link />
</Button>
</div>
Expand Down
15 changes: 14 additions & 1 deletion src/components/features/hackerapp/hacker-app-question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ import {
getEligibleSources,
} from "./hacker-app-conditions";
import type { UsedFieldsRegistry } from "./hacker-app-main";
import { LEGAL_NAME_FORM_INPUTS, isFormInputAllowedInSection } from "./hacker-app-sections";
import {
LEGAL_NAME_FORM_INPUTS,
isFormInputAllowedInSection,
isQuestionTypeAllowedInSection,
} from "./hacker-app-sections";

const QUESTION_TYPES: HackerApplicationQuestionType[] = [
"Long Answer",
Expand All @@ -44,6 +48,10 @@ const QUESTION_TYPES: HackerApplicationQuestionType[] = [
"School",
"Major",
"Country",
"Github",
"LinkedIn",
"Portfolio Website",
"Resume",
];

// Question types that if selected, bricks the select field and locks in the value
Expand All @@ -53,6 +61,10 @@ const QUESTION_TYPES_UNIQUE: HackerApplicationQuestionType[] = [
"School",
"Major",
"Country",
"Github",
"LinkedIn",
"Portfolio Website",
"Resume",
];

export const SHOW_FORM_INPUT: HackerApplicationQuestionType[] = [
Expand Down Expand Up @@ -132,6 +144,7 @@ export const HackerAppQuestion = memo(function HackerAppQuestion({
);
const usableQuestionTypes = QUESTION_TYPES?.filter(
(qt) =>
isQuestionTypeAllowedInSection(qt, section) &&
(qt !== "Full Legal Name" || !hasSplitLegalName) &&
(!QUESTION_TYPES_UNIQUE.includes(qt) || !usedFieldsRegistry.questionType.has(qt)),
);
Expand Down
19 changes: 19 additions & 0 deletions src/components/features/hackerapp/hacker-app-sections.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
HackerApplicationQuestionFormInputField,
HackerApplicationQuestionType,
HackerApplicationSections,
} from "@/lib/firebase/types";

Expand Down Expand Up @@ -69,3 +70,21 @@ export const isFormInputAllowedInSection = (
const pinned = SECTION_BY_FORM_INPUT[formInput];
return pinned === undefined || pinned === section;
};

export const QUESTION_TYPE_SECTION: Partial<
Record<HackerApplicationQuestionType, HackerApplicationSections>
> = {
Portfolio: "Skills",
Github: "Skills",
LinkedIn: "Skills",
"Portfolio Website": "Skills",
Resume: "Skills",
};

export const isQuestionTypeAllowedInSection = (
questionType: HackerApplicationQuestionType,
section: HackerApplicationSections,
): boolean => {
const pinned = QUESTION_TYPE_SECTION[questionType];
return pinned === undefined || pinned === section;
};
6 changes: 6 additions & 0 deletions src/components/layout/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BadgeCheck,
Bot,
Calendar,
CalendarCheck,
DollarSign,
FileText,
HelpCircle,
Expand Down Expand Up @@ -112,6 +113,11 @@ export const generateHackathonNavigation = (hackathons: Hackathon[]) => {
href: `/hackathons/${hackathon._id}/schedule`,
icon: Calendar,
},
{
label: "Attendance",
href: `/hackathons/${hackathon._id}/attendance`,
icon: CalendarCheck,
},
{
label: "Rewards",
href: `/hackathons/${hackathon._id}/rewards`,
Expand Down
14 changes: 13 additions & 1 deletion src/lib/firebase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ export interface HackathonDayOf {
lastModifiedBy?: string;
}

export interface PreHackathonWorkshop {
_id?: string;
eventId?: string;
name?: string;
lastModified?: Timestamp;
lastModifiedBy?: string;
}

/**
* Sub-collection: /Hackathon/[hackathon]/Projects
*
Expand Down Expand Up @@ -350,7 +358,11 @@ export type HackerApplicationQuestionType =
| "Dropdown"
| "School"
| "Major"
| "Country";
| "Country"
| "Github"
| "LinkedIn"
| "Portfolio Website"
| "Resume";
// keep in sync with portal-v2 src/lib/firebase/types/hacker-app-questions.ts, which renders these
export const FORM_INPUT_FIELDS = [
"academicYear",
Expand Down
23 changes: 23 additions & 0 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Route as AuthStampbookIndexRouteImport } from './routes/_auth/stampbook
import { Route as AuthStampbookRaffleRouteImport } from './routes/_auth/stampbook/raffle'
import { Route as AuthHackathonsHackathonIdIndexRouteImport } from './routes/_auth/hackathons/$hackathonId/index'
import { Route as AuthHackathonsHackathonIdApplicationRouteImport } from './routes/_auth/hackathons/$hackathonId/application'
import { Route as AuthHackathonsHackathonIdAttendanceRouteImport } from './routes/_auth/hackathons/$hackathonId/attendance'
import { Route as AuthHackathonsHackathonIdRewardsRouteImport } from './routes/_auth/hackathons/$hackathonId/rewards'
import { Route as AuthHackathonsHackathonIdScheduleRouteImport } from './routes/_auth/hackathons/$hackathonId/schedule'
import { Route as AuthHackathonsHackathonIdSettingsRouteImport } from './routes/_auth/hackathons/$hackathonId/settings'
Expand Down Expand Up @@ -94,6 +95,12 @@ const AuthHackathonsHackathonIdApplicationRoute =
path: '/application',
getParentRoute: () => AuthHackathonsHackathonIdRouteRoute,
} as any)
const AuthHackathonsHackathonIdAttendanceRoute =
AuthHackathonsHackathonIdAttendanceRouteImport.update({
id: '/attendance',
path: '/attendance',
getParentRoute: () => AuthHackathonsHackathonIdRouteRoute,
} as any)
const AuthHackathonsHackathonIdRewardsRoute =
AuthHackathonsHackathonIdRewardsRouteImport.update({
id: '/rewards',
Expand Down Expand Up @@ -131,6 +138,7 @@ export interface FileRoutesByFullPath {
'/stampbook/raffle': typeof AuthStampbookRaffleRoute
'/stampbook/': typeof AuthStampbookIndexRoute
'/hackathons/$hackathonId/application': typeof AuthHackathonsHackathonIdApplicationRoute
'/hackathons/$hackathonId/attendance': typeof AuthHackathonsHackathonIdAttendanceRoute
'/hackathons/$hackathonId/rewards': typeof AuthHackathonsHackathonIdRewardsRoute
'/hackathons/$hackathonId/schedule': typeof AuthHackathonsHackathonIdScheduleRoute
'/hackathons/$hackathonId/settings': typeof AuthHackathonsHackathonIdSettingsRoute
Expand All @@ -148,6 +156,7 @@ export interface FileRoutesByTo {
'/stampbook/raffle': typeof AuthStampbookRaffleRoute
'/stampbook': typeof AuthStampbookIndexRoute
'/hackathons/$hackathonId/application': typeof AuthHackathonsHackathonIdApplicationRoute
'/hackathons/$hackathonId/attendance': typeof AuthHackathonsHackathonIdAttendanceRoute
'/hackathons/$hackathonId/rewards': typeof AuthHackathonsHackathonIdRewardsRoute
'/hackathons/$hackathonId/schedule': typeof AuthHackathonsHackathonIdScheduleRoute
'/hackathons/$hackathonId/settings': typeof AuthHackathonsHackathonIdSettingsRoute
Expand All @@ -168,6 +177,7 @@ export interface FileRoutesById {
'/_auth/stampbook/raffle': typeof AuthStampbookRaffleRoute
'/_auth/stampbook/': typeof AuthStampbookIndexRoute
'/_auth/hackathons/$hackathonId/application': typeof AuthHackathonsHackathonIdApplicationRoute
'/_auth/hackathons/$hackathonId/attendance': typeof AuthHackathonsHackathonIdAttendanceRoute
'/_auth/hackathons/$hackathonId/rewards': typeof AuthHackathonsHackathonIdRewardsRoute
'/_auth/hackathons/$hackathonId/schedule': typeof AuthHackathonsHackathonIdScheduleRoute
'/_auth/hackathons/$hackathonId/settings': typeof AuthHackathonsHackathonIdSettingsRoute
Expand All @@ -188,6 +198,7 @@ export interface FileRouteTypes {
| '/stampbook/raffle'
| '/stampbook/'
| '/hackathons/$hackathonId/application'
| '/hackathons/$hackathonId/attendance'
| '/hackathons/$hackathonId/rewards'
| '/hackathons/$hackathonId/schedule'
| '/hackathons/$hackathonId/settings'
Expand All @@ -205,6 +216,7 @@ export interface FileRouteTypes {
| '/stampbook/raffle'
| '/stampbook'
| '/hackathons/$hackathonId/application'
| '/hackathons/$hackathonId/attendance'
| '/hackathons/$hackathonId/rewards'
| '/hackathons/$hackathonId/schedule'
| '/hackathons/$hackathonId/settings'
Expand All @@ -224,6 +236,7 @@ export interface FileRouteTypes {
| '/_auth/stampbook/raffle'
| '/_auth/stampbook/'
| '/_auth/hackathons/$hackathonId/application'
| '/_auth/hackathons/$hackathonId/attendance'
| '/_auth/hackathons/$hackathonId/rewards'
| '/_auth/hackathons/$hackathonId/schedule'
| '/_auth/hackathons/$hackathonId/settings'
Expand Down Expand Up @@ -329,6 +342,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthHackathonsHackathonIdApplicationRouteImport
parentRoute: typeof AuthHackathonsHackathonIdRouteRoute
}
'/_auth/hackathons/$hackathonId/attendance': {
id: '/_auth/hackathons/$hackathonId/attendance'
path: '/attendance'
fullPath: '/hackathons/$hackathonId/attendance'
preLoaderRoute: typeof AuthHackathonsHackathonIdAttendanceRouteImport
parentRoute: typeof AuthHackathonsHackathonIdRouteRoute
}
'/_auth/hackathons/$hackathonId/rewards': {
id: '/_auth/hackathons/$hackathonId/rewards'
path: '/rewards'
Expand Down Expand Up @@ -362,6 +382,7 @@ declare module '@tanstack/react-router' {

interface AuthHackathonsHackathonIdRouteRouteChildren {
AuthHackathonsHackathonIdApplicationRoute: typeof AuthHackathonsHackathonIdApplicationRoute
AuthHackathonsHackathonIdAttendanceRoute: typeof AuthHackathonsHackathonIdAttendanceRoute
AuthHackathonsHackathonIdRewardsRoute: typeof AuthHackathonsHackathonIdRewardsRoute
AuthHackathonsHackathonIdScheduleRoute: typeof AuthHackathonsHackathonIdScheduleRoute
AuthHackathonsHackathonIdSettingsRoute: typeof AuthHackathonsHackathonIdSettingsRoute
Expand All @@ -373,6 +394,8 @@ const AuthHackathonsHackathonIdRouteRouteChildren: AuthHackathonsHackathonIdRout
{
AuthHackathonsHackathonIdApplicationRoute:
AuthHackathonsHackathonIdApplicationRoute,
AuthHackathonsHackathonIdAttendanceRoute:
AuthHackathonsHackathonIdAttendanceRoute,
AuthHackathonsHackathonIdRewardsRoute:
AuthHackathonsHackathonIdRewardsRoute,
AuthHackathonsHackathonIdScheduleRoute:
Expand Down
Loading
Loading