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
2125e14
fix: colors not showing up in applications command bar on hire site
jayylmao May 28, 2026
dcabc4b
style: update status colors
jayylmao May 28, 2026
cf9c36e
fix: ts errors in command menu component
jayylmao May 28, 2026
3edd42d
style: update applications command bar appearance
jayylmao May 28, 2026
abff752
style: unify command bars appearance
jayylmao May 28, 2026
ff30e39
feat: add selected jobs list to search command bar
jayylmao May 28, 2026
31acce5
fix: offset paginator bottom padding for command bar
jayylmao May 28, 2026
52a047c
style: make command bar border consistent with other components
jayylmao May 28, 2026
6a20973
feat: selected jobs list (#469)
jayylmao May 28, 2026
0f806fb
feat: added maps for status labels to id
jayylmao May 28, 2026
dd121ae
fix: disable mass hire app action when accepted/rejected app selected
jayylmao May 28, 2026
6cedaa2
fix: disable mass hire actions conditionally (#470)
jayylmao May 28, 2026
8530748
style: match accept/reject status badge height to status dropdown
jayylmao May 28, 2026
057c485
style: make hire site header border more visible
jayylmao May 28, 2026
399aa8f
style: remove shadow from job header
jayylmao May 28, 2026
3bb4931
refactor: use unified job header in applicant page
jayylmao May 28, 2026
dd87a08
fix: reset password reset btn not clicking
anaj00 May 29, 2026
9d9bc99
fix: no jobs msg flashing on load
jayylmao May 29, 2026
4c8e3e0
fix: no jobs msg flashing on load (#472)
jayylmao May 29, 2026
5122ab6
style: add animation to checkbox
jayylmao May 30, 2026
b78d873
style: consistency tweaks on hire site (#473)
jayylmao May 30, 2026
75cf161
feat: switch between applicant pages
jayylmao May 30, 2026
8c0fc1a
feat: switch between applicant pages (#474)
jayylmao May 30, 2026
8961c4e
feat: implement edit recipient email functionality with validation
anaj00 Jun 1, 2026
b547f21
feat: implement edit recipient email functionality with validation (#…
anaj00 Jun 1, 2026
5dae026
chore: close SOFI AI listing
anaj00 Jun 1, 2026
006e3bd
fix: single signature per recipient
anaj00 Jun 2, 2026
0194a2f
chore: fix Student (You) checking
anaj00 Jun 4, 2026
993fa7a
chore: refactored command-menu to accept `React.Node`s
anaj00 Jun 4, 2026
f895714
refactor: replace ActionItem with DropdownMenuItem across components
anaj00 Jun 4, 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
24 changes: 13 additions & 11 deletions app/hire/dashboard/applicant/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"use client";

import { DB_STATUS_MAP, UI_STATUS_MAP } from "@/lib/consts/application";
import { DB_STATUS_MAP } from "@/lib/consts/application";
import ContentLayout from "@/components/features/hire/content-layout";
import { ApplicantPage } from "@/components/features/hire/dashboard/ApplicantPage";
import { type ActionItem } from "@/components/ui/action-item";
import { useEmployerApplications } from "@/hooks/use-employer-api";
import { type DropdownMenuItem } from "@/components/ui/dropdown-menu";
import {
useEmployerApplications,
useOwnedJobs,
} from "@/hooks/use-employer-api";
import { UserService } from "@/lib/api/services";
import { useDbRefs } from "@/lib/db/use-refs";
import { useSearchParams } from "next/navigation";
Expand All @@ -16,16 +19,18 @@ function ApplicantPageContent() {
const applicationId = searchParams.get("applicationId");
const [loading, setLoading] = useState(true);
const applications = useEmployerApplications();
const jobs = useOwnedJobs();
const { app_statuses } = useDbRefs();

const { triggerAction } = useApplicationActions(applications.review);

const userApplication = applications?.employer_applications.find(
(a) => applicationId === a.id,
);
const otherApplications = applications?.employer_applications.filter(
const otherUserApplications = applications?.employer_applications.filter(
(a) => a.user_id === userApplication?.user_id,
);
const jobId = userApplication?.job_id;
const userId = userApplication?.user_id;

useEffect(() => {
Expand Down Expand Up @@ -59,32 +64,29 @@ function ApplicantPageContent() {
const getStatuses = (applicationId: string) => {
return unique_app_statuses
.filter((status) => status.id !== 7 && status.id !== 5 && status.id !== 0)
.map((status): ActionItem => {
.map((status): DropdownMenuItem => {
const config = DB_STATUS_MAP[status.id];
const uiProps = UI_STATUS_MAP.get(config?.key || "pending");

return {
id: status.id.toString(),
label: status.name,
icon: uiProps?.icon,
onClick: () =>
triggerAction(
config?.action || "CHANGE_STATUS",
[application],
status.id,
),
destructive: uiProps?.destructive,
};
});
};

return (
<ContentLayout>
<ContentLayout className="!p-0">
<div className="w-full h-full">
<ApplicantPage
jobId={jobId!}
application={userApplication}
statuses={getStatuses(userApplication?.id || "")}
userApplications={otherApplications}
userApplications={otherUserApplications}
onArchive={() => {
if (!userApplication) return;
if (userApplication.visibility === "archived") {
Expand Down
6 changes: 5 additions & 1 deletion app/hire/dashboard/manage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ function ManageContent() {
return (
<ContentLayout className="!p-0">
<div className="w-full h-full flex flex-col">
<JobHeader job={jobData} onJobUpdate={handleJobUpdate} />
<JobHeader
job={jobData}
onJobUpdate={handleJobUpdate}
backHref="/dashboard"
/>
<div className="flex-1 overflow-auto pt-4 px-2 sm:px-8">
<JobTabs selectedJob={jobData} onJobUpdate={handleJobUpdate} />
</div>
Expand Down
18 changes: 4 additions & 14 deletions app/hire/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { useMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { Briefcase, Plus } from "lucide-react";
import { useState, useRef, useEffect } from "react";
import { useRef, useEffect } from "react";
import { useAuthContext } from "../authctx";
import { Job } from "@/lib/db/db.types";
import { HeaderTitle } from "@/components/ui/text";
Expand All @@ -25,17 +25,15 @@ const NORMAL_LISTING_CREATE_PATH = "/listings/create";

function DashboardContent() {
const { isMobile } = useMobile();
const { isAuthenticated, redirectIfNotLoggedIn, loading } = useAuthContext();
const { isAuthenticated, redirectIfNotLoggedIn } = useAuthContext();
const router = useRouter();
const superListingTapState = useRef({ count: 0, lastTapMs: 0 });
const profile = useProfile();
const applications = useEmployerApplications();
const { ownedJobs, update_job, delete_job } = useOwnedJobs();
const { ownedJobs, update_job, loading } = useOwnedJobs();
const activeJobs = ownedJobs.filter((job) => job.is_active);
const inactiveJobs = ownedJobs.filter((job) => !job.is_active);

const [isLoading, setLoading] = useState(true);

redirectIfNotLoggedIn();

const handleAddListingClick = () => {
Expand Down Expand Up @@ -65,14 +63,6 @@ function DashboardContent() {
return result;
};

useEffect(() => {
if (!ownedJobs) {
setLoading(true);
} else {
setLoading(false);
}
}, [ownedJobs, activeJobs, inactiveJobs]);

if (loading || !isAuthenticated()) {
return (
<ContentLayout>
Expand Down Expand Up @@ -114,7 +104,7 @@ function DashboardContent() {
employerId={profile.data?.id || ""}
updateJob={handleUpdateJob}
onAddListingClick={handleAddListingClick}
isLoading={isLoading}
isLoading={loading}
/>
{isMobile && (
<button
Expand Down
55 changes: 27 additions & 28 deletions app/hire/listings/details/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function JobDetailsPageRoute() {
<Suspense>
<JobDetailsPageRouteContent></JobDetailsPageRouteContent>
</Suspense>
)
);
}

function JobDetailsPageRouteContent() {
Expand All @@ -24,31 +24,31 @@ function JobDetailsPageRouteContent() {
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchJobData = async () => {
if (!jobId) {
setLoading(false);
return;
}

try {
setLoading(true);
const response = await JobService.getAnyJobById(jobId);
if (response?.success && response.job) {
setJobData(response.job);
} else {
console.error("failed to load job data");
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
const fetchJobData = async () => {
if (!jobId) {
setLoading(false);
return;
}

try {
setLoading(true);
const response = await JobService.getAnyJobById(jobId);
if (response?.success && response.job) {
setJobData(response.job);
} else {
console.error("failed to load job data");
}
};
fetchJobData();
}, [jobId]);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
fetchJobData();
}, [jobId]);

const handleJobUpdate = (updates: Partial<Job>) => {
setJobData(prev => prev ? { ...prev, ...updates } : null);
setJobData((prev) => (prev ? { ...prev, ...updates } : null));
};

if (loading || !jobData) {
Expand All @@ -65,13 +65,12 @@ function JobDetailsPageRouteContent() {
<JobHeader
job={jobData}
onJobUpdate={handleJobUpdate}
backHref={`/dashboard/manage?jobId=${jobId}`}
/>
<div className="flex-1 overflow-auto pt-4 px-2 sm:px-8">
<JobDetailsPage
job={jobData}
/>
<JobDetailsPage job={jobData} />
</div>
</div>
</ContentLayout>
)
}
);
}
2 changes: 1 addition & 1 deletion app/hire/reset-password/[hash]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const ResetPasswordForm = ({ hash }: { hash: string }) => {
<div className="flex justify-end items-center w-[100%]">
<Button
type="submit"
onClick={() => handle_request}
onClick={(e) => void handle_request(e)}
disabled={isLoading}
>
{isLoading ? "Changing password..." : "Change password"}
Expand Down
2 changes: 1 addition & 1 deletion app/student/companies/sofi-ai/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ function FeaturedInternship() {
{
href: "/super-listing/sofi-ai-marketing",
title: "Marketing Intern",
closed: false,
closed: true,
icon: <Megaphone className="h-6 w-6" />,
},
] as const;
Expand Down
91 changes: 12 additions & 79 deletions app/student/forms/components/FormSigningPartyTimeline.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { FormInput } from "@/components/EditForm";
import { useFormRendererContext } from "@/components/features/student/forms/form-renderer.ctx";
import { Badge } from "@/components/ui/badge";
import { Timeline, TimelineItem } from "@/components/ui/timeline";
import { StateRecord, StateRecordActions } from "@/hooks/base/useStateRecord";
import { cn } from "@/lib/utils";
import { useEffect } from "react";
import { useProfileData } from "@/lib/api/student.data.api";
import {
getRecipientEmailErrors,
RECIPIENT_EMAIL_VALIDATION_DEBOUNCE_MS,
} from "./recipient-email-validation";
import { RecipientSigningPartyTimeline } from "./RecipientSigningPartyTimeline";

export const FormSigningPartyTimeline = ({
recipientInputAPI,
Expand Down Expand Up @@ -49,80 +46,16 @@ export const FormSigningPartyTimeline = ({
]);

return (
recipients.length > 1 && (
<Timeline>
{recipients.map((recipient, index) => {
const isMe = recipient._id === "initiator";
const fromMe = recipient.signatory_source?._id === "initiator";
const fieldName = form.formMetadata.getSigningPartyFieldName(
recipient._id,
);
return (
<TimelineItem
key={recipient.signatory_title}
number={index + 1}
isMe={isMe}
title={
<span className="text-sm text-gray-700 font-light">
{recipient.signatory_title}
</span>
}
subtitle={
fromMe ? (
!recipientInputAPI?.recipientEmails ? (
<span className="text-warning text-xs tracking-normal text-semibold">
you will specify this email
</span>
) : isConfirmingRecipients ? (
<Badge type="supportive" className="text-xs">
{recipientInputAPI.recipientEmails[fieldName]}
</Badge>
) : (
<div className="mt-1">
<FormInput
onInput={() =>
recipientInputAPI?.recipientErrorActions.clearOne(
fieldName,
)
}
value={recipientInputAPI.recipientEmails[fieldName]}
placeholder={"Enter email..."}
className={cn(
recipientInputAPI.recipientErrors[fieldName]
? "border-destructive text-destructive"
: "",
)}
setter={(value) =>
recipientInputAPI.recipientEmailActions.setOne(
fieldName,
value,
)
}
/>
{recipientInputAPI.recipientErrors[fieldName] && (
<span className="mt-1 block text-xs text-destructive">
{recipientInputAPI.recipientErrors[fieldName]}
</span>
)}
</div>
)
) : recipient.signatory_account?.email ? (
<span className="text-xs text-primary tracking-wide">
{recipient.signatory_account.email ?? ""}
</span>
) : recipient._id !== "initiator" ? (
<span className="text-xs italic text-gray-400">
this email will come from someone else
</span>
) : (
<span></span>
)
}
isLast={index === recipients.length - 1}
/>
);
})}
</Timeline>
)
<RecipientSigningPartyTimeline
parties={recipients.map((recipient) => ({
id: form.formMetadata.getSigningPartyFieldName(recipient._id),
title: recipient.signatory_title,
email: recipient.signatory_account?.email ?? "",
isMe: recipient._id === "initiator",
isEditable: recipient.signatory_source?._id === "initiator",
}))}
recipientInputAPI={recipientInputAPI}
isConfirmingRecipients={isConfirmingRecipients}
/>
);
};
Loading