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