diff --git a/src/assets/images/avatar.jpeg b/src/assets/images/avatar.jpeg deleted file mode 100644 index 039c665..0000000 Binary files a/src/assets/images/avatar.jpeg and /dev/null differ diff --git a/src/assets/images/avatar.png b/src/assets/images/avatar.png new file mode 100644 index 0000000..a8086af Binary files /dev/null and b/src/assets/images/avatar.png differ diff --git a/src/components/partials/Header.astro b/src/components/partials/Header.astro index 0c4662c..47a431e 100644 --- a/src/components/partials/Header.astro +++ b/src/components/partials/Header.astro @@ -5,26 +5,37 @@ const pathname = Astro.url.pathname; const isHomePage = pathname === '/'; const isWritingPage = pathname.startsWith('/writing'); - --- - -
+ +
diff --git a/src/components/ui/WorkExperience.astro b/src/components/ui/WorkExperience.astro index 99793a9..f00a935 100644 --- a/src/components/ui/WorkExperience.astro +++ b/src/components/ui/WorkExperience.astro @@ -8,7 +8,7 @@ interface Props { const { entry } = Astro.props; const { Content } = await render(entry); -const images = entry.data.images +const images = entry.data.images ? entry.data.images.map((img) => ({ src: img.src, alt: entry.data.title, @@ -19,7 +19,9 @@ const images = entry.data.images
  • - {entry.data.from} - {entry.data.to} + {entry.data.from} - {entry.data.to ?? 'Present'}
    @@ -29,24 +31,26 @@ const images = entry.data.images
    - - {images.length > 0 && ( -
    - {images.map((image, idx) => ( - - ))} -
    - )} + + { + images.length > 0 && ( +
    + {images.map((image, idx) => ( + + ))} +
    + ) + }
  • @@ -57,19 +61,19 @@ const images = entry.data.images import { createElement } from 'react'; const container = document.querySelector('.flex.gap-2.flex-wrap.mt-2'); - + if (container) { const clickHandler = (event: Event) => { const thumb = (event.target as HTMLElement).closest('.work-image-thumb'); if (!thumb || !container.contains(thumb)) return; - + const images = JSON.parse(thumb.getAttribute('data-images') || '[]'); const index = parseInt(thumb.getAttribute('data-index') || '0'); - + const lightboxContainer = document.createElement('div'); document.body.appendChild(lightboxContainer); const root = createRoot(lightboxContainer); - + const closeHandler = () => { root.unmount(); try { @@ -78,17 +82,17 @@ const images = entry.data.images // Container may have already been removed; ignore error } }; - + root.render( createElement(ImageLightbox, { images, isOpen: true, initialIndex: index, onClose: closeHandler, - }) + }), ); }; - + container.addEventListener('click', clickHandler); } - \ No newline at end of file + diff --git a/src/content.config.ts b/src/content.config.ts index 355fa2d..1a7978a 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -49,7 +49,7 @@ const jobCollection = defineCollection({ company: z.string(), location: z.string(), from: z.number(), - to: z.number().or(z.enum(['Now'])), + to: z.number().optional(), // optional if currently working url: z.string(), images: z.array(image()).optional(), }), diff --git a/src/content/jobs/coderdiaz-studio.mdx b/src/content/jobs/coderdiaz-studio.mdx new file mode 100644 index 0000000..769067e --- /dev/null +++ b/src/content/jobs/coderdiaz-studio.mdx @@ -0,0 +1,7 @@ +--- +title: Engineering & Design +company: Coderdiaz Studio +location: Mexico City +from: 2022 +url: https://coderdiaz.com +--- diff --git a/src/content/jobs/reboot-studio/index.mdx b/src/content/jobs/reboot-studio/index.mdx index 2707320..772eefa 100644 --- a/src/content/jobs/reboot-studio/index.mdx +++ b/src/content/jobs/reboot-studio/index.mdx @@ -3,7 +3,7 @@ title: Product Engineer company: Reboot Studio location: Mexico City from: 2022 -to: 2022 # Use 'Now' if the job is still active +to: 2022 url: https://reboot.studio images: - ./image-1.jpg diff --git a/src/content/jobs/sofia/index.mdx b/src/content/jobs/sofia/index.mdx index d23b8d2..5a1bff0 100644 --- a/src/content/jobs/sofia/index.mdx +++ b/src/content/jobs/sofia/index.mdx @@ -3,7 +3,7 @@ title: Product Engineer company: Sofia location: Mexico City from: 2025 -to: Now # Use 'Now' if the job is still active +to: 2025 url: https://sofiasalud.com --- diff --git a/src/lib/constants.ts b/src/lib/constants.ts index c7e6858..813e7b2 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,6 +1,6 @@ import type { z } from 'astro/zod'; import MetaDefaultImage from '@/assets/images/meta-default.jpg'; -import avatar from '@/assets/images/avatar.jpeg'; +import avatar from '@/assets/images/avatar.png'; import type { seoSchemaWithoutImage } from '@/content.config'; import astroConfig from 'astro.config.mjs'; @@ -11,17 +11,17 @@ export type AuthorInfo = { username?: string; location?: string; pronouns?: string; -} +}; export type Seo = z.infer & { image?: any; -} +}; type DefaultConfigurationType = { - baseUrl: string, + baseUrl: string; author: AuthorInfo; seo: Seo; -} +}; export const DEFAULT_CONFIGURATION: DefaultConfigurationType = { baseUrl: astroConfig.site || 'https://getcvfolio.com', @@ -35,12 +35,13 @@ export const DEFAULT_CONFIGURATION: DefaultConfigurationType = { }, seo: { title: 'CV Folio — An Astro template inspired on Read.cv', - description: 'Clean and aesthetic portfolio website for developers and designers', + description: + 'Clean and aesthetic portfolio website for developers and designers', type: 'website', image: MetaDefaultImage, twitter: { - creator: '@cvfolio' + creator: '@cvfolio', }, robots: 'noindex, nofollow', - } -}; \ No newline at end of file + }, +}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c07f4b0..daf263c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,5 +1,4 @@ import { DEFAULT_CONFIGURATION } from './constants'; -import type { CollectionEntry } from 'astro:content'; export const formatDate = (date: Date) => { const formatter = new Intl.DateTimeFormat('en-US', { @@ -23,18 +22,41 @@ export const includeDraft = (draft: boolean) => { return draft !== true; }; -export const sortJobsByDate = (jobs: CollectionEntry<'jobs'>[]) => { - // Convert "Now" to current year, otherwise returns the year as is - const getEndYear = (job: CollectionEntry<'jobs'>) => - job.data.to === 'Now' ? new Date().getFullYear() : job.data.to; - - return jobs.sort((current, next) => { - // Compare end years first, then fall back to start years if end years are equal - const [currentEnd, nextEnd] = [getEndYear(current), getEndYear(next)]; +/* + * Generic function for sorting items with start and optional end years + * - Items without an end year are considered ongoing and are sorted accordingly + * - Items are sorted by end year descending, then by start year descending + * - If end year is not present, current year is used for comparison + */ +export const sortByDateRange = < + T extends { data: { from: number; to?: number } }, +>( + items: T[], +) => { + const getCurrentYear = () => new Date().getFullYear(); + + return items.sort((current, next) => { + // Prioritize ongoing jobs (no 'to' field) first + const currentIsOngoing = current.data.to === undefined; + const nextIsOngoing = next.data.to === undefined; + + // If one is ongoing and the other isn't, ongoing comes first + if (currentIsOngoing && !nextIsOngoing) return -1; + if (!currentIsOngoing && nextIsOngoing) return 1; + + // If both are ongoing or both have end dates, sort by end year then start year + const currentEnd = current.data.to ?? getCurrentYear(); + const nextEnd = next.data.to ?? getCurrentYear(); return nextEnd - currentEnd || next.data.from - current.data.from; }); }; -export const sortTalksByYear = (talks: CollectionEntry<'talks'>[]) => { - return talks.sort((a, b) => b.data.year - a.data.year); +/* + * Generic function for sorting items by year in descending order + * - Items are sorted by year descending + */ +export const sortByYear = ( + items: T[], +) => { + return items.sort((a, b) => b.data.year - a.data.year); }; diff --git a/src/pages/index.astro b/src/pages/index.astro index ad19802..8ffe12f 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -6,7 +6,7 @@ import Author from '@/components/ui/Author.astro'; import { DEFAULT_CONFIGURATION } from '@/lib/constants'; import WorkExperience from '@/components/ui/WorkExperience.astro'; import Talk from '@/components/ui/Talk.astro'; -import { sortJobsByDate, sortTalksByYear } from '@/lib/utils'; +import { sortByDateRange, sortByYear } from '@/lib/utils'; const entry = await getEntry('pages', 'homepage'); @@ -17,10 +17,12 @@ if (!entry) { const { Content } = await render(entry); const links = await getCollection('links'); + const jobs = await getCollection('jobs'); -const sortedJobs = sortJobsByDate(jobs); +const sortedJobs = sortByDateRange(jobs); + const talks = await getCollection('talks'); -const sortedTalks = sortTalksByYear(talks); +const sortedTalks = sortByYear(talks); ---