Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ca49328
chore: add orval and the career-fetch mutator
neue-dev Sep 21, 2026
c776436
ref: move applications and discord facade functions onto the generate…
neue-dev Sep 21, 2026
61f743f
ref: move gods facade functions onto the generated client
neue-dev Sep 22, 2026
1096063
ref: move jobs facade functions onto the generated client
neue-dev Sep 22, 2026
8e0c2f2
ref: move employers and employer users facade functions onto the gene…
neue-dev Sep 22, 2026
916fc31
Merge remote-tracking branch 'origin/develop' into
neue-dev Oct 1, 2026
554e094
Merge remote-tracking branch 'origin/develop' into chore/orval-codege…
neue-dev Oct 3, 2026
726038b
chore(orval-migration): top uni pages sync up
neue-dev Oct 3, 2026
78968f5
chore(orval-migration): regenerate the client with users responses
neue-dev Oct 3, 2026
86602c6
ref(orval-migration): move users and form facade functions onto the g…
neue-dev Oct 3, 2026
b53e6e5
chore(orval-migration): regenerate the client with auth responses
neue-dev Oct 3, 2026
22fa918
ref(orval-migration): move auth facade functions onto the generated c…
neue-dev Oct 3, 2026
02126db
chore(orval-migration): regenerate the client with the short-link res…
neue-dev Oct 3, 2026
985042e
ref(orval-migration): move hand-built URL sites onto the generated cl…
neue-dev Oct 3, 2026
d3f0a7d
chore: regenerate API client from corrected OpenAPI schemas
anaj00 Oct 4, 2026
7f4c34c
chore: regenerate API client from corrected OpenAPI schemas (#585)
anaj00 Oct 4, 2026
0e2a3ad
feat: refresh student homepage with field picker and testimonial marquee
anaj00 Oct 5, 2026
83cdeb5
feat: refine homepage bento cards and fading background
anaj00 Oct 5, 2026
3817765
feat: refine latest opportunity cards and hover interactions
anaj00 Oct 5, 2026
a35eefb
feat: refresh Discord section with shared artwork and refined layout
anaj00 Oct 5, 2026
9d6022f
feat: focus homepage FAQ on MOA and application guidance
anaj00 Oct 5, 2026
bc29828
feat: simplify homepage footer and refine spacing
anaj00 Oct 5, 2026
b825f5b
feat: refine homepage branding navigation and button interactions
anaj00 Oct 5, 2026
da9dac6
Merge remote-tracking branch 'origin/develop' into chore/orval-codege…
neue-dev Oct 6, 2026
9c89e93
Chore/orval codegen migration (#592)
neue-dev Oct 6, 2026
f03612c
feat: refresh homepage hero artwork and search styling
anaj00 Oct 7, 2026
a38efc9
feat: add landing opportunity category browser
anaj00 Oct 7, 2026
e28eea9
feat: let recruiter upload pfp on register
jayylmao Oct 8, 2026
179aea5
feat: let recruiter upload pfp on register (#593)
jayylmao Oct 8, 2026
236e0ea
feat: enhance recipient email handling and validation in forms
anaj00 Oct 8, 2026
6b46731
feat(new listing): wip new create listing layout
jayylmao Oct 8, 2026
3315492
refactor: extract reusable landing and job card components
anaj00 Oct 9, 2026
260d2c3
style: format legacy student footer
anaj00 Oct 9, 2026
330580a
feat: refine mobile landing layout and category grid
anaj00 Oct 9, 2026
581c013
feat(new job): redesign listing creation page
jayylmao Oct 9, 2026
9310d0c
feat(new/edit job): rm allowance disclosure req
jayylmao Oct 9, 2026
26857fa
style(autocomplete): autocompletemulti visual style consistency
jayylmao Oct 9, 2026
f883611
style(dropdown): dropdown visual consistency
jayylmao Oct 9, 2026
1fc82ee
style(apply): modal visual consistency
jayylmao Oct 9, 2026
b42904a
style(editable): editable text visual consistency
jayylmao Oct 9, 2026
2d4e078
chore: bump components package ver
jayylmao Oct 9, 2026
d7ba42c
fix(new job): rm unused super listing code
jayylmao Oct 9, 2026
ff078a3
style(autocomplete): autocompletemulti visual consistency
jayylmao Oct 9, 2026
8e19fbc
fix(new job): clicking empty space in desc text doesn't focus field
jayylmao Oct 9, 2026
ddc52b7
refactor(mdxeditor): move focus issue fix to editor component
jayylmao Oct 9, 2026
95a0523
feat: implement homepage listings and enhance student homepage compon…
anaj00 Oct 9, 2026
375d870
chore(new job): rm deadline field
jayylmao Oct 9, 2026
3b6ac6c
style: add cancel button to autocomplete & dropdown on mobile
jayylmao Oct 9, 2026
8924bff
Feat/student homepage refresh (#594)
anaj00 Oct 9, 2026
2c195d5
feat(new job): prefill common vals
jayylmao Oct 9, 2026
4558fb2
style(new job): new listing creation layout (#595)
jayylmao Oct 9, 2026
e297eca
chore: changed Discord image
anaj00 Oct 9, 2026
9a6d476
chore: job card routing, and OG slug image
anaj00 Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ node_modules
dist
build
.next
*.min.js
*.min.js
# copied verbatim from Career-Server/spec/spec.json
lib/api/generated/spec.json
35 changes: 35 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,34 @@
@source "../node_modules/@betterinternship/components/dist/**/*.js";

@theme {
--color-landing-navy: #101044;
--color-landing-blue: #0064ed;
--color-landing-muted: #5d719b;
--color-landing-border: #e1e7ef;
--color-landing-tint: #f0f6ff;
--color-landing-control: #d9e2ef;
--color-landing-control-hover: #c4cfdf;
--color-landing-border-hover: #cfd5df;
--color-landing-blue-hover: #0053c7;
--color-landing-nav: #374151;
--color-landing-nav-hover: #111827;
--color-landing-community: #0060e5;
--color-landing-footer: #fbfdff;
--color-landing-social: #66758c;
--color-landing-tag: #f1f4f8;
--color-landing-secondary: #c39035;
--color-landing-secondary-deep: #80571d;
--color-landing-secondary-tint: #fcf6e8;
--color-landing-secondary-border: #dfc58d;
--shadow-landing-card: 0 8px 24px rgb(15 23 42 / 5%);
--shadow-landing-interactive: 0 10px 28px rgb(31 45 68 / 8%);
--ease-landing-out: cubic-bezier(0.23, 1, 0.32, 1);

--animate-marquee-horizontal: marquee-x var(--duration) infinite linear;
--animate-marquee-vertical: marquee-y var(--duration) linear infinite;
--animate-fade-in: fade-in 0.5s ease-out;
--animate-landing-introduce: landing-introduce 360ms var(--ease-landing-out)
both;
--animate-slide-up: slide-up 0.4s ease-out;
--animate-scale-in: scale-in 0.3s ease-out;
--animate-jitter: jitter 1.5s ease-in-out infinite;
Expand Down Expand Up @@ -91,6 +116,16 @@
transform: translateY(0);
}
}
@keyframes landing-introduce {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slide-up {
0% {
opacity: 0;
Expand Down
11 changes: 5 additions & 6 deletions app/hire/auth/secure-access/[employerUserId]/[hash]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@
* Forwards minted employer magic links to the API endpoint that consumes the
* keyring and sets the employer auth cookies.
*/
import { API_ORIGIN } from "@/lib/api/api-origin";
import { secureAccessUrl } from "@/lib/api/urls";

export async function GET(
request: Request,
{
params,
}: { params: Promise<{ employerUserId: string; hash: string }> },
) {
const { employerUserId, hash } = await params;
const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL?.replace(/\/+$/, '');

if (!apiBaseUrl) {
if (!API_ORIGIN) {
return Response.redirect(new URL('/login', request.url), 307);
}

const apiUrl = new URL(
`${apiBaseUrl}/auth/secure-access/${encodeURIComponent(employerUserId)}/${encodeURIComponent(hash)}`,
);
const apiUrl = new URL(secureAccessUrl(employerUserId, hash));
apiUrl.search = new URL(request.url).search;

return Response.redirect(apiUrl, 307);
Expand Down
8 changes: 3 additions & 5 deletions app/hire/l/[slug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Resolves employer-portal short links using the shared links API.
* Unknown slugs and API failures return the visitor to the employer home page.
*/
import { LinkService } from "@/lib/api/services";

export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string }> },
Expand All @@ -10,11 +12,7 @@ export async function GET(
const origin = new URL(request.url).origin;

try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/links/${slug}`,
{ cache: "no-store" },
);
const data = (await res.json()) as { url?: string | null };
const data = await LinkService.resolve(slug, { cache: "no-store" });
return Response.redirect(data?.url ?? origin, 307);
} catch {
return Response.redirect(origin, 307);
Expand Down
88 changes: 86 additions & 2 deletions app/hire/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
"use client";

import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useDbRefs } from "@/lib/db/use-refs";
import { useAuthContext } from "../authctx";
import { useRouter } from "next/navigation";
import { isValidRequiredURL, toURL } from "@/lib/utils/url-utils";
import {
LOGO_REQUIREMENTS,
readLogoFile,
storePendingLogo,
} from "@/lib/utils/registration-logo";
import { Employer } from "@/lib/db/db.types";
import { createEditForm, FormCheckbox, FormInput } from "@/components/EditForm";
import {
createEditForm,
FormCheckbox,
FormInput,
LabelWithTooltip,
} from "@/components/EditForm";
import { ErrorLabel } from "@/components/ui/labels";
import { Button } from "@betterinternship/components";
import { isValidPHNumber } from "@/lib/utils";
Expand Down Expand Up @@ -74,9 +84,34 @@ const EmployerEditor = () => {
{} as AdditionalFields,
);
const [missingFields, setMissingFields] = useState<string[]>([]);
const [logoDataUrl, setLogoDataUrl] = useState<string | null>(null);
const [logoError, setLogoError] = useState<string | null>(null);
const logoInputRef = useRef<HTMLInputElement>(null);

const blurTransition = useBlurTransition();

const handleLogoChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const file = event.target.files?.[0];
// Reset so picking the same file again after removing it still fires.
event.target.value = "";
if (!file) return;

const result = await readLogoFile(file);
if ("error" in result) {
setLogoError(result.error);
return;
}
setLogoError(null);
setLogoDataUrl(result.dataUrl);
};

const removeLogo = () => {
setLogoDataUrl(null);
setLogoError(null);
};

const continueRegistration = () => {
// Validate required fields before submitting
const newMissing: string[] = [];
Expand Down Expand Up @@ -123,6 +158,9 @@ const EmployerEditor = () => {
contact_name: additionalFields.contact_name,
};
setIsContinuing(true);
// The account doesn't exist yet, so the verify step uploads this once the
// email is confirmed. Clears any logo left over from an earlier attempt.
storePendingLogo(logoDataUrl);
sessionStorage.removeItem("hire-registration-email");
sessionStorage.setItem(
"hire-registration-profile",
Expand Down Expand Up @@ -273,6 +311,52 @@ const EmployerEditor = () => {
: "",
)}
/>
<div>
<LabelWithTooltip
label="Company Logo (optional)"
required={false}
/>
<div className="flex items-center gap-4">
<img
src={logoDataUrl ?? "/hire/images/default-pfp.jpg"}
alt="Company logo preview"
className="h-16 w-16 shrink-0 rounded-full border border-gray-300 object-cover"
/>
<div className="flex flex-col items-start gap-1">
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => logoInputRef.current?.click()}
>
{logoDataUrl ? "Change" : "Upload image"}
</Button>
{logoDataUrl && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={removeLogo}
>
Remove
</Button>
)}
</div>
<span className="text-xs text-muted-foreground">
{LOGO_REQUIREMENTS}
</span>
</div>
<input
type="file"
ref={logoInputRef}
onChange={(event) => void handleLogoChange(event)}
accept="image/jpeg,image/png,image/webp"
className="hidden"
/>
</div>
<ErrorLabel value={logoError} />
</div>
</div>
<div className="flex items-start gap-3 mb-8">
<FormCheckbox
Expand Down
26 changes: 26 additions & 0 deletions app/hire/register/verify/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import { HireOtpInput } from "@/components/features/hire/hire-otp-input";
import { Loader } from "@/components/ui/loader";
import { useOtpVerification } from "@/hooks/use-otp-verification";
import { EmployerAuthService } from "@/lib/api/hire.api";
import { EmployerService } from "@/lib/api/services";
import { PFP_UPDATED_EVENT } from "@/hooks/use-pfp";
import { FileUploadFormBuilder } from "@/lib/multipart-form";
import { isValidEmail } from "@/lib/utils";
import {
storePendingLogo,
takePendingLogo,
} from "@/lib/utils/registration-logo";
import { useAuthContext } from "../../authctx";
import { HireAuthShell } from "@/components/features/hire/hire-auth-shell";

Expand Down Expand Up @@ -48,9 +55,26 @@ export default function VerifyHireRegistrationPage() {
}
}, []);

// The logo picked on the register page. It can only be uploaded now, once
// the activated session cookie exists; failing here must never block
// sign-in, since it can be set again from the account page.
const uploadPendingLogo = async () => {
const logo = takePendingLogo();
if (!logo) return;

try {
const form = FileUploadFormBuilder.new("logo").file(logo).build();
const result = await EmployerService.updateMyPfp(form);
if (result.success) window.dispatchEvent(new Event(PFP_UPDATED_EVENT));
} catch {
// Intentionally ignored, see above.
}
};

const completeActivation = async () => {
sessionStorage.removeItem("hire-registration-email");
sessionStorage.removeItem(OTP_SENT_AT_KEY);
await uploadPendingLogo();
await refreshAuthentication();
router.replace("/dashboard");
};
Expand Down Expand Up @@ -123,6 +147,8 @@ export default function VerifyHireRegistrationPage() {
const response = await register(formData);
if (!response.success) {
if (response.account_exists) {
// Don't let this logo get applied to whichever account logs in next.
storePendingLogo(null);
router.push(`/login?email=${encodeURIComponent(normalizedEmail)}`);
return;
}
Expand Down
4 changes: 3 additions & 1 deletion app/student/applications/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
SavedJobCard,
SavedJobItem,
} from "@/components/features/student/job/saved-job-card";
import { fileUrls } from "@/lib/api/urls";

type TabValue = "applications" | "saved" | "alerts";

Expand Down Expand Up @@ -97,7 +98,8 @@ function MyJobsPageInner() {

const { url: resumeURL, sync: syncResumeURL } = useFile({
fetcher: (resumeId: string) => UserService.getMyResumeURL(resumeId),
route: (resumeId: string) => `/users/me/resume/${resumeId}`,
route: (hash: string, resumeId: string) =>
fileUrls.myResume(hash, resumeId),
});
const { open: openResumeModal, Modal: ResumeModal } = useModal(
"application-resume-modal",
Expand Down
23 changes: 21 additions & 2 deletions app/student/forms/components/FormSigningLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,9 @@ export function FormSigningLayout({
() =>
getRecipientEmailErrors(recipientEmails, {
studentEmail: profile.data?.email,
recipients: noEsign ? undefined : recipients,
}),
[recipientEmails, profile.data?.email],
[recipientEmails, profile.data?.email, recipients, noEsign],
);

const nextEnabled = useMemo(() => {
Expand Down Expand Up @@ -595,6 +596,16 @@ export function FormSigningLayout({

const handleSubmit = useCallback(async () => {
setNextLoading(true);
if (Object.keys(recipientEmailErrors).length) {
recipientErrorActions.overwrite(recipientEmailErrors);
toast.error(
"Some recipient information is missing or incorrect",
toastPresets.destructive,
);
if (!noRecipientStep) goToStep("timeline");
setNextLoading(false);
return;
}
const finalValuesFromFiller = formFiller.getFinalValues({
...autofillValues,
...recipientEmails,
Expand All @@ -605,7 +616,10 @@ export function FormSigningLayout({
...latestValuesRef.current,
}
: finalValuesFromFiller;
const submittedValues = await withSubmittedSignatureImages(finalValues);
const submittedValues = await withSubmittedSignatureImages({
...finalValues,
...recipientEmails,
});
const firstRecipient = getFirstRecipient();
const shouldGenerateForm = !!noEsign || !firstRecipient;

Expand Down Expand Up @@ -685,6 +699,11 @@ export function FormSigningLayout({
onBack,
recipientEmails,
trackFilloutJob,
recipientEmailErrors,
recipientErrorActions,
noRecipientStep,
goToStep,
formFiller,
]);

// Clean up when switching form
Expand Down
Loading
Loading