We use cookies to enhance your experience on the site
CodeWorlds

Job Posts and Applications - Recruitment

LinkedIn is not just about profiles - it's a recruitment platform. Pirates are looking for crew members and positions on ships!

Job Post Structure

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}

Job Card - Listings View

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}

Job Detail Page

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}

Apply Modal - Apply for a Position

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          >
4041          </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}

Summary 🎓

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! 🚀

Go to CodeWorlds