diff --git a/content/.DS_Store b/content/.DS_Store index 5a270e0..cd25112 100644 Binary files a/content/.DS_Store and b/content/.DS_Store differ diff --git a/content/emails-olavea/2021/12/21-data-model/data-relationships-1-w51-2021.png b/content/emails-olavea/2021/12/21-data-model/data-relationships-1-w51-2021.png new file mode 100644 index 0000000..abad5e1 Binary files /dev/null and b/content/emails-olavea/2021/12/21-data-model/data-relationships-1-w51-2021.png differ diff --git a/content/emails-olavea/2021/12/21-data-model/index.md b/content/emails-olavea/2021/12/21-data-model/index.md new file mode 100644 index 0000000..b44feb1 --- /dev/null +++ b/content/emails-olavea/2021/12/21-data-model/index.md @@ -0,0 +1,111 @@ +--- +title: Create data relationship between TAGS and Lillian's photos of projects +--- + +## My Skill Building Session: + +![data-relationships](./data-relationships-1-w51-2021.png) + +## What did I do? + +Create data relationship between TAGS and Lillian's photos of projects. + +## Why did I do it? + +To make it easier for Lillian to ship her projects to friends. + +## How did I do it? + +**The Plan** + +S - See: `toppings` are not on `pizzas` in GraphQL +A - Array: Add `array` to pizza.js in Sanity + +N - Nodes: `toppings` are now on `pizzas` in GraphQL + +Look at these steps later: + +I - Into my webapp with GraphQL + +T - Treasure: TBD +Y - Yeah: Toppings can be linked in my web app after copy-pasting some Wes Bos wizardry. + +## The Steps + +### S - See: `toppings` are not on `pizzas` in GraphQL or in GraphiQL + +```js +// ToppingsFilter.js +// How do I get a list of all the pizzas with their toppings? +const { pizzas } = useStaticQuery(graphql` + query { + pizzas: allSanityPizza { + nodes { + // πŸ‘οΈ Look no "toppings" here πŸ‘€ + id + } + slug { + current + } + } + } +}`); +``` + +### A - Array: Add `array` to pizza.js in Sanity + +I added this code to my pizza.js. in Sanity + +```js +// schemas/pizza.js + { + name: 'toppings', + title: 'Toppings and Tools and Tags of Pirate Princess Lillian (6 πŸ΄β€β˜ οΈπŸ‘Έ)', + type: 'array', + of: [{ type: 'reference', to: [{ type: 'topping' }] }], + } +``` + +### N - Nodes: `toppings` are now on `pizzas` in GraphQL + +I looked in my GraphiQL and copy-pasted the new query into ToppingsFilter.js + +```js +// Rubys-TimeShip/src/components/ToppingsFilter.js +// This is how do I get a list of all the Pizzas with their toppings! +const { pizzas } = useStaticQuery(graphql` + query { + pizzas: allSanityPizza { + nodes { + toppings { + name + id + } + slug { + current + } + id + } + } + } +`); +``` + +And that's enough steps for this week! + +Pirate Princess Lillian joined me at the beginning of Sunday's [skill building session on youtube](https://youtu.be/ix_0vrwQnWk). + +If you would like to see how the web app will look in the end [jump to the 20 minute mark](https://youtu.be/ix_0vrwQnWk?t=1200). + +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-submarine afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Cap'n Ola Vea + +  +**PS:** If you feel like doing me a favour give me a smiley emoji comment to feed my youtube algo some vitamins. + +- [The web app I am working on](https://timeship1.gatsbyjs.io/pizzas/) +- [Direct link to the 20 minute mark in Sunday's OlaCast on YouTube](https://youtu.be/ix_0vrwQnWk?t=1200) diff --git a/content/emails-olavea/2022/01/04-tag-pages/index.md b/content/emails-olavea/2022/01/04-tag-pages/index.md new file mode 100644 index 0000000..d2cb5b6 --- /dev/null +++ b/content/emails-olavea/2022/01/04-tag-pages/index.md @@ -0,0 +1,30 @@ +--- +title: Programatically create pages for tags from Sanity.io +--- + +## My Sunday Skill Builder Session: + +![tags-on-a-pink-card](tags-on-a-pink-card-from-twitter-smaller.png) + +## What did I do? + +I followed the "red string" of one tag from the front end of the web app and all the way back to my backend in sanity.io. + +## Why did I do it? + +I want to use tags in a way that make it easier for Lillian (7 πŸ΄β€β˜ οΈπŸ‘Έ) to finish her projects by shipping them to the internet β€” and then archiving her projects on my labyrinthine loft because it is good for skill-building to finish projects. + +## How did I do it? + +See this two-minute summary where I followed the "red string" of one tag from the frontend of the web app and all the way back to my backend in sanity.io. + +[Ola follows the "red string" near the end of πŸ΄β€β˜ οΈ OlaCast: Sunday Skill Builder Session](https://youtu.be/ix_0vrwQnWk?t=1200) + +If you feel like doing me a favor, you can give me a πŸ΄β€β˜ οΈ emoji comment to feed my youtube algos some vitamins. + +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-submarine afloat this week! +πŸ”§β›΅πŸ΄β€β˜ οΈ + +  +Cap'n Ola Vea diff --git a/content/emails-olavea/2022/01/04-tag-pages/tags-on-a-pink-card-from-twitter-smaller.png b/content/emails-olavea/2022/01/04-tag-pages/tags-on-a-pink-card-from-twitter-smaller.png new file mode 100644 index 0000000..b3d663d Binary files /dev/null and b/content/emails-olavea/2022/01/04-tag-pages/tags-on-a-pink-card-from-twitter-smaller.png differ diff --git a/content/emails-olavea/2022/01/12-minimal-doable-task/index.md b/content/emails-olavea/2022/01/12-minimal-doable-task/index.md new file mode 100644 index 0000000..4b50da3 --- /dev/null +++ b/content/emails-olavea/2022/01/12-minimal-doable-task/index.md @@ -0,0 +1,32 @@ +--- +title: Minimal-doable-task +--- + +## My Sunday Skill Builder Session: + +I made a minimal-doable-task πŸ§šβ€β™€οΈ + +![Now I have a Taskerbell-Task](./skill-builder-w2-2022-ship-it.png) + +## What did I do? + +I deconstructed a task into a minimal-doable-task + +## Why did I do it? + +I wanted my task to be tiny, so that I could automate it in my skull, by doing it again and again. Automating it in my skull is skill building. + +## How did I do it? + +I wrote down the two smallest tasks i did this week. I picked the smallest task, cut it in half. I picked one half. I cut my half-task in 3. Now I have a Taskerbell-Task. + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-submarine afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Cap'n Ola Vea + +  +**PS:** Sunday's #OlaCast is rescheduled for 11:30 CET where you get to see my [minimal-doable-task in action](https://youtu.be/4fQj3YNKYoQ). diff --git a/content/emails-olavea/2022/01/12-minimal-doable-task/skill-builder-w2-2022-ship-it.png b/content/emails-olavea/2022/01/12-minimal-doable-task/skill-builder-w2-2022-ship-it.png new file mode 100644 index 0000000..a9111af Binary files /dev/null and b/content/emails-olavea/2022/01/12-minimal-doable-task/skill-builder-w2-2022-ship-it.png differ diff --git a/content/emails-olavea/2022/01/18-free-focus/index.md b/content/emails-olavea/2022/01/18-free-focus/index.md new file mode 100644 index 0000000..6e4ad08 --- /dev/null +++ b/content/emails-olavea/2022/01/18-free-focus/index.md @@ -0,0 +1,64 @@ +--- +title: How did I free up my mind to focus on ONE coding step at the time? While live coding, late at night. +--- + +## My Sunday Skill Builder Sessions: + +This Sunday, I did 2 live Skill-Builder-Sessions, and the second was terrible! πŸ™€ + +I knew it when waking up, so I prepared. I needed to prepare to keep myself un-distracted even when tired. + +My daily morning Skill-Builder-Session went well because my mind is fresh, alone, and un-distracted up in my labyrinthine loft. Just my code, my coffee, and I. + +But my second Skill-Builder-Session would be worse because live coding is distracting. You know, making sure video and audio are ok. And comments, putting them on screen, reading them out loud, removing them from the screen. And then my own talking, getting carried away into a story, for example. Distracting. But smooth sailing compared to later, in the dark and stormy night. + +However, the terrible third session would find me physically and mentally tired for four reasons. + +1. 08:00 PM is LATE for me. Most normal days, I am literally in bed reading a book about heroes in ancient Greece. + +2. No coffee. For many hours. + +3. I would have come straight up from a great-tasting but rich fish dinner cooked by the Queen herself. + +4. Before the dinner, I would have come straight in from bicycling Lillian (7 πŸ΄β€β˜ οΈπŸ‘Έ) to the wintry woods. Bicycling around after the skiing Pirate Princess between the ski tracks under snow-laden branches and bicycling back home on icy and car-filled roads. Lovely, but tiring. + +So I knew I'd be tired and distracted. How did I prepare to free up my mind to focus on only one coding step at the time? + +![focus](skill-builder-w3-2022.png) + +## What did I do? + +I used an acronym to name the steps of my task. I wrote Β«iACTiONSΒ» on paper. + +## Why did I do it? + +Looking over at Β«iACTiONSΒ» freed up my mind to focus on doing each step. Like i. id. + +## How did I do it? + +I wrote down the Β«iACTiONSΒ» on paper like this: + +i. id +A. actions +C. contentDigest +T. type +i. internal: { +O. (OLA_TUBE_ID) +N. node +S. singing + +Every time I got distracted, I could take a quick look at my paper. But because I KNEW my paper was there, I relaxed and stayed focused enough to remember that I was on Β«T. typeΒ» and get back to coding. I remember this happening several times πŸ’ͺ😺. + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Ola Vea +Cap'n of his own skill-builder-ship + +  +**PS:** Here is the video of my late-night live Skill-Builder-Session [starting at 5 minutes](https://youtu.be/_ZLxiOfhIi8?t=329) when I start coding from Β«i. idΒ» If you watch for 1 minute, you will see gorgeous Giggles-the-pug in the video of the id I am using. Giggles is one of Sid's two dogs. Sid, formerly at Gatsby, now at Cloudflare. + +**PPS:** You can find my cheat sheet for the live coding [this GitHub issue] (https://github.com/olavea/Rubys-TimeShip/issues/8). diff --git a/content/emails-olavea/2022/01/18-free-focus/skill-builder-w3-2022.png b/content/emails-olavea/2022/01/18-free-focus/skill-builder-w3-2022.png new file mode 100644 index 0000000..692774f Binary files /dev/null and b/content/emails-olavea/2022/01/18-free-focus/skill-builder-w3-2022.png differ diff --git a/content/emails-olavea/2022/01/25-wrong/OlaCast-14-POW-Day-1-WRONG-26.jpeg b/content/emails-olavea/2022/01/25-wrong/OlaCast-14-POW-Day-1-WRONG-26.jpeg new file mode 100644 index 0000000..64a488c Binary files /dev/null and b/content/emails-olavea/2022/01/25-wrong/OlaCast-14-POW-Day-1-WRONG-26.jpeg differ diff --git a/content/emails-olavea/2022/01/25-wrong/index.md b/content/emails-olavea/2022/01/25-wrong/index.md new file mode 100644 index 0000000..b1a508d --- /dev/null +++ b/content/emails-olavea/2022/01/25-wrong/index.md @@ -0,0 +1,30 @@ +--- +title: This Sunday's Skill-Builder-Session went WRONG! +--- + +![wrong](OlaCast-14-POW-Day-1-WRONG-26.jpeg) + +## My Sunday Skill Builder Session: + +This Sunday, I was only supposed to prepare 1 node and get data later. What went wrong? + +## What did I do? + +I prepared 1 node. + +## Why did I do it? + +I did just a tiiiny task again. So that I automate it in my brain. (That rhymes, when I say it at least... πŸ’ͺ😺) + +## How did I do it (WRONG)? + +I kept on coding further. For 2 more hours! Live on youtube. What was I thinking? + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/emails-olavea/2022/01/31-image-file/imageFile-1.jpeg b/content/emails-olavea/2022/01/31-image-file/imageFile-1.jpeg new file mode 100644 index 0000000..d2829da Binary files /dev/null and b/content/emails-olavea/2022/01/31-image-file/imageFile-1.jpeg differ diff --git a/content/emails-olavea/2022/01/31-image-file/index.md b/content/emails-olavea/2022/01/31-image-file/index.md new file mode 100644 index 0000000..f58c172 --- /dev/null +++ b/content/emails-olavea/2022/01/31-image-file/index.md @@ -0,0 +1,55 @@ +--- +title: I created an image file with createRemoteFileNode +--- + +![imageFile](imageFile-1.jpeg) + +## My Sunday Skill Builder Session: + +This Sunday, I created an image file with createRemoteFileNode + +## What did I do? + +I created an image file to use inside my node + +## Why did I do it? + +I wanted to use some Gatsby Image trickery on the thumbnail in our youtube data πŸ’ͺ😺. Therefore I downloaded the thumbnail into my data layer. At least, that is how I see it. + +## How did I do it? + +The short version: + +```js +// POW!-website plugins / local - source - youtube / gatsby - node.js; +const { createRemoteFileNode } = require("gatsby-source-filesystem"); + +const youTubeNodeId = createNodeId(`you-tube-${id}`); + +const imageFile = await createRemoteFileNode({ + url: embedData.thumbnail_url, + parentNodeId: youTubeNodeId, + getCache, + createNode, + createNodeId, +}); +``` + +And then, I use my image file inside my node like this. + +```js +createNode({ + thumnail___NODE: imageFile.id, +}); +``` + +For the longer version, watch [Sunday's OlaCast on YouTube](https://youtu.be/LQ2DRJbG8FY) + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/emails-olavea/2022/02/08-markdown-slug/index.md b/content/emails-olavea/2022/02/08-markdown-slug/index.md new file mode 100644 index 0000000..791de8b --- /dev/null +++ b/content/emails-olavea/2022/02/08-markdown-slug/index.md @@ -0,0 +1,71 @@ +--- +title: Ola adds a slug field to his homemade markdown node +--- + +## My Sunday Skill Builder Session: + +This Sunday, I added a slug field to my homemade markdown node πŸ› + +## What did I do? + +I added a slug field to my homemade markdown node with createNodeField from the onCreateNode hook. + +## Why did I do it? + +I need that slug to add support for a basic content section to create Markdown marketing pages with sections for Queen @raae's usepow.app. + +## How did I do it? + +Short version: + +```js +// POW!-website / gatsby - node.js; +async function slugifyMarkdownRemarkNode({ actions, node, getNode }) { + const { createNodeField } = actions; + if (node.internal.type === "MarkdownRemark") { + const slug = createFilePath({ node, getNode }); + createNodeField({ + name: "slug", + node, + value: slug, + }); + } +} + +exports.onCreateNode = async (gatsbyUtils) => { + await Promise.all([slugifyMarkdownRemarkNode(gatsbyUtils)]); +}; +``` + +Cheat Sheet: + +```js +// POW!-site/gatsby-node.js +async function slugifyMarkdownRemarkNode({ actions, node, getNode }) { + // πŸ”¨πŸ’°πŸ“ + con + // my md type of node ... internal + if ( ) { + // πŸ› = πŸ”¨ + πŸ“ + 🎒 ({ node, getNode }) + con + // πŸ”¨πŸ’°πŸ“ ({ πŸ›, πŸ’°, πŸ› }) + cre + na + no + va + }) + } +}; +``` + +If you can guess what one of the emojis mean, reply to this email πŸ˜ΊπŸ‘ + +Long version: [Sunday's OlaCast on YouTube](https://youtu.be/otRx6U5zASw) + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-builder-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/emails-olavea/2022/02/15-markdown/index.md b/content/emails-olavea/2022/02/15-markdown/index.md new file mode 100644 index 0000000..02e6bf8 --- /dev/null +++ b/content/emails-olavea/2022/02/15-markdown/index.md @@ -0,0 +1,148 @@ +--- +title: I created a Link with markdown on our POW!-website +--- + +## My Sunday Skill Builder Session: + +This Sunday, I created a `Link` with markdown on our POW!-website. + +## What did I do? + +I created a `Link` with markdown. + +## Why did I do it? + +I want to use markdown as my Content Management System (CMS) because it's my favorite CMS and Queen Raae told me to use markdown. + +## How did I do it? + +I started out on my L.O.V.E. acronym. + +`L.` Link and path and label +`O.` Open up in GraphiQL +`V.` Variable +`E.` Evol is love backward, no just kidding. I will do Β«E. EmptyΒ» on this Sunday's Skill Builder Session. + +### L. + +Link and path and label + +```js +// POW!-website / pages / {MarkdownRemark.fields__slug}.js +{} + +// POW!-website / content / index / index.md + +--- +.... + cta: + path: /signup + label: Yes to privacy +--- +.... +``` + +### O. + +Open up in GraphiQL + +```js +// POW!-website / pages / {MarkdownRemark.fields__slug}.js +export const query = graphql` + query ($id: String) { + markdownRemark(id: { eq: $id }) { + frontmatter { + sections { + cta { + path + label + } + } + } + } + } +`; +``` + +### V. + +First I make the variable: + +```js +// POW!-website / pages / {MarkdownRemark.fields__slug}.js +const { path, label } = section.cta; +``` + +Then I use the variable: + +```js +{label} +``` + +```js +// POW!-website / pages / {MarkdownRemark.fields__slug}.js +import React from "react"; +import { graphql, Link } from "gatsby"; + +const ComponentName = ({ data }) => { + const { frontmatter, html } = data.markdownRemark; + const { title, sections } = frontmatter; + + return ( + <> +
+

