mirror of
https://github.com/morten-olsen/morten-olsen.github.io.git
synced 2026-02-08 01:46:28 +01:00
update
This commit is contained in:
102
:w
102
:w
@@ -1,102 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Profile } from '../../data/profile';
|
||||
import { HeroBackground } from './background';
|
||||
|
||||
type Props = {
|
||||
profile: Profile;
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
min-height: 900px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30px;
|
||||
`;
|
||||
|
||||
const Avatar = styled.div<{src: string}>`
|
||||
background-image: url('${({ src }) => src}');
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
background-size: cover;
|
||||
border: solid 10px #fff;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||
transform: scaleX(-1);
|
||||
`;
|
||||
|
||||
const AvatarSpacer = styled.div`
|
||||
padding-bottom: 100%;
|
||||
`;
|
||||
|
||||
const Name = styled.h1`
|
||||
font-size: 4rem;
|
||||
line-height: 4rem;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
text-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Tagline = styled.h2`
|
||||
font-size: 2rem;
|
||||
font-weight: 100;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
text-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Social = styled.div`
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const SocialItem = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: #000;
|
||||
`;
|
||||
|
||||
const SocialText = styled.div`
|
||||
margin-left: 20px;
|
||||
`;
|
||||
|
||||
const SocialLogo = styled.div<{ src: string }>`
|
||||
background-image: url('${({ src }) => src}');
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-size: cover;
|
||||
`
|
||||
|
||||
const Hero: React.FC<Props> = ({ profile }) => (
|
||||
<Wrapper>
|
||||
<Avatar src={profile.avatar}>
|
||||
<AvatarSpacer />
|
||||
</Avatar>
|
||||
<Name>
|
||||
{profile.name}
|
||||
</Name>
|
||||
<Tagline>{profile.tagline}</Tagline>
|
||||
<Social>
|
||||
{profile.social.map((social) => (
|
||||
<SocialItem href={social.link} target="_blank">
|
||||
<SocialLogo src={social.logo} />
|
||||
<SocialText>{social.name}</SocialText>
|
||||
</SocialItem>
|
||||
))}
|
||||
</Social>
|
||||
<HeroBackground />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export { Hero };
|
||||
@@ -1,4 +1,5 @@
|
||||
title: How to hire engineers, by an engineer
|
||||
cover: cover.png
|
||||
published: 2021-03-15
|
||||
parts:
|
||||
- main.md
|
||||
|
||||
10
articles/hyperconnect/authentication.md
Normal file
10
articles/hyperconnect/authentication.md
Normal file
@@ -0,0 +1,10 @@
|
||||
we will need a way for our devices to identify with one and another, and since we might not have access to any particular node in the system at the time of authentication, this needs to work without a trusted third party at the time of connection.
|
||||
|
||||
The best way I can think of here is to use a signing authority.
|
||||
|
||||
Our authentication will consist of two main concept; a passport and a passport authority.
|
||||
|
||||
Each device will create a passport, which contains some various information but most important is a public key coresponding to a private key stored on the device. A device then has to go through a "claim process" where a user assign it as their device. this happens by that uses passport authority uses a private key to sign the device's passport, and giving it the authorities public key.
|
||||
|
||||
Now our device has a signed passport, and the oublic key of the authority, so when two devices needs to connect they can now go through an authentication process to verify each others passport. If both devices verifies the other device's passport as valid the connection can be established.
|
||||
|
||||
6
articles/hyperconnect/definition.md
Normal file
6
articles/hyperconnect/definition.md
Normal file
@@ -0,0 +1,6 @@
|
||||
What does hyper-connectivity mean? Well, a common interpretation is to be available on multiple different channels. We al know that one guy whom you contact on text message only to get a reply on Signal, just to get a follow up using email.
|
||||
|
||||
Then what does it mean for devices to be hyper connected? In this case it means using all available forms of communication in order to get data from one place to another, which is what this article is about.
|
||||
|
||||
Today, when a developer want a system to send data from one place to another he needs to pick a transport protocol such as http. Then connect
|
||||
|
||||
25
articles/hyperconnect/index.yml
Normal file
25
articles/hyperconnect/index.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
title: Creating a hyper connectivity framework
|
||||
parts:
|
||||
- title: definition
|
||||
file: definition.md
|
||||
- title: reason
|
||||
- title: target
|
||||
- title: design
|
||||
notes: |
|
||||
onion based stream
|
||||
4 way streams
|
||||
- title: connection
|
||||
notes: |
|
||||
peerjs, tcp, ble(?)
|
||||
session id
|
||||
- title: reconnection
|
||||
- title: authentication
|
||||
file: authentication.md
|
||||
notes: |
|
||||
passport service
|
||||
- title: security
|
||||
notes: |
|
||||
diff-hellman
|
||||
- title: transport-nodes
|
||||
- title: proof-of-concept
|
||||
- title: conclusion
|
||||
@@ -1,4 +1,5 @@
|
||||
title: My home runs Redux
|
||||
cover: cover.png
|
||||
published: 2021-03-15
|
||||
parts:
|
||||
- main.md
|
||||
|
||||
3
articles/npm-safety/index.yml
Normal file
3
articles/npm-safety/index.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
title: NPM safety
|
||||
parts:
|
||||
- main.md
|
||||
12
articles/npm-safety/main.md
Normal file
12
articles/npm-safety/main.md
Normal file
@@ -0,0 +1,12 @@
|
||||
The NPM eco system has hade a long list of stories about major dependencies turning evil lately, either by direct involvment from the package maintainer or due to the maintainer being compromised. What ever the reason, it does bring in to focus the dangers of package managers!
|
||||
|
||||
The threat landscaoe being what it is it is tine that we as developers start ti apply a little extra operational security to mitigate some of the risk that comes from installing unaudited oackages, which are in essence just arbitrary executing code which can conceil a viper virus or a remote access token as easily as a random email attachment.
|
||||
|
||||
So what can we do to protect ourselfs? we can split the attacks into to groups, one which attacks the users of our application and one that attacks our selves and our CI/CD pipelines
|
||||
|
||||
Combating the former is hard and here the only thing short of manually auditing every change in every depebdebcy is to set up monitoring of dependencies with vulnerabilities and freeze dependencies versions to known good versions
|
||||
|
||||
The later attack type is the one this article is going to focus on.
|
||||
|
||||
A NPM oackage has the ability to run post install hooks, which essentially allow it to execute any code as the current user upon installing the dependency. This allows a malicius oackage to infect your system upon installation by also installing malware or running malicious commands. This can be mitigated by disable install hooks, but some packages have legitinate use cases for these install hooks.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@types/yaml": "^1.9.7",
|
||||
"babel-plugin-styled-components": "^2.0.6",
|
||||
"file-loader": "^6.2.0",
|
||||
"framer-motion": "^6.2.8",
|
||||
"fs-extra": "^10.0.1",
|
||||
"marked": "^4.0.12",
|
||||
"next-compose-plugins": "^2.2.1",
|
||||
|
||||
51
src/components/animations/slide-in.tsx
Normal file
51
src/components/animations/slide-in.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { motion, useAnimation } from 'framer-motion';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { useOnScreen } from '../../hooks/animation';
|
||||
|
||||
const SlideIn: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const controls = useAnimation();
|
||||
const onScreen = useOnScreen(ref);
|
||||
const slideTime = useMemo(
|
||||
() => 1,
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!onScreen) {
|
||||
return;
|
||||
}
|
||||
controls.start({
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: slideTime,
|
||||
ease: "easeOut"
|
||||
}
|
||||
})
|
||||
},
|
||||
[controls, onScreen],
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
{...props as any}
|
||||
ref={ref}
|
||||
initial={{
|
||||
y: 50,
|
||||
opacity: 0,
|
||||
scale: 1.3,
|
||||
}}
|
||||
animate={controls}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
export { SlideIn };
|
||||
57
src/components/animations/transition.tsx
Normal file
57
src/components/animations/transition.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { motion, useAnimation } from 'framer-motion';
|
||||
|
||||
const Transition: React.FC = ({ children }) => {
|
||||
return <>{children}</>
|
||||
// const [displayChildren, setDisplayChildren] = useState<React.ReactNode>(children);
|
||||
// const [inTransition, setInTransition] = useState(false);
|
||||
// const controls = useAnimation();
|
||||
//
|
||||
// useEffect(() => {
|
||||
// if (children !== displayChildren) {
|
||||
// setInTransition(true);
|
||||
// }
|
||||
// }, [children, setDisplayChildren, displayChildren]);
|
||||
//
|
||||
// useEffect(() => {
|
||||
// if (!inTransition || displayChildren === children) {
|
||||
// return;
|
||||
// }
|
||||
// controls.start({
|
||||
// opacity: 1,
|
||||
// y: 0,
|
||||
// transition: {
|
||||
// duration: 0.3,
|
||||
// ease: "easeOut"
|
||||
// }
|
||||
// }).then(() => {
|
||||
// setInTransition(false);
|
||||
// setDisplayChildren(children);
|
||||
// });
|
||||
// }, [inTransition, children, displayChildren])
|
||||
//
|
||||
// return (
|
||||
// <>
|
||||
// {inTransition && (
|
||||
// <motion.div
|
||||
// animate={controls}
|
||||
// initial={{
|
||||
// opacity: 0,
|
||||
// background: '#fff',
|
||||
// position: 'fixed',
|
||||
// zIndex: 10,
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// bottom: 0,
|
||||
// }}
|
||||
// >
|
||||
// {children}
|
||||
// </motion.div>
|
||||
// )}
|
||||
// <div>{displayChildren}</div>
|
||||
// </>
|
||||
// );
|
||||
}
|
||||
|
||||
export { Transition };
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import styled from "styled-components";
|
||||
import { Experience } from "../../data/experiences"
|
||||
import { SlideIn } from '../animations/slide-in';
|
||||
|
||||
type Props = {
|
||||
experiences: Experience[];
|
||||
@@ -44,7 +45,7 @@ const ExperienceWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Inner = styled.div`
|
||||
const Inner = styled(SlideIn)`
|
||||
border: solid 1px #eee;
|
||||
padding: 20px;
|
||||
`;
|
||||
@@ -73,25 +74,31 @@ const More = styled.div`
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
const Hidden = styled.div`
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease-out;
|
||||
`;
|
||||
|
||||
const ExperienceRow = ({ experience }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<Outer>
|
||||
<ExperienceWrapper key={experience.id}>
|
||||
<ExperienceWrapper>
|
||||
<Inner>
|
||||
<CompanyName>{experience.company.name}</CompanyName>
|
||||
<JobTitle>{experience.title}</JobTitle>
|
||||
<Time>{experience.startDate} - {experience.endDate}</Time>
|
||||
{visible ? (
|
||||
<>
|
||||
<Hidden
|
||||
style={{
|
||||
maxHeight: visible ? 1000 : 0,
|
||||
opacity: visible ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<ReactMarkdown>
|
||||
{experience.content}
|
||||
</ReactMarkdown>
|
||||
<More onClick={() => setVisible(false)}>Show less</More>
|
||||
</>
|
||||
) : (
|
||||
<More onClick={() => setVisible(true)}>Show more</More>
|
||||
)}
|
||||
</Hidden>
|
||||
<More onClick={() => setVisible(!visible)}>Show {visible ? 'less' : 'more'}</More>
|
||||
</Inner>
|
||||
</ExperienceWrapper>
|
||||
</Outer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { SlideIn } from '../animations/slide-in';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Suspense, useMemo, useRef, useState } from 'react';
|
||||
import { Canvas, useFrame, useLoader } from '@react-three/fiber';
|
||||
import { Euler, Vector3 } from 'three';
|
||||
import { Clock, Euler, Vector3 } from 'three';
|
||||
import { TextureLoader } from 'three/src/loaders/TextureLoader'
|
||||
import styled from 'styled-components';
|
||||
const { default: smoke } = require('./smoke.png');
|
||||
@@ -14,6 +14,24 @@ const Wrapper = styled.div`
|
||||
z-index: -1;
|
||||
`;
|
||||
|
||||
const FrameLimiter = () => {
|
||||
const [clock] = React.useState(new Clock());
|
||||
const [fps] = React.useState(10);
|
||||
|
||||
useFrame((state: any) => {
|
||||
state.ready = false;
|
||||
const timeUntilNextFrame = (1000 / fps) - clock.getDelta();
|
||||
|
||||
setTimeout(() => {
|
||||
state.ready = true;
|
||||
state.invalidate();
|
||||
}, Math.max(0, timeUntilNextFrame));
|
||||
|
||||
});
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const Cloud = ({ texture }) => {
|
||||
const ref = useRef<any>();
|
||||
const width = window.innerWidth / 2;
|
||||
@@ -21,7 +39,7 @@ const Cloud = ({ texture }) => {
|
||||
|
||||
useFrame(() => {
|
||||
if (!ref.current) return;
|
||||
ref.current.rotation.z -= 0.001;
|
||||
ref.current.rotation.z -= 0.003;
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -45,7 +63,7 @@ const Clouds = () => {
|
||||
return (
|
||||
<>
|
||||
{items.map((_, i) => (
|
||||
<Cloud texture={colorMap} />
|
||||
<Cloud key={i} texture={colorMap} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
@@ -69,6 +87,7 @@ const Scene = () => {
|
||||
rotation: [1.16, -0.12, 0.27],
|
||||
}}
|
||||
>
|
||||
<FrameLimiter />
|
||||
<scene>
|
||||
<perspectiveCamera
|
||||
args={[60,window.innerWidth / window.innerHeight,1,1000]}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Profile } from '../../data/profile';
|
||||
import { HeroBackground } from './background';
|
||||
import { SlideIn } from '../animations/slide-in';
|
||||
|
||||
type Props = {
|
||||
profile: Profile;
|
||||
@@ -101,10 +102,12 @@ const Hero: React.FC<Props> = ({ profile }) => (
|
||||
<Tagline>{profile.tagline}</Tagline>
|
||||
<Social>
|
||||
{profile.social.map((social) => (
|
||||
<SocialItem href={social.link} target="_blank">
|
||||
<SlideIn key={social.name}>
|
||||
<SocialItem key={social.name} href={social.link} target="_blank">
|
||||
<SocialLogo src={social.logo} />
|
||||
<SocialText>{social.name}</SocialText>
|
||||
</SocialItem>
|
||||
</SlideIn>
|
||||
))}
|
||||
</Social>
|
||||
<HeroBackground />
|
||||
|
||||
@@ -3,6 +3,10 @@ import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Content } from "../content";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
const Menu = styled.aside`
|
||||
font-size: 2rem;
|
||||
padding: 1rem 0;
|
||||
@@ -13,10 +17,10 @@ const Menu = styled.aside`
|
||||
const MenuItem = styled(Link)`
|
||||
`;
|
||||
|
||||
const Navigation: React.FC<{}> = () => (
|
||||
const Navigation: React.FC<Props> = ({ name }) => (
|
||||
<Menu>
|
||||
<Content>
|
||||
by <MenuItem href="/">Morten Olsen</MenuItem>
|
||||
by <MenuItem href="/">{name}</MenuItem>
|
||||
</Content>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,13 @@ import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Article } from '../../../data/articles';
|
||||
import { SlideIn } from '../../animations/slide-in';
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
const Wrapper = styled(SlideIn)`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 15px;
|
||||
@@ -58,11 +59,13 @@ const ArticleTile: React.FC<Props> = ({ article }) => (
|
||||
<Header>
|
||||
{article.title}
|
||||
</Header>
|
||||
<Published>3 days ago</Published>
|
||||
<Published>{article.published}</Published>
|
||||
</Inner>
|
||||
{article.cover && (
|
||||
<ImageWrapper>
|
||||
<Image src={article.cover} />
|
||||
</ImageWrapper>
|
||||
)}
|
||||
</Wrapper>
|
||||
</Link>
|
||||
);
|
||||
|
||||
7
src/config.ts
Normal file
7
src/config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
class Config {
|
||||
public get dev() {
|
||||
return process && process.env.NODE_ENV === 'development';
|
||||
}
|
||||
}
|
||||
|
||||
export const config = new Config();
|
||||
@@ -5,6 +5,7 @@ import { remark } from 'remark';
|
||||
import remarkHtml from 'remark-html';
|
||||
import behead from 'remark-behead';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import { config } from '../config';
|
||||
|
||||
const imageModules = (require as any).context(
|
||||
'../../articles',
|
||||
@@ -18,7 +19,6 @@ const images = imageModules.keys().map((key) => ({
|
||||
|
||||
const replaceImages = ({ id }) => (tree: any) => {
|
||||
visit(tree, 'image', (node) => {
|
||||
console.log(id);
|
||||
if (!node.url.startsWith('./')) return;
|
||||
const correctedUrl = `.${path.resolve('/', id, node.url)}`;
|
||||
const image = images.find((i: any) => i.key === correctedUrl);
|
||||
@@ -113,7 +113,10 @@ export class Articles {
|
||||
const articles = await Promise.all(articleLocations.map(
|
||||
(location) => this.#loadArticle(path.join(rootLocation, location)),
|
||||
));
|
||||
return articles;
|
||||
return articles
|
||||
.sort((a, b) => new Date(b.published || 0).getTime() - new Date(a.published || 0).getTime())
|
||||
.filter(a => a.published || config.dev)
|
||||
|
||||
}
|
||||
|
||||
public getImage = (article: string, name: string) => {
|
||||
|
||||
@@ -18,7 +18,6 @@ const images = imageModules.keys().map((key) => ({
|
||||
|
||||
const replaceImages = ({ id }) => (tree: any) => {
|
||||
visit(tree, 'image', (node) => {
|
||||
console.log(id);
|
||||
if (!node.url.startsWith('./')) return;
|
||||
const correctedUrl = `.${path.resolve('/', id, node.url)}`;
|
||||
const image = images.find((i: any) => i.key === correctedUrl);
|
||||
|
||||
@@ -44,7 +44,6 @@ export class ProfileDB {
|
||||
structure.avatar = image.url;
|
||||
}
|
||||
structure.social = structure.social.map((social) => {
|
||||
console.log(social.logo, images)
|
||||
const image = images.find((i: any) => i.key === `.${path.resolve('/', social.logo)}`)
|
||||
return {
|
||||
...social,
|
||||
|
||||
28
src/hooks/animation.ts
Normal file
28
src/hooks/animation.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { MutableRefObject, useEffect, useState } from "react";
|
||||
|
||||
export const useOnScreen = (ref: MutableRefObject<any>, rootMargin = '0px') => {
|
||||
const [isIntersecting, setIntersecting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const { current } = ref;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIntersecting(entry.isIntersecting);
|
||||
},
|
||||
{
|
||||
rootMargin
|
||||
}
|
||||
);
|
||||
if (current) {
|
||||
observer.observe(current);
|
||||
}
|
||||
return () => {
|
||||
if (current) {
|
||||
observer.unobserve(current);
|
||||
}
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return isIntersecting;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import Head from 'next/head';
|
||||
import "@fontsource/merriweather";
|
||||
import "@fontsource/pacifico";
|
||||
import "@fontsource/fredoka";
|
||||
import { Transition } from '../components/animations/transition';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
* {
|
||||
@@ -42,7 +43,9 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
<Head>
|
||||
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||
</Head>
|
||||
<Transition>
|
||||
<Component {...pageProps} />
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,15 +4,19 @@ import { Article, articleDB } from '../../data/articles';
|
||||
import { Content } from '../../components/content';
|
||||
import ReactMarkdown, { Components } from 'react-markdown';
|
||||
import { Navigation } from '../../components/navigation';
|
||||
import Head from 'next/head';
|
||||
import { Profile, profileDB } from '../../data/profile';
|
||||
|
||||
type Props = {
|
||||
article: Article;
|
||||
profile: Profile;
|
||||
};
|
||||
|
||||
const Image = styled.img`
|
||||
`;
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
const ImageWrapper = styled.span`
|
||||
display: block;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
@@ -67,10 +71,14 @@ const components: Components = {
|
||||
|
||||
const ArticleView: React.FC<Props> = ({
|
||||
article,
|
||||
profile,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Navigation />
|
||||
<Head>
|
||||
<title>{profile.name} - {article.title}</title>
|
||||
</Head>
|
||||
<Navigation name={profile.name} />
|
||||
<Content>
|
||||
<Wrapper>
|
||||
<h1>{article.title}</h1>
|
||||
@@ -96,9 +104,11 @@ export async function getStaticPaths() {
|
||||
export async function getStaticProps({ params }: any) {
|
||||
const { id } = params;
|
||||
const article = await articleDB.get(id);
|
||||
const profile = await profileDB.get();
|
||||
return {
|
||||
props: {
|
||||
article,
|
||||
profile,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
import { Content } from '../components/content';
|
||||
import { Experiences } from '../components/experiences';
|
||||
@@ -17,6 +18,9 @@ type Props = {
|
||||
|
||||
const Home: React.FC<Props> = ({ articles, profile, experiences }) => (
|
||||
<>
|
||||
<Head>
|
||||
<title>{profile.name} - {profile.tagline}</title>
|
||||
</Head>
|
||||
<Hero profile={profile} />
|
||||
<Content>
|
||||
<Featured title="Latest articles">
|
||||
|
||||
50
yarn.lock
50
yarn.lock
@@ -155,7 +155,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-10.1.2.tgz#d2fb7b968141139e5c2419553e5295382c265e7d"
|
||||
integrity sha512-bbZIpW6fdyf7FMaeDmw3cBbkTqsecxEkwlVKgVfqqXWBPLH6azxhPA2V9F7OhoZSVrsnMYw7QuyK6qutXPjEew==
|
||||
|
||||
"@emotion/is-prop-valid@^0.8.8":
|
||||
"@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.8":
|
||||
version "0.8.8"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||
@@ -1025,6 +1025,26 @@ file-loader@^6.2.0:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
framer-motion@^6.2.8:
|
||||
version "6.2.8"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.8.tgz#02abb529191af7e2df444185fe27e932215b715d"
|
||||
integrity sha512-4PtBWFJ6NqR350zYVt9AsFDtISTqsdqna79FvSYPfYDXuuqFmiKtZdkTnYPslnsOMedTW0pEvaQ7eqjD+sA+HA==
|
||||
dependencies:
|
||||
framesync "6.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
popmotion "11.0.3"
|
||||
style-value-types "5.0.0"
|
||||
tslib "^2.1.0"
|
||||
optionalDependencies:
|
||||
"@emotion/is-prop-valid" "^0.8.2"
|
||||
|
||||
framesync@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20"
|
||||
integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
@@ -1156,6 +1176,11 @@ hast-util-whitespace@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c"
|
||||
integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==
|
||||
|
||||
hey-listen@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||
|
||||
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
@@ -1986,6 +2011,16 @@ picomatch@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
popmotion@11.0.3:
|
||||
version "11.0.3"
|
||||
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9"
|
||||
integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==
|
||||
dependencies:
|
||||
framesync "6.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
style-value-types "5.0.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
postcss-value-parser@^4.0.2:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
@@ -2517,6 +2552,14 @@ style-to-object@^0.3.0:
|
||||
dependencies:
|
||||
inline-style-parser "0.1.1"
|
||||
|
||||
style-value-types@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad"
|
||||
integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==
|
||||
dependencies:
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^2.1.0"
|
||||
|
||||
styled-components@^5.3.3:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.3.tgz#312a3d9a549f4708f0fb0edc829eb34bde032743"
|
||||
@@ -2644,6 +2687,11 @@ trough@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
||||
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
|
||||
Reference in New Issue
Block a user