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
578 changes: 332 additions & 246 deletions frontend/src/components/application/ApplicationModal.tsx

Large diffs are not rendered by default.

230 changes: 133 additions & 97 deletions frontend/src/components/application/NewApplicationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { useRef, useState } from "react";
import Modal from "../public/Modal";
import CompanyDropdown from "../company/CompanyDropdown";
import { Company } from "../../types/Company";
import { FaLink, FaUser } from "react-icons/fa";
import { FaLocationDot } from "react-icons/fa6";
import { createApplication } from "../../api/applications";
import { useAuth } from "../../contexts/useAuth";
import { Toast } from "primereact/toast";
import { parseErrorResponse } from "../../utils/errorHandler";
import { LuBuilding2, LuBriefcase, LuMapPin, LuLink, LuPlus, LuX } from "react-icons/lu";

interface NewApplicationModalProps {
isOpen: boolean;
Expand All @@ -21,7 +19,6 @@ const NewApplicationModal = ({
onNewApplication,
}: NewApplicationModalProps) => {
const { user } = useAuth();

const toast = useRef<Toast>(null);

const [company, setCompany] = useState<Company>();
Expand Down Expand Up @@ -71,7 +68,6 @@ const NewApplicationModal = ({
return;
}

// Save the application
try {
await createApplication({
userId: user._id,
Expand All @@ -91,7 +87,7 @@ const NewApplicationModal = ({
toast.current?.show({
severity: "error",
summary: "Error",
detail: parseErrorResponse(error),
detail: "Failed to create application: " + (error as Error).message,
});
}

Expand All @@ -105,117 +101,157 @@ const NewApplicationModal = ({
<Modal
isOpen={isOpen}
onClose={onClose}
className="w-[60vh] rounded-xl flex flex-col px-8 py-6"
className="w-full max-w-md rounded-2xl flex flex-col p-0 overflow-hidden"
useOverlay
style={{
background: "linear-gradient(145deg, #1e2433, #1a1f2e)",
border: "1px solid #2d3748",
}}
>
<div className="w-full h-full flex flex-col items-center gap-5">
<h1 className="text-2xl font-bold">Create a new application</h1>
<div className="w-full flex flex-col gap-3">
<div className="flex flex-col">
<label className="text-sm font-medium text-gray-700 mb-1">
Company
{/* Gradient top bar */}
<div className="h-1" style={{ background: "linear-gradient(90deg, #5b8ef4, #7c3aed)" }} />

<div className="p-6">
{/* Header */}
<div className="flex items-start justify-between mb-6">
<div className="flex items-center gap-3">
<div
className="p-2.5 rounded-lg"
style={{
background: "rgba(91,142,244,0.12)",
border: "1px solid rgba(91,142,244,0.25)",
}}
>
<LuPlus className="w-5 h-5 text-[#5b8ef4]" />
</div>
<div>
<h2 className="text-xl font-bold text-[#e8eaed]">New Application</h2>
<p className="text-xs text-[#6b7280] mt-0.5">Track a new job application</p>
</div>
</div>
<button
onClick={onClose}
className="w-8 h-8 flex items-center justify-center rounded-lg text-[#6b7280] hover:text-[#e8eaed] hover:rotate-90 transition-all duration-200"
style={{ background: "#141920", border: "1px solid #2d3748" }}
>
<LuX className="w-4 h-4" />
</button>
</div>

{/* Form */}
<div className="space-y-4">
{/* Company */}
<div>
<label className="flex items-center gap-2 text-xs font-semibold text-[#6b7280] uppercase tracking-wider mb-2">
<LuBuilding2 className="w-3.5 h-3.5" />
Company <span className="text-[#f87171]">*</span>
</label>
<CompanyDropdown
value={company}
onChange={(e) => setCompany(e.target.value)}
dropdownClassName={`w-full py-0.5 border-2 border-gray-200 rounded-lg ${
company ? "border-blue-500" : ""
}`}
dropdownClassName="w-full py-2.5 text-[#e8eaed] outline-none"
buttonClassName=""
/>
</div>
<div className="flex flex-col">
<label
htmlFor="position"
className="text-sm font-medium text-gray-700 mb-1"
>
Position

{/* Position */}
<div>
<label className="flex items-center gap-2 text-xs font-semibold text-[#6b7280] uppercase tracking-wider mb-2">
<LuBriefcase className="w-3.5 h-3.5" />
Position <span className="text-[#f87171]">*</span>
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<FaUser
size={22}
className={`${position ? "text-primary" : "text-gray-300"}`}
/>
</div>
<input
type="text"
id="position"
className="block w-full pl-10 pr-3 py-2.5 border-2 border-gray-200 rounded-lg focus:outline-none focus:border-blue-500"
placeholder="Add Position"
value={position}
onChange={(e) => onPositionChange(e.target.value)}
/>
</div>
<input
type="text"
value={position}
onChange={(e) => onPositionChange(e.target.value)}
placeholder="e.g., Software Engineer Intern"
className="w-full p-2.5 rounded-lg text-sm text-[#e8eaed] outline-none transition-all"
style={{
background: "#141920",
border: "1px solid #2d3748",
}}
onFocus={e => (e.target as HTMLInputElement).style.borderColor = "#5b8ef4"}
onBlur={e => (e.target as HTMLInputElement).style.borderColor = "#2d3748"}
/>
</div>
<div className="flex flex-col">
<label
htmlFor="location"
className="text-sm font-medium text-gray-700 mb-1"
>
Location (Optional)

{/* Location */}
<div>
<label className="flex items-center gap-2 text-xs font-semibold text-[#6b7280] uppercase tracking-wider mb-2">
<LuMapPin className="w-3.5 h-3.5" />
Location <span className="text-[#6b7280]">(Optional)</span>
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<FaLocationDot
size={22}
className={`${location ? "text-primary" : "text-gray-300"}`}
/>
</div>
<input
type="text"
id="location"
className="block w-full pl-10 pr-3 py-2.5 border-2 border-gray-200 rounded-lg focus:outline-none focus:border-blue-500"
placeholder="Add Location"
value={location}
onChange={(e) => onLocationChange(e.target.value)}
/>
</div>
<input
type="text"
value={location}
onChange={(e) => onLocationChange(e.target.value)}
placeholder="e.g., San Diego, CA"
className="w-full p-2.5 rounded-lg text-sm text-[#e8eaed] outline-none transition-all"
style={{
background: "#141920",
border: "1px solid #2d3748",
}}
onFocus={e => (e.target as HTMLInputElement).style.borderColor = "#5b8ef4"}
onBlur={e => (e.target as HTMLInputElement).style.borderColor = "#2d3748"}
/>
</div>
<div className="flex flex-col">
<label
htmlFor="link"
className="text-sm font-medium text-gray-700 mb-1"
>
Link (Optional)

{/* Link */}
<div>
<label className="flex items-center gap-2 text-xs font-semibold text-[#6b7280] uppercase tracking-wider mb-2">
<LuLink className="w-3.5 h-3.5" />
Job Posting Link <span className="text-[#6b7280]">(Optional)</span>
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<FaLink
size={22}
className={`${link ? "text-primary" : "text-gray-300"}`}
/>
</div>
<input
type="url"
id="link"
className={`block w-full pl-10 pr-3 py-2.5 border-2 border-gray-200 rounded-lg focus:outline-none ${
isValidLink || link.length === 0
? "focus:border-blue-500"
: "border-red-500"
}`}
placeholder="Add Link"
value={link}
onChange={(e) => onLinkChange(e.target.value)}
/>
</div>
<input
type="url"
value={link}
onChange={(e) => onLinkChange(e.target.value)}
placeholder="https://..."
className="w-full p-2.5 rounded-lg text-sm text-[#e8eaed] outline-none transition-all"
style={{
background: "#141920",
border: isValidLink || link.length === 0 ? "1px solid #2d3748" : "1px solid #f87171",
}}
onFocus={e => (e.target as HTMLInputElement).style.borderColor = isValidLink || link.length === 0 ? "#5b8ef4" : "#f87171"}
onBlur={e => (e.target as HTMLInputElement).style.borderColor = isValidLink || link.length === 0 ? "#2d3748" : "#f87171"}
/>
{!isValidLink && link.length > 0 && (
<p className="text-xs text-[#f87171] mt-1.5">Please enter a valid URL</p>
)}
</div>
</div>
<button
className={`w-full px-4 py-2 rounded-md text-white font-medium transition-colors mt-8 ${
company && isValidPosition && isValidLink
? "bg-blue-600 hover:bg-blue-700"
: "bg-blue-300"
}`}
onClick={onSave}
>
Save
</button>

{/* Actions */}
<div className="flex gap-3 mt-6 pt-6 border-t" style={{ borderColor: "#2d3748" }}>
<button
onClick={onClose}
className="flex-1 py-2.5 rounded-xl text-sm font-semibold text-[#9ca3af] hover:text-[#e8eaed] transition-all"
style={{ background: "#141920", border: "1px solid #2d3748" }}
>
Cancel
</button>
<button
onClick={onSave}
disabled={!company || !isValidPosition || !isValidLink}
className="flex-1 inline-flex items-center justify-center gap-2 py-2.5 rounded-xl text-sm font-semibold text-white transition-all hover:-translate-y-0.5 disabled:opacity-50 disabled:cursor-not-allowed"
style={{
background: company && isValidPosition && isValidLink
? "linear-gradient(135deg, #5b8ef4, #7c3aed)"
: "#2d3748",
boxShadow: company && isValidPosition && isValidLink
? "0 4px 14px rgba(91,142,244,0.25)"
: "none",
}}
>
<LuPlus className="w-4 h-4" />
Create Application
</button>
</div>
</div>
</Modal>
<Toast ref={toast} />
</>
);
};

export default NewApplicationModal;
export default NewApplicationModal;
Loading
Loading