diff --git a/.env.example b/.env.example index dc6996f8..889169b9 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,5 @@ GA_MEASUREMENT_ID= -REVUE_API_KEY= - API_KEY= AUTH_DOMAIN= DATABASE_URL= @@ -10,4 +8,10 @@ STORAGE_BUCKET= MESSAGING_SENDER_ID= APP_ID= MEASUREMENT_ID= -NEXT_PUBLIC_GOOGLE_ADSENSE= \ No newline at end of file +NEXT_PUBLIC_GOOGLE_ADSENSE= +TOOLBOX_SOFTWARE_DB= +TOOLBOX_HARDWARE_DB= +TOOLBOX_THIS_SITE_DB= +WORK_TIMELINE_DB= +SPEAKING_DATA_DB= +NEXT_LOOPS_API_KEY= \ No newline at end of file diff --git a/.env.production b/.env.production index 20b4e055..3eefe784 100644 --- a/.env.production +++ b/.env.production @@ -11,4 +11,6 @@ REVUE_API_KEY= TWITTER_BEARER_TOKEN= PLAUSIBLE_API_TOKEN= TWITTER_USER_ID= -NEXT_PUBLIC_GOOGLE_ADSENSE= \ No newline at end of file +NEXT_PUBLIC_GOOGLE_ADSENSE= +WORK_TIMELINE_DB= +SPEAKING_DATA_DB= \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index e45cc392..052ef0a6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,7 @@ "extends": ["next"], "rules": { "react/prop-types": 0, - "react/no-unescaped-entities": 0 + "react/no-unescaped-entities": 0, + "react/no-unknown-property": ["error", { "ignore": ["tw"] }] } } diff --git a/.frontmatter/database/mediaDb.json b/.frontmatter/database/mediaDb.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.frontmatter/database/mediaDb.json @@ -0,0 +1 @@ +{} diff --git a/.gitignore b/.gitignore index b6cad251..a0f37d69 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,6 @@ public/feed.xml .env.local .env.development .env.local -/public .netlify /node_modules /platforms diff --git a/README.md b/README.md index 4934d876..3d8d8b17 100644 --- a/README.md +++ b/README.md @@ -35,5 +35,5 @@ $ npm run dev Create a `.env` file similar to `.env.example` and include the appropriate keys. ## Notion Article Template -Duplicate [the following Notion database](https://www.notion.so/0d3e00c6bbe54231897b9fcbc7747f78?v=4d7f0006d9a144b5bd8b9251f2ec39cd), grab the database ID and add it to the environment variables in the `.env` file. +Duplicate [the following Notion database](https://www.notion.so/0d3e00c6bbe54231897b9fcbc7747f78?v=4d7f0006d9a144b5bd8b9251f2ec39cd), grab the database ID and add it to the environment variables in the `.env` file. diff --git a/assets/Inter-Bold.ttf b/assets/Inter-Bold.ttf new file mode 100644 index 00000000..8e82c70d Binary files /dev/null and b/assets/Inter-Bold.ttf differ diff --git a/assets/Inter-Medium.ttf b/assets/Inter-Medium.ttf new file mode 100644 index 00000000..b53fb1c4 Binary files /dev/null and b/assets/Inter-Medium.ttf differ diff --git a/assets/Inter-Regular.ttf b/assets/Inter-Regular.ttf new file mode 100644 index 00000000..8d4eebf2 Binary files /dev/null and b/assets/Inter-Regular.ttf differ diff --git a/components/Adsense.tsx b/components/Adsense.tsx deleted file mode 100644 index e1025be5..00000000 --- a/components/Adsense.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-nocheck -import React, { useEffect } from 'react'; - -import { AdType } from '@/lib/types'; - -interface GoogleAdProps { - variant?: AdType; -} - -const adUnitProps: Record = { - [AdType.ARTICLE]: { - 'data-ad-slot': '4448413641', - 'data-ad-format': 'fluid', - 'data-ad-layout': 'in-article' - }, - [AdType.RESPONSIVE]: { - 'data-ad-slot': '6270020953', - 'data-ad-format': 'auto', - 'data-full-width-responsive': 'true' - } -}; - -export default function Adsense({ variant = AdType.DEFAULT }: GoogleAdProps) { - const loadAds = () => { - try { - if (typeof window !== 'undefined') { - (window.adsbygoogle = window.adsbygoogle || []).push({}); - } - } catch (error) { - console.log('Adsense error', error.message); - } - }; - - useEffect(() => { - loadAds(); - }, []); - - return ( - - ); -} diff --git a/components/ArticleCard.tsx b/components/ArticleCard.tsx index b31b5909..c85e8b65 100644 --- a/components/ArticleCard.tsx +++ b/components/ArticleCard.tsx @@ -1,19 +1,16 @@ import { Article } from '@/lib/types'; -import Image from 'next/image'; +import Image from 'next/legacy/image'; import { handleArticleClicked } from '@/lib/handleArticleClick'; import siteMetadata from '@/data/siteMetadata'; import slugify from 'slugify'; import { useIsArticleRead } from '@/hooks/useIsArticleRead'; -import { useRouter } from 'next/dist/client/router'; type Props = { article: Article; }; export function ArticleCard({ article }: Props) { - const router = useRouter(); const slug = slugify(article.title).toLowerCase(); - const [hasRead] = useIsArticleRead(slug); return ( @@ -21,7 +18,7 @@ export function ArticleCard({ article }: Props) { ); }; diff --git a/components/Callout.tsx b/components/Callout.tsx index 047d2c4a..1174409a 100644 --- a/components/Callout.tsx +++ b/components/Callout.tsx @@ -1,6 +1,6 @@ export function Callout({ children }) { return ( -
+
{children}
); diff --git a/components/CommunityEntry.tsx b/components/CommunityEntry.tsx index cec1cd82..00fe9046 100644 --- a/components/CommunityEntry.tsx +++ b/components/CommunityEntry.tsx @@ -1,3 +1,4 @@ +import Image from 'next/image'; import dayjs from 'dayjs'; import { useSWRConfig } from 'swr'; @@ -26,18 +27,46 @@ export function CommunityEntry({ session, message }) { mutate('/api/community-wall'); } return ( - <> -

{message.content}

-
-

- {message.user.name} ∿ {dayjs(message.created_at).format('MM/DD/YYYY')} -

- {session && session.user.id === message.user_id ? ( - - ) : null} +
+
+
+ profile picture +
+
+ {message.user.name} +
+

+ {message.content} +

+
+ {session && session.user.id === message.user_id ? ( + + ) : null} +
+
+
+ +
+

+ {dayjs(message.created_at).format('MM/DD/YYYY')} +

+
- +
); } diff --git a/components/CommunityForm.tsx b/components/CommunityForm.tsx index 9dc279c5..6b0caa49 100644 --- a/components/CommunityForm.tsx +++ b/components/CommunityForm.tsx @@ -19,11 +19,13 @@ export function CommunityForm({ loggedIn, supabase, session }) { message: 'Sending note...' }); const content = message.current.value; + console.log(session.user?.user_metadata.avatar_url); const res = await fetch('/api/community-wall', { body: JSON.stringify({ message: { content, - userId: session.user.id + userId: session.user.id, + userPhoto: session.user?.user_metadata.avatar_url } }), headers: { @@ -52,7 +54,7 @@ export function CommunityForm({ loggedIn, supabase, session }) { } return ( <> -
+

Leave a note

Share a message with me or another visitor of my site.

{loggedIn ? ( @@ -76,7 +78,8 @@ export function CommunityForm({ loggedIn, supabase, session }) {

- Your information is only used to display your name. + Your information is only used to display your name & GitHub + profile picture.

{form.state === Form.Error ? ( {form.message} diff --git a/components/CustomLink.tsx b/components/CustomLink.tsx new file mode 100644 index 00000000..1c9b2462 --- /dev/null +++ b/components/CustomLink.tsx @@ -0,0 +1,105 @@ +import React, { useCallback } from 'react'; + +import Image from 'next/image'; +import Link from 'next/link'; +import { usePlausible } from 'next-plausible'; + +export default function CustomLink({ children, href }) { + let [imagePreview, setImagePreview] = React.useState(''); + let [isHovering, setIsHovering] = React.useState(false); + let inImagePreview = false; + let inLink = false; + const plausible = usePlausible(); + + const origin = + typeof window !== 'undefined' && window.location.origin + ? window.location.origin + : ''; + + let handleMouseEnterImage = () => { + inImagePreview = true; + setIsHovering(true); + }; + + let handleMouseLeaveImage = () => { + inImagePreview = false; + setIsHovering(inLink); + }; + + let handleMouseEnterLink = () => { + inLink = true; + setIsHovering(true); + }; + + let handleMouseLeaveLink = () => { + inLink = false; + setIsHovering(inImagePreview); + }; + + let handleFetchImage = useCallback( + async (url: string) => { + const res = await fetch(`${origin}/api/link-preview?url=${url}`); + const data = await res.json(); + setImagePreview(data.image); + plausible('Link Preview'); + }, + [origin] + ); + + React.useEffect(() => { + handleFetchImage(href); + + return () => setImagePreview(''); + }, [href, handleFetchImage]); + + return ( + + + + {children} + + {isHovering && ( + + + {imagePreview ? ( + {children} + ) : ( + + Loading... + + )} + + + )} + + + {children} + + + ); +} diff --git a/components/Footer.tsx b/components/Footer.tsx index f96dbddc..43007605 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -11,21 +11,20 @@ const navigation = { { name: 'Blog', href: '/blog' } ], specifics: [ - // { name: 'Activity', href: '/activity' }, { name: 'Stats', href: '/stats' }, { name: 'Community wall', href: '/community-wall' }, - { name: 'Toolbox', href: '/toolbox' } + { name: 'Toolbox', href: '/toolbox' }, + { name: 'Speaking', href: '/speaking' } ], extra: [ { name: 'Changelog', href: '/changelog' }, { name: 'Meet up', href: '/meetup' } - // { name: 'Books', href: '/books' } ], social: [ { name: 'Twitter', href: siteMetadata.twitter, - icon: (props) => ( + icon: () => ( ( + icon: () => ( ( + icon: () => ( ( + icon: () => (
{navigation.general.map((item) => ( - - + + {item.name} - + ))}
@@ -159,20 +163,17 @@ export function Footer() {
{navigation.specifics.map((item) => ( - - + + {item.name} - + ))} - - Snippets -
@@ -183,27 +184,32 @@ export function Footer() {
{navigation.extra.map((item) => ( - - + + {item.name} - + ))} - Newsletter + Resume - Resume + Snippets
@@ -222,7 +228,7 @@ export function Footer() { className="text-gray-600 dark:text-gray-400 important" > {item.name} -
diff --git a/components/NavMenu.tsx b/components/NavMenu.tsx index b19d0c42..4e4dcf16 100644 --- a/components/NavMenu.tsx +++ b/components/NavMenu.tsx @@ -1,9 +1,10 @@ import { useEffect, useState } from 'react'; +import DarkLogo from 'public/bcoyerlogo_dark.svg'; import { Dialog } from '@headlessui/react'; import Image from 'next/image'; -import NextLink from 'next/link'; -import siteMetadata from '@/data/siteMetadata'; +import LightLogo from 'public/bcoyerlogo_white.svg'; +import Link from 'next/link'; import { useRouter } from 'next/router'; import { useTheme } from 'next-themes'; @@ -12,25 +13,17 @@ function NavItem({ href, text }) { const isActive = router.asPath === href; return ( - - + - - {text} - - - + {text} + + ); } @@ -43,19 +36,34 @@ export function NavMenu({}) { useEffect(() => setMounted(true), []); return ( -
-
+
+
Profile Picture - Braydon Coyer + + + Braydon Coyer + + + + + Braydon Coyer + +
setIsOpen(true)}>
@@ -85,13 +93,14 @@ export function NavMenu({}) {
-