LinkedIn is not just about profiles - it's a recruitment platform. Pirates are looking for crew members and positions on ships!
1interface JobPost {
2 id: string;
3 title: string; // "First Mate"
4 company: {
5 id: string;
6 name: string;
7 logo: string;
8 industry: string;
9 size: string; // "11-50 employees"
10 };
11 location: string;
12 type: 'full-time' | 'part-time' | 'contract' | 'temporary';
13 level: 'entry' | 'mid' | 'senior' | 'executive';
14 salary?: {
15 min: number;
16 max: number;
17 currency: string;
18 };
19 description: string;
20 requirements: string[];
21 responsibilities: string[];
22 skills: string[];
23 postedDate: Date;
24 applicants: number;
25 views: number;
26}
27
28interface Application {
29 id: string;
30 jobId: string;
31 applicant: {
32 id: string;
33 name: string;
34 avatar: string;
35 headline: string;
36 };
37 coverLetter: string;
38 resume?: string; // URL
39 status: 'pending' | 'reviewed' | 'interview' | 'rejected' | 'accepted';
40 appliedDate: Date;
41}1function JobCard({ job }: { job: JobPost }) {
2 const [isSaved, setIsSaved] = useState(false);
3
4 const timeAgo = (date: Date) => {
5 const days = Math.floor((new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
6 if (days === 0) return 'Today';
7 if (days === 1) return 'Yesterday';
8 if (days < 7) return `${days} days ago`;
9 if (days < 30) return `${Math.floor(days / 7)} weeks ago`;
10 return `${Math.floor(days / 30)} months ago`;
11 };
12
13 return (
14 <div className="bg-white border rounded-lg p-6 hover:shadow-lg transition-shadow cursor-pointer">
15 <div className="flex gap-4">
16 {/* Company Logo */}
17 <img
18 src={job.company.logo}
19 alt={job.company.name}
20 className="w-16 h-16 rounded"
21 />
22
23 <div className="flex-1">
24 {/* Title */}
25 <h3 className="text-xl font-bold text-blue-600 hover:underline mb-1">
26 {job.title}
27 </h3>
28
29 {/* Company */}
30 <p className="text-gray-900 font-medium mb-1">{job.company.name}</p>
31
32 {/* Location & Type */}
33 <p className="text-sm text-gray-600 mb-2">
34 {job.location} • {job.type.replace('-', ' ')} • {job.level}-level
35 </p>
36
37 {/* Salary */}
38 {job.salary && (
39 <p className="text-sm text-gray-700 font-medium mb-2">
40 {job.salary.currency}{job.salary.min.toLocaleString()} -{' '}
41 {job.salary.currency}{job.salary.max.toLocaleString()}
42 </p>
43 )}
44
45 {/* Skills */}
46 <div className="flex flex-wrap gap-2 mb-3">
47 {job.skills.slice(0, 3).map((skill, idx) => (
48 <span
49 key={idx}
50 className="px-2 py-1 bg-gray-100 rounded text-xs font-medium"
51 >
52 {skill}
53 </span>
54 ))}
55 {job.skills.length > 3 && (
56 <span className="px-2 py-1 text-xs text-gray-600">
57 +{job.skills.length - 3} more
58 </span>
59 )}
60 </div>
61
62 {/* Stats */}
63 <div className="flex items-center gap-4 text-xs text-gray-500">
64 <span>{job.applicants} applicants</span>
65 <span>•</span>
66 <span>{timeAgo(job.postedDate)}</span>
67 </div>
68 </div>
69
70 {/* Save Button */}
71 <button
72 onClick={(e) => {
73 e.stopPropagation();
74 setIsSaved(!isSaved);
75 }}
76 className="self-start text-2xl hover:scale-110 transition"
77 >
78 {isSaved ? '🔖' : '📑'}
79 </button>
80 </div>
81 </div>
82 );
83}1'use client';
2
3import { useState } from 'react';
4
5export default function JobDetailPage({ job }: { job: JobPost }) {
6 const [showApplyModal, setShowApplyModal] = useState(false);
7
8 return (
9 <div className="max-w-6xl mx-auto p-4">
10 <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
11 {/* Main Content */}
12 <div className="lg:col-span-2 space-y-4">
13 {/* Header */}
14 <div className="bg-white rounded-lg shadow p-6">
15 <div className="flex gap-4 mb-4">
16 <img
17 src={job.company.logo}
18 alt={job.company.name}
19 className="w-20 h-20 rounded"
20 />
21 <div className="flex-1">
22 <h1 className="text-3xl font-bold mb-2">{job.title}</h1>
23 <p className="text-lg text-gray-900 mb-1">{job.company.name}</p>
24 <p className="text-gray-600">
25 {job.location} • {job.type} • {job.level}
26 </p>
27 </div>
28 </div>
29
30 <div className="flex items-center gap-3 mb-4">
31 <button
32 onClick={() => setShowApplyModal(true)}
33 className="px-8 py-3 bg-blue-600 text-white rounded-full font-bold hover:bg-blue-700 transition"
34 >
35 Apply Now
36 </button>
37 <button className="px-6 py-3 border-2 border-blue-600 text-blue-600 rounded-full font-bold hover:bg-blue-50">
38 Save
39 </button>
40 </div>
41
42 <div className="flex items-center gap-4 text-sm text-gray-600">
43 <span>{job.applicants} applicants</span>
44 <span>•</span>
45 <span>{job.views} views</span>
46 <span>•</span>
47 <span>Posted {timeAgo(job.postedDate)}</span>
48 </div>
49 </div>
50
51 {/* Description */}
52 <div className="bg-white rounded-lg shadow p-6">
53 <h2 className="text-xl font-bold mb-3">About the job</h2>
54 <p className="text-gray-700 whitespace-pre-wrap mb-6">
55 {job.description}
56 </p>
57
58 <h3 className="font-bold text-lg mb-2">Responsibilities</h3>
59 <ul className="list-disc list-inside space-y-1 mb-6 text-gray-700">
60 {job.responsibilities.map((resp, idx) => (
61 <li key={idx}>{resp}</li>
62 ))}
63 </ul>
64
65 <h3 className="font-bold text-lg mb-2">Requirements</h3>
66 <ul className="list-disc list-inside space-y-1 text-gray-700">
67 {job.requirements.map((req, idx) => (
68 <li key={idx}>{req}</li>
69 ))}
70 </ul>
71 </div>
72 </div>
73
74 {/* Sidebar */}
75 <div className="space-y-4">
76 {/* Company Info */}
77 <div className="bg-white rounded-lg shadow p-6">
78 <h3 className="font-bold mb-3">About {job.company.name}</h3>
79 <p className="text-sm text-gray-600 mb-3">{job.company.industry}</p>
80 <p className="text-sm text-gray-600 mb-3">{job.company.size}</p>
81 <button className="w-full py-2 border-2 border-blue-600 text-blue-600 rounded-full font-semibold hover:bg-blue-50">
82 View Company Page
83 </button>
84 </div>
85
86 {/* Required Skills */}
87 <div className="bg-white rounded-lg shadow p-6">
88 <h3 className="font-bold mb-3">Skills</h3>
89 <div className="flex flex-wrap gap-2">
90 {job.skills.map((skill, idx) => (
91 <span
92 key={idx}
93 className="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-sm font-medium"
94 >
95 {skill}
96 </span>
97 ))}
98 </div>
99 </div>
100 </div>
101 </div>
102 </div>
103 );
104}1'use client';
2
3import { useState } from 'react';
4
5interface ApplyModalProps {
6 job: JobPost;
7 onClose: () => void;
8 onSubmit: (application: Partial<Application>) => void;
9}
10
11export default function ApplyModal({ job, onClose, onSubmit }: ApplyModalProps) {
12 const [coverLetter, setCoverLetter] = useState('');
13 const [resumeFile, setResumeFile] = useState<File | null>(null);
14
15 const handleSubmit = (e: React.FormEvent) => {
16 e.preventDefault();
17
18 onSubmit({
19 jobId: job.id,
20 coverLetter,
21 status: 'pending',
22 appliedDate: new Date(),
23 });
24
25 onClose();
26 };
27
28 return (
29 <div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
30 <div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto">
31 <div className="flex items-center justify-between p-6 border-b sticky top-0 bg-white">
32 <div>
33 <h2 className="text-2xl font-bold">Apply to {job.company.name}</h2>
34 <p className="text-gray-600">{job.title}</p>
35 </div>
36 <button
37 onClick={onClose}
38 className="text-gray-500 hover:text-gray-700 text-3xl"
39 >
40 ✕
41 </button>
42 </div>
43
44 <form onSubmit={handleSubmit} className="p-6 space-y-6">
45 {/* Contact Info (pre-filled from profile) */}
46 <div>
47 <h3 className="font-bold mb-3">Contact Information</h3>
48 <div className="bg-gray-50 p-4 rounded-lg">
49 <p className="text-sm text-gray-600">
50 Your profile information will be shared with the employer
51 </p>
52 </div>
53 </div>
54
55 {/* Resume */}
56 <div>
57 <label className="block font-bold mb-2">Resume</label>
58 <input
59 type="file"
60 accept=".pdf,.doc,.docx"
61 onChange={(e) => setResumeFile(e.target.files?.[0] || null)}
62 className="w-full p-3 border rounded-lg"
63 />
64 <p className="text-xs text-gray-500 mt-1">
65 PDF, DOC, or DOCX (max 5MB)
66 </p>
67 </div>
68
69 {/* Cover Letter */}
70 <div>
71 <label className="block font-bold mb-2">
72 Cover Letter (Optional)
73 </label>
74 <textarea
75 value={coverLetter}
76 onChange={(e) => setCoverLetter(e.target.value)}
77 className="w-full p-3 border rounded-lg resize-none"
78 rows={8}
79 />
80 <p className="text-xs text-gray-500 mt-1">
81 {coverLetter.length} characters
82 </p>
83 </div>
84
85 {/* Submit */}
86 <div className="flex gap-3">
87 <button
88 type="submit"
89 className="flex-1 py-3 bg-blue-600 text-white rounded-full font-bold hover:bg-blue-700"
90 >
91 Submit Application
92 </button>
93 <button
94 type="button"
95 onClick={onClose}
96 className="px-6 py-3 border-2 border-gray-300 rounded-full font-bold hover:bg-gray-50"
97 >
98 Cancel
99 </button>
100 </div>
101 </form>
102 </div>
103 </div>
104 );
105}✅ Job post structure - title, company, requirements, salary ✅ Job card - list view with skills and stats ✅ Job detail page - full description, responsibilities ✅ Apply modal - cover letter, resume upload ✅ Application tracking - status (pending, reviewed, etc.) ✅ Save jobs - bookmark for later ✅ Company info - sidebar with company details ✅ Time ago - posted date formatting
In the next exercise we'll add the professional feed and networking!
See you next time! 🚀