summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-01-20 19:32:36 +0100
committerArmand Philippot <git@armandphilippot.com>2022-01-20 19:32:36 +0100
commit50a3df40bc8d41271c4cd8d6873a6d4e1dd87b42 (patch)
treeee338cebabbcce5c53c417172b25367c00e19e17 /src
parentdf9e8b1985a0f1c71c0657e72fad008bf437faba (diff)
chore: add a project summary component
Diffstat (limited to 'src')
-rw-r--r--src/components/ProjectSummary/ProjectSummary.module.scss73
-rw-r--r--src/components/ProjectSummary/ProjectSummary.tsx122
-rw-r--r--src/pages/projet/[slug].tsx2
-rw-r--r--src/services/repos/github.ts15
-rw-r--r--src/ts/types/github.ts5
5 files changed, 217 insertions, 0 deletions
diff --git a/src/components/ProjectSummary/ProjectSummary.module.scss b/src/components/ProjectSummary/ProjectSummary.module.scss
new file mode 100644
index 0000000..cf1e77f
--- /dev/null
+++ b/src/components/ProjectSummary/ProjectSummary.module.scss
@@ -0,0 +1,73 @@
+@use "@styles/abstracts/functions" as fun;
+
+.wrapper {
+ margin-bottom: var(--spacing-md);
+ padding: var(--spacing-sm) var(--spacing-md) var(--spacing-md);
+ border: fun.convert-px(1) solid var(--color-border);
+}
+
+.cover {
+ height: fun.convert-px(150);
+ position: relative;
+}
+
+.info {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(20ch, 1fr));
+ align-items: start;
+ justify-content: left;
+ column-gap: var(--spacing-md);
+ margin: var(--spacing-md) 0 0;
+}
+
+.inline-data {
+ display: inline-block;
+ margin-top: fun.convert-px(3);
+
+ &:not(:last-of-type) {
+ margin-right: var(--spacing-xs);
+ }
+}
+
+.techno {
+ padding: 0 var(--spacing-2xs);
+ border: fun.convert-px(1) solid var(--color-primary-darker);
+}
+
+.repo {
+ display: block;
+ width: 3em;
+ height: 3em;
+ background: none;
+ box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
+ var(--color-shadow),
+ fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-1)
+ var(--color-shadow),
+ fun.convert-px(3) fun.convert-px(4) fun.convert-px(4) fun.convert-px(-3)
+ var(--color-shadow),
+ 0 0 0 0 var(--color-shadow);
+ transition: all 0.3s linear 0s;
+
+ &:hover,
+ &:focus {
+ box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
+ var(--color-shadow),
+ fun.convert-px(1) fun.convert-px(1) fun.convert-px(2) fun.convert-px(-1)
+ var(--color-shadow-light),
+ fun.convert-px(3) fun.convert-px(3) fun.convert-px(4) fun.convert-px(-4)
+ var(--color-shadow-light),
+ fun.convert-px(6) fun.convert-px(6) fun.convert-px(10) fun.convert-px(-3)
+ var(--color-shadow);
+ transform: scale(1.15);
+ }
+
+ &:focus {
+ outline: var(--color-primary) dashed fun.convert-px(2);
+ }
+
+ &:active {
+ box-shadow: 0 0 0 0 var(--color-shadow);
+ outline: none;
+ transform: scale(0.9);
+ }
+}
diff --git a/src/components/ProjectSummary/ProjectSummary.tsx b/src/components/ProjectSummary/ProjectSummary.tsx
new file mode 100644
index 0000000..0d00f06
--- /dev/null
+++ b/src/components/ProjectSummary/ProjectSummary.tsx
@@ -0,0 +1,122 @@
+import GithubIcon from '@assets/images/social-media/github.svg';
+import GitlabIcon from '@assets/images/social-media/gitlab.svg';
+import { t } from '@lingui/macro';
+import { getRepoData } from '@services/repos/github';
+import { ProjectMeta } from '@ts/types/app';
+import { RepoData } from '@ts/types/github';
+import { slugify } from '@utils/helpers/slugify';
+import Image from 'next/image';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import styles from './ProjectSummary.module.scss';
+
+const ProjectSummary = ({
+ slug,
+ title,
+ cover,
+ meta,
+}: {
+ slug: string;
+ title: string;
+ cover: string;
+ meta: ProjectMeta;
+}) => {
+ const { license, repos, technologies } = meta;
+ const [data, setData] = useState<RepoData>();
+ const { locale } = useRouter();
+ const githubUser = process.env.NEXT_PUBLIC_GITHUB_USER;
+
+ useEffect(() => {
+ getRepoData(slug)
+ .then((repoData) => setData(repoData))
+ .catch((e) => console.error(e));
+ }, [slug]);
+
+ const getFormattedDate = (date: string) => {
+ const dateOptions: Intl.DateTimeFormatOptions = {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ };
+
+ return new Date(date).toLocaleDateString(locale, dateOptions);
+ };
+
+ return (
+ <div className={styles.wrapper}>
+ <div className={styles.cover}>
+ <Image
+ src={cover}
+ alt={t`${title} preview`}
+ layout="fill"
+ objectFit="contain"
+ />
+ </div>
+ <dl className={styles.info}>
+ {data && (
+ <div className={styles.info__item}>
+ <dt>{t`Created on`}</dt>
+ <dd>{t`${getFormattedDate(data.created_at)}`}</dd>
+ </div>
+ )}
+ {data && (
+ <div className={styles.info__item}>
+ <dt>{t`Last updated on`}</dt>
+ <dd>{t`${getFormattedDate(data.updated_at)}`}</dd>
+ </div>
+ )}
+ <div className={styles.info__item}>
+ <dt>{t`License`}</dt>
+ <dd>{license}</dd>
+ </div>
+ {technologies && (
+ <div className={styles.info__item}>
+ <dt>{t`Technologies`}</dt>
+ {technologies.map((techno) => (
+ <dd
+ key={slugify(techno)}
+ className={`${styles.techno} ${styles['inline-data']}`}
+ >
+ {techno}
+ </dd>
+ ))}
+ </div>
+ )}
+ {repos && (
+ <div className={styles.info__item}>
+ <dt>{t`Repositories`}</dt>
+ {repos.github && (
+ <dd className={styles['inline-data']}>
+ <a href={repos.github} className={styles.repo}>
+ <GithubIcon />
+ <span className="screen-reader-text">Github</span>
+ </a>
+ </dd>
+ )}
+ {repos.gitlab && (
+ <dd className={styles['inline-data']}>
+ <a href={repos.gitlab} className={styles.repo}>
+ <GitlabIcon />
+ <span className="screen-reader-text">Gitlab</span>
+ </a>
+ </dd>
+ )}
+ </div>
+ )}
+ {data && (
+ <div>
+ <dt>{t`Popularity`}</dt>
+ <dd>
+ ⭐&nbsp;
+ <a href={`https://github.com/${githubUser}/${slug}/stargazers`}>
+ {t`${data.stargazers_count} stars on Github`}
+ </a>
+ </dd>
+ </div>
+ )}
+ </dl>
+ </div>
+ );
+};
+
+export default ProjectSummary;
diff --git a/src/pages/projet/[slug].tsx b/src/pages/projet/[slug].tsx
index 03aa6ea..ce5dd66 100644
--- a/src/pages/projet/[slug].tsx
+++ b/src/pages/projet/[slug].tsx
@@ -1,5 +1,6 @@
import { getLayout } from '@components/Layouts/Layout';
import PostHeader from '@components/PostHeader/PostHeader';
+import ProjectSummary from '@components/ProjectSummary/ProjectSummary';
import Sidebar from '@components/Sidebar/Sidebar';
import { ToC } from '@components/Widgets';
import { config } from '@config/website';
@@ -102,6 +103,7 @@ const Project: NextPageWithLayout<ProjectProps> = ({
<ToC />
</Sidebar>
<div className={styles.body}>
+ <ProjectSummary slug={id} title={title} cover={cover} meta={meta} />
<ProjectContent />
</div>
</article>
diff --git a/src/services/repos/github.ts b/src/services/repos/github.ts
new file mode 100644
index 0000000..37400ad
--- /dev/null
+++ b/src/services/repos/github.ts
@@ -0,0 +1,15 @@
+import { RepoData } from '@ts/types/github';
+
+/**
+ * Retrieve repository data from Github by slug.
+ * @param repo - The repository slug.
+ * @returns {Promise<RepoData>} - The repository data.
+ */
+export const getRepoData = async (repo: string): Promise<RepoData> => {
+ const user = process.env.NEXT_PUBLIC_GITHUB_USER;
+ const api = `https://api.github.com/repos/${user}/${repo}`;
+
+ const response = await fetch(api);
+
+ return response.json();
+};
diff --git a/src/ts/types/github.ts b/src/ts/types/github.ts
new file mode 100644
index 0000000..923fb08
--- /dev/null
+++ b/src/ts/types/github.ts
@@ -0,0 +1,5 @@
+export type RepoData = {
+ created_at: string;
+ updated_at: string;
+ stargazers_count: number;
+};