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', now: 'now',
}, },
}, },
seo: { meta: {
title: 'Mark Freeman - Senior React Developer', title: 'Mark Freeman - Senior React Developer',
description: 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.', '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', 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', title: 'Domain-Driven Design: Tackling Complexity in the Heart of Software',
author: 'Eric Evans', author: 'Eric Evans',
url: 'https://www.goodreads.com/book/show/179133.Domain_Driven_Design', url: 'https://www.goodreads.com/book/show/179133.Domain_Driven_Design',

View file

@ -1,9 +1,23 @@
import type { Locale } from 'date-fns'; import type { Locale } from 'date-fns';
export interface I18nConfig { export interface I18nConfig {
/**
* Language code used for date formatting, translations, and value of the page `lang` attribute.
*/
locale: Locale; locale: Locale;
/**
* Date format used when displaying date ranges in some sections.
*/
dateFormat: string; dateFormat: string;
/**
* List of translations used in the application.
*/
translations: { translations: {
/**
* Used in date ranges to represent the current date.
*/
now: string; 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 { 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; 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 { I18nConfig } from './config/i18n-config.types';
import type { PdfConfig } from './config/pdf-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 { EducationSection } from './sections/education-section.types';
import type { ExperienceSection } from './sections/experience-section.types'; import type { ExperienceSection } from './sections/experience-section.types';
import type { FavoritesSection } from './sections/favorites-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'; import type { TestimonialsSection } from './sections/testimonials-section.types';
export interface Config { export interface Config {
seo: SeoConfig; /**
* [WEB] Page metadata used for SEO and social media sharing.
*/
meta: MetaConfig;
/**
* Language and date display configuration.
*/
i18n: I18nConfig; i18n: I18nConfig;
/**
* [PDF] Configuration of the pdf generation.
*/
pdf?: PdfConfig; pdf?: PdfConfig;
} }
export interface Sections { export interface Sections {
/**
* Basic information about you.
*/
main: MainSection; main: MainSection;
/**
* Grouped lists of your skills.
*/
skills: SkillsSection; skills: SkillsSection;
/**
* Your employment history.
*/
experience: ExperienceSection; experience: ExperienceSection;
/**
* Your projects and initiatives.
*/
portfolio: PortfolioSection; portfolio: PortfolioSection;
/**
* Your education degrees and certifications.
*/
education: EducationSection; education: EducationSection;
/**
* [WEB] Recommendations from your previous employers and people you worked with.
*/
testimonials: TestimonialsSection; testimonials: TestimonialsSection;
/**
* [WEB] List of sources you use to gain knowledge and inspiration.
*/
favorites: FavoritesSection; favorites: FavoritesSection;
} }
/**
* All data used to generate the cv.
*/
export interface Data { export interface Data {
/**
* Global configuration of the web and pdf versions of the cv.
*/
config: Config; config: Config;
/**
* Configurations for the particular sections of the cv.
*/
sections: Sections; sections: Sections;
} }

View file

@ -1,13 +1,35 @@
import type { DateRange, Description, LinkButton, Section } from '../shared'; import type { DateRange, Description, LinkButton, Section } from '../shared';
export interface Diploma { export interface Diploma {
/**
* Name of the certificate or the degree you got.
*/
title: string; title: string;
/**
* Name of the institution that issued the certificate or degree.
*/
institution: string; institution: string;
/**
* Date range when you were studying in the institution.
*/
dates: DateRange; 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; description: Description;
/**
* [WEB] Links related to your studies (e.g. course/university website, link to realized project).
*/
links: LinkButton[]; links: LinkButton[];
} }
export interface EducationSection extends Section { export interface EducationSection extends Section {
/**
* List of your diplomas, certificates, .etc. Start with the most recent one.
*/
diplomas: Diploma[]; diplomas: Diploma[];
} }

View file

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

View file

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

View file

@ -1,13 +1,58 @@
import type { DownloadButton, Photo, LabelledValue, LinkButton, Section, Tag } from '../shared'; import type { DownloadButton, Photo, LabelledValue, LinkButton, Section, Tag } from '../shared';
export interface MainSection extends Section { export interface MainSection extends Section {
/**
* Your image.
*
* **Ratio**: 1:1
*
* **Display size**: 208x208px
*/
image: Photo; image: Photo;
/**
* Your name.
*/
fullName: string; fullName: string;
/**
* Your current role.
*/
role: string; role: string;
/**
* Label-value pairs with some key details about you.
*
* E.g. phone, email, location, expected salary.
*/
details: LabelledValue[]; 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[]; pdfDetails?: LabelledValue[];
/**
* A short overview of you and your experience.
*/
description: string; description: string;
/**
* [WEB] Any information that you want to highlight.
*/
tags: Tag[]; tags: Tag[];
/**
* [WEB] A button that will be used to download your resume.
*/
action: DownloadButton; action: DownloadButton;
/**
* [WEB] Your social media profiles.
*/
links: LinkButton[]; links: LinkButton[];
} }

View file

@ -1,16 +1,59 @@
import type { DateRange, Photo, LabelledValue, LinkButton, Section, TagsList, Description } from '../shared'; import type { DateRange, Photo, LabelledValue, LinkButton, Section, TagsList, Description } from '../shared';
export interface Project { export interface Project {
/**
* Name of the project.
*/
name: string; name: string;
/**
* [WEB] Logo of the project.
*
* **Ratio**: 1:1
*
* **Display size**: 120x120px
*/
image: Photo; image: Photo;
/**
* Date range when you were working on the project.
*/
dates: DateRange; dates: DateRange;
/**
* Label-value pairs with some key details about the project.
*/
details: LabelledValue[]; 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[]; 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; description: Description;
/**
* Any information that you want to highlight.
* We recommend to describe the technologies used in the project.
*/
tagsList: TagsList; tagsList: TagsList;
/**
* [WEB] Links related to your project (e.g. GitHub repository, live demo, mockups).
*/
links: LinkButton[]; links: LinkButton[];
} }
export interface PortfolioSection extends Section { export interface PortfolioSection extends Section {
/**
* List of your projects in a chronological order. Start with the most recent one.
*/
projects: Project[]; projects: Project[];
} }

View file

@ -5,14 +5,30 @@ export interface Skill extends Tag {}
export type SkillLevel = 1 | 2 | 3 | 4 | 5; export type SkillLevel = 1 | 2 | 3 | 4 | 5;
export interface LevelledSkill extends Skill { export interface LevelledSkill extends Skill {
/**
* Your level of skill proficiency in a 1-5 scale.
*/
level: SkillLevel; level: SkillLevel;
} }
export interface SkillSet { export interface SkillSet {
/**
* Title that will be displayed above the list of skills.
*/
title: string; 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[]; skills: Skill[] | LevelledSkill[];
} }
export interface SkillsSection extends Section { export interface SkillsSection extends Section {
/**
* Grouped lists of your skills.
*/
skillSets: SkillSet[]; skillSets: SkillSet[];
} }

View file

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

View file

@ -1,52 +1,162 @@
import type { CircleFlags, Fa6Brands, Fa6Solid, Ri, SimpleIcons } from 'iconify-icon-names'; 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; 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; 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]; 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 type Description = string | string[];
export interface SectionConfig { 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; title: string;
/**
* [WEB] URL hash used to navigate to the section.
*/
slug: string; slug: string;
/**
* [WEB] Icon used in sidebar navigation to represent the section.
*/
icon: IconName; icon: IconName;
/**
* Should section be displayed on the page.
*/
visible: boolean; visible: boolean;
} }
export interface Section { export interface Section {
/**
* Base information about the section.
*/
config: SectionConfig; config: SectionConfig;
} }
export interface LabelledValue { export interface LabelledValue {
/**
* Bolder text displayed on the left side of the value.
*/
label: string; label: string;
/**
* Text displayed on the right side.
* If a list of strings provided, they will be separated by a comma.
*/
value: string | string[]; value: string | string[];
/**
* URL that will be opened in a new tab, when the value is clicked.
*/
url?: string; url?: string;
/**
* [PDF] When labelled value is displayed in a grid, it will span the whole row.
*/
fullRow?: boolean; fullRow?: boolean;
} }
export interface Tag { export interface Tag {
/**
* Text displayed within the tag.
*/
name: string; name: string;
/**
* [WEB] Icon displayed next to the tag text.
*/
icon?: IconName; icon?: IconName;
/**
* [WEB] Color of the icon. By default, the color is inherited from the text.
*/
iconColor?: string; iconColor?: string;
/**
* [WEB] URL that will be opened in a new tab, when the tag is clicked.
*/
url?: string; url?: string;
/**
* [WEB] Text displayed in the tooltip when someone hovers over the tag.
*/
description?: string; description?: string;
} }
export interface TagsList { export interface TagsList {
/**
* [PDF] Text displayed before the list of tags.
*/
title: string; 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[]; tags: Tag[];
} }
export interface DownloadButton { export interface DownloadButton {
/**
* [WEB] Text displayed within the download button.
*/
label: string; label: string;
/**
* [WEB] URL or path to the CV file.
*/
url: string; 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; downloadedFileName?: string;
} }
export interface LinkButton { export interface LinkButton {
/**
* [WEB] Name displayed in the tooltip when someone hovers over the button.
*/
name: string; name: string;
/**
* [WEB] Icon displayed within the button.
*/
icon: IconName; icon: IconName;
/**
* [WEB] URL that will be opened in a new tab, when the button is clicked.
*/
url: string; url: string;
} }

View file

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

View file

@ -4,7 +4,11 @@ import { isServer } from './env';
const getInitialHash = () => { const getInitialHash = () => {
if (isServer) return ''; 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 = () => { const createHashState = () => {