Document interfaces that describes cv data (#168)

Co-authored-by: Szymon Kin <68154191+hoolek77@users.noreply.github.com>
This commit is contained in:
Konrad Szwarc 2023-01-25 10:39:45 +01:00 committed by GitHub
parent 5ed3ad4fa3
commit 8d0cd278eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 518 additions and 21 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -10,7 +10,7 @@ const config = {
now: 'now',
},
},
seo: {
meta: {
title: 'Mark Freeman - Senior React Developer',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales ac dui at vestibulum. In condimentum metus id dui tincidunt, in blandit mi vehicula.',

View file

@ -18,7 +18,7 @@ const favoritesSectionData = {
url: 'https://www.goodreads.com/book/show/4099.The_Pragmatic_Programmer',
},
{
image: 'https://m.media-amazon.com/images/I/61aFldsgAmL._SY344_BO1,204,203,200_QL70_ML2_.jpg',
image: import('@/assets/favorites/books/book-2.jpg'),
title: 'Domain-Driven Design: Tackling Complexity in the Heart of Software',
author: 'Eric Evans',
url: 'https://www.goodreads.com/book/show/179133.Domain_Driven_Design',

View file

@ -1,9 +1,23 @@
import type { Locale } from 'date-fns';
export interface I18nConfig {
/**
* Language code used for date formatting, translations, and value of the page `lang` attribute.
*/
locale: Locale;
/**
* Date format used when displaying date ranges in some sections.
*/
dateFormat: string;
/**
* List of translations used in the application.
*/
translations: {
/**
* Used in date ranges to represent the current date.
*/
now: string;
};
}

View file

@ -0,0 +1,55 @@
export interface MetaConfig {
/**
* [WEB] Page title.
*
* Displayed as browser tab title and in search results.
* It's recommended to keep it between 30 and 60 characters.
*
* @see https://www.screamingfrog.co.uk/learn-seo/page-title
*/
title: string;
/**
* [WEB] Page description.
*
* Displayed under the title in search results.
* It's recommended to keep it between 70 and 155 characters.
*
* @see https://www.screamingfrog.co.uk/learn-seo/meta-description
*/
description: string;
/**
* [WEB] URL or path to the page's favicon.
*
* Specified icon will be displayed next to the page title in browser tab.
*/
favicon: string;
/**
* [WEB] Title used in open graph links.
*
* If not specified, the title property will be used.
*
* @see https://ahrefs.com/blog/open-graph-meta-tags
*/
ogTitle?: string;
/**
* [WEB] Description used in open graph links.
*
* If not specified, the description property will be used.
*
* @see https://ahrefs.com/blog/open-graph-meta-tags
*/
ogDescription?: string;
/**
* [WEB] Image used in open graph links.
*
* It's recommended to keep it between 30 and 60 characters.
*
* @see https://ahrefs.com/blog/open-graph-meta-tags
*/
ogImage?: string;
}

View file

@ -1,3 +1,8 @@
export interface PdfConfig {
/**
* [PDF] Displays footer with specified content on each PDF page.
*
* You can use it to add the data processing clause.
*/
footer?: string;
}

View file

@ -1,8 +0,0 @@
export interface SeoConfig {
title: string;
description: string;
favicon: string;
ogTitle?: string;
ogDescription?: string;
ogImage?: string;
}

View file

@ -1,6 +1,6 @@
import type { I18nConfig } from './config/i18n-config.types';
import type { PdfConfig } from './config/pdf-config.types';
import type { SeoConfig } from './config/seo-config.types';
import type { MetaConfig } from './config/meta-config.types';
import type { EducationSection } from './sections/education-section.types';
import type { ExperienceSection } from './sections/experience-section.types';
import type { FavoritesSection } from './sections/favorites-section.types';
@ -10,22 +10,70 @@ import type { SkillsSection } from './sections/skills-section.types';
import type { TestimonialsSection } from './sections/testimonials-section.types';
export interface Config {
seo: SeoConfig;
/**
* [WEB] Page metadata used for SEO and social media sharing.
*/
meta: MetaConfig;
/**
* Language and date display configuration.
*/
i18n: I18nConfig;
/**
* [PDF] Configuration of the pdf generation.
*/
pdf?: PdfConfig;
}
export interface Sections {
/**
* Basic information about you.
*/
main: MainSection;
/**
* Grouped lists of your skills.
*/
skills: SkillsSection;
/**
* Your employment history.
*/
experience: ExperienceSection;
/**
* Your projects and initiatives.
*/
portfolio: PortfolioSection;
/**
* Your education degrees and certifications.
*/
education: EducationSection;
/**
* [WEB] Recommendations from your previous employers and people you worked with.
*/
testimonials: TestimonialsSection;
/**
* [WEB] List of sources you use to gain knowledge and inspiration.
*/
favorites: FavoritesSection;
}
/**
* All data used to generate the cv.
*/
export interface Data {
/**
* Global configuration of the web and pdf versions of the cv.
*/
config: Config;
/**
* Configurations for the particular sections of the cv.
*/
sections: Sections;
}

View file

@ -1,13 +1,35 @@
import type { DateRange, Description, LinkButton, Section } from '../shared';
export interface Diploma {
/**
* Name of the certificate or the degree you got.
*/
title: string;
/**
* Name of the institution that issued the certificate or degree.
*/
institution: string;
/**
* Date range when you were studying in the institution.
*/
dates: DateRange;
/**
* A short overview of your studies. You can write it as a paragraph (string) or as a list of bullet points (string[]).
*/
description: Description;
/**
* [WEB] Links related to your studies (e.g. course/university website, link to realized project).
*/
links: LinkButton[];
}
export interface EducationSection extends Section {
/**
* List of your diplomas, certificates, .etc. Start with the most recent one.
*/
diplomas: Diploma[];
}

View file

@ -1,14 +1,41 @@
import type { DateRange, Description, LinkButton, Section, TagsList } from '../shared';
export interface Job {
/**
* Your position in the company.
*/
role: string;
/**
* Name of the company.
*/
company: string;
/**
* Date range when you were working in the company.
*/
dates: DateRange;
/**
* A short overview of your job. You can write it as a paragraph (string) or as a list of bullet points (string[]).
*/
description: Description;
/**
* Any information that you want to highlight.
* We recommend to describe the technologies used in the project.
*/
tagsList: TagsList;
/**
* [WEB] Links related to your job (e.g. production app, company's website, project website).
*/
links: LinkButton[];
}
export interface ExperienceSection extends Section {
/**
* List of your jobs in a chronological order. Start with the most recent one.
*/
jobs: Job[];
}

View file

@ -1,39 +1,129 @@
import type { Photo, Section } from '../shared';
export interface Book {
/**
* [WEB] Book title.
*/
title: string;
/**
* [WEB] Book cover.
*
* **Ratio**: 3:4
*
* **Display size**: 300x400px
*/
image: Photo;
/**
* [WEB] Full name of the book author.
*/
author: string;
/**
* [WEB] Website to buy the book or read more about it.
*/
url: string;
}
export interface Person {
/**
* [WEB] Full name of the person.
*/
name: string;
/**
* [WEB] Photo of the person.
*
* **Ratio**: 1:1
*
* **Display size**: 200x200px
*/
image: Photo;
/**
* [WEB] Main website related to the person.
*/
url?: string;
}
export interface Video {
/**
* [WEB] Title of the video.
*/
title: string;
/**
* [WEB] Thumbnail of the video.
*
* **Ratio**: 16:9
*
* **Display size**: 448x252px
*/
image: Photo;
/**
* [WEB] Link to the video.
*/
url: string;
}
export interface Media {
/**
* [WEB] Title of the media.
*/
title: string;
/**
* [WEB] Type of the media (e.g. podcast, blog, newsletter, YouTube channel, .etc).
*/
type: string;
/**
* [WEB] Logo of the media.
*
* **Ratio**: 1:1
*
* **Display size**: 200x200px
*/
image: Photo;
/**
* [WEB] URL to the main website related to the media.
*/
url: string;
}
export interface SubSection<Data = unknown> {
/**
* [WEB] Title that will be displayed above the list of items.
*/
title: string;
/**
* [WEB] List of items to display within the subsection.
*/
data: Data[];
}
export interface FavoritesSection extends Section {
/**
* [WEB] List of your favorite books.
*/
books?: SubSection<Book>;
/**
* [WEB] List of the people that inspire you.
*/
people?: SubSection<Person>;
/**
* [WEB] List of the videos you learned the most from.
*/
videos?: SubSection<Video>;
/**
* [WEB] List of other media types that helps you to growth in your field.
*/
medias?: SubSection<Media>;
}

View file

@ -1,13 +1,58 @@
import type { DownloadButton, Photo, LabelledValue, LinkButton, Section, Tag } from '../shared';
export interface MainSection extends Section {
/**
* Your image.
*
* **Ratio**: 1:1
*
* **Display size**: 208x208px
*/
image: Photo;
/**
* Your name.
*/
fullName: string;
/**
* Your current role.
*/
role: string;
/**
* Label-value pairs with some key details about you.
*
* E.g. phone, email, location, expected salary.
*/
details: LabelledValue[];
/**
* [PDF] Labeled-value pairs that will be used in the PDF version of your resume.
*
* You can use it to add your social media profiles as those listed under the `links` property aren't used in the PDF.
*
* If not provided, the `details` will be used instead.
*/
pdfDetails?: LabelledValue[];
/**
* A short overview of you and your experience.
*/
description: string;
/**
* [WEB] Any information that you want to highlight.
*/
tags: Tag[];
/**
* [WEB] A button that will be used to download your resume.
*/
action: DownloadButton;
/**
* [WEB] Your social media profiles.
*/
links: LinkButton[];
}

View file

@ -1,16 +1,59 @@
import type { DateRange, Photo, LabelledValue, LinkButton, Section, TagsList, Description } from '../shared';
export interface Project {
/**
* Name of the project.
*/
name: string;
/**
* [WEB] Logo of the project.
*
* **Ratio**: 1:1
*
* **Display size**: 120x120px
*/
image: Photo;
/**
* Date range when you were working on the project.
*/
dates: DateRange;
/**
* Label-value pairs with some key details about the project.
*/
details: LabelledValue[];
/**
* [PDF] Labeled-value pairs that will be used in the PDF version of your resume.
*
* You can use it to add some links related to your project as those listed under the `links` property aren't used in the PDF.
*
* If not provided, the `details` will be used instead.
*/
pdfDetails?: LabelledValue[];
/**
* A short overview of the project. You can write it as a paragraph (string) or as a list of bullet points (string[]).
*/
description: Description;
/**
* Any information that you want to highlight.
* We recommend to describe the technologies used in the project.
*/
tagsList: TagsList;
/**
* [WEB] Links related to your project (e.g. GitHub repository, live demo, mockups).
*/
links: LinkButton[];
}
export interface PortfolioSection extends Section {
/**
* List of your projects in a chronological order. Start with the most recent one.
*/
projects: Project[];
}

View file

@ -5,14 +5,30 @@ export interface Skill extends Tag {}
export type SkillLevel = 1 | 2 | 3 | 4 | 5;
export interface LevelledSkill extends Skill {
/**
* Your level of skill proficiency in a 1-5 scale.
*/
level: SkillLevel;
}
export interface SkillSet {
/**
* Title that will be displayed above the list of skills.
*/
title: string;
/**
* List of skills with or without levels.
*
* If you want to displays skills with levels, we recommend to also provide the `description` property.
* This way anyone viewing your resume will know what is the meaning of each level.
*/
skills: Skill[] | LevelledSkill[];
}
export interface SkillsSection extends Section {
/**
* Grouped lists of your skills.
*/
skillSets: SkillSet[];
}

View file

@ -1,13 +1,39 @@
import type { Photo, LinkButton, Section } from '../shared';
export interface Testimonial {
/**
* [WEB] Photo of the testimonial author.
*
* **Ratio**: 1:1
*
* **Display size**: 56x56px
*/
image: Photo;
/**
* [WEB] Full name of the testimonial author.
*/
author: string;
/**
* [WEB] Your relation to the testimonial author (e.g. supervisor, colleague, subordinate).
*/
relation: string;
/**
* [WEB] Content of the testimonial.
*/
content: string;
/**
* [WEB] Social media (e.g. LinkedIn profile, website) of the testimonial author.
*/
links: LinkButton[];
}
export interface TestimonialsSection extends Section {
/**
* [WEB] List of your testimonials in a chronological order. Start with the most recent one.
*/
testimonials: Testimonial[];
}

View file

@ -1,52 +1,162 @@
import type { CircleFlags, Fa6Brands, Fa6Solid, Ri, SimpleIcons } from 'iconify-icon-names';
/**
* Name of the icon from the iconify library.
*
* @see https://icon-sets.iconify.design
*/
export type IconName = Fa6Brands | Fa6Solid | SimpleIcons | CircleFlags | Ri;
/**
* - Dynamic import of the image from `src/assets` folder. Recommended as it enables image optimization.
* - Path to the image placed in the `public` folder.
* - URL of the image stored online.
*/
export type Photo = Promise<{ default: ImageMetadata }> | string;
/**
* Two date objects representing some time period.
*
* If the second date is `null`, it means that the period is still ongoing.
* In such case, the translation from `config.i18n.translations.now` will be used.
*/
export type DateRange = [from: Date, to: Date | null];
/**
* If a string is provided, it will be displayed as a single justified paragraph.
* If an array of strings is provided it will be displayed as a list.
*/
export type Description = string | string[];
export interface SectionConfig {
/**
* Name displayed as a section header (except for the main section).
*
* [WEB] Content of the tooltip displayed when someone hovers over the section in the sidebar.
*/
title: string;
/**
* [WEB] URL hash used to navigate to the section.
*/
slug: string;
/**
* [WEB] Icon used in sidebar navigation to represent the section.
*/
icon: IconName;
/**
* Should section be displayed on the page.
*/
visible: boolean;
}
export interface Section {
/**
* Base information about the section.
*/
config: SectionConfig;
}
export interface LabelledValue {
/**
* Bolder text displayed on the left side of the value.
*/
label: string;
/**
* Text displayed on the right side.
* If a list of strings provided, they will be separated by a comma.
*/
value: string | string[];
/**
* URL that will be opened in a new tab, when the value is clicked.
*/
url?: string;
/**
* [PDF] When labelled value is displayed in a grid, it will span the whole row.
*/
fullRow?: boolean;
}
export interface Tag {
/**
* Text displayed within the tag.
*/
name: string;
/**
* [WEB] Icon displayed next to the tag text.
*/
icon?: IconName;
/**
* [WEB] Color of the icon. By default, the color is inherited from the text.
*/
iconColor?: string;
/**
* [WEB] URL that will be opened in a new tab, when the tag is clicked.
*/
url?: string;
/**
* [WEB] Text displayed in the tooltip when someone hovers over the tag.
*/
description?: string;
}
export interface TagsList {
/**
* [PDF] Text displayed before the list of tags.
*/
title: string;
/**
* Tags to be displayed within the list.
* [WEB] Tags are displayed as gray blocks. All tag properties are used.
* [PDF] Tags are displayed comma separated list. Only the `name` property are used.
*/
tags: Tag[];
}
export interface DownloadButton {
/**
* [WEB] Text displayed within the download button.
*/
label: string;
/**
* [WEB] URL or path to the CV file.
*/
url: string;
/**
* [WEB] Name of the file that will be downloaded.
*
* If not specified, the original file name will be used and file will open in a PDF viewer in some browsers.
*
* If specified, the file will be downloaded immediately (without preview) in all browsers.
*/
downloadedFileName?: string;
}
export interface LinkButton {
/**
* [WEB] Name displayed in the tooltip when someone hovers over the button.
*/
name: string;
/**
* [WEB] Icon displayed within the button.
*/
icon: IconName;
/**
* [WEB] URL that will be opened in a new tab, when the button is clicked.
*/
url: string;
}

View file

@ -2,9 +2,9 @@
import type { Data } from '@/types/data';
import Analytics from '@/web/analytics/analytics.astro';
export interface Props extends Pick<Data['config'], 'seo' | 'i18n'> {}
export interface Props extends Pick<Data['config'], 'meta' | 'i18n'> {}
const { seo, i18n } = Astro.props;
const { meta, i18n } = Astro.props;
---
<!DOCTYPE html>
@ -13,12 +13,12 @@ const { seo, i18n } = Astro.props;
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>{seo.title}</title>
<meta name="description" content={seo.description} />
<link rel="icon" type="image/svg+xml" href={seo.favicon} />
<meta property="og:title" content={seo.ogTitle ?? seo.title} />
<meta property="og:description" content={seo.ogDescription ?? seo.description} />
{seo.ogImage && <meta property="og:image" content={seo.ogImage} />}
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<link rel="icon" type="image/svg+xml" href={meta.favicon} />
<meta property="og:title" content={meta.ogTitle ?? meta.title} />
<meta property="og:description" content={meta.ogDescription ?? meta.description} />
{meta.ogImage && <meta property="og:image" content={meta.ogImage} />}
<script is:inline>
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {

View file

@ -4,7 +4,11 @@ import { isServer } from './env';
const getInitialHash = () => {
if (isServer) return '';
return window.location.hash || `#${sections.main.config.slug}`;
const firstVisibleSection = Object.values(sections).find((section) => section.config.visible);
if (!firstVisibleSection) return '';
return window.location.hash || `#${firstVisibleSection.config.slug}`;
};
const createHashState = () => {