Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ba85078
fix: make sure autoapply step shows up
neue-dev Mar 28, 2026
2627964
feat: new layout for paraluman super listing
anaj00 Mar 30, 2026
98b4704
chore: better overview section
anaj00 Mar 30, 2026
79075c8
chore: better mobile top bar + remove sticky apply button
anaj00 Mar 30, 2026
4949757
chore: better submission page
anaj00 Mar 30, 2026
b96beba
chore: remove placeholder
anaj00 Mar 30, 2026
dd1de43
chore: minor cleanup
neue-dev Mar 31, 2026
f26a4f6
chore: embed pdf instead of redirecting
neue-dev Mar 31, 2026
2b9e45f
chore: change Facebook to Sherwin Yaun - BetterInternship
anaj00 Mar 31, 2026
35c27b9
Merge branch 'develop' of https://github.com/BetterInternship/Client …
anaj00 Mar 31, 2026
3134020
chore: add messenger link on forms
anaj00 Mar 31, 2026
ba55a9d
chore: Facebook styling
anaj00 Mar 31, 2026
482642d
Merge branch 'main' into develop
anaj00 Mar 31, 2026
5fb7582
feat: super listing v3
anaj00 Mar 31, 2026
df80c70
chore: better navbar, confetti etc
anaj00 Mar 31, 2026
5a443c3
chore: better header on mobile
anaj00 Mar 31, 2026
6450a03
chore: remove download pdf btn
anaj00 Mar 31, 2026
45fa12c
chore: fix navbar height
anaj00 Mar 31, 2026
f7f20d0
feat: pointing tabs
anaj00 Mar 31, 2026
c0d570f
chore: better transition
anaj00 Mar 31, 2026
5e750bb
chore: faster transition
anaj00 Mar 31, 2026
5e342b9
chore: paraluman logo back on header, and apply button on top as well…
anaj00 Mar 31, 2026
8afc197
chore: scroll of how to apply btn
anaj00 Mar 31, 2026
7669959
chore: place back the hero section only on overview
anaj00 Mar 31, 2026
5284733
chore: no resume needed. response in 24 hours subtext
anaj00 Mar 31, 2026
1770128
feat: copy paraluman layout to anteriore
anaj00 Mar 31, 2026
4221843
feat: put the correct challenge to Anteriore
anaj00 Mar 31, 2026
719814c
feat: enhance HeroPanel and HowToApplyPanel with updated links and st…
anaj00 Mar 31, 2026
b357e7d
feat: hero section in the apply page in Paraluman
anaj00 Mar 31, 2026
9dea4f4
chore: change some wording
anaj00 Apr 1, 2026
6aa19b5
patch: disable paraluman submisions
neue-dev Apr 2, 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
8 changes: 0 additions & 8 deletions app/student/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,11 @@ export default function ProfilePage() {
const { redirectIfNotLoggedIn } = useAuthContext();
const profile = useProfileData();
const profileActions = useProfileActions();
const modalRegistry = useModalRegistry();
const [isEditing, setIsEditing] = useState(false);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const [autoApplySaving, setAutoApplySaving] = useState(false);
const [autoApplyError, setAutoApplyError] = useState<string | null>(null);
const router = useRouter();

const { url: resumeURL, sync: syncResumeURL } = useFile({
fetcher: UserService.getMyResumeURL,
Expand Down Expand Up @@ -110,18 +108,12 @@ export default function ProfilePage() {
setAutoApplySaving(true);
setAutoApplyError(null);

const prev = !!profile.data?.apply_for_me;

try {
// await profileActions.update.mutateAsync({ apply_for_me: !prev });
await UserService.updateMyProfile({
apply_for_me: newEnabled,
auto_apply_enabled_at: newEnabled ? new Date().toISOString() : null,
});
void queryClient.invalidateQueries({ queryKey: ["my-profile"] });

// console.log("apply_for_me: ", profile?.data?.apply_for_me);
// console.log("auto_apply_enabled_at:", profile.data?.auto_apply_enabled_at);
} catch (e: any) {
setAutoApplyError((e as string) ?? "Failed to update auto-apply");
} finally {
Expand Down
135 changes: 95 additions & 40 deletions app/student/saved/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,49 @@
"use client";

import React from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Heart } from "lucide-react";
import { ArrowUpRight, Heart } from "lucide-react";
import { useJobsData } from "@/lib/api/student.data.api";
import { useAuthContext } from "@/lib/ctx-auth";
import { Loader } from "@/components/ui/loader";
import { Card } from "@/components/ui/card";
import { JobHead, SuperListingBadge } from "@/components/shared/jobs";
import { Job } from "@/lib/db/db.types";
import { Job, SavedJob } from "@/lib/db/db.types";
import { HeaderIcon, HeaderText } from "@/components/ui/text";
import { Separator } from "@/components/ui/separator";
import { PageError } from "@/components/ui/error";
import { useJobActions } from "@/lib/api/student.actions.api";
import { SaveJobButton } from "@/components/features/student/job/save-job-button";
import { cn } from "@/lib/utils";

type SavedJobItem = SavedJob & Partial<Job>;

export default function SavedJobsPage() {
const { isAuthenticated, redirectIfNotLoggedIn } = useAuthContext();
const jobs = useJobsData();
const jobActions = useJobActions();
const savedJobs = React.useMemo(
() =>
[...(jobs.savedJobs as SavedJobItem[])].sort((a, b) => {
const firstSavedAt =
(a as { saved_at?: string | null }).saved_at ??
a.created_at ??
a.job?.created_at ??
a.jobs?.created_at ??
"";
const secondSavedAt =
(b as { saved_at?: string | null }).saved_at ??
b.created_at ??
b.job?.created_at ??
b.jobs?.created_at ??
"";
return (
new Date(secondSavedAt).getTime() - new Date(firstSavedAt).getTime()
);
}),
[jobs.savedJobs],
);

redirectIfNotLoggedIn();

Expand All @@ -32,7 +56,7 @@ export default function SavedJobsPage() {
<HeaderIcon icon={Heart} />
<HeaderText>Saved Jobs</HeaderText>
</div>
<Badge>{jobs.savedJobs?.length} saved</Badge>
<Badge>{savedJobs.length} saved</Badge>
</div>
</div>
<Separator className="mt-4 mb-8" />
Expand All @@ -44,7 +68,7 @@ export default function SavedJobsPage() {
title="Failed to load saved jobs."
description={jobs.error.message}
/>
) : jobs.savedJobs.length === 0 ? (
) : savedJobs.length === 0 ? (
<div className="text-center py-16 animate-fade-in">
<Card className="max-w-md m-auto">
<h3 className="text-xl font-semibold text-gray-900 mb-2">
Expand All @@ -63,13 +87,10 @@ export default function SavedJobsPage() {
</div>
) : (
<div className="space-y-3">
{jobs.savedJobs.map((savedJob) => (
{savedJobs.map((savedJob, index) => (
<SavedJobCard
key={savedJob.id ?? savedJob.job_id ?? `saved-job-${index}`}
savedJob={savedJob}
handleUnsaveJob={() => {
void jobActions.toggleSave.mutateAsync(savedJob.id ?? "");
}}
saving={jobActions.toggleSave.isPending}
/>
))}
</div>
Expand All @@ -79,45 +100,79 @@ export default function SavedJobsPage() {
);
}

const SavedJobCard = ({
savedJob,
handleUnsaveJob,
saving,
}: {
savedJob: Job;
handleUnsaveJob: () => void;
saving: boolean;
}) => {
const superListingTitle = savedJob.challenge?.title?.trim();
const isSuperListing = Boolean(superListingTitle);
const SavedJobCard = ({ savedJob }: { savedJob: SavedJobItem }) => {
const router = useRouter();
const job = savedJob.job ?? savedJob.jobs ?? savedJob;
const jobRecord = job as Record<string, unknown> | undefined;
const challengeTitleFromJoin = (
jobRecord?.jobs_challenge as { title?: unknown } | undefined
)?.title;
const superListingTitle = job.challenge?.title?.trim();
const isSuperListing =
Boolean(superListingTitle) ||
(typeof challengeTitleFromJoin === "string" &&
challengeTitleFromJoin.trim().length > 0);
const isUnavailable = job?.is_active === false || job?.is_deleted === true;
const jobId = job.id ?? savedJob.job_id ?? savedJob.id;
const canOpenListing = !!jobId && !isUnavailable;
const saveButtonJob = {
...(job ?? {}),
id: jobId ?? "",
} as Job;

return (
<Card
key={savedJob.id}
className={cn(
canOpenListing &&
!isSuperListing &&
"cursor-pointer transition-colors hover:bg-primary/5 hover:border-primary/20",
canOpenListing &&
isSuperListing &&
"cursor-pointer hover:ring-1 hover:ring-primary/20",
isSuperListing &&
"super-card relative isolate overflow-hidden bg-[radial-gradient(ellipse_at_top_left,rgba(254,240,138,0.5),transparent_40%),radial-gradient(ellipse_at_bottom_right,rgba(251,146,60,0.18),transparent_35%),linear-gradient(150deg,rgba(255,251,235,1)_0%,rgba(255,255,255,1)_45%,rgba(254,243,199,0.98)_100%)]",
)}
onClick={() => {
if (!canOpenListing) return;
void router.push(`/search/${jobId}`);
}}
role={canOpenListing ? "button" : undefined}
tabIndex={canOpenListing ? 0 : undefined}
onKeyDown={(event) => {
if (!canOpenListing) return;
if (event.key !== "Enter" && event.key !== " ") return;
event.preventDefault();
void router.push(`/search/${jobId}`);
}}
>
<div className="flex flex-col gap-1">
{isSuperListing && <SuperListingBadge compact className="mb-2 w-fit" />}
<JobHead title={savedJob.title} employer={savedJob.employer?.name} />
<div className="flex flex-col gap-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2">
{isSuperListing && <SuperListingBadge compact className="w-fit" />}
{isUnavailable && (
<Badge type="destructive">Job no longer available.</Badge>
)}
</div>
<div className="flex items-center gap-2">
{jobId ? (
<div
onClick={(event) => event.stopPropagation()}
onKeyDown={(event) => event.stopPropagation()}
>
<SaveJobButton job={saveButtonJob} />
</div>
) : null}
{canOpenListing && (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-[0.33em] bg-primary/5 text-primary/80">
<ArrowUpRight className="h-3.5 w-3.5" />
</span>
)}
</div>
</div>
<JobHead title={job.title} employer={job.employer?.name} />
<p className="text-gray-500 text-sm leading-relaxed line-clamp-2 mt-2 mb-4">
{savedJob.description}
{job.description}
</p>
<div className="flex flex-row items-center gap-3 pt-3 border-t border-gray-100">
<Link href={`/search/${savedJob.id}`}>
<Button>View Details</Button>
</Link>
<Button
variant="outline"
scheme="destructive"
disabled={saving}
onClick={() => handleUnsaveJob()}
>
Unsave
</Button>
</div>
</div>
</Card>
);
Expand Down
Loading