diff --git a/.eslintrc.json b/.eslintrc.json index dfded19..902ad5e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -36,7 +36,24 @@ "react/jsx-props-no-spreading": "off", "jsx-a11y/anchor-is-valid": "off", "react/no-unescaped-entities": "off", - "react/jsx-indent": "warn" + "react/jsx-indent": "warn", + "no-return-await": "off", + "no-console": "warn", + "array-callback-return": "off", + "no-param-reassign": "off", + "no-plusplus": "off", + "prefer-const": "warn", + "prefer-destructuring": [ + "warn", + { + "array": false, + "object": true + }, + { + "enforceForRenamedProperties": false + } + ], + "react/no-danger": "off" }, "ignorePatterns": [ ".DS_Store", diff --git a/components/Blog/Featured.module.scss b/components/Blog/Featured.module.scss new file mode 100644 index 0000000..cdfb7a9 --- /dev/null +++ b/components/Blog/Featured.module.scss @@ -0,0 +1,36 @@ +@use 'styles/colors'; +@use 'styles/breakpoints'; +@use 'styles/functions' as *; + +$radial-size: 0.75px; + +.root { + margin: 1em; + margin-top: 0; + background-image: radial-gradient(colors.$primary $radial-size, transparent $radial-size); + background-size: css-calc(10 * $radial-size) css-calc(10 * $radial-size); + + @media (max-width: breakpoints.$tablet) { + margin-top: 1em; + } +} + +.wrapper { + background-color: map-get(colors.$background, primary); + transform: translate(20px, -20px); + padding-left: 0.5em; + padding-bottom: 0.5em; + padding-top: 10px; +} + +.icon { + font-size: 0.85em; +} + +.posts { + margin-top: 0.5em; +} +.post + .post { + margin-top: 0.75em; +} + diff --git a/components/Blog/Featured.tsx b/components/Blog/Featured.tsx new file mode 100644 index 0000000..d4b1fef --- /dev/null +++ b/components/Blog/Featured.tsx @@ -0,0 +1,43 @@ +import { useContext } from 'react'; + +import { FaBookmark } from 'react-icons/fa'; +import FeaturedPost from './FeaturedPost'; +import featured from './Featured.module.scss'; + +import { PostsContext } from '../../pages/blog/index'; + +const Featured = () => { + const posts = useContext(PostsContext); + const featuredPosts = posts + .filter((post) => post.featured === true) + .sort(() => 0.5 - Math.random()) + .slice(0, 3); + + featuredPosts.map((post) => { + post.tagList = post.tags.map((tag) => tag.name).join(' '); + }); + + return ( +
+
+

+ Featured +

+
+ {featuredPosts.map((post) => ( +
+ +
+ ))} +
+
+
+ ); +}; + +export default Featured; diff --git a/components/Blog/FeaturedPost.module.scss b/components/Blog/FeaturedPost.module.scss new file mode 100644 index 0000000..66dd369 --- /dev/null +++ b/components/Blog/FeaturedPost.module.scss @@ -0,0 +1,13 @@ +@use 'styles/colors'; + +.root { + font-size: 0.8em; + + p { + color: colors.$gray; + } + + h3:hover { + text-decoration: underline; + } +} diff --git a/components/Blog/FeaturedPost.tsx b/components/Blog/FeaturedPost.tsx new file mode 100644 index 0000000..4ec7a2a --- /dev/null +++ b/components/Blog/FeaturedPost.tsx @@ -0,0 +1,18 @@ +import Link from 'next/link'; + +import styles from './FeaturedPost.module.scss'; + +const FeaturedPost = (props) => { + return ( + + +
+

{props.title}

+

{props.tags}

+
+
+ + ); +}; + +export default FeaturedPost; diff --git a/components/Blog/Form.module.scss b/components/Blog/Form.module.scss new file mode 100644 index 0000000..0a6013c --- /dev/null +++ b/components/Blog/Form.module.scss @@ -0,0 +1,51 @@ +@use 'styles/colors'; + +.root { + margin-top: 1em; + input { + background-color: map-get(colors.$background, secondary); + padding: 0.5em 1em; + width: 100%; + color: map-get(colors.$text, primary); + border-radius: max(0.3em, 5px); + outline: none; + border: none; + font-size: 1rem; + + &[type='submit'] { + color: colors.$white; + background-color: colors.$gray; + transition: all 0.1s ease-in-out; + + &:hover, + &:active { + cursor: pointer; + } + + &:hover { + transform: scale(1.08); + } + + &:active { + transform: scale(1); + } + } + } + + label { + font-size: 0.8rem; + padding: 0.25em 1em; + } +} + +.group + .group { + display: flex; + flex-direction: column; + margin-top: 0.5em; +} + +.inline { + display: grid; + grid-template-columns: 7fr 3fr; + grid-gap: 0.5em; +} diff --git a/components/Blog/Form.tsx b/components/Blog/Form.tsx new file mode 100644 index 0000000..9688de3 --- /dev/null +++ b/components/Blog/Form.tsx @@ -0,0 +1,22 @@ +import form from './Form.module.scss'; + +const Form = () => { + return ( +
+

Newsletter

+
+ + {/* */} +
+
+
+ + {/* */} + +
+
+
+ ); +}; + +export default Form; diff --git a/components/Blog/Header.module.scss b/components/Blog/Header.module.scss new file mode 100644 index 0000000..1626733 --- /dev/null +++ b/components/Blog/Header.module.scss @@ -0,0 +1,17 @@ +@use 'styles/colors'; +@use 'styles/breakpoints'; + +.root { + display: grid; + grid-template-columns: 1fr 1fr; + // gap: 1em; + + @media (max-width: breakpoints.$tablet) { + display: flex; + flex-direction: column; + } +} + +.wrapper:nth-of-type(2) { + padding-top: 15px; +} \ No newline at end of file diff --git a/components/Blog/Header.tsx b/components/Blog/Header.tsx new file mode 100644 index 0000000..b6e6aaf --- /dev/null +++ b/components/Blog/Header.tsx @@ -0,0 +1,19 @@ +import header from './Header.module.scss'; +import Form from './Form'; +import Featured from './Featured'; + +const Header = () => { + return ( +
+
+

Thoughts.

+
+
+
+ +
+
+ ); +}; + +export default Header; diff --git a/components/Blog/PostPreview.module.scss b/components/Blog/PostPreview.module.scss new file mode 100644 index 0000000..b60a83e --- /dev/null +++ b/components/Blog/PostPreview.module.scss @@ -0,0 +1,20 @@ +@use 'styles/colors'; + +.root { + font-size: 0.8em; + max-width: 65ch; + margin: 1.5em auto; + + h2:hover { + text-decoration: underline; + } +} + +.meta { + color: colors.$gray; +} + +.icon { + font-size: 0.8em; + margin-left: 0.25em; +} diff --git a/components/Blog/PostPreview.tsx b/components/Blog/PostPreview.tsx new file mode 100644 index 0000000..c2c41f2 --- /dev/null +++ b/components/Blog/PostPreview.tsx @@ -0,0 +1,24 @@ +import Link from 'next/link'; +import { FaExternalLinkAlt } from 'react-icons/fa'; +import postPreview from './PostPreview.module.scss'; + +const PostPreview = (props) => { + return ( + + +
+

+ {props.title} +

+

+ {props.pubDate} | {props.readTime} | {props.tags} +

+
+

{props.excerpt}

+
+
+ + ); +}; + +export default PostPreview; diff --git a/components/Post.module.scss b/components/Copy.module.scss similarity index 100% rename from components/Post.module.scss rename to components/Copy.module.scss diff --git a/components/Post.tsx b/components/Copy.tsx similarity index 78% rename from components/Post.tsx rename to components/Copy.tsx index 692a4af..97ae8d9 100644 --- a/components/Post.tsx +++ b/components/Copy.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import post from './Post.module.scss'; +import post from './Copy.module.scss'; const Post = (props) => { return
{props.children}
; diff --git a/components/Layouts/Layout.module.scss b/components/Layouts/Layout.module.scss index 13a57b0..21537c3 100644 --- a/components/Layouts/Layout.module.scss +++ b/components/Layouts/Layout.module.scss @@ -13,6 +13,6 @@ .contentWrapper { padding: 0.5em 1em; margin: 0 auto; + width: 100%; max-width: 65ch; - max-width: 750px; } diff --git a/components/Nav.tsx b/components/Nav.tsx index bd7d622..1c12615 100644 --- a/components/Nav.tsx +++ b/components/Nav.tsx @@ -12,20 +12,20 @@ const Nav = () => { @saharshy29 - {/*
+
About - + Thoughts - + {/* Projects Story - -
*/} + */} +
); }; diff --git a/package.json b/package.json index c7c13b7..8487e5d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "next start" }, "dependencies": { + "@tryghost/content-api": "^1.4.4", "imagemin-optipng": "^8.0.0", "next": "9.5.3", "next-optimized-images": "^2.6.2", @@ -17,11 +18,11 @@ "sass": "^1.26.10" }, "devDependencies": { - "@types/node": "^14.6.2", - "@types/react": "^16.9.49", - "@typescript-eslint/parser": "^3.10.1", - "@typescript-eslint/eslint-plugin": "^4.0.0", - "eslint": "^7.8.1", + "@types/node": "^14.6.0", + "@types/react": "^16.9.47", + "@typescript-eslint/eslint-plugin": "^3.10.0", + "@typescript-eslint/parser": "^3.10.0", + "eslint": "^7.7.0", "eslint-config-airbnb": "^18.2.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.22.0", diff --git a/pages/api/posts.js b/pages/api/posts.js new file mode 100644 index 0000000..4e9a5ee --- /dev/null +++ b/pages/api/posts.js @@ -0,0 +1,22 @@ +import GhostContentAPI from '@tryghost/content-api'; + +// Create API instance with site credentials +const api = new GhostContentAPI({ + url: 'https://shuy.ghost.io', + key: '4b1a5b85b966c4f3072ca8b307', // This shouldn't be publicly exposed, but it's not a big deal since it will not work soon enough, and even now, it does not provide access to much. + version: 'v3', +}); + +export async function getPosts() { + return await api.posts + .browse({ + include: 'tags', + limit: 'all', + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error(err); + }); +} + +export default getPosts(); diff --git a/pages/blog/[slug].tsx b/pages/blog/[slug].tsx new file mode 100644 index 0000000..ad3405e --- /dev/null +++ b/pages/blog/[slug].tsx @@ -0,0 +1,55 @@ +import { GetStaticProps, GetStaticPaths } from 'next'; +import { useRouter } from 'next/router'; +import Layout from '../../components/Layouts/Layout'; +import Copy from '../../components/Copy'; + +import { getPosts } from '../api/posts'; + +const Post = (props) => { + const { slug } = useRouter().query; + let post = props.posts.filter((p) => p.slug === slug); + post = post[0]; + + return ( + +

{post.title}

+

{post.tagList}

+
+ +
+ + + ); +}; + +export default Post; + +export const getStaticProps = async () => { + const posts = await getPosts(); + + posts.map((post) => { + post.dateFormatted = new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }).format(new Date(post.published_at)); + }); + + posts.map((post) => { + post.tagList = post.tags.map((tag) => tag.name).join(' '); + }); + + return { + props: { + posts, + }, + }; +}; + +export async function getStaticPaths() { + const posts = await getPosts(); + return { + paths: posts?.map((post) => `/blog/${post.slug}`), + fallback: false, + }; +} diff --git a/pages/blog/index.tsx b/pages/blog/index.tsx new file mode 100644 index 0000000..350d9e1 --- /dev/null +++ b/pages/blog/index.tsx @@ -0,0 +1,65 @@ +import { GetStaticProps } from 'next'; +import { createContext } from 'react'; + +import Layout from '../../components/Layouts/Layout'; +import Header from '../../components/Blog/Header'; +import PostPreview from '../../components/Blog/PostPreview'; + +import { getPosts } from '../api/posts'; + +interface Post { + Post: []; + title: string; + featured: boolean; + tags: any; + tagList: string; + uuid: string; + slug: string; +} + +export const PostsContext = createContext([]); + +const Home = (props) => { + return ( + + +
+
+ {props.posts.map((post) => ( + + ))} + + + ); +}; + +export default Home; + +export const getStaticProps = async () => { + const posts = await getPosts(); + + posts.map((post) => { + post.dateFormatted = new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + }).format(new Date(post.published_at)); + }); + + posts.map((post) => { + post.tagList = post.tags.map((tag) => tag.name).join(' '); + }); + + return { + props: { + posts, + }, + }; +}; diff --git a/pages/index.tsx b/pages/index.tsx index 4a57dd8..a5aa24a 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,6 +1,6 @@ import Link from 'next/link'; import Layout from '../components/Layouts/Layout'; -import Post from '../components/Post'; +import Post from '../components/Copy'; const Home = () => { return ( diff --git a/pages/tutoring.tsx b/pages/tutoring.tsx index ac58bd6..7d39e87 100644 --- a/pages/tutoring.tsx +++ b/pages/tutoring.tsx @@ -1,5 +1,5 @@ import Layout from '../components/Layouts/Layout'; -import Post from '../components/Post'; +import Post from '../components/Copy'; import CalendlyEmbed from '../components/CalendlyEmbed'; const Tutoring = () => { diff --git a/styles/_colors.scss b/styles/_colors.scss index b7ee7ca..d5991f4 100644 --- a/styles/_colors.scss +++ b/styles/_colors.scss @@ -12,5 +12,5 @@ $background: ( $text: ( primary: var(--text--primary), - opposite: var(--text--color--opposite), + secondary: var(--text--color--secondary), ); diff --git a/styles/_functions.scss b/styles/_functions.scss index 8b97919..bbf4f9e 100644 --- a/styles/_functions.scss +++ b/styles/_functions.scss @@ -10,6 +10,10 @@ @return m#{a}x(#{$numbers}); } +@function css-calc($numbers...) { + @return c#{a}lc(#{$numbers}); +} + @function headings($from:1, $to:6) { @if $from == $to { @return 'h#{$from}'; diff --git a/styles/globals.scss b/styles/globals.scss index 62852af..1258db4 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -17,7 +17,7 @@ --background--secondary: var(--color--white); --text--primary: var(--color--black); - --text-color--opposite: var(--color--white); + --text--color--secondary: var(--color--white); @media (prefers-color-scheme: dark) { --color--primary: #ff9a1f; @@ -27,7 +27,7 @@ --background--secondary: #2d3b4e; --text--primary: var(--color--white); - --text-color--opposite: var(--color--black); + --text--color--secondary: var(--color--black); } } @@ -47,11 +47,15 @@ body { // For sticky footer min-height: 100vh; - @media (max-width: breakpoints.$mobile) { + @media (max-width: breakpoints.$mobile) { // font-size: 1em; } } +#{headings(1,6)} { + font-family: fonts.$serif; +} + * { padding: 0; margin: 0; @@ -61,7 +65,49 @@ body { a, a:visited, a:hover, -a:active{ +a:active { color: currentColor; text-decoration: none; -} \ No newline at end of file +} + +hr { + border-top: 1px solid colors.$gray; + margin: 0.5em 0; + + &.fancy { + background: linear-gradient( + to left, + currentColor calc(50% - 12px), + transparent calc(50% - 12px), + transparent calc(50% + 12px), + currentColor calc(50% + 12px) + ); + background-color: transparent; + border: none; + height: 0.08rem; + overflow: visible; + position: relative; + color: var(--color--gray); + margin: 1.2rem auto; + + &::before, + &::after { + background: currentColor; + content: ''; + display: block; + height: 0.8rem; + position: absolute; + top: calc(50% - 0.4rem); + transform: rotate(22.5deg); + width: 0.1rem; + } + + &::before { + left: calc(50% - 0.25rem); + } + + &::after { + right: calc(50% - 0.25rem); + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 908001c..1ceb1ea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ "jsx": "preserve", "baseUrl": "." }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "components/CalendlyEmbed.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } diff --git a/yarn.lock b/yarn.lock index fc7daa5..a9d657c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1109,6 +1109,13 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@tryghost/content-api@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@tryghost/content-api/-/content-api-1.4.4.tgz#ae52a86e694edbe50a7460693f4b7fff619d6174" + integrity sha512-5q/M/fcE9OwPHKM+DQUrrjQD7CaNShBxtI/3/aBrcbQPX/aIL2FjbFA5pi3KJ/oe2f0IFlUPGeM1IGuXWsIV4Q== + dependencies: + axios "0.19.2" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -1716,6 +1723,13 @@ axe-core@^3.5.4: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== +axios@0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + axobject-query@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -2635,6 +2649,13 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3622,6 +3643,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"