Add prettier configuration

This commit is contained in:
Hazem Krimi
2023-07-01 00:38:49 +01:00
parent de22cafb12
commit 5e01140d6e
57 changed files with 1896 additions and 1718 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[*.{ts,js,json,tsx,md,mdx,html}]
charset = utf-8
indent_style = tab
indent_style = space
indent_size = 2
quote_type= single
+32
View File
@@ -0,0 +1,32 @@
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
+8
View File
@@ -0,0 +1,8 @@
{
"printWidth": 80,
"singleQuote": true,
"jsxSingleQuote": true,
"tabWidth": 2,
"semi": true,
"quoteProps": "as-needed"
}
+15 -9
View File
@@ -2,16 +2,22 @@ import { Props } from './types';
import { StyledButton } from './styles';
const Button = ({
variant = 'text',
href,
target,
onClick,
children,
className
variant = 'text',
href,
target,
onClick,
children,
className,
}: Props) => (
<StyledButton href={href} target={target} className={className} onClick={onClick} variant={variant}>
{children}
</StyledButton>
<StyledButton
href={href}
target={target}
className={className}
onClick={onClick}
variant={variant}
>
{children}
</StyledButton>
);
export default Button;
+41 -37
View File
@@ -3,45 +3,49 @@ import Link from 'next/link';
import { Props } from './types';
export const StyledButton = styled(Link)<Props>`
position: relative;
display: inline;
cursor: pointer;
background: none;
color: var(--text);
border: ${({ variant }) =>
variant === 'outline' ? '2px solid var(--text)' : '2px solid transparent'};
font-weight: bold;
text-transform: ${({ variant }) => (variant === 'outline' ? 'uppercase' : 'inherit')};
padding: ${({ variant }) => (variant === 'outline' ? '.5rem 1rem' : '0rem')};
text-align: left;
text-decoration: none;
transition: color 250ms ease-in-out, border 250ms ease-in-out;
z-index: 1;
position: relative;
display: inline;
cursor: pointer;
background: none;
color: var(--text);
border: ${({ variant }) =>
variant === 'outline' ? '2px solid var(--text)' : '2px solid transparent'};
font-weight: bold;
text-transform: ${({ variant }) =>
variant === 'outline' ? 'uppercase' : 'inherit'};
padding: ${({ variant }) => (variant === 'outline' ? '.5rem 1rem' : '0rem')};
text-align: left;
text-decoration: none;
transition: color 250ms ease-in-out, border 250ms ease-in-out;
z-index: 1;
@media (max-width: 768px) {
padding: ${({ variant }) => (variant === 'outline' ? '.5rem .75rem' : '0rem')};
}
@media (max-width: 768px) {
padding: ${({ variant }) =>
variant === 'outline' ? '.5rem .75rem' : '0rem'};
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: -1;
background-color: ${({ variant }) => (variant === 'outline' ? 'var(--text)' : 'inherit')};
transition: transform 250ms ease-in-out;
transform: scaleX(0);
transform-origin: left;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: -1;
background-color: ${({ variant }) =>
variant === 'outline' ? 'var(--text)' : 'inherit'};
transition: transform 250ms ease-in-out;
transform: scaleX(0);
transform-origin: left;
}
&:hover {
color: ${({ variant }) => (variant === 'outline' ? 'var(--background)' : 'inherit')};
border: 2px solid transparent;
}
&:hover {
color: ${({ variant }) =>
variant === 'outline' ? 'var(--background)' : 'inherit'};
border: 2px solid transparent;
}
&:hover::before {
transform: scaleX(1);
}
&:hover::before {
transform: scaleX(1);
}
`;
+6 -6
View File
@@ -1,8 +1,8 @@
export type Props = {
variant?: 'outline' | 'text';
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
children: React.ReactNode;
className?: string;
variant?: 'outline' | 'text';
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
children: React.ReactNode;
className?: string;
};
+34 -21
View File
@@ -2,27 +2,40 @@ import Image from 'next/image';
import { StyledCard } from './styles';
import { Props } from './types';
const Card = ({ title, description, image, tags, href, target, onClick }: Props) => {
return (
<StyledCard href={href} onClick={onClick} image={image ? Boolean(image) : undefined} target={target}>
<div className='card-content'>
<h3>{title}</h3>
<p>{description}</p>
{tags && (
<div className='tags-wrapper'>
{tags.map((tag, index) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
)}
</div>
{image && (
<div className='card-image'>
<Image alt={title} src={image} fill />
</div>
)}
</StyledCard>
);
const Card = ({
title,
description,
image,
tags,
href,
target,
onClick,
}: Props) => {
return (
<StyledCard
href={href}
onClick={onClick}
image={image ? Boolean(image) : undefined}
target={target}
>
<div className='card-content'>
<h3>{title}</h3>
<p>{description}</p>
{tags && (
<div className='tags-wrapper'>
{tags.map((tag, index) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
)}
</div>
{image && (
<div className='card-image'>
<Image alt={title} src={image} fill />
</div>
)}
</StyledCard>
);
};
export default Card;
+59 -58
View File
@@ -2,75 +2,76 @@ import styled from 'styled-components';
import Link from 'next/link';
export const StyledCard = styled(Link)<{ image?: boolean }>`
cursor: pointer;
width: 100%;
display: grid;
grid-template-columns: ${({ image }) => (image ? 'auto 9.375rem' : 'auto')};
align-items: stretch;
transition: color 0ms ease-in-out;
text-decoration: none;
color: var(--text);
cursor: pointer;
width: 100%;
display: grid;
grid-template-columns: ${({ image }) => (image ? 'auto 9.375rem' : 'auto')};
align-items: stretch;
transition: color 0ms ease-in-out;
text-decoration: none;
color: var(--text);
@media (max-width: 320px) {
grid-template-columns: ${({ image }) => (image ? 'auto 7.813rem' : 'auto')};
}
@media (max-width: 320px) {
grid-template-columns: ${({ image }) => (image ? 'auto 7.813rem' : 'auto')};
}
@media (min-width: 1440px) {
grid-template-columns: ${({ image }) => (image ? 'auto 15.625rem' : 'auto')};
}
@media (min-width: 1440px) {
grid-template-columns: ${({ image }) =>
image ? 'auto 15.625rem' : 'auto'};
}
&:hover {
& > div {
background: ${({ theme }) => theme.colors.blue};
&:hover {
& > div {
background: ${({ theme }) => theme.colors.blue};
* {
color: ${({ theme }) => theme.colors.dark.text} !important;
}
}
* {
color: ${({ theme }) => theme.colors.dark.text} !important;
}
}
img {
filter: ${({ image }) => (image ? 'grayscale(80%)' : 'none')};
}
}
img {
filter: ${({ image }) => (image ? 'grayscale(80%)' : 'none')};
}
}
.card-content {
padding: 1rem 0rem;
background: var(--secondary-background);
display: grid;
row-gap: 0.5rem;
.card-content {
padding: 1rem 0rem;
background: var(--secondary-background);
display: grid;
row-gap: 0.5rem;
@media (max-width: 768px) {
padding: 0.75rem 0rem;
}
}
@media (max-width: 768px) {
padding: 0.75rem 0rem;
}
}
.card-image {
position: relative;
width: 100%;
}
.card-image {
position: relative;
width: 100%;
}
h3,
p,
.tags-wrapper {
padding: 0rem 1rem;
h3,
p,
.tags-wrapper {
padding: 0rem 1rem;
@media (max-width: 768px) {
padding: 0rem 0.5rem;
}
}
@media (max-width: 768px) {
padding: 0rem 0.5rem;
}
}
h3 {
font-size: 1.3rem;
}
h3 {
font-size: 1.3rem;
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
}
span {
font-size: 0.7rem;
}
span {
font-size: 0.7rem;
}
`;
+8 -8
View File
@@ -1,9 +1,9 @@
export interface Props {
title: string;
description: string;
image?: string;
tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
title: string;
description: string;
image?: string;
tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
+24 -24
View File
@@ -4,31 +4,31 @@ import { Props } from './types';
import { Line, LineContent, LineNo, Pre } from './styles';
const CodeBlock = ({ children, className }: Props) => {
const language = className.replace(/language-/, '') as Language;
const language = className.replace(/language-/, '') as Language;
return (
<Highlight
{...defaultProps}
theme={theme}
code={(children as string).trim()}
language={language}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<Pre className={className} style={style}>
{tokens.map((line, i) => (
<Line key={i} {...getLineProps({ line, key: i })}>
<LineNo>{i + 1}</LineNo>
<LineContent>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</LineContent>
</Line>
))}
</Pre>
)}
</Highlight>
);
return (
<Highlight
{...defaultProps}
theme={theme}
code={(children as string).trim()}
language={language}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<Pre className={className} style={style}>
{tokens.map((line, i) => (
<Line key={i} {...getLineProps({ line, key: i })}>
<LineNo>{i + 1}</LineNo>
<LineContent>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</LineContent>
</Line>
))}
</Pre>
)}
</Highlight>
);
};
export default CodeBlock;
+11 -11
View File
@@ -1,24 +1,24 @@
import styled from 'styled-components';
export const Pre = styled.pre`
text-align: left;
margin: 1em 0;
padding: 0.5em;
overflow: scroll;
text-align: left;
margin: 1em 0;
padding: 0.5em;
overflow: scroll;
`;
export const Line = styled.div`
display: table-row;
display: table-row;
`;
export const LineNo = styled.span`
display: table-cell;
text-align: right;
padding-right: 1em;
user-select: none;
opacity: 0.5;
display: table-cell;
text-align: right;
padding-right: 1em;
user-select: none;
opacity: 0.5;
`;
export const LineContent = styled.span`
display: table-cell;
display: table-cell;
`;
+3 -3
View File
@@ -1,4 +1,4 @@
export interface Props {
className: string;
children: React.ReactNode;
}
className: string;
children: React.ReactNode;
}
+5 -5
View File
@@ -1,12 +1,12 @@
import styled from 'styled-components';
const Container = styled.div`
width: 85%;
margin: auto;
width: 85%;
margin: auto;
@media (max-width: 768px) {
width: 95%;
}
@media (max-width: 768px) {
width: 95%;
}
`;
export default Container;
+44 -32
View File
@@ -4,39 +4,51 @@ import { StyledFooter } from './styles';
import IconButton from '../IconButton';
const Footer = () => {
const { mode } = useContext(ThemeContext);
const { mode } = useContext(ThemeContext);
return (
<StyledFooter>
<div className='contact'>
<IconButton
alt='GitHub'
icon={mode === 'dark' ? '/icons/light-github.svg' : '/icons/dark-github.svg'}
width={16}
height={16}
href='https://github.com/hazemKrimi'
target='_blank'
/>
<IconButton
alt='Twitter'
icon={mode === 'dark' ? '/icons/light-twitter.svg' : '/icons/dark-twitter.svg'}
width={16}
height={16}
href='https://twitter.com/HazemKrimi'
target='_blank'
/>
<IconButton
alt='LinkedIn'
icon={mode === 'dark' ? '/icons/light-linkedin.svg' : '/icons/dark-linkedin.svg'}
width={16}
height={16}
href='https://linkedin.com/in/hazemkrimi'
target='_blank'
/>
</div>
<p>Hazem Krimi &copy; {new Date().getFullYear()}</p>
</StyledFooter>
);
return (
<StyledFooter>
<div className='contact'>
<IconButton
alt='GitHub'
icon={
mode === 'dark'
? '/icons/light-github.svg'
: '/icons/dark-github.svg'
}
width={16}
height={16}
href='https://github.com/hazemKrimi'
target='_blank'
/>
<IconButton
alt='Twitter'
icon={
mode === 'dark'
? '/icons/light-twitter.svg'
: '/icons/dark-twitter.svg'
}
width={16}
height={16}
href='https://twitter.com/HazemKrimi'
target='_blank'
/>
<IconButton
alt='LinkedIn'
icon={
mode === 'dark'
? '/icons/light-linkedin.svg'
: '/icons/dark-linkedin.svg'
}
width={16}
height={16}
href='https://linkedin.com/in/hazemkrimi'
target='_blank'
/>
</div>
<p>Hazem Krimi &copy; {new Date().getFullYear()}</p>
</StyledFooter>
);
};
export default Footer;
+32 -32
View File
@@ -1,41 +1,41 @@
import styled from 'styled-components';
export const StyledFooter = styled.footer`
position: absolute;
bottom: 0;
min-height: 100px;
width: 85%;
margin: auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
justify-content: flex-end;
align-content: center;
padding: 1rem 0rem;
position: absolute;
bottom: 0;
min-height: 100px;
width: 85%;
margin: auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
justify-content: flex-end;
align-content: center;
padding: 1rem 0rem;
@media (max-width: 768px) {
width: 95%;
}
@media (max-width: 768px) {
width: 95%;
}
.contact {
display: grid;
grid-template-columns: repeat(auto-fill, 16px);
column-gap: 1rem;
align-items: center;
justify-content: flex-start;
.contact {
display: grid;
grid-template-columns: repeat(auto-fill, 16px);
column-gap: 1rem;
align-items: center;
justify-content: flex-start;
* {
user-select: none;
}
* {
user-select: none;
}
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
p {
display: inline;
text-align: right;
font-weight: bold;
}
p {
display: inline;
text-align: right;
font-weight: bold;
}
`;
+11 -11
View File
@@ -2,17 +2,17 @@ import { Wrapper } from './styles';
import Image from 'next/image';
const Hero = () => (
<Wrapper>
<div className='intro'>
<h2>Hi, I am Hazem</h2>
<h2>I Like Building Software</h2>
<h2 className='blue'>Full Stack TypeScript Developer</h2>
<h2 className='blue'>Life Long Learner</h2>
</div>
<div className='photo'>
<Image alt='Hazem Krimi' src='/photo.jpg' width={515} height={535} />
</div>
</Wrapper>
<Wrapper>
<div className='intro'>
<h2>Hi, I am Hazem</h2>
<h2>I Like Building Software</h2>
<h2 className='blue'>Full Stack TypeScript Developer</h2>
<h2 className='blue'>Life Long Learner</h2>
</div>
<div className='photo'>
<Image alt='Hazem Krimi' src='/photo.jpg' width={515} height={535} />
</div>
</Wrapper>
);
export default Hero;
+25 -25
View File
@@ -1,35 +1,35 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 45vh;
display: grid;
grid-template-columns: 1fr 32.188rem;
align-items: center;
height: auto;
text-align: left;
min-height: 45vh;
display: grid;
grid-template-columns: 1fr 32.188rem;
align-items: center;
height: auto;
text-align: left;
@media (max-width: 1024px) {
min-height: 35vh;
grid-template-columns: 1fr;
@media (max-width: 1024px) {
min-height: 35vh;
grid-template-columns: 1fr;
.photo {
display: none;
}
}
.photo {
display: none;
}
}
h2 {
font-size: 1.5rem;
h2 {
font-size: 1.5rem;
@media (min-width: 1440px) {
font-size: 2rem;
}
@media (min-width: 1440px) {
font-size: 2rem;
}
@media (min-width: 2560px) {
font-size: 3.5rem;
}
}
@media (min-width: 2560px) {
font-size: 3.5rem;
}
}
.blue {
color: ${({ theme }) => theme.colors.blue};
}
.blue {
color: ${({ theme }) => theme.colors.blue};
}
`;
+23 -17
View File
@@ -3,22 +3,28 @@ import { Props } from './types';
import { StyledButton, StyledLink } from './styles';
const IconButton = ({
alt,
icon,
href,
target,
onClick,
className,
width = 24,
height = 24
}: Props) => href ? (
<StyledLink href={href} target={target} onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledLink>
) : (
<StyledButton onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledButton>
);
alt,
icon,
href,
target,
onClick,
className,
width = 24,
height = 24,
}: Props) =>
href ? (
<StyledLink
href={href}
target={target}
onClick={onClick}
className={className}
>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledLink>
) : (
<StyledButton onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledButton>
);
export default IconButton;
+8 -8
View File
@@ -2,17 +2,17 @@ import styled, { css } from 'styled-components';
import Link from 'next/link';
const sharedStyles = css`
cursor: pointer;
background: none;
border: none;
display: inline-flex;
align-items: center;
cursor: pointer;
background: none;
border: none;
display: inline-flex;
align-items: center;
`;
export const StyledLink = styled(Link)`
${sharedStyles}
${sharedStyles}
`;
export const StyledButton = styled.button`
${sharedStyles}
`;
${sharedStyles}
`;
+9 -9
View File
@@ -1,10 +1,10 @@
export interface Props {
alt: string;
icon: string;
width?: number;
height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
className?: string;
}
alt: string;
icon: string;
width?: number;
height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
className?: string;
}
+31 -31
View File
@@ -1,37 +1,37 @@
import { BigField, SmallField } from "./styles";
import { Props } from "./types";
import { BigField, SmallField } from './styles';
import { Props } from './types';
const Input = ({
type = 'text',
variant = 'small',
name,
value,
required,
placeholder,
className,
onChange
type = 'text',
variant = 'small',
name,
value,
required,
placeholder,
className,
onChange,
}: Props) => {
return variant === 'small' ? (
<SmallField
type={type}
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
) : (
<BigField
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
rows={3}
/>
);
return variant === 'small' ? (
<SmallField
type={type}
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
) : (
<BigField
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
rows={3}
/>
);
};
export default Input;
+11 -11
View File
@@ -1,16 +1,16 @@
import styled from "styled-components";
import styled from 'styled-components';
export const SmallField = styled.input`
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
export const BigField = styled.textarea`
resize: none;
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
resize: none;
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
+10 -8
View File
@@ -1,10 +1,12 @@
export interface Props {
placeholder?: string;
type: 'text' | 'email';
variant: 'small' | 'big';
name: string;
value: string;
required?: boolean;
onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
className?: string;
placeholder?: string;
type: 'text' | 'email';
variant: 'small' | 'big';
name: string;
value: string;
required?: boolean;
onChange?: (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
className?: string;
}
+31 -25
View File
@@ -4,33 +4,39 @@ import { Props } from './types';
import { StyledLink, StyledButton } from './styles';
const MDXButton = ({
variant = 'text',
type = 'button',
link,
target,
children,
disabled,
className
variant = 'text',
type = 'button',
link,
target,
children,
disabled,
className,
}: Props) => {
const { mode } = useContext(ThemeContext);
const { mode } = useContext(ThemeContext);
return link ? (
<StyledLink
href={link}
target={target}
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledLink>
) : (
<StyledButton variant={variant} type={type} mode={mode} disabled={disabled} className={className}>
{children}
</StyledButton>
);
return link ? (
<StyledLink
href={link}
target={target}
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledLink>
) : (
<StyledButton
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledButton>
);
};
export default MDXButton;
+36 -27
View File
@@ -3,42 +3,51 @@ import styled, { css } from 'styled-components';
import { Props } from './types';
export const sharedStyles = css<Props>`
cursor: pointer;
display: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'block' : 'inline'};
width: ${({ variant }) => (['action', 'outline'].includes(variant as string) ? '100%' : 'auto')};
background: ${({ variant }) => (variant === 'action' ? '#1573CA' : 'none')};
color: ${({ variant, mode }) =>
variant === 'action' ? 'white' : mode === 'dark' ? 'white' : 'black'};
border: ${({ variant, mode }) =>
variant === 'outline' ? `2px solid ${mode === 'dark' ? 'white' : 'black'}` : 'none'};
font-weight: bold;
font-size: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '1.05rem' : 'inherit'};
text-transform: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'uppercase' : 'inherit'};
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '.5rem 1rem' : '0rem'};
text-align: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'center' : 'left'};
text-decoration: none;
transition: color 250ms ease-in-out;
cursor: pointer;
display: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'block' : 'inline'};
width: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '100%' : 'auto'};
background: ${({ variant }) => (variant === 'action' ? '#1573CA' : 'none')};
color: ${({ variant, mode }) =>
variant === 'action' ? 'white' : mode === 'dark' ? 'white' : 'black'};
border: ${({ variant, mode }) =>
variant === 'outline'
? `2px solid ${mode === 'dark' ? 'white' : 'black'}`
: 'none'};
font-weight: bold;
font-size: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '1.05rem' : 'inherit'};
text-transform: ${({ variant }) =>
['action', 'outline'].includes(variant as string)
? 'uppercase'
: 'inherit'};
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '.5rem 1rem' : '0rem'};
text-align: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'center' : 'left'};
text-decoration: none;
transition: color 250ms ease-in-out;
${({ disabled }) => disabled && `
${({ disabled }) =>
disabled &&
`
background: gray;
cursor: default;
`}
@media (max-width: 768px) {
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '.5rem .75rem' : '0rem'};
}
@media (max-width: 768px) {
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string)
? '.5rem .75rem'
: '0rem'};
}
`;
export const StyledLink = styled(Link)<Props>`
${sharedStyles}
${sharedStyles}
`;
export const StyledButton = styled.button<Omit<Props, 'href'>>`
${sharedStyles}
${sharedStyles}
`;
+8 -8
View File
@@ -1,10 +1,10 @@
export type Props = {
variant?: 'outline' | 'text' | 'action';
type?: 'button' | 'submit';
link?: string;
target?: HTMLAnchorElement['target'];
mode?: string;
disabled?: boolean;
className?: string;
children: React.ReactNode;
variant?: 'outline' | 'text' | 'action';
type?: 'button' | 'submit';
link?: string;
target?: HTMLAnchorElement['target'];
mode?: string;
disabled?: boolean;
className?: string;
children: React.ReactNode;
};
+60 -56
View File
@@ -6,65 +6,69 @@ import IconButton from '../IconButton';
import Button from '../Button';
const MobileNav = ({ open, close }: Props) => {
const { mode, toggle } = useContext(ThemeContext);
const ref = useRef<HTMLDivElement>(null);
const { mode, toggle } = useContext(ThemeContext);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
document.addEventListener('mousedown', (event: MouseEvent) => {
if (ref.current && ref.current.contains(event.target as Node)) {
document.addEventListener('mouseup', event => {
if (ref.current && !ref.current.contains(event.target as Node)) return;
});
} else {
document.addEventListener('mouseup', event => {
if (ref.current && !ref.current.contains(event.target as Node)) close();
});
}
});
useEffect(() => {
document.addEventListener('mousedown', (event: MouseEvent) => {
if (ref.current && ref.current.contains(event.target as Node)) {
document.addEventListener('mouseup', (event) => {
if (ref.current && !ref.current.contains(event.target as Node))
return;
});
} else {
document.addEventListener('mouseup', (event) => {
if (ref.current && !ref.current.contains(event.target as Node))
close();
});
}
});
return () => {
document.removeEventListener('mousedown', () => {});
document.removeEventListener('mouseup', () => {});
};
});
return () => {
document.removeEventListener('mousedown', () => {});
document.removeEventListener('mouseup', () => {});
};
});
return (
<Bar open={open} ref={ref}>
<div className='close'>
<IconButton
alt='Theme toggler'
icon={mode === 'dark' ? '/icons/dark-close.svg' : '/icons/light-close.svg'}
onClick={close}
/>
</div>
<div className='mobile-button-wrapper'>
<Button
href='#'
onClick={() => {
toggle();
close();
}}
>
{mode === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/projects' onClick={() => close()}>
Projects
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/blog' onClick={() => close()}>
Blog
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/contact' onClick={() => close()}>
Contact
</Button>
</div>
</Bar>
);
return (
<Bar open={open} ref={ref}>
<div className='close'>
<IconButton
alt='Theme toggler'
icon={
mode === 'dark' ? '/icons/dark-close.svg' : '/icons/light-close.svg'
}
onClick={close}
/>
</div>
<div className='mobile-button-wrapper'>
<Button
href='#'
onClick={() => {
toggle();
close();
}}
>
{mode === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/projects' onClick={() => close()}>
Projects
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/blog' onClick={() => close()}>
Blog
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/contact' onClick={() => close()}>
Contact
</Button>
</div>
</Bar>
);
};
export default MobileNav;
+28 -28
View File
@@ -2,36 +2,36 @@ import styled from 'styled-components';
import { StyledProps } from './types';
export const Bar = styled.nav<StyledProps>`
position: fixed;
z-index: 2;
top: 0;
right: 0;
transform-origin: right;
transform: ${({ open }) => (open ? 'translateX(0%)' : 'translateX(100%)')};
width: 80%;
height: 100vh;
background: var(--text);
transition: transform 250ms ease-in-out;
display: grid;
grid-template-rows: 30% repeat(4, 50px);
padding: 1rem 1rem 5rem 1rem;
position: fixed;
z-index: 2;
top: 0;
right: 0;
transform-origin: right;
transform: ${({ open }) => (open ? 'translateX(0%)' : 'translateX(100%)')};
width: 80%;
height: 100vh;
background: var(--text);
transition: transform 250ms ease-in-out;
display: grid;
grid-template-rows: 30% repeat(4, 50px);
padding: 1rem 1rem 5rem 1rem;
@media (orientation: landscape) {
grid-template-rows: auto;
}
@media (orientation: landscape) {
grid-template-rows: auto;
}
.close {
justify-self: flex-end;
align-self: flex-start;
margin-top: 0.5rem;
}
.close {
justify-self: flex-end;
align-self: flex-start;
margin-top: 0.5rem;
}
.mobile-button-wrapper {
display: flex;
margin: 0rem 1rem;
.mobile-button-wrapper {
display: flex;
margin: 0rem 1rem;
a {
color: var(--text-inverted) !important;
}
}
a {
color: var(--text-inverted) !important;
}
}
`;
+3 -3
View File
@@ -1,8 +1,8 @@
export type Props = {
open: boolean;
close: () => void;
open: boolean;
close: () => void;
};
export type StyledProps = {
open: boolean;
open: boolean;
};
+42 -40
View File
@@ -8,47 +8,49 @@ import IconButton from '../IconButton';
import MobileNav from '../MobileNav';
const Nav = () => {
const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false);
const { mode, toggle } = useContext(ThemeContext);
const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false);
const { mode, toggle } = useContext(ThemeContext);
return (
<Bar>
<Link className='logo' href='/'>
<Image
className='logo-image'
src={mode === 'dark' ? '/light-logo.svg' : '/dark-logo.svg'}
alt='Logo Image'
width={48}
height={48}
/>
<h1>Hazem Krimi</h1>
</Link>
<div className='buttons'>
<IconButton
alt='Theme toggler'
icon={mode === 'dark' ? '/icons/sun.svg' : '/icons/moon.svg'}
onClick={toggle}
/>
<Button href='/projects'>Projects</Button>
<Button href='/blog'>Blog</Button>
<Button href='/contact'>Contact</Button>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
</div>
<div className='mobile-buttons'>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
<IconButton
alt='Hamburger menu'
icon={mode === 'dark' ? '/icons/light-menu.svg' : '/icons/dark-menu.svg'}
onClick={() => setMobileNavOpen(true)}
/>
</div>
<MobileNav open={mobileNavOpen} close={() => setMobileNavOpen(false)} />
</Bar>
);
return (
<Bar>
<Link className='logo' href='/'>
<Image
className='logo-image'
src={mode === 'dark' ? '/light-logo.svg' : '/dark-logo.svg'}
alt='Logo Image'
width={48}
height={48}
/>
<h1>Hazem Krimi</h1>
</Link>
<div className='buttons'>
<IconButton
alt='Theme toggler'
icon={mode === 'dark' ? '/icons/sun.svg' : '/icons/moon.svg'}
onClick={toggle}
/>
<Button href='/projects'>Projects</Button>
<Button href='/blog'>Blog</Button>
<Button href='/contact'>Contact</Button>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
</div>
<div className='mobile-buttons'>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
<IconButton
alt='Hamburger menu'
icon={
mode === 'dark' ? '/icons/light-menu.svg' : '/icons/dark-menu.svg'
}
onClick={() => setMobileNavOpen(true)}
/>
</div>
<MobileNav open={mobileNavOpen} close={() => setMobileNavOpen(false)} />
</Bar>
);
};
export default Nav;
+45 -45
View File
@@ -1,59 +1,59 @@
import styled from 'styled-components';
export const Bar = styled.nav`
width: 100%;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
padding: 1rem 0rem;
width: 100%;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
padding: 1rem 0rem;
* {
user-select: none;
}
* {
user-select: none;
}
h1 {
font-size: 1.7rem;
h1 {
font-size: 1.7rem;
@media (max-width: 768px) {
font-size: 1rem;
}
}
@media (max-width: 768px) {
font-size: 1rem;
}
}
div,
a.logo {
display: grid;
align-items: center;
column-gap: 1rem;
div,
a.logo {
display: grid;
align-items: center;
column-gap: 1rem;
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
a.logo {
text-decoration: none;
color: var(--text);
cursor: pointer;
grid-template-columns: repeat(2, auto);
justify-content: flex-start;
}
a.logo {
text-decoration: none;
color: var(--text);
cursor: pointer;
grid-template-columns: repeat(2, auto);
justify-content: flex-start;
}
.buttons {
grid-template-columns: repeat(5, auto);
justify-content: flex-end;
.buttons {
grid-template-columns: repeat(5, auto);
justify-content: flex-end;
@media (max-width: 768px) {
display: none;
}
}
@media (max-width: 768px) {
display: none;
}
}
.mobile-buttons {
display: none;
.mobile-buttons {
display: none;
@media (max-width: 768px) {
display: grid;
grid-template-columns: repeat(2, auto);
justify-content: flex-end;
}
}
@media (max-width: 768px) {
display: grid;
grid-template-columns: repeat(2, auto);
justify-content: flex-end;
}
}
`;
Vendored
+10 -10
View File
@@ -1,14 +1,14 @@
declare module '@mdx-js/react' {
import * as React from 'react';
import * as React from 'react';
export type Components = {
[key]?: React.ComponentType<any>;
};
export type Components = {
[key]?: React.ComponentType<any>;
};
export interface MDXProviderProps {
children: React.ReactNode;
components?: Components;
}
export class MDXProvider extends React.Component<MDXProviderProps> {}
export interface MDXProviderProps {
children: React.ReactNode;
components?: Components;
}
export class MDXProvider extends React.Component<MDXProviderProps> {}
}
+8 -8
View File
@@ -1,13 +1,13 @@
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
providerImportSource: '@mdx-js/react'
}
extension: /\.mdx?$/,
options: {
providerImportSource: '@mdx-js/react',
},
});
module.exports = withMDX({
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
images: {
domains: ['res.cloudinary.com']
}
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
images: {
domains: ['res.cloudinary.com'],
},
});
+3 -1
View File
@@ -5,7 +5,8 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
"start": "next start",
"format": "prettier --write ./**/*.{js,jsx,ts,tsx}"
},
"dependencies": {
"@formspree/react": "^2.4.1",
@@ -28,6 +29,7 @@
"@types/styled-components": "^5.1.26",
"babel-plugin-styled-components": "^2.0.7",
"babel-runtime": "^6.26.0",
"prettier": "^2.8.8",
"typescript": "^5.0.2"
},
"engines": {
+32 -32
View File
@@ -4,45 +4,45 @@ import IconButton from '../components/IconButton';
import { Wrapper } from '../styles/404';
const NotFound = () => {
const router = useRouter();
const router = useRouter();
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>404 Not Found | Hazem Krimi</title>
</Head>
<Wrapper>
<h1>404: This page could not be found</h1>
<div className='back' onClick={() => router.push('/')}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Go Home</span>
</div>
</Wrapper>
</>
);
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>404 Not Found | Hazem Krimi</title>
</Head>
<Wrapper>
<h1>404: This page could not be found</h1>
<div className='back' onClick={() => router.push('/')}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Go Home</span>
</div>
</Wrapper>
</>
);
};
export default NotFound;
+39 -39
View File
@@ -20,50 +20,50 @@ import { initStyles } from '../utils/styles';
NProgress.configure({ showSpinner: false });
const App = ({ Component, pageProps }: AppProps) => {
const router = useRouter();
const router = useRouter();
useEffect(() => {
router.events.on('routeChangeStart', () => {
NProgress.start();
});
useEffect(() => {
router.events.on('routeChangeStart', () => {
NProgress.start();
});
router.events.on('routeChangeComplete', url => {
NProgress.done();
pageview(url);
});
router.events.on('routeChangeComplete', (url) => {
NProgress.done();
pageview(url);
});
router.events.on('routeChangeError', () => {
NProgress.done();
});
router.events.on('routeChangeError', () => {
NProgress.done();
});
return () => {
router.events.off('routeChangeComplete', url => {
pageview(url);
});
};
}, [router.events]);
return () => {
router.events.off('routeChangeComplete', (url) => {
pageview(url);
});
};
}, [router.events]);
return (
<>
<Script
id='styles-init'
strategy='afterInteractive'
dangerouslySetInnerHTML={{
__html: initStyles()
}}
/>
<Theme>
<SharedStyles>
<GlobalStyles />
<Container>
<Nav />
<Component {...pageProps} />
<Footer />
</Container>
</SharedStyles>
</Theme>
</>
);
return (
<>
<Script
id='styles-init'
strategy='afterInteractive'
dangerouslySetInnerHTML={{
__html: initStyles(),
}}
/>
<Theme>
<SharedStyles>
<GlobalStyles />
<Container>
<Nav />
<Component {...pageProps} />
<Footer />
</Container>
</SharedStyles>
</Theme>
</>
);
};
export default App;
+72 -55
View File
@@ -1,4 +1,10 @@
import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document';
import Document, {
DocumentContext,
Html,
Head,
Main,
NextScript,
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';
@@ -6,63 +12,74 @@ import { GOOGLE_ANALYTICS_KEY, initAnalytics } from '../utils/gtag';
import { initStyles } from '../utils/styles';
class Doc extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
});
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
};
} finally {
sheet.seal();
}
}
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head>
<link rel='shortcut icon' href='/light-favicon.png' id='light-favicon'></link>
<link rel='shortcut icon' href='/dark-favicon.png' id='dark-favicon'></link>
<link rel='preconnect' href='https://fonts.gstatic.com' />
<link
href='https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;700&display=swap'
rel='stylesheet'
/>
<script src={`https://www.googletagmanager.com/gtag/js?id=${GOOGLE_ANALYTICS_KEY}`} />
<script
id='analytics-init'
dangerouslySetInnerHTML={{
__html: initAnalytics()
}}
/>
<script
id='styles-init'
dangerouslySetInnerHTML={{
__html: initStyles()
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
render() {
return (
<Html>
<Head>
<link
rel='shortcut icon'
href='/light-favicon.png'
id='light-favicon'
></link>
<link
rel='shortcut icon'
href='/dark-favicon.png'
id='dark-favicon'
></link>
<link rel='preconnect' href='https://fonts.gstatic.com' />
<link
href='https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;700&display=swap'
rel='stylesheet'
/>
<script
src={`https://www.googletagmanager.com/gtag/js?id=${GOOGLE_ANALYTICS_KEY}`}
/>
<script
id='analytics-init'
dangerouslySetInnerHTML={{
__html: initAnalytics(),
}}
/>
<script
id='styles-init'
dangerouslySetInnerHTML={{
__html: initStyles(),
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default Doc;
+105 -101
View File
@@ -14,126 +14,130 @@ import readingTime from 'reading-time';
import Image from 'next/image';
interface Props {
source: MDXRemoteSerializeResult;
frontMatter: any;
text: string;
source: MDXRemoteSerializeResult;
frontMatter: any;
text: string;
}
const BlogPost = ({ source, frontMatter, text }: Props) => {
const router = useRouter();
const stats = readingTime(text);
const htmlOverrides = { code: CodeBlock };
const mdxComponents = {};
const router = useRouter();
const stats = readingTime(text);
const htmlOverrides = { code: CodeBlock };
const mdxComponents = {};
useEffect(() => {
window.scrollTo(0, 0);
}, []);
useEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
}
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<meta property='og:title' content={`${frontMatter?.title} | Hazem Krimi`} />
<meta
name='keywords'
content={
frontMatter?.tags
? frontMatter.tags.join(' ')
: `Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js`
}
/>
<title>{`${frontMatter?.title} | Hazem Krimi`}</title>
</Head>
<Wrapper>
<div className='meta'>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>{frontMatter?.title}</h1>
<p>{frontMatter?.description}</p>
<p>
By <b>{frontMatter?.author}</b> on <b>{frontMatter?.date}</b> ({stats.text})
</p>
{frontMatter?.tags ? (
<div className='tags-wrapper'>
{frontMatter.tags.map((tag: string, index: number) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
) : null}
{frontMatter?.image ? (
<div className='image'>
<Image alt={frontMatter?.title} src={frontMatter?.image} fill />
</div>
) : null}
<hr />
</div>
<MDXProvider components={{ ...htmlOverrides, ...mdxComponents }}>
<div className='content'>
<MDXRemote {...source} />
</div>
</MDXProvider>
</Wrapper>
</>
);
}
/>
<meta
property='og:title'
content={`${frontMatter?.title} | Hazem Krimi`}
/>
<meta
name='keywords'
content={
frontMatter?.tags
? frontMatter.tags.join(' ')
: `Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js`
}
/>
<title>{`${frontMatter?.title} | Hazem Krimi`}</title>
</Head>
<Wrapper>
<div className='meta'>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>{frontMatter?.title}</h1>
<p>{frontMatter?.description}</p>
<p>
By <b>{frontMatter?.author}</b> on <b>{frontMatter?.date}</b> (
{stats.text})
</p>
{frontMatter?.tags ? (
<div className='tags-wrapper'>
{frontMatter.tags.map((tag: string, index: number) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
) : null}
{frontMatter?.image ? (
<div className='image'>
<Image alt={frontMatter?.title} src={frontMatter?.image} fill />
</div>
) : null}
<hr />
</div>
<MDXProvider components={{ ...htmlOverrides, ...mdxComponents }}>
<div className='content'>
<MDXRemote {...source} />
</div>
</MDXProvider>
</Wrapper>
</>
);
};
export default BlogPost;
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getBlogPostsSlugs();
return {
paths,
fallback: false
};
const paths = getBlogPostsSlugs();
return {
paths,
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }: any) => {
const blogPostContent = await getBlogPostdata(params.slug);
const blogPostContent = await getBlogPostdata(params.slug);
if (!blogPostContent)
return {
props: {
source: undefined,
frontMatter: undefined
}
};
if (!blogPostContent)
return {
props: {
source: undefined,
frontMatter: undefined,
},
};
const { data, content } = matter(blogPostContent);
const mdxSource = await serialize(content, {
scope: data
});
const { data, content } = matter(blogPostContent);
const mdxSource = await serialize(content, {
scope: data,
});
return {
props: {
source: mdxSource,
frontMatter: data,
text: content
}
};
return {
props: {
source: mdxSource,
frontMatter: data,
text: content,
},
};
};
+56 -56
View File
@@ -6,69 +6,69 @@ import IconButton from '../../components/IconButton';
import Head from 'next/head';
interface Props {
blogPosts: {
title: string;
author: string;
description: string;
image?: string;
slug: string;
date: string;
tags?: string[];
}[];
blogPosts: {
title: string;
author: string;
description: string;
image?: string;
slug: string;
date: string;
tags?: string[];
}[];
}
const Index = ({ blogPosts }: Props) => {
const router = useRouter();
const router = useRouter();
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is a Full Stack JavaScript Developer and a Software Engineering Enthusiast'
/>
<link rel='canonical' href='https://hazemkrimi.tech/blog' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is a Full Stack JavaScript Developer and a Software Engineering Enthusiast'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News'
/>
<title>Blog | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>Blog</h1>
<div className='articles-wrapper'>
{blogPosts.length !== 0 ? (
blogPosts.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/blog/${slug}`} />
))
) : (
<h4>Nothing for now</h4>
)}
</div>
</Wrapper>
</>
);
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is a Full Stack JavaScript Developer and a Software Engineering Enthusiast'
/>
<link rel='canonical' href='https://hazemkrimi.tech/blog' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is a Full Stack JavaScript Developer and a Software Engineering Enthusiast'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News'
/>
<title>Blog | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>Blog</h1>
<div className='articles-wrapper'>
{blogPosts.length !== 0 ? (
blogPosts.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/blog/${slug}`} />
))
) : (
<h4>Nothing for now</h4>
)}
</div>
</Wrapper>
</>
);
};
export default Index;
export const getStaticProps = async () => {
const blogPosts = getBlogPosts();
return {
props: {
blogPosts
}
};
const blogPosts = getBlogPosts();
return {
props: {
blogPosts,
},
};
};
+113 -95
View File
@@ -6,110 +6,128 @@ import Input from '../components/Input';
import MDXButton from '../components/MDXButton';
const About = () => {
const [form, setForm] = useState<{ name: string; email: string; message: string }>({
name: '',
email: '',
message: ''
});
const [state, Submit] = useForm(`${process.env.NEXT_PUBLIC_FORMSPREE_KEY}`);
const [submitted, setSubmitted] = useState<boolean>(false);
const [form, setForm] = useState<{
name: string;
email: string;
message: string;
}>({
name: '',
email: '',
message: '',
});
const [state, Submit] = useForm(`${process.env.NEXT_PUBLIC_FORMSPREE_KEY}`);
const [submitted, setSubmitted] = useState<boolean>(false);
const handleChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setForm({ ...form, [event.target.name]: event.target.value });
};
const handleChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setForm({ ...form, [event.target.name]: event.target.value });
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
try {
await Submit(event);
setSubmitted(true);
} finally {
setTimeout(() => setSubmitted(false), 1000);
setForm({ name: '', email: '', message: '' });
}
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
try {
await Submit(event);
setSubmitted(true);
} finally {
setTimeout(() => setSubmitted(false), 1000);
setForm({ name: '', email: '', message: '' });
}
};
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Contact | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='content'>
<div>
<h1>Contact Me {submitted && <span className='success'>Message sent </span>}</h1>
<form className='contact' onSubmit={handleSubmit}>
<Input
type='text'
placeholder='Name'
variant='small'
name='name'
required
value={form.name}
onChange={handleChange}
/>
<ValidationError className='error' prefix='Name' field='name' errors={state.errors} />
<Input
type='text'
placeholder='Email'
variant='small'
name='email'
required
value={form.email}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Email'
field='email'
errors={state.errors}
/>
<Input
type='text'
placeholder='Message'
variant='big'
name='message'
required
value={form.message}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Message'
field='message'
errors={state.errors}
/>
<MDXButton type='submit' variant='action' disabled={state.submitting || submitted}>
Submit
</MDXButton>
</form>
</div>
</div>
</Wrapper>
</>
);
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Contact | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='content'>
<div>
<h1>
Contact Me{' '}
{submitted && <span className='success'>Message sent </span>}
</h1>
<form className='contact' onSubmit={handleSubmit}>
<Input
type='text'
placeholder='Name'
variant='small'
name='name'
required
value={form.name}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Name'
field='name'
errors={state.errors}
/>
<Input
type='text'
placeholder='Email'
variant='small'
name='email'
required
value={form.email}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Email'
field='email'
errors={state.errors}
/>
<Input
type='text'
placeholder='Message'
variant='big'
name='message'
required
value={form.message}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Message'
field='message'
errors={state.errors}
/>
<MDXButton
type='submit'
variant='action'
disabled={state.submitting || submitted}
>
Submit
</MDXButton>
</form>
</div>
</div>
</Wrapper>
</>
);
};
export default About;
+91 -88
View File
@@ -8,106 +8,109 @@ import Card from '../components/Card';
import Head from 'next/head';
interface Props {
blogPosts: {
title: string;
author: string;
description: string;
slug: string;
date: string;
tags?: string[];
}[];
projects: {
title: string;
description: string;
slug: string;
date: string;
tags?: string[];
}[];
blogPosts: {
title: string;
author: string;
description: string;
slug: string;
date: string;
tags?: string[];
}[];
projects: {
title: string;
description: string;
slug: string;
date: string;
tags?: string[];
}[];
}
const Index = ({ blogPosts, projects }: Props) => {
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Hazem Krimi</title>
</Head>
<Wrapper>
<Hero />
<div className='content'>
<h1>About</h1>
<div className='about'>
<p>
Experienced Full Stack developer with a focus on building user-friendly web and cross-platform mobile applications using cutting-edge technologies. Passionate about ongoing learning and staying up-to-date with the latest trends in software engineering.
</p>
</div>
{projects.length !== 0 && (
<>
<h1>Projects</h1>
<Button href='/projects' className='blue'>
See More
</Button>
<div className='projects'>
<div className='projects-wrapper'>
{projects.slice(0, 3).map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/projects/${slug}`} />
))}
</div>
</div>
</>
)}
{blogPosts.length !== 0 && (
<>
<h1>Blog</h1>
<Button href='/blog' className='blue'>
See More
</Button>
<div className='blog'>
<div className='articles-wrapper'>
{blogPosts.slice(0, 3).map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/blog/${slug}`} />
))}
</div>
</div>
</>
)}
</div>
</Wrapper>
</>
);
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Hazem Krimi</title>
</Head>
<Wrapper>
<Hero />
<div className='content'>
<h1>About</h1>
<div className='about'>
<p>
Experienced Full Stack developer with a focus on building
user-friendly web and cross-platform mobile applications using
cutting-edge technologies. Passionate about ongoing learning and
staying up-to-date with the latest trends in software engineering.
</p>
</div>
{projects.length !== 0 && (
<>
<h1>Projects</h1>
<Button href='/projects' className='blue'>
See More
</Button>
<div className='projects'>
<div className='projects-wrapper'>
{projects.slice(0, 3).map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/projects/${slug}`} />
))}
</div>
</div>
</>
)}
{blogPosts.length !== 0 && (
<>
<h1>Blog</h1>
<Button href='/blog' className='blue'>
See More
</Button>
<div className='blog'>
<div className='articles-wrapper'>
{blogPosts.slice(0, 3).map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/blog/${slug}`} />
))}
</div>
</div>
</>
)}
</div>
</Wrapper>
</>
);
};
export default Index;
export const getStaticProps: GetStaticProps = async () => {
const blogPosts = getBlogPosts();
const projects = getProjects();
return {
props: {
blogPosts,
projects
}
};
const blogPosts = getBlogPosts();
const projects = getProjects();
return {
props: {
blogPosts,
projects,
},
};
};
+115 -104
View File
@@ -14,129 +14,140 @@ import MDXButton from '../../components/MDXButton';
import Image from 'next/image';
interface Props {
source: MDXRemoteSerializeResult;
frontMatter: any;
source: MDXRemoteSerializeResult;
frontMatter: any;
}
const Project = ({ source, frontMatter }: Props) => {
const router = useRouter();
const htmlOverrides = { code: CodeBlock };
const mdxComponents = { Button: MDXButton };
const router = useRouter();
const htmlOverrides = { code: CodeBlock };
const mdxComponents = { Button: MDXButton };
useEffect(() => {
window.scrollTo(0, 0);
}, []);
useEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
}
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<meta property='og:title' content={`${frontMatter?.title} | Hazem Krimi`} />
<meta
name='keywords'
content={
frontMatter?.tags
? frontMatter.tags.join(' ')
: `Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js`
}
/>
<title>{`${frontMatter?.title} | Hazem Krimi`}</title>
</Head>
<Wrapper>
<div className='meta'>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>{frontMatter?.title}</h1>
<p>{frontMatter?.description}</p>
{frontMatter?.tags ? (
<div className='tags-wrapper'>
{frontMatter.tags.map((tag: string, index: number) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
) : null}
{frontMatter?.image && !frontMatter?.hideImage ? (
<div className='image'>
<Image alt={frontMatter?.title} src={frontMatter?.image} fill />
</div>
) : null}
<hr />
</div>
<MDXProvider components={{ ...htmlOverrides, ...mdxComponents }}>
<div className='content'>
<MDXRemote {...source} />
<h1>Showcase</h1>
<div className='showcase-buttons'>
<MDXButton variant='action' link={frontMatter?.demo} target='_blank'>
Demo
</MDXButton>
<MDXButton variant='outline' link={frontMatter?.code} target='_blank'>
Source Code
</MDXButton>
</div>
</div>
</MDXProvider>
</Wrapper>
</>
);
}
/>
<meta
property='og:title'
content={`${frontMatter?.title} | Hazem Krimi`}
/>
<meta
name='keywords'
content={
frontMatter?.tags
? frontMatter.tags.join(' ')
: `Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js`
}
/>
<title>{`${frontMatter?.title} | Hazem Krimi`}</title>
</Head>
<Wrapper>
<div className='meta'>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>{frontMatter?.title}</h1>
<p>{frontMatter?.description}</p>
{frontMatter?.tags ? (
<div className='tags-wrapper'>
{frontMatter.tags.map((tag: string, index: number) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
) : null}
{frontMatter?.image && !frontMatter?.hideImage ? (
<div className='image'>
<Image alt={frontMatter?.title} src={frontMatter?.image} fill />
</div>
) : null}
<hr />
</div>
<MDXProvider components={{ ...htmlOverrides, ...mdxComponents }}>
<div className='content'>
<MDXRemote {...source} />
<h1>Showcase</h1>
<div className='showcase-buttons'>
<MDXButton
variant='action'
link={frontMatter?.demo}
target='_blank'
>
Demo
</MDXButton>
<MDXButton
variant='outline'
link={frontMatter?.code}
target='_blank'
>
Source Code
</MDXButton>
</div>
</div>
</MDXProvider>
</Wrapper>
</>
);
};
export default Project;
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getPorjectsSlugs();
return {
paths,
fallback: false
};
const paths = getPorjectsSlugs();
return {
paths,
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }: any) => {
const projectContent = await getProjectdata(params.slug);
const projectContent = await getProjectdata(params.slug);
if (!projectContent)
return {
props: {
source: undefined,
frontMatter: undefined
}
};
if (!projectContent)
return {
props: {
source: undefined,
frontMatter: undefined,
},
};
const { data, content } = matter(projectContent);
const mdxSource = await serialize(content, {
scope: data
});
const { data, content } = matter(projectContent);
const mdxSource = await serialize(content, {
scope: data,
});
return {
props: {
source: mdxSource,
frontMatter: data
}
};
return {
props: {
source: mdxSource,
frontMatter: data,
},
};
};
+55 -55
View File
@@ -6,74 +6,74 @@ import IconButton from '../../components/IconButton';
import Head from 'next/head';
interface Props {
projects: {
title: string;
description: string;
image?: string;
slug: string;
date: string;
tags?: string[];
}[];
projects: {
title: string;
description: string;
image?: string;
slug: string;
date: string;
tags?: string[];
}[];
}
const Index = ({ projects }: Props) => {
const router = useRouter();
const router = useRouter();
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Projects | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>Projects</h1>
<div className='projects-wrapper'>
{projects.length !== 0 ? (
projects.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/projects/${slug}`} />
))
) : (
<h4>Nothing for now</h4>
)}
</div>
</Wrapper>
</>
);
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Projects | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>Projects</h1>
<div className='projects-wrapper'>
{projects.length !== 0 ? (
projects.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/projects/${slug}`} />
))
) : (
<h4>Nothing for now</h4>
)}
</div>
</Wrapper>
</>
);
};
export default Index;
export const getStaticProps = async () => {
const projects = getProjects();
return {
props: {
projects
}
};
const projects = getProjects();
return {
props: {
projects,
},
};
};
Vendored
+13 -13
View File
@@ -1,17 +1,17 @@
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
colors: {
dark: {
background: string;
text: string;
};
light: {
background: string;
text: string;
};
blue: string;
};
}
export interface DefaultTheme {
colors: {
dark: {
background: string;
text: string;
};
light: {
background: string;
text: string;
};
blue: string;
};
}
}
+18 -18
View File
@@ -1,25 +1,25 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
display: grid;
justify-items: center;
text-align: center;
min-height: 75vh;
display: grid;
justify-items: center;
text-align: center;
@media (max-width: 768px) {
min-height: 65vh;
}
@media (max-width: 768px) {
min-height: 65vh;
}
h1 {
font-size: 1.7rem;
align-self: flex-end;
}
h1 {
font-size: 1.7rem;
align-self: flex-end;
}
.back {
cursor: pointer;
color: #3f9aee;
display: flex;
align-items: center;
align-self: flex-start;
}
.back {
cursor: pointer;
color: #3f9aee;
display: flex;
align-items: center;
align-self: flex-start;
}
`;
+29 -29
View File
@@ -1,38 +1,38 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
min-height: 75vh;
padding: 1rem 0rem;
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
h1 {
font-size: 1.7rem;
margin-bottom: 1rem;
}
h1 {
font-size: 1.7rem;
margin-bottom: 1rem;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
.articles-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
.articles-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
`;
+72 -72
View File
@@ -1,93 +1,93 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
display: grid;
grid-template-rows: auto 1fr;
row-gap: 2rem;
min-height: 75vh;
padding: 1rem 0rem;
display: grid;
grid-template-rows: auto 1fr;
row-gap: 2rem;
@media (max-width: 768px) {
row-gap: 1rem;
}
@media (max-width: 768px) {
row-gap: 1rem;
}
.meta {
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
.meta {
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
.image {
margin: 1rem 0rem;
}
.image {
margin: 1rem 0rem;
}
h1,
p,
.tags-wrapper {
text-align: left;
}
h1,
p,
.tags-wrapper {
text-align: left;
}
h1 {
font-size: 2rem;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.2rem;
}
h2 {
font-size: 1.2rem;
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
}
hr {
height: 0.1rem;
opacity: 0.3;
margin: 1rem auto 0rem auto;
hr {
height: 0.1rem;
opacity: 0.3;
margin: 1rem auto 0rem auto;
@media (max-width: 768px) {
margin: 1rem auto 0rem auto;
}
}
@media (max-width: 768px) {
margin: 1rem auto 0rem auto;
}
}
.content {
h1 {
font-size: 1.5rem;
}
.content {
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.3rem;
}
h2 {
font-size: 1.3rem;
}
h3 {
font-size: 1.1rem;
}
h3 {
font-size: 1.1rem;
}
& > * {
margin: 0.5rem 0rem;
}
& > * {
margin: 0.5rem 0rem;
}
button {
margin: 1rem 0rem;
}
button {
margin: 1rem 0rem;
}
p * {
width: 100%;
height: auto;
}
p * {
width: 100%;
height: auto;
}
ul,
ol {
margin-left: 1.5rem;
}
}
ul,
ol {
margin-left: 1.5rem;
}
}
`;
+36 -36
View File
@@ -1,49 +1,49 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
padding: 1rem 0rem;
padding: 1rem 0rem;
@media (max-width: 768px) {
padding: 0rem;
grid-template-columns: auto;
column-gap: 0rem;
row-gap: 1rem;
}
@media (max-width: 768px) {
padding: 0rem;
grid-template-columns: auto;
column-gap: 0rem;
row-gap: 1rem;
}
h1 {
font-size: 1.7rem;
}
h1 {
font-size: 1.7rem;
}
.content {
display: flex;
flex-direction: column;
}
.content {
display: flex;
flex-direction: column;
}
.contact {
margin: 1rem 0rem;
.contact {
margin: 1rem 0rem;
@media (max-width: 768px) {
margin: 1rem 0rem;
}
}
@media (max-width: 768px) {
margin: 1rem 0rem;
}
}
.success {
color: #73d26b;
align-self: center;
font-weight: normal;
}
.success {
color: #73d26b;
align-self: center;
font-weight: normal;
}
.contact {
display: grid;
grid-template-columns: auto;
row-gap: 1.5rem;
.contact {
display: grid;
grid-template-columns: auto;
row-gap: 1.5rem;
.error {
color: #d75050;
}
.error {
color: #d75050;
}
@media (max-width: 768px) {
row-gap: 0.5rem;
}
}
@media (max-width: 768px) {
row-gap: 0.5rem;
}
}
`;
+38 -38
View File
@@ -1,49 +1,49 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
padding: 1rem 0rem;
display: grid;
grid-template-columns: auto;
row-gap: 1rem;
padding: 1rem 0rem;
display: grid;
grid-template-columns: auto;
row-gap: 1rem;
h1 {
display: inline;
font-size: 1.7rem;
margin-right: 1rem;
}
h1 {
display: inline;
font-size: 1.7rem;
margin-right: 1rem;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
.blue {
color: ${({ theme }) => theme.colors.blue};
.blue {
color: ${({ theme }) => theme.colors.blue};
@media (max-width: 768px) {
margin-top: 0.5rem;
}
}
@media (max-width: 768px) {
margin-top: 0.5rem;
}
}
.about,
.projects,
.blog {
margin: 1rem 0rem;
}
.about,
.projects,
.blog {
margin: 1rem 0rem;
}
.projects {
margin-bottom: 3rem;
}
.projects {
margin-bottom: 3rem;
}
.projects-wrapper,
.articles-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
.projects-wrapper,
.articles-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
`;
+29 -29
View File
@@ -1,38 +1,38 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
min-height: 75vh;
padding: 1rem 0rem;
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
h1 {
font-size: 1.7rem;
margin-bottom: 1rem;
}
h1 {
font-size: 1.7rem;
margin-bottom: 1rem;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
.projects-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
.projects-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
`;
+71 -71
View File
@@ -1,91 +1,91 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
display: grid;
grid-template-rows: auto 1fr;
row-gap: 2rem;
min-height: 75vh;
padding: 1rem 0rem;
display: grid;
grid-template-rows: auto 1fr;
row-gap: 2rem;
@media (max-width: 768px) {
row-gap: 1rem;
}
@media (max-width: 768px) {
row-gap: 1rem;
}
.meta {
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
.meta {
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
.image {
margin: 1rem 0rem;
}
.image {
margin: 1rem 0rem;
}
h1,
p {
text-align: left;
}
h1,
p {
text-align: left;
}
h1 {
font-size: 2rem;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.2rem;
}
}
h2 {
font-size: 1.2rem;
}
}
hr {
height: 0.1rem;
opacity: 0.3;
margin: 1rem auto 0rem auto;
}
hr {
height: 0.1rem;
opacity: 0.3;
margin: 1rem auto 0rem auto;
}
.content {
h1 {
font-size: 1.5rem;
}
.content {
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.3rem;
}
h2 {
font-size: 1.3rem;
}
h3 {
font-size: 1.1rem;
}
h3 {
font-size: 1.1rem;
}
& > * {
margin: 0.5rem 0rem;
}
& > * {
margin: 0.5rem 0rem;
}
button {
margin: 1rem 0rem;
}
button {
margin: 1rem 0rem;
}
p * {
width: 100%;
height: auto;
}
p * {
width: 100%;
height: auto;
}
ul,
ol {
margin-inline-start: 0.5rem;
list-style-position: inside;
}
ul,
ol {
margin-inline-start: 0.5rem;
list-style-position: inside;
}
.showcase-buttons {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
justify-content: space-between;
align-items: center;
}
}
.showcase-buttons {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
justify-content: space-between;
align-items: center;
}
}
`;
+14 -14
View File
@@ -1,21 +1,21 @@
import { ThemeProvider, DefaultTheme } from 'styled-components';
const Shared = ({ children }: { children: React.ReactNode }) => {
const theme: DefaultTheme = {
colors: {
dark: {
background: '#262626',
text: 'white'
},
light: {
background: '#F9F9F9',
text: 'black'
},
blue: '#1573CA'
}
};
const theme: DefaultTheme = {
colors: {
dark: {
background: '#262626',
text: 'white',
},
light: {
background: '#F9F9F9',
text: 'black',
},
blue: '#1573CA',
},
};
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
};
export default Shared;
+48 -42
View File
@@ -1,57 +1,63 @@
import { useReducer, useEffect, createContext } from 'react';
export const ThemeContext = createContext<{ mode: string; toggle: () => void }>({
mode: 'light',
toggle: () => {}
});
export const ThemeContext = createContext<{ mode: string; toggle: () => void }>(
{
mode: 'light',
toggle: () => {},
}
);
const reducer = (state: string, action: { type: string }) => {
switch (action.type) {
case 'TOGGLE':
return state === 'light' ? 'dark' : 'light';
case 'DARK':
return 'dark';
case 'LIGHT':
return 'light';
default:
return state;
}
switch (action.type) {
case 'TOGGLE':
return state === 'light' ? 'dark' : 'light';
case 'DARK':
return 'dark';
case 'LIGHT':
return 'light';
default:
return state;
}
};
const Theme = ({ children }: { children: React.ReactNode }) => {
const [mode, dispatch] = useReducer(reducer, 'light');
const toggle = () => {
const root = window.document.documentElement;
const lightFavicon = document.querySelector('link#light-favicon')!;
const darkFavicon = document.querySelector('link#dark-favicon')!;
const [mode, dispatch] = useReducer(reducer, 'light');
const toggle = () => {
const root = window.document.documentElement;
const lightFavicon = document.querySelector('link#light-favicon')!;
const darkFavicon = document.querySelector('link#dark-favicon')!;
if (mode === 'dark') {
window.localStorage.setItem('mode', 'light');
root.style.setProperty('--background', '#F9F9F9');
root.style.setProperty('--secondary-background', 'white');
root.style.setProperty('--text', 'black');
root.style.setProperty('--text-inverted', 'white');
document.head.append(darkFavicon);
} else {
window.localStorage.setItem('mode', 'dark');
root.style.setProperty('--background', '#262626');
root.style.setProperty('--secondary-background', '#2F2F2F');
root.style.setProperty('--text', 'white');
root.style.setProperty('--text-inverted', 'black');
document.head.append(lightFavicon);
}
if (mode === 'dark') {
window.localStorage.setItem('mode', 'light');
root.style.setProperty('--background', '#F9F9F9');
root.style.setProperty('--secondary-background', 'white');
root.style.setProperty('--text', 'black');
root.style.setProperty('--text-inverted', 'white');
document.head.append(darkFavicon);
} else {
window.localStorage.setItem('mode', 'dark');
root.style.setProperty('--background', '#262626');
root.style.setProperty('--secondary-background', '#2F2F2F');
root.style.setProperty('--text', 'white');
root.style.setProperty('--text-inverted', 'black');
document.head.append(lightFavicon);
}
dispatch({ type: 'TOGGLE' });
};
dispatch({ type: 'TOGGLE' });
};
useEffect(() => {
const root = window.document.documentElement;
const initialThemeMode = root.style.getPropertyValue('--mode');
useEffect(() => {
const root = window.document.documentElement;
const initialThemeMode = root.style.getPropertyValue('--mode');
dispatch({ type: initialThemeMode === 'dark' ? 'DARK' : 'LIGHT' });
}, []);
dispatch({ type: initialThemeMode === 'dark' ? 'DARK' : 'LIGHT' });
}, []);
return <ThemeContext.Provider value={{ mode, toggle }}>{children}</ThemeContext.Provider>;
return (
<ThemeContext.Provider value={{ mode, toggle }}>
{children}
</ThemeContext.Provider>
);
};
export default Theme;
+54 -51
View File
@@ -5,71 +5,74 @@ import matter from 'gray-matter';
const blogPostsDirectory = path.join(process.cwd(), '_blog');
export const getBlogPosts = () => {
try {
const fileNames = fs.readdirSync(blogPostsDirectory);
try {
const fileNames = fs.readdirSync(blogPostsDirectory);
if (!fileNames) return [];
if (!fileNames) return [];
const allBlogPostsData = fileNames.map(filename => {
const slug = filename.replace('.mdx', '');
const allBlogPostsData = fileNames.map((filename) => {
const slug = filename.replace('.mdx', '');
const fullPath = path.join(blogPostsDirectory, filename);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
const fullPath = path.join(blogPostsDirectory, filename);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
// @ts-ignore
const formattedDate = new Date(data.date).toLocaleDateString('en-IN', options);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
// @ts-ignore
const formattedDate = new Date(data.date).toLocaleDateString(
'en-IN',
options
);
const frontmatter = {
...data,
date: formattedDate
};
return {
slug,
...frontmatter
};
});
const frontmatter = {
...data,
date: formattedDate,
};
return {
slug,
...frontmatter,
};
});
return allBlogPostsData.sort((a, b) => {
if (new Date(a.date) < new Date(b.date)) {
return 1;
} else {
return -1;
}
});
} catch {
return [];
}
return allBlogPostsData.sort((a, b) => {
if (new Date(a.date) < new Date(b.date)) {
return 1;
} else {
return -1;
}
});
} catch {
return [];
}
};
export const getBlogPostsSlugs = () => {
try {
const fileNames = fs.readdirSync(blogPostsDirectory);
try {
const fileNames = fs.readdirSync(blogPostsDirectory);
if (!fileNames) return [];
if (!fileNames) return [];
return fileNames.map(filename => {
return {
params: {
slug: filename.replace('.mdx', '')
}
};
});
} catch {
return [];
}
return fileNames.map((filename) => {
return {
params: {
slug: filename.replace('.mdx', ''),
},
};
});
} catch {
return [];
}
};
export const getBlogPostdata = async (slug: string) => {
try {
const fullPath = path.join(blogPostsDirectory, `${slug}.mdx`);
const postContent = fs.readFileSync(fullPath, 'utf8');
try {
const fullPath = path.join(blogPostsDirectory, `${slug}.mdx`);
const postContent = fs.readFileSync(fullPath, 'utf8');
if (!postContent) return undefined;
if (!postContent) return undefined;
return postContent;
} catch {
return undefined;
}
return postContent;
} catch {
return undefined;
}
};
+12 -11
View File
@@ -1,19 +1,20 @@
export const GOOGLE_ANALYTICS_KEY = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY;
export const GOOGLE_ANALYTICS_KEY =
process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY;
export const pageview = (url: any) => {
// @ts-ignore
window.gtag('config', GOOGLE_ANALYTICS_KEY, {
page_path: url
});
// @ts-ignore
window.gtag('config', GOOGLE_ANALYTICS_KEY, {
page_path: url,
});
};
export const event = ({ action, category, label, value }: any) => {
// @ts-ignore
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value
});
// @ts-ignore
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
};
export const initAnalytics = () => `
+54 -51
View File
@@ -5,71 +5,74 @@ import matter from 'gray-matter';
const projects = path.join(process.cwd(), '_projects');
export const getProjects = () => {
try {
const fileNames = fs.readdirSync(projects);
try {
const fileNames = fs.readdirSync(projects);
if (!fileNames) return [];
if (!fileNames) return [];
const allProjectsData = fileNames.map(filename => {
const slug = filename.replace('.mdx', '');
const allProjectsData = fileNames.map((filename) => {
const slug = filename.replace('.mdx', '');
const fullPath = path.join(projects, filename);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
const fullPath = path.join(projects, filename);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
// @ts-ignore
const formattedDate = new Date(data.date).toLocaleDateString('en-IN', options);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
// @ts-ignore
const formattedDate = new Date(data.date).toLocaleDateString(
'en-IN',
options
);
const frontmatter = {
...data,
date: formattedDate
};
return {
slug,
...frontmatter
};
});
const frontmatter = {
...data,
date: formattedDate,
};
return {
slug,
...frontmatter,
};
});
return allProjectsData.sort((a, b) => {
if (new Date(a.date) < new Date(b.date)) {
return 1;
} else {
return -1;
}
});
} catch {
return [];
}
return allProjectsData.sort((a, b) => {
if (new Date(a.date) < new Date(b.date)) {
return 1;
} else {
return -1;
}
});
} catch {
return [];
}
};
export const getPorjectsSlugs = () => {
try {
const fileNames = fs.readdirSync(projects);
try {
const fileNames = fs.readdirSync(projects);
if (!fileNames) return [];
if (!fileNames) return [];
return fileNames.map(filename => {
return {
params: {
slug: filename.replace('.mdx', '')
}
};
});
} catch {
return [];
}
return fileNames.map((filename) => {
return {
params: {
slug: filename.replace('.mdx', ''),
},
};
});
} catch {
return [];
}
};
export const getProjectdata = async (slug: string) => {
try {
const fullPath = path.join(projects, `${slug}.mdx`);
const postContent = fs.readFileSync(fullPath, 'utf8');
try {
const fullPath = path.join(projects, `${slug}.mdx`);
const postContent = fs.readFileSync(fullPath, 'utf8');
if (!postContent) return undefined;
if (!postContent) return undefined;
return postContent;
} catch {
return undefined;
}
return postContent;
} catch {
return undefined;
}
};
+5
View File
@@ -1338,6 +1338,11 @@ postcss@8.4.14:
picocolors "^1.0.0"
source-map-js "^1.0.2"
prettier@^2.8.8:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prism-react-renderer@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085"