{title}

+
+ {(sections || []).map((section) => { + const { title } = section; + const { html } = section.body.childMarkdownRemark || {}; + const { path, label } = section.cta || {}; + return ( +
+

{title}

+ {html &&
} + {path && {label}} +
+ ); + })} +
+ + ); +}; + +export const query = graphql` + query ($id: String) { + markdownRemark(id: { eq: $id }) { + html + frontmatter { + title + sections { + cta { + path + label + } + title + body { + childMarkdownRemark { + html + } + } + } + } + } + } +`; + +export default ComponentName; +``` + +  +For the long version, watch [Sunday's OlaCast](https://youtu.be/rPiQi_bOk8s) on YouTube. + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-builder-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/emails-olavea/2022/02/22-only-md-pages/index.md b/content/emails-olavea/2022/02/22-only-md-pages/index.md new file mode 100644 index 0000000..5a2ea37 --- /dev/null +++ b/content/emails-olavea/2022/02/22-only-md-pages/index.md @@ -0,0 +1,44 @@ +--- +title: Failed at "create pages only for markdown pages, not for markdown sections" +--- + +## My Sunday Skill Builder Session: + +This Sunday, I tried to create pages only for markdown pages, not for markdown sections. On our POW! Website. + +## What did I do? + +I created pages with markdown files and the createPages hook from @GatsbyJS + +## Why did I do it? + +Because it's tidier and because Queen Benedicte @raae told me to do it. + +## How did I do it? + +The short version is I started out with my 1.2.3 A.B.C. mnemonic Gingerbread house. + +1. Supplies: allMarkdownRemark.node +2. Bakingsong = bakingSong.js +3. Loop over the supply node and create a page + +A. Ahoy! Aroma path! +B. BakingSong is a component +C. Catsby id is context + +## What went wrong? + +I'll get into what went wrong on Thursday at 7 pm CET! + +That's right; I'll be live coding alone on our Gatsby Deep-Dives because the Queen and the Pirate Princess are skiing in the mountains. ⛰️⛷️ + +For the long version of My Sunday Skill Builder Session watch [Sunday's OlaCast on YouTube](https://youtu.be/lMDA0WuAZSA) + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/emails-olavea/2022/03/01-supplies/index.md b/content/emails-olavea/2022/03/01-supplies/index.md new file mode 100644 index 0000000..79944ab --- /dev/null +++ b/content/emails-olavea/2022/03/01-supplies/index.md @@ -0,0 +1,62 @@ +--- +title: I found one error while creating pages only for markdown pages, not for markdown sections +--- + +## My Sunday Skill Builder Session: + +This Sunday, I created pages only for markdown pages, not for markdown sections. On our POW!-website. + +## What did I do? + +I created pages with markdown and the createPages hook from @GatsbyJS + +## Why did I do it? + +Because it's tidier than creating a bunch of "bonus" pages with content I already show on my index page. And because Queen Benedicte @raae told me to do it. + +## How did I do it? + +The short version is I started out with my 1.2.3 A.B.C. mnemonic Gingerbread house. And I found one error; see below. + +1. filter β˜• first +2. bakingSong 🎡 🦒 +3. aromaNode πŸ°πŸ’° + +A. aromaNodePath 🍰.πŸ“.πŸ› +B. bakingSong 🎡 πŸ™€ +C. catsbyId πŸ˜ΌπŸ†” + +## What was the error I found? + +I forgot to rename `allMarkdownRemark` to `supplies` in my graphql query: + +```js +// wrong + +const { data } = await graphql(`{ + allMarkdownRemark( + filter: { fileAbsolutePath: { regex: "/index.md/" } } + ) {} + }`); + +// right + +const { data } = await graphql(`{ + supplies: allMarkdownRemark( + filter: { fileAbsolutePath: { regex: "/index.md/" } } + ) {} + }`); +``` + +So that when I was going to use my data: `data.supplies.nodes.forEach` I got an error message, and the pages were not created. + +For the long version of My Sunday Skill Builder Session, watch [Sunday's OlaCast on YouTube](https://youtu.be/hkGZiodGe7U) + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/emails-olavea/2022/03/15-pow-password-peek-a-boo/OlaCast-20-POW-Day-51-POW-Password-Peek-a-boo-41.png b/content/emails-olavea/2022/03/15-pow-password-peek-a-boo/OlaCast-20-POW-Day-51-POW-Password-Peek-a-boo-41.png new file mode 100644 index 0000000..7d28e15 Binary files /dev/null and b/content/emails-olavea/2022/03/15-pow-password-peek-a-boo/OlaCast-20-POW-Day-51-POW-Password-Peek-a-boo-41.png differ diff --git a/content/emails-olavea/2022/03/15-pow-password-peek-a-boo/index.md b/content/emails-olavea/2022/03/15-pow-password-peek-a-boo/index.md new file mode 100644 index 0000000..2a8d98b --- /dev/null +++ b/content/emails-olavea/2022/03/15-pow-password-peek-a-boo/index.md @@ -0,0 +1,48 @@ +--- +title: POW! Password Peek-a-boo +--- + +## My Sunday Skill Builder Session: + +This Sunday, I made Password Peek-a-boo on the login page on our POW!-website. So now we're showing you your password if you poke the eye icon inside the password field. Later we will use that login page on the POW! app + +## What did I do? + +POW! Password Peek-a-boo +![POW! Password Peek-a-boo](OlaCast-20-POW-Day-51-POW-Password-Peek-a-boo-41.png) + +## Why did I do it? + +I like to see my pasSwords when I copy-paste them from 1Password and write them from memory because I am a visual guy. So we want our POW! customers to be able to choose for themselves. And because Queen @raae told me to do it. + +## How did I do it? + +The short version: + +- made a button with MUI + +```js +πŸ‘οΈ +``` + +- added an aria-label and an onClick handler + +```js + + πŸ‘οΈ + +``` + +For the long version of My Sunday Skill Builder Session:, watch [Sunday's OlaCast on YouTube](https://youtu.be/v00Uro6UQvY) + +  +πŸ’ͺπŸ˜ΊπŸ‘ +Keep your skill-building-ship afloat this week! +β›΅πŸ”§πŸ΄β€β˜ οΈ + +  +Ola Vea +Cap'n of his own skill-builder-ship diff --git a/content/landing/POW-birthday-1.png b/content/landing/POW-birthday-1.png new file mode 100644 index 0000000..7c5bab3 Binary files /dev/null and b/content/landing/POW-birthday-1.png differ diff --git a/gatsby-config.js b/gatsby-config.js index af7812c..8e5a444 100755 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -4,11 +4,29 @@ require("dotenv").config({ module.exports = { siteMetadata: { - siteUrl: "https://timeship1.gatsbyjs.io/", + // siteUrl: "https://timeship1.gatsbyjs.io/", title: `TimeShip1`, + tagline: `tagline`, description: "Lillian and Friends Building Skill", + url: `https://www.olavea.com/`, + lang: `en`, + // social: { + // image: `/raae.png`, + // alt: "Queen Raae holding a laptop in front of her gallery wall", + // twitter: { + // site: "@raae", + // card: "summary_large_image", + // }, + // }, }, plugins: [ + { + resolve: "local-source-emails", + options: { + basePath: "/emails", + }, + }, + "gatsby-plugin-gatsby-cloud", "gatsby-plugin-netlify", "@raae/gatsby-plugin-new-css", @@ -88,5 +106,6 @@ module.exports = { resolve: `gatsby-transformer-remark`, options: {}, }, + `@raae/gatsby-theme-mui`, ], }; diff --git a/package-lock.json b/package-lock.json index 0b0161b..969304e 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1256,6 +1256,132 @@ "to-fast-properties": "^2.0.0" } }, + "@emotion/babel-plugin": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz", + "integrity": "sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==", + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "@emotion/react": { + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.8.1.tgz", + "integrity": "sha512-XGaie4nRxmtP1BZYBXqC5JGqMYF2KRKKI7vjqNvQxyRpekVAZhb6QqrElmZCAYXH1L90lAelADSVZC4PFsrJ8Q==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.2", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz", + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/server": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/@emotion/server/-/server-11.4.0.tgz", + "integrity": "sha512-IHovdWA3V0DokzxLtUNDx4+hQI82zUXqQFcVz/om2t44O0YSc+NHB+qifnyAOoQwt3SXcBTgaSntobwUI9gnfA==", + "requires": { + "@emotion/utils": "^1.0.0", + "html-tokenize": "^2.0.0", + "multipipe": "^1.0.2", + "through": "^2.3.8" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/styled": { + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz", + "integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/is-prop-valid": "^1.1.2", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0" + } + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, "@endemolshinegroup/cosmiconfig-typescript-loader": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", @@ -2037,6 +2163,194 @@ "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==" }, + "@mui/base": { + "version": "5.0.0-alpha.72", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.72.tgz", + "integrity": "sha512-WCAooa9eqbsC68LhyKtDBRumH4hV1eRZ0A3SDKFHSwYG9fCOdsFv/H1dIYRJM0rwD45bMnuDiG3Qmx7YsTiptw==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/is-prop-valid": "^1.1.2", + "@mui/utils": "^5.4.4", + "@popperjs/core": "^2.11.3", + "clsx": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, + "@mui/icons-material": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.5.1.tgz", + "integrity": "sha512-40f68p5+Yhq3dCn3QYHqQt5RETPyR3AkDw+fma8PtcjqvZ+d+jF84kFmT6NqwA3he7TlwluEtkyAmPzUE4uPdA==", + "requires": { + "@babel/runtime": "^7.17.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "@mui/material": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.5.1.tgz", + "integrity": "sha512-bJSYgymgSZ7btPTNnWFrr2EmGoVQc4A/0WLfP/ESY2dxnhnbFDwt7twiOKmJp3u84YXriEDt5v9EZQLf7A+y0Q==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/base": "5.0.0-alpha.72", + "@mui/system": "^5.5.1", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.4.4", + "@types/react-transition-group": "^4.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "react-transition-group": "^4.4.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, + "@mui/private-theming": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.4.4.tgz", + "integrity": "sha512-V/gxttr6736yJoU9q+4xxXsa0K/w9Hn9pg99zsOHt7i/O904w2CX5NHh5WqDXtoUzVcayLF0RB17yr6l79CE+A==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.4.4", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "@mui/styled-engine": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.4.4.tgz", + "integrity": "sha512-AKx3rSgB6dmt5f7iP4K18mLFlE5/9EfJe/5EH9Pyqez8J/CPkTgYhJ/Va6qtlrcunzpui+uG/vfuf04yAZekSg==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/cache": "^11.7.1", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "@mui/system": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.5.1.tgz", + "integrity": "sha512-2hynI4hN8304hOCT8sc4knJviwUUYJ7XK3mXwQ0nagVGOPnWSOad/nYADm7K0vdlCeUXLIbDbe7oNN3Kaiu2kA==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.4.4", + "@mui/styled-engine": "^5.4.4", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + } + } + }, + "@mui/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz", + "integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==" + }, + "@mui/utils": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "@types/prop-types": "^15.7.4", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "@netlify/functions": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-0.7.2.tgz", @@ -2108,6 +2422,11 @@ } } }, + "@popperjs/core": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==" + }, "@raae/gatsby-plugin-donations": { "version": "1.0.0-next.1", "resolved": "https://registry.npmjs.org/@raae/gatsby-plugin-donations/-/gatsby-plugin-donations-1.0.0-next.1.tgz", @@ -2212,6 +2531,70 @@ } } }, + "@raae/gatsby-theme-mui": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@raae/gatsby-theme-mui/-/gatsby-theme-mui-0.1.1.tgz", + "integrity": "sha512-Cz4eHxCcvfRFNnY7SJ0Qnwz8XIjsQlM0fqBZ5f1kBYx5wzcxrH26Esz4HJ0WdOsfU5ocdKaSQ9WxCljsiqNPgQ==", + "requires": { + "@emotion/react": "11.8.1", + "@emotion/server": "11.4.0", + "@emotion/styled": "11.8.1", + "@mui/material": "5.5.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz", + "integrity": "sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@mui/base": { + "version": "5.0.0-alpha.71", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.71.tgz", + "integrity": "sha512-LinacyjmZOS+roUqCyhrcbNIW7TlRf1U+15ETGwMn6biNXI9YEVgcc1Kak08CRtjM0yczxxzLWetiAjHMCVSjQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/is-prop-valid": "^1.1.2", + "@mui/utils": "^5.4.4", + "@popperjs/core": "^2.11.2", + "clsx": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + } + }, + "@mui/material": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.5.0.tgz", + "integrity": "sha512-E12rxqLaWBrebJCxKxBtyRrzJgpPIQSCt4MUHns2Yl9gxOx4c7vDDKuks7Qc6S36wTQf+FP4aiey72Z2WKdYgQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/base": "5.0.0-alpha.71", + "@mui/system": "^5.5.0", + "@mui/types": "^7.1.2", + "@mui/utils": "^5.4.4", + "@types/react-transition-group": "^4.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "react-transition-group": "^4.4.2" + } + }, + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "@rexxars/eventsource-polyfill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@rexxars/eventsource-polyfill/-/eventsource-polyfill-1.0.0.tgz", @@ -2616,6 +2999,22 @@ "csstype": "^3.0.2" } }, + "@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "requires": { + "@types/react": "*" + } + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -4171,6 +4570,11 @@ "mimic-response": "^1.0.0" } }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, "co-body": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", @@ -5406,6 +5810,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", @@ -5693,6 +6106,43 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -7035,6 +7485,11 @@ } } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -9595,6 +10050,14 @@ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "hosted-git-info": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", @@ -9623,6 +10086,58 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==" }, + "html-tokenize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz", + "integrity": "sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w==", + "requires": { + "buffer-from": "~0.1.1", + "inherits": "~2.0.1", + "minimist": "~1.2.5", + "readable-stream": "~1.0.27-1", + "through2": "~0.4.1" + }, + "dependencies": { + "buffer-from": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", + "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, "html-void-elements": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", @@ -12696,6 +13211,15 @@ "xtend": "^4.0.0" } }, + "multipipe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-1.0.2.tgz", + "integrity": "sha1-zBPv2DPJzamfIk+GhGG44aP9k50=", + "requires": { + "duplexer2": "^0.1.2", + "object-assign": "^4.1.0" + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -14700,6 +15224,17 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==" }, + "react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -16654,6 +17189,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "subscriptions-transport-ws": { "version": "0.9.19", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", diff --git a/package.json b/package.json index cbc3b6e..d191c96 100755 --- a/package.json +++ b/package.json @@ -15,11 +15,14 @@ "clean": "gatsby clean" }, "dependencies": { + "@mui/icons-material": "^5.5.1", + "@mui/material": "^5.5.1", "@raae/gatsby-plugin-donations": "^1.0.0-next.1", "@raae/gatsby-plugin-let-it-snow": "^0.6.0", "@raae/gatsby-plugin-new-css": "0.0.1", "@raae/gatsby-plugin-starter": "^1.5.0", "@raae/gatsby-remark-oembed": "^0.1.1", + "@raae/gatsby-theme-mui": "^0.1.1", "@sindresorhus/slugify": "^2.1.0", "axios": "^0.24.0", "gatsby": "4.0.2", @@ -35,6 +38,7 @@ "gatsby-source-sanity": "^7.3.2", "gatsby-transformer-remark": "^5.4.0", "gatsby-transformer-sharp": "^4.2.0", + "lodash": "^4.17.21", "normalize.css": "^8.0.1", "prismjs": "^1.25.0", "react": "17.0.2", diff --git a/plugins/local-source-emails/gatsby-config.js b/plugins/local-source-emails/gatsby-config.js new file mode 100644 index 0000000..fcf9e61 --- /dev/null +++ b/plugins/local-source-emails/gatsby-config.js @@ -0,0 +1,20 @@ +const path = require("path"); + +module.exports = { + plugins: [ + // { + // resolve: "gatsby-source-filesystem", + // options: { + // name: `QueenEmail`, + // path: path.join(__dirname, "..", "..", "/content/emails-queen"), + // }, + // }, + { + resolve: "gatsby-source-filesystem", + options: { + name: `OlaVeaEmail`, + path: path.join(__dirname, "..", "..", "/content/emails-olavea"), + }, + }, + ], +}; diff --git a/plugins/local-source-emails/gatsby-node.js b/plugins/local-source-emails/gatsby-node.js new file mode 100644 index 0000000..83f6dcf --- /dev/null +++ b/plugins/local-source-emails/gatsby-node.js @@ -0,0 +1,151 @@ +const { isString } = require("lodash"); +const { createFilePath } = require("gatsby-source-filesystem"); +const { typeDefs } = require("./type-defs"); + +const IS_DEV = process.env.NODE_ENV === "development"; +const NOW = new Date().toISOString().substring(0, 10); +const FAR_FUTURE = "2300-01-01"; +const CUT_OFF = IS_DEV ? FAR_FUTURE : NOW; + +const findInSource = async ({ type, field, source, args, context, info }) => { + const fields = info.schema.getType(type).getFields(); + const resolver = fields[field]?.resolve; + if (resolver) { + return await resolver(source, args, context, info); + } +}; + +const findInMarkdownNode = async ({ markdownNode, ...params }) => { + if (params.field === "excerpt") { + params.args = { + pruneLength: 155, + }; + } + + let resolved = await findInSource({ + type: "MarkdownRemark", + source: markdownNode, + ...params, + }); + + if (!resolved) { + // Try frontmatter + resolved = await findInSource({ + type: "MarkdownRemarkFrontmatter", + source: markdownNode.frontmatter, + ...params, + }); + } + + return resolved; +}; + +exports.createSchemaCustomization = ({ actions }) => { + const { createTypes, createFieldExtension } = actions; + + createFieldExtension({ + name: "childMarkdownRemarkResolver", + args: { + from: { + type: "String", + }, + alternative: { + type: "String", + }, + default: { + type: "String", + }, + }, + extend(options) { + return { + async resolve(source, args, context, info) { + const markdownNode = context.nodeModel.getNodeById({ + id: source.childMarkdownRemark, + }); + + const params = { args, context, info }; + + let resolved = await findInMarkdownNode({ + markdownNode: markdownNode, + field: options.from || info.fieldName, + ...params, + }); + + if (!resolved) { + // Try alternative field + resolved = await findInMarkdownNode({ + markdownNode: markdownNode, + field: options.alternative, + ...params, + }); + } + + return resolved || options.default; + }, + }; + }, + }); + + createTypes(typeDefs); +}; + +exports.onCreateNode = async (gatsbyUtils, pluginOptions) => { + const { + node, + actions: { createNode, createNodeField }, + createNodeId, + getNode, + reporter, + } = gatsbyUtils; + + if (!isString(pluginOptions.basePath)) { + reporter.panic("Email pages need a base path"); + } + + if (node.internal.type === "MarkdownRemark") { + const markdownNode = node; + const fileNode = getNode(node.parent); + const type = fileNode?.sourceInstanceName || ""; + + if (type.includes("Email")) { + // Create email node + const filePath = createFilePath({ node: fileNode, getNode }); + + const pattern = + /((\d{4})\/(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01]))-(.*)/; + const dateSearch = pattern.exec(fileNode.relativeDirectory); + + try { + const emailId = createNodeId(`${markdownNode.id} >>> ${type}`); + const dateString = `${dateSearch[2]}-${dateSearch[3]}-${dateSearch[4]}`; + const slug = `${pluginOptions.basePath}/${dateString}-${dateSearch[5]}/`; + + // createNodeField({ + // name: "date", + // node: markdownNode, + // value: dateString, + // }); + + if (dateString <= CUT_OFF) { + createNode({ + id: emailId, + slug: slug, + date: dateString, + parent: fileNode.id, + childMarkdownRemark: markdownNode.id, + internal: { + contentDigest: markdownNode.internal.contentDigest, + type: type, + }, + }); + + reporter.info(`${type} created for ${filePath} at ${slug} `); + } else { + reporter.warn(`${type} for ${filePath} is in the far future `); + } + } catch (error) { + reporter.error(`${type} for ${filePath} failed: ${error.message}`); + } + } + } +}; diff --git a/plugins/local-source-emails/package.json b/plugins/local-source-emails/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/local-source-emails/package.json @@ -0,0 +1 @@ +{} diff --git a/plugins/local-source-emails/type-defs.js b/plugins/local-source-emails/type-defs.js new file mode 100644 index 0000000..99f9032 --- /dev/null +++ b/plugins/local-source-emails/type-defs.js @@ -0,0 +1,30 @@ +exports.typeDefs = ` + interface Email implements Node { + id: ID! + slug: String + title: String + emojii: String + html: String + description: String + date: Date @dateformat + } + + type QueenEmail implements Node & Email { + slug: String + date: Date @dateformat + title: String @childMarkdownRemarkResolver + emojii: String @childMarkdownRemarkResolver + html: String @childMarkdownRemarkResolver + description: String @childMarkdownRemarkResolver(alternative: "excerpt") + ogImage: File @link(from: "fields.ogImage") + } + + type OlaVeaEmail implements Node & Email { + slug: String + date: Date @dateformat + title: String @childMarkdownRemarkResolver + emojii: String @childMarkdownRemarkResolver(default: "β›΅ πŸ”§") + html: String @childMarkdownRemarkResolver + description: String @childMarkdownRemarkResolver(alternative: "excerpt") + } +`; diff --git a/src/@raae/gatsby-theme-mui/app-bar.js b/src/@raae/gatsby-theme-mui/app-bar.js new file mode 100644 index 0000000..80b8f9f --- /dev/null +++ b/src/@raae/gatsby-theme-mui/app-bar.js @@ -0,0 +1,19 @@ +const themeAppBar = (theme) => { + return { + MuiAppBar: { + defaultProps: { + color: "transparent", + elevation: 0, + border: true, + }, + styleOverrides: { + colorTransparent: { + backgroundColor: "#fffaf0b3", + backdropFilter: "blur(20px)", + }, + }, + }, + }; +}; + +export default themeAppBar; diff --git a/src/@raae/gatsby-theme-mui/config.js b/src/@raae/gatsby-theme-mui/config.js new file mode 100644 index 0000000..c5918b3 --- /dev/null +++ b/src/@raae/gatsby-theme-mui/config.js @@ -0,0 +1,37 @@ +import { red } from "@mui/material/colors"; + +const config = { + palette: { + primary: { + main: red.A700, + }, + }, + typography: { + fontSize: 18, + }, + components: { + // Name of the component + MuiAppBar: { + defaultProps: { + elevation: 0, + position: "sticky", + color: "transparent", + }, + styleOverrides: { + colorTransparent: { + backgroundColor: "#ffffffb3", + backdropFilter: "blur(20px)", + borderBottom: "thin", + borderColorBottom: "#fff", + }, + }, + }, + MuiButton: { + defaultProps: { + disableElevation: true, + }, + }, + }, +}; + +export default config; diff --git a/src/@raae/gatsby-theme-mui/form.js b/src/@raae/gatsby-theme-mui/form.js new file mode 100644 index 0000000..0152301 --- /dev/null +++ b/src/@raae/gatsby-theme-mui/form.js @@ -0,0 +1,23 @@ +const themeForm = (theme) => { + return { + MuiButton: { + styleOverrides: { + root: { + textTransform: "none", + fontWeight: theme.typography.fontWeightBold, + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + borderWidth: "2px !important", + }, + fullWidth: { + justifyContent: "space-between", + }, + }, + defaultProps: { + disableElevation: true, + }, + }, + }; +}; + +export default themeForm; diff --git a/src/@raae/gatsby-theme-mui/list.js b/src/@raae/gatsby-theme-mui/list.js new file mode 100644 index 0000000..5309a26 --- /dev/null +++ b/src/@raae/gatsby-theme-mui/list.js @@ -0,0 +1,42 @@ +const themeList = (theme) => { + return { + MuiListItem: { + defaultProps: { + disableGutters: true, + }, + }, + MuiListItemText: { + defaultProps: { + secondaryTypographyProps: { + variant: "overline", + gutterBottom: true, + }, + primaryTypographyProps: { + variant: "h5", + gutterBottom: true, + }, + }, + styleOverrides: { + root: { + maxWidth: "48ch", + display: "flex", + flexDirection: "column-reverse", + }, + }, + }, + MuiListItemButton: { + styleOverrides: { + root: { + ".MuiListItemText-primary": { + textDecoration: "underline", + "&:hover": { + textDecoration: "none", + }, + }, + }, + }, + }, + }; +}; + +export default themeList; diff --git a/src/@raae/gatsby-theme-mui/theme.js b/src/@raae/gatsby-theme-mui/theme.js new file mode 100644 index 0000000..ed20529 --- /dev/null +++ b/src/@raae/gatsby-theme-mui/theme.js @@ -0,0 +1,60 @@ +import { brown, deepOrange, amber } from "@mui/material/colors"; +import { createTheme } from "@mui/material/styles"; +import themeForm from "./form"; +import themeList from "./list"; +import themeAppBar from "./app-bar"; +import themeTypography from "./typography"; + +// A custom theme for this app +let theme = createTheme({ + typography: { + fontFamily: [ + "-apple-system", + "BlinkMacSystemFont", + '"Segoe UI"', + "Roboto", + '"Helvetica Neue"', + "Arial", + "sans-serif", + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(","), + fontWeightRegular: 500, + fontWeightExtraBold: 900, + }, + palette: { + brand: deepOrange, + secondary: amber, + primary: { + main: "#007b7d", + }, + background: { + paper: "#fffaf0", + default: "#fcedd8", + }, + }, + shape: { + borderRadius: 2, + }, +}); + +const options = { + palette: { + text: { + primary: brown[900], + secondary: theme.palette.primary.dark, + disabled: brown[200], + }, + }, + typography: themeTypography(theme), + components: { + ...themeAppBar(theme), + ...themeList(theme), + ...themeForm(theme), + }, +}; + +theme = createTheme(theme, options); + +export default theme; diff --git a/src/@raae/gatsby-theme-mui/typography.js b/src/@raae/gatsby-theme-mui/typography.js new file mode 100644 index 0000000..669d44a --- /dev/null +++ b/src/@raae/gatsby-theme-mui/typography.js @@ -0,0 +1,59 @@ +const themeTypography = (theme) => { + return { + h6: { + fontSize: "0.875rem", + fontWeight: theme.typography.fontWeightBold, + }, + h5: { + fontSize: "1rem", + fontWeight: theme.typography.fontWeightBold, + }, + h4: { + fontSize: "1.25rem", + fontWeight: theme.typography.fontWeightBold, + }, + h3: { + fontSize: "1.75rem", + fontWeight: theme.typography.fontWeightBold, + }, + h2: { + fontSize: "2rem", + fontWeight: theme.typography.fontWeightExtraBold, + }, + h1: { + letterSpacing: "-0.025em", + lineHeight: 1, + fontSize: "2.75rem", + fontWeight: theme.typography.fontWeightExtraBold, + }, + subtitle1: { + fontSize: "1.25rem", + lineHeight: 1.35, + fontWeight: theme.typography.fontWeightBold, + }, + subtitle2: { + fontSize: "1.25rem", + lineHeight: 1.35, + fontWeight: theme.typography.fontWeightMedium, + }, + overline: { + lineHeight: 1.35, + }, + // caption: { + // fontSize: "0.75rem", + // fontWeight: 400, + // }, + // body1: { + // fontSize: "0.875rem", + // fontWeight: 400, + // lineHeight: "1.334em", + // }, + // body2: { + // letterSpacing: "0em", + // fontWeight: 400, + // lineHeight: "1.5em", + // }, + }; +}; + +export default themeTypography; diff --git a/src/components/SignUpForm.js b/src/components/SignUpForm.js new file mode 100644 index 0000000..26ca3fa --- /dev/null +++ b/src/components/SignUpForm.js @@ -0,0 +1,209 @@ +import * as React from "react"; +import { useState } from "react"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import CssBaseline from "@mui/material/CssBaseline"; +import TextField from "@mui/material/TextField"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import Typography from "@mui/material/Typography"; +import Container from "@mui/material/Container"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import Visibility from "@mui/icons-material/Visibility"; +import VisibilityOff from "@mui/icons-material/VisibilityOff"; +import { IconButton, InputAdornment, OutlinedInput } from "@mui/material"; + +function Copyright(props) { + return ( + + {"Copyright Β© "} + + POW! + {" "} + {new Date().getFullYear()} + {"."} + + ); +} + +const theme = createTheme(); + +export default function SignUp() { + const [isPending, setIsPending] = useState(); + const [error, setError] = useState(); + const [rememberMe, setRememberMe] = useState("local"); + + const handleSubmit = (event) => { + event.preventDefault(); + + setIsPending(true); + setError(null); + + const data = new FormData(event.currentTarget); + // eslint-disable-next-line no-console + console.log({ + email: data.get("email"), + username: data.get("userName"), + password: data.get("password"), + }); + // if (result.error) { + // setIsPending(false); + // setError(result.error); + // } else { + // if (onSubmitFulfilled) { + // onSubmitFulfilled(); + // } else { + // navigate("/timeline/"); + // } + + // setIsPending(false); + //} + }; + const [values, setValues] = React.useState({ + showPassword: false, + }); + + const handleChange = (prop) => (event) => { + setValues({ ...values, [prop]: event.target.value }); + }; + + const handleClickShowPassword = () => { + setValues({ + showPassword: !values.showPassword, + }); + }; + + const handleMouseDownPassword = (event) => { + event.preventDefault(); + }; + + const disabled = isPending; + + return ( + + + + + + + + + Get started for free + + + + + + + + + + + + + + + + } + label="I want emails." + /> + + + + + + + Already have an account? Sign in + + + + + + + + + ); +} diff --git a/src/components/newsletter.js b/src/components/newsletter.js new file mode 100644 index 0000000..df676ee --- /dev/null +++ b/src/components/newsletter.js @@ -0,0 +1,137 @@ +import React, { useState } from "react"; +import { + Alert, + AlertTitle, + Box, + Button, + TextField, + Typography, +} from "@mui/material"; +import { addSubscriber } from "../services/subscriptions"; + +const ALERT = { + PENDING: { severity: "info", message: "Hold on..." }, + FULFILLED: { + severity: "success", + title: "Almost there!", + message: "Check your inbox to confirm...", + }, + FAILED: { + severity: "error", + message: "Oh no...something went wrong...", + }, +}; + +const NewsletterForm = ({ + formKey, + children, + cta, + tags = [], + anchor = "", + sx, + ...props +}) => { + const [status, setStatus] = useState("INITIAL"); + const alert = ALERT[status]; + + const handleOnSubmit = async (event) => { + event.preventDefault(); + setStatus("PENDING"); + + try { + // TODO: Already subscribed through this form message + await addSubscriber({ + email: event.target.elements.email.value, + formKey: formKey, + tags: tags, + }); + setStatus("FULFILLED"); + } catch (error) { + console.warn(error); + setStatus("FAILED"); + } + }; + + const handleDismiss = async (event) => { + event.preventDefault(); + setStatus("INITIAL"); + }; + + if (!formKey) { + console.error("NewsletterForm missing formKey"); + return null; + } + + if (!cta) { + console.error("NewsletterForm missing cta"); + return null; + } + + return ( + + {children && ( + + {children} + + )} + + + + + + + {alert && ( + + {alert.title && {alert.title}} + {alert.message && <>{alert.message}} + + )} + + ); +}; + +export default NewsletterForm; diff --git a/src/components/page-section/index.js b/src/components/page-section/index.js new file mode 100644 index 0000000..64a617c --- /dev/null +++ b/src/components/page-section/index.js @@ -0,0 +1,3 @@ +export { PageSection as default } from "./page-section"; +export * from "./page-section-header"; +export * from "./page-section-breadcrumbs"; diff --git a/src/components/page-section/page-section-breadcrumbs.js b/src/components/page-section/page-section-breadcrumbs.js new file mode 100644 index 0000000..6725866 --- /dev/null +++ b/src/components/page-section/page-section-breadcrumbs.js @@ -0,0 +1,33 @@ +import * as React from "react"; +import { Link } from "gatsby"; +import { Breadcrumbs, Link as MuiLink } from "@mui/material"; + +export const PageSectionBreadcrumbs = ({ + items = [], + component = "nav", + ...props +}) => { + return ( + + {items.map(({ label, to }, index) => { + return ( + + {label} + + ); + })} + + ); +}; diff --git a/src/components/page-section/page-section-header.js b/src/components/page-section/page-section-header.js new file mode 100644 index 0000000..69d254a --- /dev/null +++ b/src/components/page-section/page-section-header.js @@ -0,0 +1,54 @@ +import * as React from "react"; +import { Typography } from "@mui/material"; + +export const PageSectionHeader = ({ badge, title, lead, hLevel = 2 }) => { + const blocks = [ + { type: "badge", children: badge }, + { type: "title", children: title }, + { type: "lead", children: lead }, + ].filter((block) => Boolean(block.children)); + + return blocks.map((block, index) => { + const { children, type } = block; + const blockProps = { + component: hLevel + index <= 2 ? `h${hLevel + index}` : "p", + }; + const titleVariant = `h${hLevel}`; + const leadVariant = `subtitle${hLevel}`; + + switch (type) { + case "badge": + return ( + + {children} + + ); + case "title": + return ( + + {children} + + ); + case "lead": + return ( + + {children} + + ); + + default: + return null; + } + }); +}; diff --git a/src/components/page-section/page-section.js b/src/components/page-section/page-section.js new file mode 100644 index 0000000..cbb1568 --- /dev/null +++ b/src/components/page-section/page-section.js @@ -0,0 +1,29 @@ +import * as React from "react"; +import { Box, Container } from "@mui/material"; + +export const PageSection = ({ + children, + component = "section", + maxWidth = "md", + ...props +}) => { + return ( + * > *": { + maxWidth: "34rem", + }, + ...props.sx, + }} + > + {children} + + ); +}; diff --git a/src/components/prose.js b/src/components/prose.js new file mode 100644 index 0000000..0a85cb0 --- /dev/null +++ b/src/components/prose.js @@ -0,0 +1,91 @@ +import * as React from "react"; +import { styled } from "@mui/material/styles"; +import { Box } from "@mui/material"; + +const Root = styled("div")(({ theme }) => ({ + "> *": { + ...theme.typography.body1, + maxWidth: "100%", + marginBottom: "1rem", + }, + "& h1": { + ...theme.typography.h1, + marginTop: "1.5em", + marginBottom: "1em", + }, + "& h2": { + ...theme.typography.h2, + marginTop: "1.5em", + marginBottom: "1em", + }, + "& h3": { + ...theme.typography.h3, + marginTop: "1.5em", + }, + "& h4": { + ...theme.typography.h4, + marginTop: "1.5em", + }, + "& h5": { + ...theme.typography.h5, + marginTop: "1.5em", + }, + "& h6": { + ...theme.typography.h6, + marginTop: "1.5em", + }, + "& ul": { + listStyle: "none", + padding: 0, + + "& li": { + margin: "0.25rem 0", + a: { + display: "inline-block", + "&:after": { content: `""`, display: "block" }, + }, + "&:before": { + content: "'➽'", + display: "inline-block", + marginRight: "0.5rem", + color: theme.palette.secondary.dark, + }, + }, + }, + "& ol": { + listStyle: "inside", + padding: 0, + + "& li": { + margin: "0.25rem 0", + }, + }, + "& a": { + color: theme.palette.primary.dark, + }, + "& .gatsby-highlight": { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(4), + }, + "& .gatsby-resp-image-wrapper": { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(4), + }, +})); + +const Prose = ({ html, children, ...props }) => { + return ( + + {html && ( + + )} + {children && {children}} + + ); +}; + +export default Prose; diff --git a/src/components/seo.js b/src/components/seo.js new file mode 100644 index 0000000..cbd113a --- /dev/null +++ b/src/components/seo.js @@ -0,0 +1,79 @@ +import React from "react"; +import { Helmet } from "react-helmet"; +import { useStaticQuery, graphql } from "gatsby"; + +// social { +// image +// alt +// twitter { +// site +// card +// } + +const Seo = ({ location, meta, children }) => { + const { + site: { siteMetadata }, + } = useStaticQuery( + graphql` + query { + site { + siteMetadata { + title + tagline + description + url + lang + } + } + } + ` + ); + + const title = meta?.title; + const siteName = `${siteMetadata.title} β€” ${siteMetadata.tagline}`; + const lang = meta?.lang || siteMetadata.lang; + const image = meta?.image || siteMetadata.social.image; + + const description = meta?.description || siteMetadata.description; + const canonical = location && `${siteMetadata.url}${location.pathname}`; + const socialType = meta?.type || "website"; + const socialTitle = title ? title : siteName; + const socialImage = image && `${siteMetadata.url}${image}`; + const socialImageAlt = meta?.image ? meta?.alt : siteMetadata.social.alt; + const socialDescription = description; + const twitterSite = siteMetadata.social.twitter.site; + const twitterCreator = meta?.creator; + const twitterCard = siteMetadata.social.twitter.card; + + return ( + + + {title} + + + + + + + + + + + + + + + + + + + + {children} + + ); +}; + +export default Seo; diff --git a/src/components/site-header.js b/src/components/site-header.js new file mode 100644 index 0000000..6488fe7 --- /dev/null +++ b/src/components/site-header.js @@ -0,0 +1,27 @@ +import React from "react"; +import { Link } from "gatsby"; +import { AppBar, Toolbar, Button, Container, Typography } from "@mui/material"; + +const SiteHeader = ({ children }) => { + return ( + + + + + {children} + + + + ); +}; + +export default SiteHeader; diff --git a/src/content/newsletter.js b/src/content/newsletter.js new file mode 100644 index 0000000..8b354cd --- /dev/null +++ b/src/content/newsletter.js @@ -0,0 +1,33 @@ +import React from "react"; +import NewsletterForm from "../components/newsletter"; +import { defaults, isNull, omitBy } from "lodash"; + +const QUEEN_DEFAULTS = { + formKey: "queen", + cta: "Yes, please!", + tagline: "Serious about Gatsby?", + message: + "Sign up for emails sent every weekday to help you get the most out of Gatsby!", +}; + +const DEFAULTS = { + cta: "Yes, please!", +}; + +export const Newsletter = (initialProps) => { + let props = omitBy(initialProps, isNull); + + if (!props.formKey || props.formKey === "queen") { + props = defaults(QUEEN_DEFAULTS, props); + } else { + props = defaults(DEFAULTS, props); + } + + const { tagline, message, ...rest } = props; + + return ( + + {tagline && {tagline} } {message} + + ); +}; diff --git a/src/content/newsletter1.js b/src/content/newsletter1.js new file mode 100644 index 0000000..8b354cd --- /dev/null +++ b/src/content/newsletter1.js @@ -0,0 +1,33 @@ +import React from "react"; +import NewsletterForm from "../components/newsletter"; +import { defaults, isNull, omitBy } from "lodash"; + +const QUEEN_DEFAULTS = { + formKey: "queen", + cta: "Yes, please!", + tagline: "Serious about Gatsby?", + message: + "Sign up for emails sent every weekday to help you get the most out of Gatsby!", +}; + +const DEFAULTS = { + cta: "Yes, please!", +}; + +export const Newsletter = (initialProps) => { + let props = omitBy(initialProps, isNull); + + if (!props.formKey || props.formKey === "queen") { + props = defaults(QUEEN_DEFAULTS, props); + } else { + props = defaults(DEFAULTS, props); + } + + const { tagline, message, ...rest } = props; + + return ( + + {tagline && {tagline} } {message} + + ); +}; diff --git a/src/pages/signup.js b/src/pages/signup.js new file mode 100644 index 0000000..cc89f8f --- /dev/null +++ b/src/pages/signup.js @@ -0,0 +1,26 @@ +import React from "react"; +import { Link } from "gatsby"; +import { Link as MuiLink, Typography } from "@mui/material"; + +import SignUp from "../components/SignUpForm"; +import SiteHeader from "../components/site-header"; + +const SignUpPage = () => { + return ( + <> + + + Already have an account?{" "} + + Login + + + +
+ +
+ + ); +}; + +export default SignUpPage; diff --git a/src/pages/{OlaVeaEmail.slug}.js b/src/pages/{OlaVeaEmail.slug}.js new file mode 100644 index 0000000..d154c36 --- /dev/null +++ b/src/pages/{OlaVeaEmail.slug}.js @@ -0,0 +1,64 @@ +import React from "react"; +import { graphql } from "gatsby"; + +import Seo from "../components/seo"; +import Prose from "../components/prose"; +import SiteHeader from "../components/site-header"; +import PageSection, { + PageSectionBreadcrumbs, + PageSectionHeader, +} from "../components/page-section"; + +import { Newsletter } from "../content/newsletter"; + +const OlaVeaEmail = ({ data, ...props }) => { + const { date, title, emojii, description, html } = data.email || {}; + const emojis = emojii.split(" "); + + return ( + <> + + +
+ + + + {title}  {emojis[0]} {emojis[1]} + + } + /> + + + + + + +
+ + ); +}; + +export default OlaVeaEmail; + +export const query = graphql` + query OlaVeaEmailById($id: String!) { + email: olaVeaEmail(id: { eq: $id }) { + title + emojii + description + html + date(formatString: "MMMM Do, YYYY") + } + } +`; diff --git a/src/services/subscriptions.js b/src/services/subscriptions.js new file mode 100644 index 0000000..71b5727 --- /dev/null +++ b/src/services/subscriptions.js @@ -0,0 +1,49 @@ +import axios from "axios"; + +const TAGS = { + // "TOPIC:SERVERLESS": "", + // "TOPIC:GATSBY": "", + // "TOPIC:NETLIFY": "", + // "TOPIC:GATSBYCLOUD": "", + // "TOPIC:POW": "", + // "TOPIC:CONVERTKIT": "", + // "TOPIC:USERLIST": "", + // "TOPIC:SSR": "", + // "TOPIC:SSG": "", + // "TOPIC:CSR": "", +}; + +const FORM_ID = { + QUEEN: "2454187", + TIMESHIP: "2604593", + VERSION4: "2608546", + REMINDERS: "2816322", + OLAVEA: "2604593", + MPYA: "2846086", +}; + +export const addSubscriber = async ({ formKey, email, tags }) => { + const formId = FORM_ID[`${formKey.toUpperCase()}`]; + if (!formId) { + throw new Error("Not a valid form key"); + } + + const endpoint = `https://api.convertkit.com/v3/forms/${formId}/subscribe`; + const tagIds = tags.map((tag) => { + return TAGS[`${tag.toUpperCase()}`]; + }); + + const result = await axios.post(endpoint, { + api_key: process.env.GATSBY_CK_API_KEY, + email: email, + tags: tagIds, + }); + + if (window?.fathom) { + const eventId = "LPC2JAXX"; + window.fathom.trackGoal(eventId, 0); + console.log("Track event: ", eventId); + } + + return result; +};