Init hugo project

This commit is contained in:
Hazem Krimi
2023-10-18 19:27:12 +01:00
parent 193e285569
commit 357ac123c1
91 changed files with 9 additions and 4566 deletions
-11
View File
@@ -1,11 +0,0 @@
{
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
{
"ssr": true
}
]
]
}
-2
View File
@@ -1,2 +0,0 @@
NEXT_PUBLIC_FORMSPREE_KEY=FORMSPREE_KEY
NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY=GOOGLE_ANALYTICS_KEY
-34
View File
@@ -1,34 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# 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
-32
View File
@@ -1,32 +0,0 @@
# 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
-66
View File
@@ -1,66 +0,0 @@
---
title: 'Astrobuild'
description: 'Prototype of a collaboration tool between stakeholders for building software projects'
date: '2023-06-11'
demo: 'https://astrobuild.vercel.app'
code: 'https://github.com/hazemKrimi/astrobuild'
tags: ['react', 'typescript', 'graphql', 'styled-components', 'apollographql', 'vite', 'react-flow']
---
# Introduction
This was my final year of studies projects where my collegue and I worked on a prototype for a project building solution for the agency we had an internship in called [Astrolab](https://astrolab-agency.com).
As there were lockdowns in Tunisia in 2020/2021 because of covid software agencies and clients could not have in person meetings to discuss a client's software project.
So the idea of Astrobuild is to reduce meetings and make the client participate with other stakeholders in the process of creating a software project by tracking and communicating with their associated product owner.
# Features
### Client
- Account management
- Project creation from choosing the templates, features and deliverables (Specification, design, MVP or full build)
- Project tracking
- Chat with associated product owner for tracking and support
- Payment (WIP)
### Product Owner
- Account management
- Management of templates
- Review of projects and transferring deliverables
- Chat with clients on their projects
### Developer
- Account management
- Features, categories and wireframes management
### Admin
- Identity and access management (WIP)
# Technologies Used
The frontend project is a [React](https://react.dev) application with [TypeScript](https://www.typescriptlang.org) which consumes a set of [GraphQL](https://graphql.org) APIs using [Apollo GraphQL](https://www.apollographql.com) and a some REST APIs.
A small components library with a custom theme was made for this project using [Styled Components](https://styled-components.com) which can be viewed at the components folder.
The prototyping feature was done using [React Flow](https://reactflow.dev) which is a library for creating and interacting with diagrams.
To view the full architecture of the application go [here](https://github.com/MedAmineFouzai/astrobuild-api/blob/main/README.md).
# Screenshots
### Project page for the client
![Project](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/project.png?raw=true)
### Template page for the product owner
![Template](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/template.png?raw=true)
### Prototype page for the developer
![Prototype](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/prototype.png?raw=true)
### Support page for the product owner
![Support](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/support.png?raw=true)
### User editing page for the admin
![Admin](https://github.com/hazemKrimi/astrobuild/blob/main/screenshots/admin.png?raw=true)
# Credits
- Mohamed Amine Fouzai: [GitHub](https://github.com/MedAmineFouzai), [LinkedIn](https://www.linkedin.com/in/amine-fouzai)
-23
View File
@@ -1,23 +0,0 @@
---
title: 'React Weather App'
description: 'Weather app made with React, TypeScript and OpenWeatherMap API'
date: '2021-09-19'
demo: 'https://hazemkrimi.github.io/react-weather-app'
code: 'https://github.com/hazemKrimi/react-weather-app'
tags: ['react', 'typescript', 'openweathermap']
---
# About the project
This is a project that I made as a step in the interview process for my final year internship.
# Technologies
- React
- TypeScript
- Styled Components
- OpenWeatherMap API
# Screenshots
![Desktop](https://res.cloudinary.com/dun9hhyz1/image/upload/v1643548378/personal-website/portfolio/react-weather-app/screenshot_ueu2a4.png)
+6
View File
@@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---
-23
View File
@@ -1,23 +0,0 @@
import { Props } from './types';
import { StyledButton } from './styles';
const Button = ({
variant = 'text',
href,
target,
onClick,
children,
className,
}: Props) => (
<StyledButton
href={href}
target={target}
className={className}
onClick={onClick}
variant={variant}
>
{children}
</StyledButton>
);
export default Button;
-51
View File
@@ -1,51 +0,0 @@
import styled from 'styled-components';
import Link from 'next/link';
import { Props } from './types';
export const StyledButton = styled(Link)<Props>`
position: relative;
display: inline;
cursor: pointer;
background: none;
color: var(--text);
border: ${({ variant }) =>
variant === 'outline' ? '2px solid var(--text)' : '2px solid transparent'};
font-weight: bold;
text-transform: ${({ variant }) =>
variant === 'outline' ? 'uppercase' : 'inherit'};
padding: ${({ variant }) => (variant === 'outline' ? '.5rem 1rem' : '0rem')};
text-align: left;
text-decoration: none;
transition: color 250ms ease-in-out, border 250ms ease-in-out;
z-index: 1;
@media (max-width: 768px) {
padding: ${({ variant }) =>
variant === 'outline' ? '.5rem .75rem' : '0rem'};
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: -1;
background-color: ${({ variant }) =>
variant === 'outline' ? 'var(--text)' : 'inherit'};
transition: transform 250ms ease-in-out;
transform: scaleX(0);
transform-origin: left;
}
&:hover {
color: ${({ variant }) =>
variant === 'outline' ? 'var(--background)' : 'inherit'};
border: 2px solid transparent;
}
&:hover::before {
transform: scaleX(1);
}
`;
-8
View File
@@ -1,8 +0,0 @@
export type Props = {
variant?: 'outline' | 'text';
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
children: React.ReactNode;
className?: string;
};
-41
View File
@@ -1,41 +0,0 @@
import Image from 'next/image';
import { StyledCard } from './styles';
import { Props } from './types';
const Card = ({
title,
description,
image,
tags,
href,
target,
onClick,
}: Props) => {
return (
<StyledCard
href={href}
onClick={onClick}
image={image ? Boolean(image) : undefined}
target={target}
>
<div className='card-content'>
<h3>{title}</h3>
<p>{description}</p>
{tags && (
<div className='tags-wrapper'>
{tags.map((tag, index) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
)}
</div>
{image && (
<div className='card-image'>
<Image alt={title} src={image} fill />
</div>
)}
</StyledCard>
);
};
export default Card;
-77
View File
@@ -1,77 +0,0 @@
import styled from 'styled-components';
import Link from 'next/link';
export const StyledCard = styled(Link)<{ image?: boolean }>`
cursor: pointer;
width: 100%;
display: grid;
grid-template-columns: ${({ image }) => (image ? 'auto 9.375rem' : 'auto')};
align-items: stretch;
transition: color 0ms ease-in-out;
text-decoration: none;
color: var(--text);
@media (max-width: 320px) {
grid-template-columns: ${({ image }) => (image ? 'auto 7.813rem' : 'auto')};
}
@media (min-width: 1440px) {
grid-template-columns: ${({ image }) =>
image ? 'auto 15.625rem' : 'auto'};
}
&:hover {
& > div {
background: ${({ theme }) => theme.colors.blue};
* {
color: ${({ theme }) => theme.colors.dark.text} !important;
}
}
img {
filter: ${({ image }) => (image ? 'grayscale(80%)' : 'none')};
}
}
.card-content {
padding: 1rem 0rem;
background: var(--secondary-background);
display: grid;
row-gap: 0.5rem;
@media (max-width: 768px) {
padding: 0.75rem 0rem;
}
}
.card-image {
position: relative;
width: 100%;
}
h3,
p,
.tags-wrapper {
padding: 0rem 1rem;
@media (max-width: 768px) {
padding: 0rem 0.5rem;
}
}
h3 {
font-size: 1.3rem;
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
}
span {
font-size: 0.7rem;
}
`;
-9
View File
@@ -1,9 +0,0 @@
export interface Props {
title: string;
description: string;
image?: string;
tags?: string[];
href: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
}
-34
View File
@@ -1,34 +0,0 @@
import Highlight, { defaultProps, Language } from 'prism-react-renderer';
import theme from 'prism-react-renderer/themes/vsDark';
import { Props } from './types';
import { Line, LineContent, LineNo, Pre } from './styles';
const CodeBlock = ({ children, className }: Props) => {
const language = className.replace(/language-/, '') as Language;
return (
<Highlight
{...defaultProps}
theme={theme}
code={(children as string).trim()}
language={language}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<Pre className={className} style={style}>
{tokens.map((line, i) => (
<Line key={i} {...getLineProps({ line, key: i })}>
<LineNo>{i + 1}</LineNo>
<LineContent>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</LineContent>
</Line>
))}
</Pre>
)}
</Highlight>
);
};
export default CodeBlock;
-24
View File
@@ -1,24 +0,0 @@
import styled from 'styled-components';
export const Pre = styled.pre`
text-align: left;
margin: 1em 0;
padding: 0.5em;
overflow: scroll;
`;
export const Line = styled.div`
display: table-row;
`;
export const LineNo = styled.span`
display: table-cell;
text-align: right;
padding-right: 1em;
user-select: none;
opacity: 0.5;
`;
export const LineContent = styled.span`
display: table-cell;
`;
-4
View File
@@ -1,4 +0,0 @@
export interface Props {
className: string;
children: React.ReactNode;
}
-12
View File
@@ -1,12 +0,0 @@
import styled from 'styled-components';
const Container = styled.div`
width: 85%;
margin: auto;
@media (max-width: 768px) {
width: 95%;
}
`;
export default Container;
-54
View File
@@ -1,54 +0,0 @@
import { useContext } from 'react';
import { ThemeContext } from '../../styles/theme';
import { StyledFooter } from './styles';
import IconButton from '../IconButton';
const Footer = () => {
const { mode } = useContext(ThemeContext);
return (
<StyledFooter>
<div className='contact'>
<IconButton
alt='GitHub'
icon={
mode === 'dark'
? '/icons/light-github.svg'
: '/icons/dark-github.svg'
}
width={16}
height={16}
href='https://github.com/hazemKrimi'
target='_blank'
/>
<IconButton
alt='Twitter'
icon={
mode === 'dark'
? '/icons/light-twitter.svg'
: '/icons/dark-twitter.svg'
}
width={16}
height={16}
href='https://twitter.com/HazemKrimi'
target='_blank'
/>
<IconButton
alt='LinkedIn'
icon={
mode === 'dark'
? '/icons/light-linkedin.svg'
: '/icons/dark-linkedin.svg'
}
width={16}
height={16}
href='https://linkedin.com/in/hazemkrimi'
target='_blank'
/>
</div>
<p>Hazem Krimi &copy; {new Date().getFullYear()}</p>
</StyledFooter>
);
};
export default Footer;
-41
View File
@@ -1,41 +0,0 @@
import styled from 'styled-components';
export const StyledFooter = styled.footer`
position: absolute;
bottom: 0;
min-height: 100px;
width: 85%;
margin: auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
column-gap: 2rem;
justify-content: flex-end;
align-content: center;
padding: 1rem 0rem;
@media (max-width: 768px) {
width: 95%;
}
.contact {
display: grid;
grid-template-columns: repeat(auto-fill, 16px);
column-gap: 1rem;
align-items: center;
justify-content: flex-start;
* {
user-select: none;
}
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
p {
display: inline;
text-align: right;
font-weight: bold;
}
`;
-72
View File
@@ -1,72 +0,0 @@
import { createGlobalStyle } from 'styled-components';
const GlobalStyles = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Source Code Pro', monospace;
font-size: 16px;
line-height: 1.5;
outline: none;
user-select: text;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
@media(max-width: 768px) {
overflow-x: scroll;
}
&::-webkit-scrollbar {
width: 0;
height: 0;
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: transparent;
}
&::selection {
background: var(--text);
color: var(--background);
}
}
html {
position: relative;
min-height: 100%;
background: var(--background) !important;
}
* {
color: var(--text);
}
ul, ol {
margin-inline-start: 1.9rem;
}
body {
margin: 0 0 100px;
transition: color 250ms ease-in-out, background 250ms ease-in-out;
scroll-behavior: smooth;
#nprogress .bar {
background: var(--text) !important;
}
#nprogress .peg {
box-shadow: 0 0 10px var(--text), 0 0 5px var(--text) !important;
}
}
body::-webkit-scrollbar {
width: 0.5rem !important;
}
body::-webkit-scrollbar-thumb {
background-color: var(--text) !important;
}
`;
export default GlobalStyles;
-18
View File
@@ -1,18 +0,0 @@
import { Wrapper } from './styles';
import Image from 'next/image';
const Hero = () => (
<Wrapper>
<div className='intro'>
<h2>Hi, I am Hazem</h2>
<h2>I Like Building Software</h2>
<h2 className='blue'>Full Stack TypeScript Developer</h2>
<h2 className='blue'>Life Long Learner</h2>
</div>
<div className='photo'>
<Image alt='Hazem Krimi' src='/photo.jpg' width={515} height={535} />
</div>
</Wrapper>
);
export default Hero;
-35
View File
@@ -1,35 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 45vh;
display: grid;
grid-template-columns: 1fr 32.188rem;
align-items: center;
height: auto;
text-align: left;
@media (max-width: 1024px) {
min-height: 35vh;
grid-template-columns: 1fr;
.photo {
display: none;
}
}
h2 {
font-size: 1.5rem;
@media (min-width: 1440px) {
font-size: 2rem;
}
@media (min-width: 2560px) {
font-size: 3.5rem;
}
}
.blue {
color: ${({ theme }) => theme.colors.blue};
}
`;
-30
View File
@@ -1,30 +0,0 @@
import Image from 'next/image';
import { Props } from './types';
import { StyledButton, StyledLink } from './styles';
const IconButton = ({
alt,
icon,
href,
target,
onClick,
className,
width = 24,
height = 24,
}: Props) =>
href ? (
<StyledLink
href={href}
target={target}
onClick={onClick}
className={className}
>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledLink>
) : (
<StyledButton onClick={onClick} className={className}>
<Image alt={alt} src={icon} width={width} height={height} />
</StyledButton>
);
export default IconButton;
-18
View File
@@ -1,18 +0,0 @@
import styled, { css } from 'styled-components';
import Link from 'next/link';
const sharedStyles = css`
cursor: pointer;
background: none;
border: none;
display: inline-flex;
align-items: center;
`;
export const StyledLink = styled(Link)`
${sharedStyles}
`;
export const StyledButton = styled.button`
${sharedStyles}
`;
-10
View File
@@ -1,10 +0,0 @@
export interface Props {
alt: string;
icon: string;
width?: number;
height?: number;
href?: string;
target?: HTMLAnchorElement['target'];
onClick?: () => void;
className?: string;
}
-37
View File
@@ -1,37 +0,0 @@
import { BigField, SmallField } from './styles';
import { Props } from './types';
const Input = ({
type = 'text',
variant = 'small',
name,
value,
required,
placeholder,
className,
onChange,
}: Props) => {
return variant === 'small' ? (
<SmallField
type={type}
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
/>
) : (
<BigField
name={name}
value={value}
required={required}
placeholder={placeholder}
className={className}
onChange={onChange}
rows={3}
/>
);
};
export default Input;
-16
View File
@@ -1,16 +0,0 @@
import styled from 'styled-components';
export const SmallField = styled.input`
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
export const BigField = styled.textarea`
resize: none;
border: none;
padding: 1rem;
background: var(--secondary-background);
color: var(--text);
`;
-12
View File
@@ -1,12 +0,0 @@
export interface Props {
placeholder?: string;
type: 'text' | 'email';
variant: 'small' | 'big';
name: string;
value: string;
required?: boolean;
onChange?: (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
className?: string;
}
-42
View File
@@ -1,42 +0,0 @@
import { useContext } from 'react';
import { ThemeContext } from '../../styles/theme';
import { Props } from './types';
import { StyledLink, StyledButton } from './styles';
const MDXButton = ({
variant = 'text',
type = 'button',
link,
target,
children,
disabled,
className,
}: Props) => {
const { mode } = useContext(ThemeContext);
return link ? (
<StyledLink
href={link}
target={target}
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledLink>
) : (
<StyledButton
variant={variant}
type={type}
mode={mode}
disabled={disabled}
className={className}
>
{children}
</StyledButton>
);
};
export default MDXButton;
-53
View File
@@ -1,53 +0,0 @@
import Link from 'next/link';
import styled, { css } from 'styled-components';
import { Props } from './types';
export const sharedStyles = css<Props>`
cursor: pointer;
display: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'block' : 'inline'};
width: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '100%' : 'auto'};
background: ${({ variant }) => (variant === 'action' ? '#1573CA' : 'none')};
color: ${({ variant, mode }) =>
variant === 'action' ? 'white' : mode === 'dark' ? 'white' : 'black'};
border: ${({ variant, mode }) =>
variant === 'outline'
? `2px solid ${mode === 'dark' ? 'white' : 'black'}`
: 'none'};
font-weight: bold;
font-size: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '1.05rem' : 'inherit'};
text-transform: ${({ variant }) =>
['action', 'outline'].includes(variant as string)
? 'uppercase'
: 'inherit'};
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? '.5rem 1rem' : '0rem'};
text-align: ${({ variant }) =>
['action', 'outline'].includes(variant as string) ? 'center' : 'left'};
text-decoration: none;
transition: color 250ms ease-in-out;
${({ disabled }) =>
disabled &&
`
background: gray;
cursor: default;
`}
@media (max-width: 768px) {
padding: ${({ variant }) =>
['action', 'outline'].includes(variant as string)
? '.5rem .75rem'
: '0rem'};
}
`;
export const StyledLink = styled(Link)<Props>`
${sharedStyles}
`;
export const StyledButton = styled.button<Omit<Props, 'href'>>`
${sharedStyles}
`;
-10
View File
@@ -1,10 +0,0 @@
export type Props = {
variant?: 'outline' | 'text' | 'action';
type?: 'button' | 'submit';
link?: string;
target?: HTMLAnchorElement['target'];
mode?: string;
disabled?: boolean;
className?: string;
children: React.ReactNode;
};
-74
View File
@@ -1,74 +0,0 @@
import { useContext, useRef, useEffect } from 'react';
import { ThemeContext } from '../../styles/theme';
import { Props } from './types';
import { Bar } from './styles';
import IconButton from '../IconButton';
import Button from '../Button';
const MobileNav = ({ open, close }: Props) => {
const { mode, toggle } = useContext(ThemeContext);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
document.addEventListener('mousedown', (event: MouseEvent) => {
if (ref.current && ref.current.contains(event.target as Node)) {
document.addEventListener('mouseup', (event) => {
if (ref.current && !ref.current.contains(event.target as Node))
return;
});
} else {
document.addEventListener('mouseup', (event) => {
if (ref.current && !ref.current.contains(event.target as Node))
close();
});
}
});
return () => {
document.removeEventListener('mousedown', () => {});
document.removeEventListener('mouseup', () => {});
};
});
return (
<Bar open={open} ref={ref}>
<div className='close'>
<IconButton
alt='Theme toggler'
icon={
mode === 'dark' ? '/icons/dark-close.svg' : '/icons/light-close.svg'
}
onClick={close}
/>
</div>
<div className='mobile-button-wrapper'>
<Button
href='#'
onClick={() => {
toggle();
close();
}}
>
{mode === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/projects' onClick={() => close()}>
Projects
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/blog' onClick={() => close()}>
Blog
</Button>
</div>
<div className='mobile-button-wrapper'>
<Button href='/contact' onClick={() => close()}>
Contact
</Button>
</div>
</Bar>
);
};
export default MobileNav;
-37
View File
@@ -1,37 +0,0 @@
import styled from 'styled-components';
import { StyledProps } from './types';
export const Bar = styled.nav<StyledProps>`
position: fixed;
z-index: 2;
top: 0;
right: 0;
transform-origin: right;
transform: ${({ open }) => (open ? 'translateX(0%)' : 'translateX(100%)')};
width: 80%;
height: 100vh;
background: var(--text);
transition: transform 250ms ease-in-out;
display: grid;
grid-template-rows: 30% repeat(4, 50px);
padding: 1rem 1rem 5rem 1rem;
@media (orientation: landscape) {
grid-template-rows: auto;
}
.close {
justify-self: flex-end;
align-self: flex-start;
margin-top: 0.5rem;
}
.mobile-button-wrapper {
display: flex;
margin: 0rem 1rem;
a {
color: var(--text-inverted) !important;
}
}
`;
-8
View File
@@ -1,8 +0,0 @@
export type Props = {
open: boolean;
close: () => void;
};
export type StyledProps = {
open: boolean;
};
-56
View File
@@ -1,56 +0,0 @@
import { useContext, useState } from 'react';
import { ThemeContext } from '../../styles/theme';
import { Bar } from './styles';
import Link from 'next/link';
import Image from 'next/image';
import Button from '../Button';
import IconButton from '../IconButton';
import MobileNav from '../MobileNav';
const Nav = () => {
const [mobileNavOpen, setMobileNavOpen] = useState<boolean>(false);
const { mode, toggle } = useContext(ThemeContext);
return (
<Bar>
<Link className='logo' href='/'>
<Image
className='logo-image'
src={mode === 'dark' ? '/light-logo.svg' : '/dark-logo.svg'}
alt='Logo Image'
width={48}
height={48}
/>
<h1>Hazem Krimi</h1>
</Link>
<div className='buttons'>
<IconButton
alt='Theme toggler'
icon={mode === 'dark' ? '/icons/sun.svg' : '/icons/moon.svg'}
onClick={toggle}
/>
<Button href='/projects'>Projects</Button>
<Button href='/blog'>Blog</Button>
<Button href='/contact'>Contact</Button>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
</div>
<div className='mobile-buttons'>
<Button href='/resume.pdf' target='_blank' variant='outline'>
Resume
</Button>
<IconButton
alt='Hamburger menu'
icon={
mode === 'dark' ? '/icons/light-menu.svg' : '/icons/dark-menu.svg'
}
onClick={() => setMobileNavOpen(true)}
/>
</div>
<MobileNav open={mobileNavOpen} close={() => setMobileNavOpen(false)} />
</Bar>
);
};
export default Nav;
-59
View File
@@ -1,59 +0,0 @@
import styled from 'styled-components';
export const Bar = styled.nav`
width: 100%;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
padding: 1rem 0rem;
* {
user-select: none;
}
h1 {
font-size: 1.7rem;
@media (max-width: 768px) {
font-size: 1rem;
}
}
div,
a.logo {
display: grid;
align-items: center;
column-gap: 1rem;
@media (max-width: 768px) {
column-gap: 0.5rem;
}
}
a.logo {
text-decoration: none;
color: var(--text);
cursor: pointer;
grid-template-columns: repeat(2, auto);
justify-content: flex-start;
}
.buttons {
grid-template-columns: repeat(5, auto);
justify-content: flex-end;
@media (max-width: 768px) {
display: none;
}
}
.mobile-buttons {
display: none;
@media (max-width: 768px) {
display: grid;
grid-template-columns: repeat(2, auto);
justify-content: flex-end;
}
}
`;
+3
View File
@@ -0,0 +1,3 @@
baseURL = 'http://hazemkrimi.tech/'
languageCode = 'en-us'
title = 'Hazem Krimi'
Vendored
-14
View File
@@ -1,14 +0,0 @@
declare module '@mdx-js/react' {
import * as React from 'react';
export type Components = {
[key]?: React.ComponentType<any>;
};
export interface MDXProviderProps {
children: React.ReactNode;
components?: Components;
}
export class MDXProvider extends React.Component<MDXProviderProps> {}
}
-5
View File
@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
-13
View File
@@ -1,13 +0,0 @@
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
providerImportSource: '@mdx-js/react',
},
});
module.exports = withMDX({
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
images: {
domains: ['res.cloudinary.com'],
},
});
-38
View File
@@ -1,38 +0,0 @@
{
"name": "personal-website",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"format": "prettier --write ./**/*.{js,jsx,ts,tsx}"
},
"dependencies": {
"@formspree/react": "^2.4.1",
"@mdx-js/loader": "^2.3.0",
"@next/mdx": "^13.2.4",
"gray-matter": "^4.0.3",
"next": "^13.2.4",
"next-mdx-remote": "^4.4.1",
"nprogress": "^0.2.0",
"prism-react-renderer": "^1.3.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"reading-time": "^1.5.0",
"styled-components": "^5.3.9"
},
"devDependencies": {
"@types/node": "^18.15.3",
"@types/nprogress": "^0.2.0",
"@types/react": "^18.0.28",
"@types/styled-components": "^5.1.26",
"babel-plugin-styled-components": "^2.0.7",
"babel-runtime": "^6.26.0",
"prettier": "^2.8.8",
"typescript": "^5.0.2"
},
"engines": {
"node": ">=14.x"
}
}
-48
View File
@@ -1,48 +0,0 @@
import { useRouter } from 'next/router';
import Head from 'next/head';
import IconButton from '../components/IconButton';
import { Wrapper } from '../styles/404';
const NotFound = () => {
const router = useRouter();
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>404 Not Found | Hazem Krimi</title>
</Head>
<Wrapper>
<h1>404: This page could not be found</h1>
<div className='back' onClick={() => router.push('/')}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Go Home</span>
</div>
</Wrapper>
</>
);
};
export default NotFound;
-69
View File
@@ -1,69 +0,0 @@
import { useEffect } from 'react';
import type { AppProps } from 'next/app';
import Script from 'next/script';
import { useRouter } from 'next/router';
import Nav from '../components/Nav';
import Theme from '../styles/theme';
import Container from '../components/Container';
import GlobalStyles from '../components/GlobalStyles';
import SharedStyles from '../styles/shared';
import Footer from '../components/Footer';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { pageview } from '../utils/gtag';
import { initStyles } from '../utils/styles';
NProgress.configure({ showSpinner: false });
const App = ({ Component, pageProps }: AppProps) => {
const router = useRouter();
useEffect(() => {
router.events.on('routeChangeStart', () => {
NProgress.start();
});
router.events.on('routeChangeComplete', (url) => {
NProgress.done();
pageview(url);
});
router.events.on('routeChangeError', () => {
NProgress.done();
});
return () => {
router.events.off('routeChangeComplete', (url) => {
pageview(url);
});
};
}, [router.events]);
return (
<>
<Script
id='styles-init'
strategy='afterInteractive'
dangerouslySetInnerHTML={{
__html: initStyles(),
}}
/>
<Theme>
<SharedStyles>
<GlobalStyles />
<Container>
<Nav />
<Component {...pageProps} />
<Footer />
</Container>
</SharedStyles>
</Theme>
</>
);
};
export default App;
-85
View File
@@ -1,85 +0,0 @@
import Document, {
DocumentContext,
Html,
Head,
Main,
NextScript,
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';
import { GOOGLE_ANALYTICS_KEY, initAnalytics } from '../utils/gtag';
import { initStyles } from '../utils/styles';
class Doc extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head>
<link
rel='shortcut icon'
href='/light-favicon.png'
id='light-favicon'
></link>
<link
rel='shortcut icon'
href='/dark-favicon.png'
id='dark-favicon'
></link>
<link rel='preconnect' href='https://fonts.gstatic.com' />
<link
href='https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;700&display=swap'
rel='stylesheet'
/>
<script
src={`https://www.googletagmanager.com/gtag/js?id=${GOOGLE_ANALYTICS_KEY}`}
/>
<script
id='analytics-init'
dangerouslySetInnerHTML={{
__html: initAnalytics(),
}}
/>
<script
id='styles-init'
dangerouslySetInnerHTML={{
__html: initStyles(),
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default Doc;
-143
View File
@@ -1,143 +0,0 @@
import { useEffect } from 'react';
import { getBlogPostsSlugs, getBlogPostdata } from '../../utils/blog';
import { useRouter } from 'next/router';
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
import { MDXProvider } from '@mdx-js/react';
import { GetStaticPaths, GetStaticProps } from 'next';
import { serialize } from 'next-mdx-remote/serialize';
import { Wrapper } from '../../styles/blog/slug';
import matter from 'gray-matter';
import Head from 'next/head';
import IconButton from '../../components/IconButton';
import CodeBlock from '../../components/CodeBlock';
import readingTime from 'reading-time';
import Image from 'next/image';
interface Props {
source: MDXRemoteSerializeResult;
frontMatter: any;
text: string;
}
const BlogPost = ({ source, frontMatter, text }: Props) => {
const router = useRouter();
const stats = readingTime(text);
const htmlOverrides = { code: CodeBlock };
const mdxComponents = {};
useEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<meta
property='og:title'
content={`${frontMatter?.title} | Hazem Krimi`}
/>
<meta
name='keywords'
content={
frontMatter?.tags
? frontMatter.tags.join(' ')
: `Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js`
}
/>
<title>{`${frontMatter?.title} | Hazem Krimi`}</title>
</Head>
<Wrapper>
<div className='meta'>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>{frontMatter?.title}</h1>
<p>{frontMatter?.description}</p>
<p>
By <b>{frontMatter?.author}</b> on <b>{frontMatter?.date}</b> (
{stats.text})
</p>
{frontMatter?.tags ? (
<div className='tags-wrapper'>
{frontMatter.tags.map((tag: string, index: number) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
) : null}
{frontMatter?.image ? (
<div className='image'>
<Image alt={frontMatter?.title} src={frontMatter?.image} fill />
</div>
) : null}
<hr />
</div>
<MDXProvider components={{ ...htmlOverrides, ...mdxComponents }}>
<div className='content'>
<MDXRemote {...source} />
</div>
</MDXProvider>
</Wrapper>
</>
);
};
export default BlogPost;
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getBlogPostsSlugs();
return {
paths,
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }: any) => {
const blogPostContent = await getBlogPostdata(params.slug);
if (!blogPostContent)
return {
props: {
source: undefined,
frontMatter: undefined,
},
};
const { data, content } = matter(blogPostContent);
const mdxSource = await serialize(content, {
scope: data,
});
return {
props: {
source: mdxSource,
frontMatter: data,
text: content,
},
};
};
-74
View File
@@ -1,74 +0,0 @@
import { useRouter } from 'next/router';
import { getBlogPosts } from '../../utils/blog';
import { Wrapper } from '../../styles/blog';
import Card from '../../components/Card';
import IconButton from '../../components/IconButton';
import Head from 'next/head';
interface Props {
blogPosts: {
title: string;
author: string;
description: string;
image?: string;
slug: string;
date: string;
tags?: string[];
}[];
}
const Index = ({ blogPosts }: Props) => {
const router = useRouter();
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is a Full Stack JavaScript Developer and a Software Engineering Enthusiast'
/>
<link rel='canonical' href='https://hazemkrimi.tech/blog' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is a Full Stack JavaScript Developer and a Software Engineering Enthusiast'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News'
/>
<title>Blog | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>Blog</h1>
<div className='articles-wrapper'>
{blogPosts.length !== 0 ? (
blogPosts.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/blog/${slug}`} />
))
) : (
<h4>Nothing for now</h4>
)}
</div>
</Wrapper>
</>
);
};
export default Index;
export const getStaticProps = async () => {
const blogPosts = getBlogPosts();
return {
props: {
blogPosts,
},
};
};
-133
View File
@@ -1,133 +0,0 @@
import { useState } from 'react';
import { useForm, ValidationError } from '@formspree/react';
import Head from 'next/head';
import { Wrapper } from '../styles/contact';
import Input from '../components/Input';
import MDXButton from '../components/MDXButton';
const About = () => {
const [form, setForm] = useState<{
name: string;
email: string;
message: string;
}>({
name: '',
email: '',
message: '',
});
const [state, Submit] = useForm(`${process.env.NEXT_PUBLIC_FORMSPREE_KEY}`);
const [submitted, setSubmitted] = useState<boolean>(false);
const handleChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setForm({ ...form, [event.target.name]: event.target.value });
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
try {
await Submit(event);
setSubmitted(true);
} finally {
setTimeout(() => setSubmitted(false), 1000);
setForm({ name: '', email: '', message: '' });
}
};
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Contact | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='content'>
<div>
<h1>
Contact Me{' '}
{submitted && <span className='success'>Message sent </span>}
</h1>
<form className='contact' onSubmit={handleSubmit}>
<Input
type='text'
placeholder='Name'
variant='small'
name='name'
required
value={form.name}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Name'
field='name'
errors={state.errors}
/>
<Input
type='text'
placeholder='Email'
variant='small'
name='email'
required
value={form.email}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Email'
field='email'
errors={state.errors}
/>
<Input
type='text'
placeholder='Message'
variant='big'
name='message'
required
value={form.message}
onChange={handleChange}
/>
<ValidationError
className='error'
prefix='Message'
field='message'
errors={state.errors}
/>
<MDXButton
type='submit'
variant='action'
disabled={state.submitting || submitted}
>
Submit
</MDXButton>
</form>
</div>
</div>
</Wrapper>
</>
);
};
export default About;
-116
View File
@@ -1,116 +0,0 @@
import { getProjects } from '../utils/projects';
import { getBlogPosts } from '../utils/blog';
import { GetStaticProps } from 'next';
import { Wrapper } from '../styles/home';
import Hero from '../components/Hero';
import Button from '../components/Button';
import Card from '../components/Card';
import Head from 'next/head';
interface Props {
blogPosts: {
title: string;
author: string;
description: string;
slug: string;
date: string;
tags?: string[];
}[];
projects: {
title: string;
description: string;
slug: string;
date: string;
tags?: string[];
}[];
}
const Index = ({ blogPosts, projects }: Props) => {
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Hazem Krimi</title>
</Head>
<Wrapper>
<Hero />
<div className='content'>
<h1>About</h1>
<div className='about'>
<p>
Experienced Full Stack developer with a focus on building
user-friendly web and cross-platform mobile applications using
cutting-edge technologies. Passionate about ongoing learning and
staying up-to-date with the latest trends in software engineering.
</p>
</div>
{projects.length !== 0 && (
<>
<h1>Projects</h1>
<Button href='/projects' className='blue'>
See More
</Button>
<div className='projects'>
<div className='projects-wrapper'>
{projects.slice(0, 3).map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/projects/${slug}`} />
))}
</div>
</div>
</>
)}
{blogPosts.length !== 0 && (
<>
<h1>Blog</h1>
<Button href='/blog' className='blue'>
See More
</Button>
<div className='blog'>
<div className='articles-wrapper'>
{blogPosts.slice(0, 3).map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/blog/${slug}`} />
))}
</div>
</div>
</>
)}
</div>
</Wrapper>
</>
);
};
export default Index;
export const getStaticProps: GetStaticProps = async () => {
const blogPosts = getBlogPosts();
const projects = getProjects();
return {
props: {
blogPosts,
projects,
},
};
};
-153
View File
@@ -1,153 +0,0 @@
import { useEffect } from 'react';
import { getPorjectsSlugs, getProjectdata } from '../../utils/projects';
import { useRouter } from 'next/router';
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
import { MDXProvider } from '@mdx-js/react';
import { GetStaticPaths, GetStaticProps } from 'next';
import { serialize } from 'next-mdx-remote/serialize';
import matter from 'gray-matter';
import { Wrapper } from '../../styles/projects/slug';
import Head from 'next/head';
import IconButton from '../../components/IconButton';
import CodeBlock from '../../components/CodeBlock';
import MDXButton from '../../components/MDXButton';
import Image from 'next/image';
interface Props {
source: MDXRemoteSerializeResult;
frontMatter: any;
}
const Project = ({ source, frontMatter }: Props) => {
const router = useRouter();
const htmlOverrides = { code: CodeBlock };
const mdxComponents = { Button: MDXButton };
useEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content={
frontMatter?.description
? frontMatter.description
: `Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.`
}
/>
<meta
property='og:title'
content={`${frontMatter?.title} | Hazem Krimi`}
/>
<meta
name='keywords'
content={
frontMatter?.tags
? frontMatter.tags.join(' ')
: `Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js`
}
/>
<title>{`${frontMatter?.title} | Hazem Krimi`}</title>
</Head>
<Wrapper>
<div className='meta'>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>{frontMatter?.title}</h1>
<p>{frontMatter?.description}</p>
{frontMatter?.tags ? (
<div className='tags-wrapper'>
{frontMatter.tags.map((tag: string, index: number) => (
<span key={index}>#{tag}&nbsp;</span>
))}
</div>
) : null}
{frontMatter?.image && !frontMatter?.hideImage ? (
<div className='image'>
<Image alt={frontMatter?.title} src={frontMatter?.image} fill />
</div>
) : null}
<hr />
</div>
<MDXProvider components={{ ...htmlOverrides, ...mdxComponents }}>
<div className='content'>
<MDXRemote {...source} />
<h1>Showcase</h1>
<div className='showcase-buttons'>
<MDXButton
variant='action'
link={frontMatter?.demo}
target='_blank'
>
Demo
</MDXButton>
<MDXButton
variant='outline'
link={frontMatter?.code}
target='_blank'
>
Source Code
</MDXButton>
</div>
</div>
</MDXProvider>
</Wrapper>
</>
);
};
export default Project;
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getPorjectsSlugs();
return {
paths,
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }: any) => {
const projectContent = await getProjectdata(params.slug);
if (!projectContent)
return {
props: {
source: undefined,
frontMatter: undefined,
},
};
const { data, content } = matter(projectContent);
const mdxSource = await serialize(content, {
scope: data,
});
return {
props: {
source: mdxSource,
frontMatter: data,
},
};
};
-79
View File
@@ -1,79 +0,0 @@
import { getProjects } from '../../utils/projects';
import { useRouter } from 'next/router';
import { Wrapper } from '../../styles/projects';
import Card from '../../components/Card';
import IconButton from '../../components/IconButton';
import Head from 'next/head';
interface Props {
projects: {
title: string;
description: string;
image?: string;
slug: string;
date: string;
tags?: string[];
}[];
}
const Index = ({ projects }: Props) => {
const router = useRouter();
return (
<>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='author' content='Hazem Krimi' />
<meta
name='description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<link rel='canonical' href='https://hazemkrimi.tech' />
<meta property='og:image' content='/logo.png' />
<meta
property='og:description'
content='Hazem Krimi is an experienced Full Stack developer with a focus on building user-friendly
web and cross-platform mobile applications using cutting-edge
technologies. Passionate about ongoing learning and staying up-to-date
with the latest trends in software engineering.'
/>
<meta property='og:title' content='Hazem Krimi' />
<meta
name='keywords'
content='Hazem, Krimi, Hazem Krimi, Developer, Software, Engineer, Web, Mobile, Frontend, Backend, Fullstack, JavaScript, TypeScript, React.js, React Native, Node.js, Portfolio, Blog, Tutorials, Tech News, Software Developer, Software Engineer, Full Stack TypeScript Developer, Next.js'
/>
<title>Projects | Hazem Krimi</title>
</Head>
<Wrapper>
<div className='back' onClick={() => router.back()}>
<IconButton alt='Back' icon='/icons/arrow-left.svg' />
<span>Back</span>
</div>
<h1>Projects</h1>
<div className='projects-wrapper'>
{projects.length !== 0 ? (
projects.map(({ slug, ...rest }) => (
<Card {...rest} key={slug} href={`/projects/${slug}`} />
))
) : (
<h4>Nothing for now</h4>
)}
</div>
</Wrapper>
</>
);
};
export default Index;
export const getStaticProps = async () => {
const projects = getProjects();
return {
props: {
projects,
},
};
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 B

-1
View File
@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1010.51 567.82"><defs><style>.cls-1{fill:#262626;}</style></defs><title>dark-logo</title><polygon points="478.54 0 221.41 0 0 283.91 257.13 283.91 478.54 0"/><polygon class="cls-1" points="0 283.91 257.13 283.91 477.86 567.82 220.74 567.82 0 283.91"/><polygon class="cls-1" points="531.97 0 789.09 0 1010.51 283.91 753.38 283.91 531.97 0"/><polygon points="1010.51 283.91 753.38 283.91 532.64 567.82 789.77 567.82 1010.51 283.91"/></svg>

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1573ca" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>

Before

Width:  |  Height:  |  Size: 307 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 294 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-codepen"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"></polygon><line x1="12" y1="22" x2="12" y2="15.5"></line><polyline points="22 8.5 12 15.5 2 8.5"></polyline><polyline points="2 15.5 12 8.5 22 15.5"></polyline><line x1="12" y1="2" x2="12" y2="8.5"></line></svg>

Before

Width:  |  Height:  |  Size: 478 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>

Before

Width:  |  Height:  |  Size: 519 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>

Before

Width:  |  Height:  |  Size: 392 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 341 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitter"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>

Before

Width:  |  Height:  |  Size: 400 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 294 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-codepen"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"></polygon><line x1="12" y1="22" x2="12" y2="15.5"></line><polyline points="22 8.5 12 15.5 2 8.5"></polyline><polyline points="2 15.5 12 8.5 22 15.5"></polyline><line x1="12" y1="2" x2="12" y2="8.5"></line></svg>

Before

Width:  |  Height:  |  Size: 478 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>

Before

Width:  |  Height:  |  Size: 519 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>

Before

Width:  |  Height:  |  Size: 392 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 341 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitter"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>

Before

Width:  |  Height:  |  Size: 400 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-moon"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>

Before

Width:  |  Height:  |  Size: 276 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>

Before

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

-1
View File
@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 872.54 490.3"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#ccc;}</style></defs><title>light-logo</title><g id="Hazem_logo" data-name="Hazem logo"><polygon class="cls-1" points="413.2 0 191.18 0 0 245.15 222.02 245.15 413.2 0"/><polygon class="cls-2" points="0 245.15 222.02 245.15 412.62 490.3 190.6 490.3 0 245.15"/><polygon class="cls-2" points="459.34 0 681.36 0 872.54 245.15 650.52 245.15 459.34 0"/><polygon class="cls-1" points="872.54 245.15 650.52 245.15 459.92 490.3 681.94 490.3 872.54 245.15"/></g></svg>

Before

Width:  |  Height:  |  Size: 601 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

BIN
View File
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
User-agent: *
Allow: *
Sitemap: https://hazemkrimi.tech/sitemap.xml
-42
View File
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<!-- created with Free Online Sitemap Generator www.xml-sitemaps.com -->
<url>
<loc>https://hazemkrimi.tech/</loc>
<lastmod>2022-02-13T19:02:40+00:00</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://hazemkrimi.tech/portfolio</loc>
<lastmod>2022-02-13T19:02:40+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://hazemkrimi.tech/blog</loc>
<lastmod>2022-02-13T19:02:40+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://hazemkrimi.tech/contact</loc>
<lastmod>2022-02-13T19:02:40+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://hazemkrimi.tech/resume.pdf</loc>
<lastmod>2022-02-13T19:02:40+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://hazemkrimi.tech/portfolio/react-weather-app</loc>
<lastmod>2022-02-13T19:02:40+00:00</lastmod>
<priority>0.80</priority>
</url>
</urlset>
Vendored
-17
View File
@@ -1,17 +0,0 @@
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
colors: {
dark: {
background: string;
text: string;
};
light: {
background: string;
text: string;
};
blue: string;
};
}
}
-25
View File
@@ -1,25 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
display: grid;
justify-items: center;
text-align: center;
@media (max-width: 768px) {
min-height: 65vh;
}
h1 {
font-size: 1.7rem;
align-self: flex-end;
}
.back {
cursor: pointer;
color: #3f9aee;
display: flex;
align-items: center;
align-self: flex-start;
}
`;
-38
View File
@@ -1,38 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
h1 {
font-size: 1.7rem;
margin-bottom: 1rem;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
.articles-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
`;
-93
View File
@@ -1,93 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
display: grid;
grid-template-rows: auto 1fr;
row-gap: 2rem;
@media (max-width: 768px) {
row-gap: 1rem;
}
.meta {
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
.image {
margin: 1rem 0rem;
}
h1,
p,
.tags-wrapper {
text-align: left;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.2rem;
}
.tags-wrapper {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
}
hr {
height: 0.1rem;
opacity: 0.3;
margin: 1rem auto 0rem auto;
@media (max-width: 768px) {
margin: 1rem auto 0rem auto;
}
}
.content {
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.3rem;
}
h3 {
font-size: 1.1rem;
}
& > * {
margin: 0.5rem 0rem;
}
button {
margin: 1rem 0rem;
}
p * {
width: 100%;
height: auto;
}
ul,
ol {
margin-left: 1.5rem;
}
}
`;
-49
View File
@@ -1,49 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
padding: 1rem 0rem;
@media (max-width: 768px) {
padding: 0rem;
grid-template-columns: auto;
column-gap: 0rem;
row-gap: 1rem;
}
h1 {
font-size: 1.7rem;
}
.content {
display: flex;
flex-direction: column;
}
.contact {
margin: 1rem 0rem;
@media (max-width: 768px) {
margin: 1rem 0rem;
}
}
.success {
color: #73d26b;
align-self: center;
font-weight: normal;
}
.contact {
display: grid;
grid-template-columns: auto;
row-gap: 1.5rem;
.error {
color: #d75050;
}
@media (max-width: 768px) {
row-gap: 0.5rem;
}
}
`;
-49
View File
@@ -1,49 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
padding: 1rem 0rem;
display: grid;
grid-template-columns: auto;
row-gap: 1rem;
h1 {
display: inline;
font-size: 1.7rem;
margin-right: 1rem;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
.blue {
color: ${({ theme }) => theme.colors.blue};
@media (max-width: 768px) {
margin-top: 0.5rem;
}
}
.about,
.projects,
.blog {
margin: 1rem 0rem;
}
.projects {
margin-bottom: 3rem;
}
.projects-wrapper,
.articles-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
`;
-38
View File
@@ -1,38 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
h1 {
font-size: 1.7rem;
margin-bottom: 1rem;
}
h4 {
font-size: 1.3rem;
grid-column: 1 / -1;
align-self: center;
justify-self: center;
}
.projects-wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18.75rem, 1fr));
grid-auto-rows: minmax(6.25rem, auto);
align-items: stretch;
justify-items: center;
gap: 1rem;
}
`;
-91
View File
@@ -1,91 +0,0 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
min-height: 75vh;
padding: 1rem 0rem;
display: grid;
grid-template-rows: auto 1fr;
row-gap: 2rem;
@media (max-width: 768px) {
row-gap: 1rem;
}
.meta {
.back {
cursor: pointer;
text-align: left;
display: inline-flex;
align-items: center;
span {
color: ${({ theme }) => theme.colors.blue} !important;
}
}
.image {
margin: 1rem 0rem;
}
h1,
p {
text-align: left;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.2rem;
}
}
hr {
height: 0.1rem;
opacity: 0.3;
margin: 1rem auto 0rem auto;
}
.content {
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.3rem;
}
h3 {
font-size: 1.1rem;
}
& > * {
margin: 0.5rem 0rem;
}
button {
margin: 1rem 0rem;
}
p * {
width: 100%;
height: auto;
}
ul,
ol {
margin-inline-start: 0.5rem;
list-style-position: inside;
}
.showcase-buttons {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
justify-content: space-between;
align-items: center;
}
}
`;
-21
View File
@@ -1,21 +0,0 @@
import { ThemeProvider, DefaultTheme } from 'styled-components';
const Shared = ({ children }: { children: React.ReactNode }) => {
const theme: DefaultTheme = {
colors: {
dark: {
background: '#262626',
text: 'white',
},
light: {
background: '#F9F9F9',
text: 'black',
},
blue: '#1573CA',
},
};
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
};
export default Shared;
-63
View File
@@ -1,63 +0,0 @@
import { useReducer, useEffect, createContext } from 'react';
export const ThemeContext = createContext<{ mode: string; toggle: () => void }>(
{
mode: 'light',
toggle: () => {},
}
);
const reducer = (state: string, action: { type: string }) => {
switch (action.type) {
case 'TOGGLE':
return state === 'light' ? 'dark' : 'light';
case 'DARK':
return 'dark';
case 'LIGHT':
return 'light';
default:
return state;
}
};
const Theme = ({ children }: { children: React.ReactNode }) => {
const [mode, dispatch] = useReducer(reducer, 'light');
const toggle = () => {
const root = window.document.documentElement;
const lightFavicon = document.querySelector('link#light-favicon')!;
const darkFavicon = document.querySelector('link#dark-favicon')!;
if (mode === 'dark') {
window.localStorage.setItem('mode', 'light');
root.style.setProperty('--background', '#F9F9F9');
root.style.setProperty('--secondary-background', 'white');
root.style.setProperty('--text', 'black');
root.style.setProperty('--text-inverted', 'white');
document.head.append(darkFavicon);
} else {
window.localStorage.setItem('mode', 'dark');
root.style.setProperty('--background', '#262626');
root.style.setProperty('--secondary-background', '#2F2F2F');
root.style.setProperty('--text', 'white');
root.style.setProperty('--text-inverted', 'black');
document.head.append(lightFavicon);
}
dispatch({ type: 'TOGGLE' });
};
useEffect(() => {
const root = window.document.documentElement;
const initialThemeMode = root.style.getPropertyValue('--mode');
dispatch({ type: initialThemeMode === 'dark' ? 'DARK' : 'LIGHT' });
}, []);
return (
<ThemeContext.Provider value={{ mode, toggle }}>
{children}
</ThemeContext.Provider>
);
};
export default Theme;
-22
View File
@@ -1,22 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"incremental": true,
"jsx": "preserve"
},
"exclude": ["node_modules"],
"include": ["**/*.d.ts", "**/*.ts", "**/*.tsx"]
}
-78
View File
@@ -1,78 +0,0 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const blogPostsDirectory = path.join(process.cwd(), '_blog');
export const getBlogPosts = () => {
try {
const fileNames = fs.readdirSync(blogPostsDirectory);
if (!fileNames) return [];
const allBlogPostsData = fileNames.map((filename) => {
const slug = filename.replace('.mdx', '');
const fullPath = path.join(blogPostsDirectory, filename);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
const formattedDate = new Date(data.date).toLocaleDateString(
'en-IN',
// @ts-ignore
options
);
const frontmatter = {
...data,
date: formattedDate,
};
return {
slug,
...frontmatter,
};
});
return allBlogPostsData.sort((a, b) => {
if (new Date(a.date) < new Date(b.date)) {
return 1;
} else {
return -1;
}
});
} catch {
return [];
}
};
export const getBlogPostsSlugs = () => {
try {
const fileNames = fs.readdirSync(blogPostsDirectory);
if (!fileNames) return [];
return fileNames.map((filename) => {
return {
params: {
slug: filename.replace('.mdx', ''),
},
};
});
} catch {
return [];
}
};
export const getBlogPostdata = async (slug: string) => {
try {
const fullPath = path.join(blogPostsDirectory, `${slug}.mdx`);
const postContent = fs.readFileSync(fullPath, 'utf8');
if (!postContent) return undefined;
return postContent;
} catch {
return undefined;
}
};
-31
View File
@@ -1,31 +0,0 @@
export const GOOGLE_ANALYTICS_KEY =
process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY;
export const pageview = (url: any) => {
// @ts-ignore
window.gtag('config', GOOGLE_ANALYTICS_KEY, {
page_path: url,
});
};
export const event = ({ action, category, label, value }: any) => {
// @ts-ignore
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
};
export const initAnalytics = () => `
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', ${GOOGLE_ANALYTICS_KEY}, {
page_path: window.location.pathname,
});
`;
-78
View File
@@ -1,78 +0,0 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const projects = path.join(process.cwd(), '_projects');
export const getProjects = () => {
try {
const fileNames = fs.readdirSync(projects);
if (!fileNames) return [];
const allProjectsData = fileNames.map((filename) => {
const slug = filename.replace('.mdx', '');
const fullPath = path.join(projects, filename);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
const formattedDate = new Date(data.date).toLocaleDateString(
'en-IN',
// @ts-ignore
options
);
const frontmatter = {
...data,
date: formattedDate,
};
return {
slug,
...frontmatter,
};
});
return allProjectsData.sort((a, b) => {
if (new Date(a.date) < new Date(b.date)) {
return 1;
} else {
return -1;
}
});
} catch {
return [];
}
};
export const getPorjectsSlugs = () => {
try {
const fileNames = fs.readdirSync(projects);
if (!fileNames) return [];
return fileNames.map((filename) => {
return {
params: {
slug: filename.replace('.mdx', ''),
},
};
});
} catch {
return [];
}
};
export const getProjectdata = async (slug: string) => {
try {
const fullPath = path.join(projects, `${slug}.mdx`);
const postContent = fs.readFileSync(fullPath, 'utf8');
if (!postContent) return undefined;
return postContent;
} catch {
return undefined;
}
};
-45
View File
@@ -1,45 +0,0 @@
export const initStyles = () => `
function getInitialThemeMode() {
const persistedColorPreference = window.localStorage.getItem('mode');
const hasPersistedPreference = typeof persistedColorPreference === 'string';
if (hasPersistedPreference) {
return persistedColorPreference;
}
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const hasMediaQueryPreference = typeof mql.matches === 'boolean';
if (hasMediaQueryPreference) {
return mql.matches ? 'dark' : 'light';
}
return 'light';
}
(() => {
const mode = getInitialThemeMode();
const root = document.documentElement;
const lightFavicon = document.querySelector('link#light-favicon');
const darkFavicon = document.querySelector('link#dark-favicon');
root.style.setProperty('--mode', mode);
root.style.setProperty(
'--background',
mode === 'light' ? '#F9F9F9' : '#262626'
);
root.style.setProperty(
'--secondary-background',
mode === 'light' ? 'white' : '#2F2F2F'
);
root.style.setProperty(
'--text',
mode === 'light' ? 'black' : 'white'
);
root.style.setProperty(
'--text-inverted',
mode === 'light' ? 'white' : 'black'
);
document.head.append(mode === 'light' ? darkFavicon : lightFavicon);
})();
`;
-1663
View File
File diff suppressed because it is too large Load Diff