Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jobs board implementation #119

Merged
merged 19 commits into from
Oct 24, 2024
99 changes: 40 additions & 59 deletions src/components/FreelancerCard.astro
Original file line number Diff line number Diff line change
@@ -1,41 +1,29 @@
---
import { Image } from 'astro:assets';
import type { ImageMetadata } from 'astro';
import { getFlagEmoji, hasSprykerCertifications } from '../utils/utils.js';
const { freelancer } = Astro.props;

// Import social icons
import { Icon } from 'astro-icon/components';

import { Icon } from 'astro-icon/components'; // social icons
import CQIcon from '@components/ui/icons/CQIcon.astro';
import SecondaryCTA from "@components/ui/buttons/SecondaryCTA.astro";
import type { Freelancer } from '../types';

interface Props {
freelancer: Freelancer;
}

// Define the type for a skill
type Skill = string;

// Define a boolean to check if any certification is true
const hasSprykerCertifications = freelancer.sprykerCertifications.backEndDeveloper || freelancer.sprykerCertifications.solutionArchitect;
// Get certification status using the utility function
const hasCertifications = hasSprykerCertifications(freelancer.sprykerCertifications);

// Function to convert country code to flag emoji
function getFlagEmoji(countryCode: string) {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
// Import all images at build time with proper typing
const imageFiles: Record<string, ImageMetadata> = import.meta.glob('/src/images/freelancers/*.{jpeg,jpg,png,gif}', {
eager: true,
import: 'default'
});

const forumIconFiles: Record<string, ImageMetadata> = import.meta.glob('/src/images/cq.png', {
eager: true,
import: 'default'
});

// Get the forum icon image
const forumIcon = Object.values(forumIconFiles)[0];

// Add image import utility function
async function getFreelancerImage(photoPath: string) {
try {
Expand Down Expand Up @@ -72,7 +60,7 @@ const freelancerImage: ImageMetadata | undefined = imageFiles[imagePath];
src={freelancerImage}
alt={`${freelancer.firstName} ${freelancer.lastName}`}
width={400}
height={192}
height={400}
class="w-full h-48 object-cover"
/>
) : (
Expand All @@ -82,13 +70,29 @@ const freelancerImage: ImageMetadata | undefined = imageFiles[imagePath];
)}
</a>
<div class="p-6">
<h2 class="text-2xl font-semibold mb-2 text-blue-500 dark:text-blue-400">{`${freelancer.firstName} ${freelancer.lastName}`}</h2>
<p class="text-pink-500 dark:text-pink-400 mb-2">{freelancer.headline}</p>
<a href={`/jobs/${freelancer.id}`} class="group"> <!-- Added group for hover effect -->
<h2 class="text-2xl font-semibold mb-2 text-blue-500 dark:text-blue-400 group-hover:text-blue-600 dark:group-hover:text-blue-300 transition-colors">
{`${freelancer.firstName} ${freelancer.lastName}`}
</h2>
</a> <p class="text-pink-500 dark:text-pink-400 mb-2">{freelancer.headline}</p>
<p class="text-sm text-neutral-500 dark:text-neutral-500 mb-2">
📍 {freelancer.location} {getFlagEmoji(freelancer.countryCode)}
</p>
<p class="text-sm text-neutral-500 dark:text-neutral-500 mb-2">🗣️ {freelancer.language}</p>
<p class="text-sm text-neutral-500 dark:text-neutral-500 mb-6">Availability: {freelancer.availability}</p>
<div class="flex items-center mb-4 mt-4 gap-2">
<CQIcon profileUrl={freelancer.forumProfile} size="sm" />
{freelancer.linkedIn && (
<a href={freelancer.linkedIn} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100 inline-flex items-center">
<Icon name="mdi:linkedin" class="w-6 h-6 mr-2" />
</a>
)}
{freelancer.github && (
<a href={freelancer.github} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100 inline-flex items-center">
<Icon name="mdi:github" class="w-6 h-6 mr-2" />
</a>
)}
</div>
<div class="short-pitch mb-6">
<p class="text-sm text-neutral-800 dark:text-neutral-100 mb-4">{freelancer.shortPitch}</p>
</div>
Expand All @@ -100,42 +104,19 @@ const freelancerImage: ImageMetadata | undefined = imageFiles[imagePath];
))}
</div>
</div>
{hasSprykerCertifications && (
<div class="mb-4">
<h3 class="text-lg font-semibold mb-2 text-neutral-800 dark:text-neutral-100">Spryker Certifications</h3>
{freelancer.sprykerCertifications.backEndDeveloper && (
<p class="text-sm text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Back End Developer</p>
)}
{freelancer.sprykerCertifications.solutionArchitect && (
<p class="text-sm text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Solution Architect</p>
)}
</div>
{hasCertifications && (
<div class="mb-4">
<h3 class="text-lg font-semibold mb-2 text-neutral-800 dark:text-neutral-100">Spryker Certifications</h3>
{freelancer.sprykerCertifications.backEndDeveloper && (
<p class="text-sm text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Back End Developer</p>
)}
{freelancer.sprykerCertifications.solutionArchitect && (
<p class="text-sm text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Solution Architect</p>
)}
</div>
)}
<div class="flex left items-center mb-4">
{freelancer.forumProfile && (
<a href={freelancer.forumProfile} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100 mr-2">
<Image
src={forumIcon}
alt="CommerceQuest Forum Profile"
width={24}
height={24}
class="w-6 h-6"
/>
</a>
)}
{freelancer.linkedIn && (
<a href={freelancer.linkedIn} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100">
<Icon name="mdi:linkedin" class="w-6 h-6 mr-1" />
</a>
)}
{freelancer.github && (
<a href={freelancer.github} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100">
<Icon name="mdi:github" class="w-6 h-6 mr-1" />
</a>
)}
</div>
<div class="mt-0 grid w-full gap-3 sm:inline-flex">
<SecondaryCTA title={`View ${freelancer.firstName}'s profile`} url={`/jobs/${freelancer.id}`} />
<SecondaryCTA title={`View ${freelancer.firstName}'s job profile`} url={`/jobs/${freelancer.id}`} />
</div>
</div>
</div>
54 changes: 54 additions & 0 deletions src/components/ui/icons/CQIcon.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
import { Image } from 'astro:assets';
import type { ImageMetadata } from 'astro';

interface Props {
profileUrl: string | undefined;
size?: 'sm' | 'lg'; // sm for FreelancerCard, lg for detail page
}

const { profileUrl, size = 'sm' } = Astro.props;

// Import CQ icon
const cqIconFiles: Record<string, ImageMetadata> = import.meta.glob('/src/images/cq.png', {
eager: true,
import: 'default'
});

// Get the CQ icon image
const cqIcon = Object.values(cqIconFiles)[0];

// Define sizes based on the size prop to match other icons exactly
const dimensions = {
sm: {
width: 24,
height: 24,
class: 'w-6 h-6 align-left' // Added align-middle for better vertical alignment
},
lg: {
width: 32,
height: 32,
class: 'w-8 h-8 align-left'
}
};

const { width, height, class: sizeClass } = dimensions[size];
---

{profileUrl && (
<a
href={profileUrl}
target="_blank"
rel="noopener noreferrer"
class="text-neutral-800 dark:text-neutral-100 mr-2 inline-flex items-center"
title="CommerceQuest Forum Profile"
>
<Image
src={cqIcon}
alt="CommerceQuest Forum Profile"
width={width}
height={height}
class={sizeClass}
/>
</a>
)}
2 changes: 1 addition & 1 deletion src/data_files/freelancers.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"lastName": "Hrychenko",
"photo": "volodymyr-rychenko.jpg",
"contact": "volodymyr.hrychenko@gmail.com",
"headline": "Spryker Developer | Tech Lead | Solution Architect",
"headline": "Spryker Developer & Solution Architect",
"language": "English, Ukrainian, Polish",
"location": "Warsaw",
"countryCode": "PL",
Expand Down
34 changes: 1 addition & 33 deletions src/pages/jobs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,11 @@ import MainLayout from '../layouts/MainLayout.astro';
import MainSection from "../components/ui/blocks/MainSection.astro";
import FreelancerCard from '../components/FreelancerCard.astro';
import freelancers from '../data_files/freelancers.json';
// Import PrimaryCTA component
import PrimaryCTA from "@components/ui/buttons/PrimaryCTA.astro";
import type { Freelancer } from '../types.ts';

const pageTitle = "Available Spryker Developers";

// Type definitions for the Freelancer object
type Freelancer = {
id: number;
firstName: string;
lastName: string;
photo: string;
headline?: string; // Optional
availability: string;
location: string;
countryCode: string; // ISO Country Code, e.g., "US"
language: string;
shortPitch: string;
linkedIn?: string; // Optional as not all freelancers may have LinkedIn
github?: string; // Optional as not all freelancers may have GitHub
sprykerCertifications: {
backEndDeveloper: boolean;
solutionArchitect: boolean;
};
skills: string[];
timezoneRange: string;
yearStartedWebDev: number;
yearStartedSpryker: number;
references?: string; // Optional, could include HTML markup
idealCustomer?: string; // Optional, could include HTML markup
locationFlexibility: string;
otherCertifications?: string; // Optional
employmentType: string;
contact: string;
forumProfile?: string;
isVisible: boolean;
};

// Function to shuffle the freelancers array
function shuffle(array: Freelancer[]): Freelancer[] {
for (let i = array.length - 1; i > 0; i--) {
Expand Down
68 changes: 21 additions & 47 deletions src/pages/jobs/[id].astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import MainSection from "../../components/ui/blocks/MainSection.astro";
import freelancers from '../../data_files/freelancers.json';
import { Image } from 'astro:assets';
import type { ImageMetadata } from 'astro';
import { Icon } from 'astro-icon/components';
// Import PrimaryCTA component
import { getFlagEmoji, hasSprykerCertifications } from '../../utils/utils.js';
import { Icon } from 'astro-icon/components'; // social icons
import CQIcon from '@components/ui/icons/CQIcon.astro';
import PrimaryCTA from "@components/ui/buttons/PrimaryCTA.astro";

// Import all images at build time with proper typing
Expand All @@ -14,14 +15,6 @@ const imageFiles: Record<string, ImageMetadata> = import.meta.glob('/src/images/
import: 'default'
});

const forumIconFiles: Record<string, ImageMetadata> = import.meta.glob('/src/images/cq.png', {
eager: true,
import: 'default'
});

// Get the forum icon image
const forumIcon = Object.values(forumIconFiles)[0];

export function getStaticPaths() {
return freelancers.filter(freelancer => freelancer.isVisible).map((freelancer) => ({
params: { id: freelancer.id.toString() },
Expand All @@ -32,17 +25,8 @@ export function getStaticPaths() {
const { freelancer } = Astro.props;
const pageTitle = `${`${freelancer.firstName} ${freelancer.lastName}`} - Spryker Developer Profile`;

// Define a boolean to check if any certification is true
const hasSprykerCertifications = freelancer.sprykerCertifications.backEndDeveloper || freelancer.sprykerCertifications.solutionArchitect;

// Function to convert country code to flag emoji
function getFlagEmoji(countryCode: string) {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
// Get certification status using the utility function
const hasCertifications = hasSprykerCertifications(freelancer.sprykerCertifications);

// Construct the correct image path
const imagePath = `/src/images/freelancers/${freelancer.photo}`;
Expand Down Expand Up @@ -98,26 +82,16 @@ const freelancerImage: ImageMetadata | undefined = imageFiles[imagePath];
<span class="text-gray-500 dark:text-gray-400">Image not available</span>
</div>
)}
<div class="flex space-x-4 m-4 text-center">
{freelancer.forumProfile && (
<a href={freelancer.forumProfile} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100">
<Image
src={forumIcon}
alt="CommerceQuest Forum Profile"
width={32}
height={32}
class="w-8 h-8"
/>
</a>
)}
<div class="flex items-center mb-4 mt-4 ml-3 gap-2">
<CQIcon profileUrl={freelancer.forumProfile} size="sm" />
gxjansen marked this conversation as resolved.
Show resolved Hide resolved
{freelancer.linkedIn && (
<a href={freelancer.linkedIn} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100">
<Icon name="mdi:linkedin" class="w-8 h-8 mr-1" />
<a href={freelancer.linkedIn} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100 inline-flex items-center">
<Icon name="mdi:linkedin" class="w-6 h-6 mr-2" />
</a>
)}
{freelancer.github && (
<a href={freelancer.github} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100">
<Icon name="mdi:github" class="w-8 h-8 mr-1" />
<a href={freelancer.github} target="_blank" rel="noopener noreferrer" class="text-neutral-800 dark:text-neutral-100 inline-flex items-center">
<Icon name="mdi:github" class="w-6 h-6 mr-2" />
</a>
)}
</div>
Expand Down Expand Up @@ -147,16 +121,16 @@ const freelancerImage: ImageMetadata | undefined = imageFiles[imagePath];
<div class="mb-4 text-neutral-500 dark:text-neutral-500">
<p>Started with Spryker in {freelancer.yearStartedSpryker}</p>
</div>
{hasSprykerCertifications && (
<div class="mb-4 text-neutral-800 dark:text-neutral-100">
<h3 class="font-semibold mb-2 ">{`${freelancer.firstName}'s `}Spryker Certifications</h3>
{freelancer.sprykerCertifications.backEndDeveloper && (
<p class="text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Back End Developer</p>
)}
{freelancer.sprykerCertifications.solutionArchitect && (
<p class="text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Solution Architect</p>
)}
</div>
{hasCertifications && (
<div class="mb-4">
<h3 class="text-lg font-semibold mb-2 text-neutral-800 dark:text-neutral-100">Spryker Certifications</h3>
{freelancer.sprykerCertifications.backEndDeveloper && (
<p class="text-sm text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Back End Developer</p>
)}
{freelancer.sprykerCertifications.solutionArchitect && (
<p class="text-sm text-neutral-500 dark:text-neutral-500">✓ Certified Spryker Solution Architect</p>
)}
</div>
)}
{freelancer.otherCertifications && (
<div class="mb-4 text-neutral-500 dark:text-neutral-500">
Expand Down
Loading