Skip to content
Open
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
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

35 changes: 0 additions & 35 deletions .eslintrc.json

This file was deleted.

8 changes: 7 additions & 1 deletion app/api/export/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export async function GET(request: NextRequest) {
'Diversity, Equity, & Inclusion',
'Conflict Situation',
'Campaign Blurb',
'Boston Campus Full Term',
'Boston Campus Explanation',
'Created At'
];

Expand All @@ -51,6 +53,8 @@ export async function GET(request: NextRequest) {
app.diversityEquityInclusionLongAnswer || '',
app.conflictSituationLongAnswer || '',
app.campaignBlurb || '',
app.bostonCampus ? 'Yes' : 'No',
app.bostonCampusExplanation || '',
app.createdAt?.toISOString() || ''
]);

Expand All @@ -66,7 +70,9 @@ export async function GET(request: NextRequest) {
// Build CSV content
const csvContent = [
headers.map(escapeCSVField).join(','),
...rows.map((row) => row.map(escapeCSVField).join(','))
...rows.map((row: (string | null | undefined)[]) =>
row.map(escapeCSVField).join(',')
)
].join('\n');

const filename = `applicants_${new Date().toISOString().split('T')[0]}.csv`;
Expand Down
78 changes: 78 additions & 0 deletions app/applications/application-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ const applicationSchema = z
campaignBlurb: z
.string()
.min(50, 'Please provide a detailed response (at least 50 characters)'),
bostonCampus: z.boolean({
error: 'Please indicate if you will be on the Boston campus',
}),
bostonCampusExplanation: z.string().optional(),
})
.refine(
(data) => {
Expand All @@ -85,6 +89,19 @@ const applicationSchema = z
message: 'Constituency must be one of the selected colleges',
path: ['constituency'],
},
)
.refine(
(data) => {
// If not on Boston campus, explanation is required
if (data.bostonCampus === false) {
return data.bostonCampusExplanation && data.bostonCampusExplanation.trim().length > 0;
}
return true;
},
{
message: 'Please explain your situation if you will not be on the Boston campus for the entirety of your term',
path: ['bostonCampusExplanation'],
},
);

type ApplicationFormData = z.infer<typeof applicationSchema>;
Expand Down Expand Up @@ -133,11 +150,13 @@ export default function ApplicationForm({
diversityEquityInclusionLongAnswer: '',
conflictSituationLongAnswer: '',
campaignBlurb: '',
bostonCampusExplanation: '',
},
});

const colleges = watch('college');
const constituency = watch('constituency');
const bostonCampus = watch('bostonCampus');

// Auto-select constituency if only one college is selected, clear if invalid
useEffect(() => {
Expand Down Expand Up @@ -795,6 +814,65 @@ export default function ApplicationForm({
</p>
)}
</div>

<div className="space-y-2">
<Label>
Do you plan to be on the Boston campus for the
entirety of your potential term?
</Label>
<div className="flex gap-6">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
value="yes"
checked={bostonCampus === true}
onChange={() =>
setValue('bostonCampus', true, {
shouldValidate: true,
})
}
/>
Yes
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
value="no"
checked={bostonCampus === false}
onChange={() =>
setValue('bostonCampus', false, {
shouldValidate: true,
})
}
/>
No
</label>
</div>
{errors.bostonCampus && (
<p className="text-sm text-destructive">
{errors.bostonCampus.message}
</p>
)}
</div>

{bostonCampus === false && (
<div className="space-y-2">
<Label htmlFor="bostonCampusExplanation">
Please explain your situation.
</Label>
<textarea
id="bostonCampusExplanation"
className="w-full min-h-[120px] px-3 py-2 text-sm border border-input bg-background rounded-md focus:outline-none focus:ring-2 focus:ring-ring"
placeholder="Please explain why you will not be on the Boston campus for the entirety of your term"
{...register('bostonCampusExplanation')}
/>
{errors.bostonCampusExplanation && (
<p className="text-sm text-destructive">
{errors.bostonCampusExplanation.message}
</p>
)}
</div>
)}
</div>
</div>

Expand Down
20 changes: 20 additions & 0 deletions components/AdminDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,26 @@ export default function AdminDashboard({ applications, getApplicationDetails, se
<p className="text-sm font-semibold text-muted-foreground mb-1">Campaign Blurb</p>
<p className="text-sm">{selectedApplicant.campaignBlurb}</p>
</div>
<div>
<p className="text-sm font-semibold text-muted-foreground mb-1">
Boston campus for full term
</p>
<p className="text-sm">
{selectedApplicant.bostonCampus ? 'Yes' : 'No'}
</p>
</div>
{!selectedApplicant.bostonCampus && (
<div>
<p className="text-sm font-semibold text-muted-foreground mb-1">
Boston campus explanation
</p>
<p className="text-sm">
{selectedApplicant.bostonCampusExplanation?.trim()
? selectedApplicant.bostonCampusExplanation
: '—'}
</p>
</div>
)}
</div>
</div>

Expand Down
26 changes: 26 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import nextCoreWebVitals from 'eslint-config-next/core-web-vitals';
import nextTypeScript from 'eslint-config-next/typescript';

const eslintConfig = [
{
ignores: ['node_modules/**', '.next/**', 'out/**', 'build/**', 'coverage/**'],
},
...nextCoreWebVitals,
...nextTypeScript,
{
rules: {
// Existing repository content includes many unescaped apostrophes in JSX copy.
'react/no-unescaped-entities': 'off',
// Disable new React Compiler migration blockers until components are refactored.
'react-hooks/set-state-in-effect': 'off',
'react-hooks/preserve-manual-memoization': 'off',
// Keep legacy utility component typing and CommonJS Next config unblocked.
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-require-imports': 'off',
// Keep signal, but do not fail CI for style-only reassignment.
'prefer-const': 'warn',
},
},
];

export default eslintConfig;
4 changes: 4 additions & 0 deletions lib/actions/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type ApplicationData = {
diversityEquityInclusionLongAnswer: string;
conflictSituationLongAnswer: string;
campaignBlurb: string;
bostonCampus: boolean;
bostonCampusExplanation?: string;
};

export async function submitApplication(formData: ApplicationData) {
Expand Down Expand Up @@ -70,6 +72,8 @@ export async function submitApplication(formData: ApplicationData) {
diversityEquityInclusionLongAnswer: formData.diversityEquityInclusionLongAnswer,
conflictSituationLongAnswer: formData.conflictSituationLongAnswer,
campaignBlurb: formData.campaignBlurb,
bostonCampus: formData.bostonCampus,
bostonCampusExplanation: formData.bostonCampusExplanation || null,
};

await createOrUpdateApplication(data);
Expand Down
Loading