From 8833e441453945bf6737c2c5df15fa15ffd3d592 Mon Sep 17 00:00:00 2001 From: Ryan Parag Date: Mon, 23 Aug 2021 09:47:44 -0400 Subject: [PATCH] next image --- components/Featured/index.js | 4 +- components/ImgZoom/index.js | 1 + components/PostList/index.js | 15 +- components/Projects/index.js | 160 +-- notes/showing-my-listening-activity.md | 4 +- pages/index.js | 24 +- pages/notes/[postname].js | 27 +- pages/{notes.js => notes/index.js} | 2 +- pages/rss.js | 4 +- pages/work/index.js | 21 +- projects/chargebacks911.md | 5 +- projects/masonite.md | 3 +- projects/soleventure.md | 14 + public/feed/feed.xml | 1528 +++++++++++++++++++++++- 14 files changed, 1680 insertions(+), 132 deletions(-) rename pages/{notes.js => notes/index.js} (96%) create mode 100644 projects/soleventure.md diff --git a/components/Featured/index.js b/components/Featured/index.js index 53086da..be4a8db 100644 --- a/components/Featured/index.js +++ b/components/Featured/index.js @@ -1,4 +1,4 @@ -import { ProjectItem } from '@components/Projects' +import { WorkItem } from '@components/Projects' import List, { ListItem } from '@components/List' import useSWR from 'swr'; import fetcher from '@utils/fetcher'; @@ -18,7 +18,7 @@ const Featured = () => { data ? ( data.items.map(project => ( - + )) ) diff --git a/components/ImgZoom/index.js b/components/ImgZoom/index.js index 49a188b..b621eea 100644 --- a/components/ImgZoom/index.js +++ b/components/ImgZoom/index.js @@ -1,5 +1,6 @@ import React from 'react' import Zoom from 'react-medium-image-zoom' +import Image from 'next/image' const ImgZoom = ({src,alt}) => { return( diff --git a/components/PostList/index.js b/components/PostList/index.js index b945cee..9580e71 100644 --- a/components/PostList/index.js +++ b/components/PostList/index.js @@ -3,6 +3,8 @@ import styled from 'styled-components' import { designTokens } from '../Theme/designTokens' import List, { ListItem } from '@components/List' import { truncateString } from '@utils/text' +import Image from 'next/image' +import { LoadingSpinner } from '@components/LoadingBox' const NewImage = styled.div` width: ${designTokens.space[7]}; @@ -12,12 +14,14 @@ const NewImage = styled.div` background-size: cover; background-position: center; background-repeat: no-repeat; - background-image: url(${props => props.bg}); position: absolute; right: ${designTokens.space[1]}; top: 50%; transform: translateY(-50%) scale(1); transition: all 120ms ease-out 0ms; + img { + border-radius: ${designTokens.space[1]}; + } @media screen and (max-width: ${designTokens.breakpoints[4]}) { width: ${designTokens.space[6]}; height: ${designTokens.space[6]}; @@ -107,7 +111,14 @@ export default function PostList({ posts }) {

- + + {post.frontmatter?.title} + diff --git a/components/Projects/index.js b/components/Projects/index.js index 746a92a..2db882c 100644 --- a/components/Projects/index.js +++ b/components/Projects/index.js @@ -6,8 +6,9 @@ import List, { ListItem } from '@components/List' import { designTokens } from '@components/Theme/designTokens' import { truncateString } from '@utils/text' import Chip from '@components/Chip' +import Image from 'next/image' -const NewProjectImage = styled.img` +const NewProjectImage = styled.div` width: ${designTokens.space[7]}; height: ${designTokens.space[7]}; position: absolute; @@ -17,6 +18,9 @@ const NewProjectImage = styled.img` border-radius: ${designTokens.space[1]}; box-shadow: 0px 0px 0px 2px var(--grey200); transition: all 120ms ease-out 0ms; + img { + border-radius: ${designTokens.space[1]}; + } @media screen and (max-width: ${designTokens.breakpoints[4]}) { width: ${designTokens.space[6]}; height: ${designTokens.space[6]}; @@ -77,67 +81,41 @@ const NewProjectContent = styled.div` export const ProjectItem = ({project}) => { return( <> - { - project.outbound ? ( - + + + -
-

- {project.name} -

- { - project.time && ( - {project.time} - ) - } -
-

- {truncateString(project.description, 72)} -

+
+

+ {project?.frontmatter?.title} +

+ { + project?.frontmatter?.startDate && ( + {project?.frontmatter?.startDate} - {project?.frontmatter?.endDate} + ) + } +
+

+ {project?.frontmatter?.description} +

{ - project.image && ( - + project?.frontmatter?.logo && ( + + {project?.frontmatter?.title} + ) } -
- ) - : - ( - - - - - -
-

- {project.name} -

- { - project.time && ( - {project.time} - ) - } -
-

- {truncateString(project.description, 72)} -

-
-
- { - project.image && ( - - ) - } -
- - - ) - } + + + ) } @@ -152,17 +130,24 @@ export const WorkItem = ({project}) => { > -
+

+ {project.name} +

+

{project.description} - -

- {project.name} โ€ข {project.time}

{ project.image && ( - + + {project.name} + ) } @@ -175,16 +160,23 @@ export const WorkItem = ({project}) => {

- {project.description} + {project.name}

- {project.name} โ€ข {project.time} + {project.description}

{ project.image && ( - + + {project.name} + ) } @@ -196,43 +188,15 @@ export const WorkItem = ({project}) => { ) } -export const WorkList = () => { - const work = [ - { - name: 'Masonite', - description: 'Connecting doors to the cloud and simplifying the home-remodeling experience?', - image: '/static/projects/icon-masonite.png', - link:'/work/masonite', - outbound: false, - time: '2019 - Present' - }, { - name: 'DisputeLab', - description: 'Enabling financial enterprises to filter, optimize, and submit thousands of disputes', - image: '/static/projects/icon-disputelab.png', - link:'https://work.ryanparag.com/work/disputelab', - outbound: true, - time: '2017 - 2019' - }, { - name: 'Chargebacks911', - description: 'Helping online merchants optimize profitability by intelligently managing payment disputes', - image: '/static/projects/icon-cb911.png', - link:'/work/chargebacks911', - outbound: false, - time: '2016 - 2019' - }, { - name: 'SoleVenture', - description: 'Giving freelancers the security of steady income and traditional benefits', - image: '/static/projects/icon-sv.png', - link:'https://work.ryanparag.com/work/soleventure', - outbound: true, - time: '2019 - 2020' - } - ] +export const WorkList = ({work}) => { + + const sorted = work.slice().sort((a, b) => new Date(b.frontmatter.startDate) - new Date(a.frontmatter.startDate)) + return( { - work.map(project => ( - + sorted.map(project => ( + )) @@ -327,7 +291,7 @@ export default function Projects(){ { projects.map(project => ( - + )) } diff --git a/notes/showing-my-listening-activity.md b/notes/showing-my-listening-activity.md index 962b621..5f993e2 100644 --- a/notes/showing-my-listening-activity.md +++ b/notes/showing-my-listening-activity.md @@ -121,12 +121,12 @@ We would repeat the same function for the currently playing podcast - making sli Let's see how we can design a single component to account for all 3 of these states: -![component states](../static/showing-my-listening-activity_1.png) +![component states](/../static/showing-my-listening-activity_1.png) This component can account for each of the scenarios our API response may give us - helping us only surface the correct information in a way that is a bit more seamless to the user. ### What's next? -![podcast subscriptions](../static/showing-my-listening-activity_2.png) +![podcast subscriptions](/../static/showing-my-listening-activity_2.png) I've been having some fun grabbing my recent top tracks and my recent podcast subscriptions - [check it out!](/listening/music) If you have feedback or ideas of what else could be a fun way to make this information more transparent and tangible, I'd love to know - let me know using the form below. \ No newline at end of file diff --git a/pages/index.js b/pages/index.js index 1f61454..ec2cf01 100644 --- a/pages/index.js +++ b/pages/index.js @@ -9,11 +9,12 @@ import Featured from '@components/Featured' import Randomizer from '@components/Randomizer' import FAQ from '@components/FAQ' import { ArrowRight } from 'react-feather' +import { WorkList } from '@components/Projects' import fs from 'fs' import path from 'path' import matter from 'gray-matter' -const Index = ({ posts, title, description, ...props }) => { +const Index = ({ posts, work, title, description, ...props }) => { const sortedPosts = posts.slice().sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date)) const latestPosts = sortedPosts.slice(0, 5) @@ -41,12 +42,7 @@ const Index = ({ posts, title, description, ...props }) => {

Selected Work ๐Ÿ’ผ

-

I'm in the process of moving over my work to this site, but before that happens:

- -
+

@@ -104,9 +100,23 @@ export async function getStaticProps() { } }) + const filesWork = fs.readdirSync(path.join('projects')) + + const work = filesWork.map(filename => { + const slug = filename.replace('.md','') + const markdownWithMeta = fs.readFileSync(path.join('projects', filename), 'utf-8') + const {data:frontmatter} = matter(markdownWithMeta) + return { + slug, + frontmatter + } + }) + + return { props: { posts, + work, title: configData.default.title, description: configData.default.description, }, diff --git a/pages/notes/[postname].js b/pages/notes/[postname].js index f974122..7a25d9b 100644 --- a/pages/notes/[postname].js +++ b/pages/notes/[postname].js @@ -14,6 +14,7 @@ import rehypeSlug from 'rehype-slug' import rehypePrism from 'rehype-prism-plus' import Layout, { Wrapper } from '@components/Layout' import getSlugs from '@utils/getSlugs' +import Image from 'next/image' const ScrolledButton = styled(Button)` position: fixed; @@ -30,6 +31,13 @@ const LinkContainer = styled.div` justify-content: center; ` +const HeroImage = styled.div` + width: 100%; + position: relative; + height: 412px; + margin-bottom: ${designTokens.space[4]}; +` + export default function BlogPost({ siteTitle, frontmatter, markdownBody }) { if (!frontmatter) return <> const scrollToTop = () => { @@ -65,15 +73,16 @@ export default function BlogPost({ siteTitle, frontmatter, markdownBody }) {

{frontmatter.title}

{frontmatter.hero_image && ( - {frontmatter.title} + + {frontmatter.title} + )}
{ export default Notes export async function getStaticProps() { - const configData = await import(`../siteconfig.json`) + const configData = await import(`../../siteconfig.json`) const files = fs.readdirSync(path.join('notes')) diff --git a/pages/rss.js b/pages/rss.js index 5f8c5c3..a55f465 100644 --- a/pages/rss.js +++ b/pages/rss.js @@ -4,7 +4,7 @@ import Link from 'next/link' import getPosts from '@utils/getPosts' import fs from 'fs' import { ButtonLink } from '@components/Button' -import { ProjectItem } from '@components/Projects' +import { WorkItem } from '@components/Projects' import Subscribe from '@components/Subscribe' import { designTokens } from '@components/Theme/designTokens' import Title from '@components/Title' @@ -37,7 +37,7 @@ const Notes = ({ posts, title, description, ...props }) => {

Need an RSS reader? Here's what I use:

- + diff --git a/pages/work/index.js b/pages/work/index.js index a9f2986..cf8d43f 100644 --- a/pages/work/index.js +++ b/pages/work/index.js @@ -3,9 +3,13 @@ import Title from '@components/Title' import { WorkLogo } from '@components/Logo' import Projects, { WorkList } from '@components/Projects' import FAQ from '@components/FAQ' +import fs from 'fs' +import path from 'path' +import matter from 'gray-matter' -const Work = ({ title, description}) => { +const Work = ({ title, description, work}) => { + console.log(work) return ( <> @@ -22,7 +26,7 @@ const Work = ({ title, description}) => {

I'm in the proceess of moving my work/case studies to this site, but you can find my current work here or through one of the links below.

- +

... or take a look at a few of my side projects

@@ -39,8 +43,21 @@ export default Work export async function getStaticProps() { const configData = await import(`../../siteconfig.json`) + const files = fs.readdirSync(path.join('projects')) + + const work = files.map(filename => { + const slug = filename.replace('.md','') + const markdownWithMeta = fs.readFileSync(path.join('projects', filename), 'utf-8') + const {data:frontmatter} = matter(markdownWithMeta) + return { + slug, + frontmatter + } + }) + return { props: { + work, title: configData.default.title, description: configData.default.description, }, diff --git a/projects/chargebacks911.md b/projects/chargebacks911.md index db7eda2..48fbc00 100644 --- a/projects/chargebacks911.md +++ b/projects/chargebacks911.md @@ -1,11 +1,12 @@ --- logo: '/static/projects/icon-cb911.png' title: 'Chargebacks911' -description: 'Helping online merchants optimize profitability by intelligently managing payment disputes' +description: 'Helping online merchants and banks intelligently manage payment disputes' text: "Chargebacks911ยฎ provides solutions for businesses dealing with falsely protested credit card charges by intelligently managing payment disputes. I was hired as the first designer on Chargebacks911's product team, juggling between a role as the designer on the marketing team and the sole designer on our application development team (30+ developers)." location: 'Tampa, FL' role: ['Product Design', 'Front-end Engineering', 'Product'] -date: '2015 - 2019' +startDate: '2015' +endDate: '2019' spaces: ['Finance', 'Payments', 'Banking', 'Integration'] platforms: ['Web'] --- diff --git a/projects/masonite.md b/projects/masonite.md index 61b8238..8c29dc0 100644 --- a/projects/masonite.md +++ b/projects/masonite.md @@ -5,7 +5,8 @@ description: 'Connecting doors to the cloud and simplifying home-remodeling' text: 'At Masonite (a global company known for manufacturing residential and architectural doors), I help build cross-platform design systems and products for core experience, integrations, native mobile, and growth - focusing on establishing a process of lean user research and working directly with software engineers, product management, and other stakeholders.' location: 'Tampa, FL' role: ['Product Design', 'Front-end Engineering', 'Product'] -date: '2019 - Present' +startDate: '2019' +endDate: 'Present' spaces: ['Enterprise', 'Growth', 'Building Materials'] platforms: ['Web', 'Mobile', 'Tablet'] --- diff --git a/projects/soleventure.md b/projects/soleventure.md new file mode 100644 index 0000000..624d6c7 --- /dev/null +++ b/projects/soleventure.md @@ -0,0 +1,14 @@ +--- +logo: '/static/projects/icon-sv.png' +title: 'Soleventure' +description: 'The back-office platform for your company of one' +text: "SoleVenture makes insurance benefits and invoicing more accessible for freelancers by combining a business manager, insurance marketplace, company formation, accounting automation, and reporting all in one place." +location: 'St Petersburg, FL' +role: ['UI/UX', 'Front-end Engineering', 'Freelance'] +startDate: '2019' +endDate: '2020' +spaces: ['Insurance', 'Payments'] +platforms: ['Web', 'Mobile'] +--- + +> Case study coming soon \ No newline at end of file diff --git a/public/feed/feed.xml b/public/feed/feed.xml index 47b19d3..1156d95 100644 --- a/public/feed/feed.xml +++ b/public/feed/feed.xml @@ -4,7 +4,7 @@ Ryan's Notes https://notes.ryanparag.com Hello, I'm Ryan Parag - these are my notes about designing in the open and building thoughtful products. - Wed, 17 Mar 2021 03:18:24 GMT + Mon, 23 Aug 2021 13:36:36 GMT https://validator.w3.org/feed/docs/rss2.html Next.js using Feed for Node.js en @@ -39,7 +39,7 @@

Airtable

-

By using Airtable, I could grab all upcoming events for all the communities in a single API call. After an event's date had passed, it would no longer be listed. I also included a column called verified to mark an event as verified and listed in the event list on the website.

+

By using Airtable, I could grab all upcoming events for all the communities in a single API call. After an event's date had passed, it would no longer be listed. I also included a column called Verified to mark an event as verified and listed in the event list on the website.

If an event was unverified, I would receive a notification and a message on the website, letting me know to verify the event in Airtable, either on Airtable's website or mobile app - case closed ๐Ÿ‘.

@@ -94,6 +94,7 @@ Slack's Theme Customizer

Slack gives us the ability to customize 9 colors in a theme, all denoted by a label (eg. Active Item). Why don't we take a look at a data object of a theme that contains the items listed above:

js +// theme object { theme_name: 'Example Theme', active_item: '#5469D4', @@ -1444,13 +1445,1532 @@ const NOWPLAYINGPODCAST_ENDPOINT = https://api.spotify.com/v1/me/

Let's see how we can design a single component to account for all 3 of these states:

-

component states

+

component states

This component can account for each of the scenarios our API response may give us - helping us only surface the correct information in a way that is a bit more seamless to the user.

What's next?

-

podcast subscriptions

+

podcast subscriptions

+ +

I've been having some fun grabbing my recent top tracks and my recent podcast subscriptions - check it out! If you have feedback or ideas of what else could be a fun way to make this information more transparent and tangible, I'd love to know - let me know using the form below.

]]> + + + <![CDATA[Starting the Journey]]> + https://notes.ryanparag.com/notes/starting-the-journey + Tue, 07 Jul 2020 00:00:00 GMT + +

Illustration inspired by Jay Fletcher

+ +

For the past year, I've pondered whether to begin writing and sharing about the things I'm tinkering with - not only as a way to pull back the curtain a bit on desiging products, but also a way to begin tracking my progress on learning new things. I also want to share things I find interesting and maybe even spotlighting the designs of other products.

+ +

Expect to see sketches and ideas about some of the recent tools and ideas I've been playing with:

+ +
  • Figma plugins
  • Design token tooling
  • Prototyping
  • Design portfolios ๐Ÿ˜ญ
  • SwiftUI/Flutter
  • React, Next.js, Gatsby
+ +

Besides design/development tools, I've also been thinking about how designers fit in teams and how cross-team collaboration works between different team structures (eg. solo designers to large design teams).

+ +

I have alot of other thoughts and hobbies (โ˜•๏ธ, ๐Ÿšดโ€โ™€๏ธ, ๐Ÿš˜), so maybe I'll sprinkle some of those in as well.

+ +

If you have an idea or want to hear my take on something in particular, let me know by filling out the form below.

+ +

Stay tuned!

]]>
+
+ + <![CDATA[Thoughts on Design Tools]]> + https://notes.ryanparag.com/notes/thoughts-on-design-tools + Thu, 10 Sep 2020 00:00:00 GMT + +

Illustrations from Lattice's Simplified Wireframes

+ +

๐Ÿ”ฅ Hot take ๐Ÿ”ฅ - just kidding. I want to preface this post by saying that the opinions below are just my current thoughts and my main goal in this was to think out loud. I mainly wanted to promote discussions about how others may feel about this - especially from designers from various team structures.

+ +

If you've been keeping up with these posts chronologically, I've mostly been writing about the more pragmatic aspects of design. For this note, I thought to take a more introspective, cathartic take on things that I've been thinking about.

+ +

As designers, we've seen an era of innovation in our design tools recently - tools like Sketch, Figma, Adobe XD, Framer have transformed the way we share what we design. These innovations have also spurred debate in design communities around being "the best design tool" - sometimes where we tie ourselves to a particular tool, rather than why we're using a design tool.

+ +

Are we evaluating what was built?

+ +

We share mockups and prototypes often - in our portfolios, Dribbble, Twitter, Slack workspaces, etc. But from those designs, what are we actually shipping? What was the end result of that design after it goes through development and the gamut of unhappy paths that often happen?

+ +

Many times, we even share designs that may be slightly different in actuality - due to something that may have popped up in QA or an error state that alters a bit of the user flow.

+ +

"What you deliver matters - you are what you ship"

+ +

An analogy would be if we were to only evaluate an architect's sketches rather than the structure that was built. Shouldn't the same be true of product designers? Shouldn't we critique our shipped end result and not just the Figma prototype alone?

+ +

So what is the purpose of a design tool for us?

+ +

Don't get me wrong - I still think design tools like Sketch and Figma should be fundamental to our process. They allow us to iterate on workable solutions and share our ideas quicker for validation.

+ +

Maybe the purpose of the design tool is to help our teams better communicate what we're setting out to build - helping surface things with the appropriate team members:

+ +
  • if we're on target for our business goals with our PM's
  • if we're meeting engineering constraints from developers
  • if our experience is meeting our design goals
+ +

What is the best design tool?

+ +

In my opinion, I don't think it matters. I think what we all want is to be able to design as close as we can to an end result - interactions, dynamic data, error states, statefulness, etc. - all to help communicate the experience we want to design to our stakeholders and teams. If this can be done in Figma - awesome! If this can be done on a sheet of paper and you're working within an established design system - also awesome! Whichever tools helps align your team most thoroughly and efficiently is probably the best tool for you.

+ +

I think what might be the most important factor to consider is how your team works and which tool helps us communicate over the designs for our product most thoroughly.

+ +

Learning how we can better communicate our ideas through a design tool can help us align to what ends up as the shipped result. Balancing time and engineering constraints in this phase, along with talking through the unhappy paths can help us make our Sketch files look more like our end results.

+ +

Maybe this means accounting for small front-end constraints while we're dragging rectangles around? Maybe this means thinking about our timelines and roadmap while we're working in Figma? I don't know, but I'm curious to figure out how we can use design tools to better align with our teams, and help us have a clearer picture of what will end up as the end result of our design.

+ +

Maybe all of these concerns can be answered in learning when to use a particular tool:

+ +
  • I need to visualize a few layouts - "Sketch?"
  • I need to test a certain user flow - "Figma?"
  • I need to test an interaction - "Code?"
+ +

This method also means we have to remain flexible in our skillsets.

+ +

What works for me, currently

+ +

I'm not sure I have the answers to the things I've been thinking about - but currently, tools that allow me to work faster / align in higher frequency / communicate more meaningfully, help me surface these "real world" issues sooner and more frequently. Even more helpful for aligning engineers on my current team is documenting and sharing the design doc of why we're setting out to incorporate a new design - essentially a case study for each feature design.

+ +

With the innovations in no-code tools, and even the development of accessible code (React, SwiftUI, Flutter), we're inching closer and closer to the day where design and engineering are closely speaking the same language - maybe we're already there on teams that have a high degree of fluidity and t-shapedness.

+ +

To be honest, I feel we're still far away from the day where we can design an end result directly from a design tool - so the problem we're left with is how we can better surface core problems from our users and communicate through ambiguity with the engineers/PM's on our teams. How can we learn to use our design tools to better communicate the "why" and "how" when we're in the build phase? How can we receive more thoughtful "buy in" from our team members and stakeholders?

+ +

I'm on a neverending journey on trying to figure this out for myself and how I work, so I'll probably revisit this note and make revisions. But I thought it would be helpful to at least share what was on my mind and what my process looks like from a high level. I'd love to hear your thoughts on design tools and how you feel they're used best - send me a message!

]]>
+
+ + <![CDATA[Building a Community Website]]> + https://notes.ryanparag.com/notes/building-a-community-website + Mon, 20 Jul 2020 00:00:00 GMT + +

A few weeks back (~ late June 2020), I was having trouble tracking down where I could join a few of the local design Slack communities in the Tampa Bay area. There wasn't a central location for me to see which communities were in the area and what events they had coming up.

+ +

So, I set out to quickly build a website that would aggregate that data in a central location and help those in the area that were just starting out in design (or are not currently part of any of the communities) to join in on their discussions.

+ +

I've been playing with Next.js and Styled Components lately and thought it would be a fun way to experiment with a few modern web tools. In this post, I'll quickly go over how I built it. We needed a few basic functions on the website:

+ +
  • Show local design communities hosting events
  • Post links to join the various local Slack communities
  • Allow new, or not listed design communities, to submit their org to be listed
  • Allow communities to submit events
+ +

Using Next.js, Styled Components, and Google Forms, I quickly spun up a small site that lists the major communities designers can join:

+ +

Tampabay.design

+ +

Users can fill out the Google Form using the link to notify me about their organization to be listed,

+ +

With my limited knowledge as a non-dev, I also needed a way for communities to submit events to a database, but not list them until I can verify them. Some orgs are using Meetup, some using Eventbrite, and some using another event hosting site du jour.

+ +

I decided I would just grab all of the currently upcoming events and throw them in an Airtable doc:

+ +

Airtable

+ +

By using Airtable, I could grab all upcoming events for all the communities in a single API call. After an event's date had passed, it would no longer be listed. I also included a column called Verified to mark an event as verified and listed in the event list on the website.

+ +

If an event was unverified, I would receive a notification and a message on the website, letting me know to verify the event in Airtable, either on Airtable's website or mobile app - case closed ๐Ÿ‘.

+ +

Events

+ +

Hopefully that was helpful in case you want to do the same for your community, or even if you want to help contribute on this website. I'll keep updating this site with more features that could be helpful to other designers in the area, but if you have an idea, ping me using the form below - I'd love to hear about your ideas.

]]>
+
+ + <![CDATA[Designing an App for Slack Themes]]> + https://notes.ryanparag.com/notes/building-an-app-for-slack-themes + Thu, 17 Dec 2020 00:00:00 GMT + +

Can we not only design a web app, but plan and build one as well? Sure we can!

+ +

About a year and a half ago, I began to realize I had too many Slack workspaces and had a difficult time differentiating between them. So I set out to build a web app to help collect a bunch Slack Themes where users are able to copy and paste into Slack - you can find it here!

+ +

Sidenote: I'm not going to delve too much into the code or design, but if you have questions, feel free to look at the repo on GitHub or contact me!

+ +

Slack gives us a way to customize the theme of each workspace sidebar, and I thought this could be a neat way for others to help personalize and identify their different workspaces.

+ +

Slack's Theme Customizer +Slack's Theme Customizer

+ +

But, in order to change a theme, I had to go through multiple steps: open the menu, click preferences, change theme.

+ +
  • How can we make the process of selecting themes simpler?
  • What if a user wanted to do this in less steps?
  • What if a user wanted access to more themes and options?
+ +

End Goal

+ +

Let's set out to build a web application where users can find one of many curated themes to copy and paste quickly in their Slack workspaces

+ +

Although there was a v1 of this web application, I'm going to go through the high-level steps of how I set out to build v2 from the ground up.

+ +

What will we use to build it? +- Figma: to help design our idea +- React & Next.js: to house all of our front-end logic and server-rendered pages +- Tailwind: to help build styles super quickly (and a bit of Styled Components) +- Firebase/Firestore: to help house all of our themes inside of a database

+ +

There are a few other things, like framer-motion and PostCSS, but they won't be necessary.

+ +

Getting Started with Data and Firestore

+ +

The first thing we should probably do is start to think how we will store our themes and which types of data will be associated with each theme: +- Theme name +- Theme colors +- Created by +- Date created +- Categories/groups +- Likes

+ +

Slack gives us the ability to customize 9 colors in a theme, all denoted by a label (eg. Active Item). Why don't we take a look at a data object of a theme that contains the items listed above:

+ +

js +// theme object +{ + theme_name: 'Example Theme', + active_item: '#5469D4', + active_item_text: '#FFFFFF', + active_presence:'#4CAF50', + column_bg: '#191D27', + hover_item: '#283040', + mention_badge: '#F2453D', + text_color: '#DEE5EE', + top_nav_bg: '#000000', + top_nav_text: '#DEE5EE', + categories: ['dark', 'brand'], + likes: 0, + submittedBy: 'Ryan Parag', + created: 1608255573 +} +

+ +

Now let's take a look on how to use and insert this data inside Google's Cloud Firestore. If you've never used Firebase/Firestore, you would first have to create a new project before setting up a database inside the project. Here's a step-by-step tutorial on the setup

+ +

After you've created a new project, we would need to enable Cloud Firestore as a databse. Here's a quick glance at mine, along with the how we would structure our data (collections and documents):

+ +

Data inside Firestore

+ +

Firestore gives us a few functions to add/edit/delete themes (documents) that we'll use across our app.

+ +

Example functions: +js +// Add an item to a collection +const addTheme = (Theme) => { + firebase.firestore() + .collection(YourCollection) + .doc(Theme.Id) + .set(Theme) +} +

+ +

js +// Delete an item from a collection +const deleteTheme = (Theme) => { + firebase.firestore() + .collection(YourCollection) + .doc(Theme.Id) + .delete() +} +

+ +

js +// Update an item in a collection +const updateTheme = (Theme) => { + firebase.firestore() + .collection(YourCollection) + .doc(Theme.Id) + .update(Theme) +} +

+ +

Designing the App

+ +

Now that we have a place to store and grab our themes, we need to think about how we would design an easy-to-use interface for our stored themes.

+ +

What would each theme look like?

+ +

Theme Item

+ +

We can also let users copy the string of hex colors easily by clicking each theme card - where the user would subsequently paste into Slack and click the button that Slack generates to switch themes.

+ +

Filtering and Sorting

+ +

How do we expect users to find themes that fits into their exploring criteria? Would they be searching for dark themes? Or purple themes? Would they be expecting to sort alphabetically or by which themes are most popular?

+ +

Since each theme has a set of groups/categories, we can build sets of filters and sorting mechanisms to make the UI for theme browsing flexible.

+ +

Sorting

+ +

In order to get our group filtering to work with our data in Firestore, we'll need to build a few indexes - these will help get our complex data query really fast:

+ +

Firestore Indexes

+ +

Designing for Low Data

+ +

How do we design an app for users who may not have the fastest internet or may have a lesser-performing device?

+ +

To help lessen the load from the browser and network connection, we can limit how much data we pull with our Firestore query. Here's what that query looks like in React:

+ +

`js + const [loadedThemes, setLoadedThemes] = useState([]) // initial array of themes + const [sort, setSort] = useState('themename') // initial sorting (by themename or likes) + const [order, setOrder] = useState('asc') // initial sort order + const [queryAmount, setQueryAmount] = useState(27) // initial amount of themes

+ +

firebase.firestore().collection('themes').limit(queryAmount).orderBy(sort, order).onSnapshot(snapshot => { + const fetchedThemes = snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data() + })) + setLoadedThemes(fetchedThemes) + }) +`

+ +

We can let users look at the next set of data by creating a button that increments the query limit:

+ +

Update Query Limit

+ +

`js + const updateQueryAmount = () => { + setQueryAmount(prev => prev + 27) + }

+ +

...

+ +

<button + onClick={updateQueryAmount} + > + Show me more + </button> +`

+ +

So we've finished our storage, design, and data transformation - let's take a look at what we have:

+ +

Exploring Themes

+ +

Designing Options

+ +

On top of letting users browse and copy Slack themes, I wanted to give users a way to fine tune the listed themes to better fit how they would like to visualize them:

+ +
  • Toggle the intrusive Top Nav Bg color to help make each theme a bit more minimal
  • Toggle the Theme Name in the copied theme (this would help label the theme when pasting to a thread)
+ +

Let's design a space where users may toggle those settings:

+ +

Settings

+ +

Collecting Submissions

+ +

What if a user has a great idea for a theme and wants to add it to our list?

+ +

Why don't we design a way for a user to easily submit a theme through our web app? We'll need to ask the user for a few things:

+ +
  • What colors are in your theme?
  • What do you want to call your theme?
  • Let us know who you are - to give you credit, of course
+ +

Theme Form

+ +

Once submitted, we can add this to another Firestore collection (called submitted) in order to await our verification (we'll go over that a bit later).

+ +

Building Engagement

+ +

Besides giving users the ability to upvote themes, we can also list a few of the most recently submitted themes:

+ +

Success State

+ +
+ +

All done! or are we?

+ +

Behind the Scenes

+ +

I don't want to have to manage theme submissions or editing through Firestore or by pushing new code. If we have all of our data in Firestore, we could design an easier way to update this data through our web app - all hidden behind some user authentication.

+ +

What would a scenario for this look like?

+ +
  1. User submits a new theme via our new theme submission form
  2. Theme gets added to our submitted collection in Firestore
  3. I have to go into Firestore and manually move new theme from submitted to themes collection
+ +

Rather than do this in Firestore and entering this theme into the themes collection manually, we could setup a todo list of sorts in an Admin page on our website.

+ +

To do this, I used: +- Firebase Auth: authenticate our admin logins +- Nookies: create authenticate tokens for server-side apps

+ +

Login Page

+ +

We need a space for admins to login to the admin dashboard. Instead of walking through each step of setting up firebase/auth and nookies, here's a tutorial you can follow to do just that.

+ +

Login

+ +

Dashboard

+ +

Once logged in, what do need to display and how do we display to build context around our scenario?

+ +

We basically need a way to manage the different collections in our database, so why don't we build a simple navigation separating them?

+ +

Dashboard

+ +

From the image above, it seems as though there's a theme submission awaiting to be verified. We can view the submission as well as associate any groups we feel are suitable for the respective theme:

+ +

Theme Submission

+ +

Once we make any edits, we can click Verify & Transfer to move the submission from our submitted collection to our themes collection.

+ +
+ +

That's it, for now

+ +

This is as far as I've made it, but now that we have our data in Firestore, my next objective is to design an experience for an integrated Slack app - rather than copy/paste themes, we could give users the ability to get a theme directly from a / command!

+ +

I didn't delve too much into the code or design here, but if you have questions, feel free to look at the repo on GitHub or contact me using the details below!

]]>
+
+ + <![CDATA[Collecting Theme Toggles]]> + https://notes.ryanparag.com/notes/collecting-theme-toggles + Thu, 27 Aug 2020 00:00:00 GMT + +

For the past few years, I've been tinkering with designing dark/light modes and themes for the web - experimenting with CSS variables, different stylesheets, CSS-in-JS, etc. It's been awesome to be inspired by how other products, websites, and design portfolios are experimenting with dark/light modes - inspiring some of the recreations in code which you can find below.

+ +

One of my most complex experiments is actually the theme toggle on this website! (Play with it using the button in the top navigation)

+ +

I'll try to keep this list updated as I add something new to CodePen or when I throw together a new design. Click an image to play with a toggle and check out the code.

+ +
+ +

Themes in my Portfolio

+ +

I've documented the theme toggles in my own portfolio (new and old), but here they are again:

+ +

[![Theme toggle](../static/collecting-theme-toggles_new-p.png)] +[View the toggle](https://ryanparag.com)

+ +

[![Theme toggle](../static/collecting-theme-toggles_old-p.png)] +[View the toggle](https://grapalab.com)

+ +
+ +

More Theme Toggles

+ +

Here are some code sketches in CodePen - click an image to play with a toggle and check out the code:

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +

Theme toggle +View the toggle

+ +
+ +

Do you have a cool idea for a way to toggle themes? Let me know what you're playing around with using the form below.

]]>
+
+ + <![CDATA[A Figma Plugin for Design Tokens?]]> + https://notes.ryanparag.com/notes/creating-a-design-token-plugin + Tue, 29 Dec 2020 00:00:00 GMT + +

I recently began exploring Figma's developer API to try and make a few processes a bit better at the day job.

+ +

It all started out with a problem we were facing when trying to make our hand-off for design system components a bit easier between design and engineering - so I started fumbling around with building a custom plugin to find a way to help ease that burden.

+ +

I was first inspired to venture into building a plugin after browsing other indie plugins and reading about Spotify's in-house plugins.

+ +

Resources

+ +

If you're interested in building a plugin to fix a problem on your own projects, here are a few resources I used: +- Figplug +- Figma Plugin Helpers +- Figma's Developer API Docs +- Figma Plugins by Varun Vachhar

+ +

What do I need to know?

+ +

If you're familiar with some basic web development, and even if you're not, building Figma Plugins is really easy - it would be helpful to know: +- HTML +- CSS +- JavaScript (Typescript helps!) +- React (with Figplug)

+ +

Building the design token plugin

+ +

Let's start off with the flow we want the plugin to solve:

+ +

Flow

+ +

Working out some bugs, but I'll upload screenshots soon!

]]>
+
+ + <![CDATA[Designing for Personalization]]> + https://notes.ryanparag.com/notes/designing-for-personalization + Thu, 17 Sep 2020 00:00:00 GMT + +

As users, we expect our modern digital products to understand us more thoroughly - to help drive a more seamless, personalized experience per our individual preferences. We see this in a multitude of ways, across mobile apps, websites, and even (more so) in our content streaming experiences:

+ +
  • Custom app icons
  • Personalized ecommerce recommendations
  • Categorized TV shows and movies
  • Dark / Light modes
+ +

Neilsen Norman Group summarizes the difference between customization and personalization fairly well:

+ +

Customization gives control to the user and personalization gives control to the site. Both can enhance usersโ€™ experience, but only when carefully implemented.

+ +

Customization vs. Personalization in the User Experience

+ +

Personalizing Experiences

+ +

The basic goal of personalizing an experience is to prevent users from struggling to find information / content / products. Personalization is a way for a product to identify an individual and help build relevancy to the things they see and the options they have. How can we show our users things we think they might prefer from the sea of content that we host on our platform?

+ +

What are the goals of building personalization in a product and why does it matter? +- Build loyalty from individuals in a user pool +- Decrease content noise for users +- Increase product engagement/retention +- Increase upselling of similar goods

+ +

Most goals around personalization revolve around increasing conversion rates and retaining a more loyal user. Experience personalization has greatly influenced the way we shop for things and consume content in the modern era. We're seeing how detailed metadata associated with individual products, movies, podcasts, music, etc. is being used to categorize things the way systems assume we, as individuals, prefer.

+ +

What kind of things can a product use to categorize individuals? We actually see quite a bit of personalization being used across the modern web: +- Geolocation/geofencing +- Profile information +- Survey results +- Referral links and campaign source

+ +

Even by using these basic data points, systems can automatically sort users and gauge common content when paired with a machine-learning/big data platform. Some of these data points are given from an individual's self-identification, whereas some of the more complex forms of identification are based on what and how a user consumed particular content (eg. I watched a sci-fi movie on Netflix, so the system recommends more sci-fi movies in my feed).

+ +

We can take a quick look at how Duolingo is using a mix of identification data to help build engagement through simple email updates:

+ +

Duolingo personalization

+ +

Let's take a look at how Netflix shows us things based on the things we've previously watched and our individual user profile:

+ +

Netflix personalization

+ +

Netflix is also A/B testing on top of this, driving their deep learning platform and content designers to learn how and why certain users choose particular UI cards:

+ +

Netflix personalization

+ +

In these examples, we're seeing how designing the system to perform certain background tasks can help provide a more personalized experience. Even though Netflix's example is a very complex model, I think the starting point of how to design around personalization begins with a few basic questions:

+ +
  • Who are our users?
  • What do we know about them?
  • What and how can we learn about them?
  • What data can we sort for them based on what we know about each user?
  • How do we show them this sorted data?
  • How do we measure the success of "smarter" sorting?
+ +

Let's take a look at how Spotify answers those questions and opts to show users recommended content:

+ +

Spotify personalization

+ +

Spotify also chooses to mix content together, showing us a variation of content we've consumed alongside content that matches a higher frequency of metadata - all pointing to a more seamless, personalized experience.

+ +

Spotify personalization

+ +

This is a super interesting method of designing for a better experience, and can probably be expounded on greatly. Here are a few more resources digging into these details more thoroughly:

+ + + +

We also see personalization across many of our products that enable the way we work - usually distributed across different roles/permissions contained in a system. For instance, admins on a platform may have more functionality in the things they are able to do, more so than normal users.

+ +

In the example below, we can see a handful of example roles in a system and the different goals associated with each. Each goal can be mapped to drive a particular experience - whether that means surfacing particular actions or altering a view dependent on that role/goal.

+ +

Role personalization

+ +

Customized Experiences

+ +

Rather than let the system dictate what a user sees, is it possible for us to let the user dictate their own experience upon segments of a digital product experience?

+ +

We see this in a ton of ways already - throughout our iOS settings, news feeds, theme pickers ๐Ÿ˜‰, etc. Personalizing an experience based on the user's set preference can allow the user to accommodate themselves to a product more effectively - they would be situating themselves to an environment more relevant to their types of engagement.

+ +

Let's take a look at how some teams are tackling customization in a few unique and common ways. In GitHub for Mobile, we can see a wide range of ways they're allowing the user to customize based on their preferences - providing fixed options to modify certain settings:

+ +

GitHub personalization

+ +

Some apps even let us choose the app icon we see in our view, before we jump into the app! Let's see how GitHub, PocketCasts, and Todoist gives users an array of app icon options:

+ +

App icon personalization

+ +

One of the most common ways digital products give to customize the UI and experience for individual users is through dark/light theming (and maybe a step further). Commonly dictated based on the user's OS settings, apps and browsers can grab and feed that preference to our digital products - building a sensible default and predicting a more seamless experience for the user.

+ +

If you'd like to do this on the web, all it takes is a bit of CSS or JS. If you're opting to do this in CSS, we can accomplish this in a simple way in modern browsers:

+ +

`css +@media (prefers-color-scheme: dark) { + body { + color: white; + background: black; + } +}

+ +

@media (prefers-color-scheme: light) { + body { + color: black; + background: white; + } +} +`

+ +

In JS, we can grab a user's OS theme with a simple function:

+ +

js +if (window.matchMedia && + window.matchMedia('(prefers-color-scheme: dark)').matches) { + document.body.style.background = 'black'; + document.body.style.color = 'white'; +} +

+ +

Some apps take this to another level, giving users options in choosing pre-selected themes (kind of like this site). Todoist lets users select a theme and which type (light/dark) of neutral colors they prefer. They even let the user toggle if they would like to match per their OS preferences and make a more cohesive cross-platform experience by syncing:

+ +

Todoist Customization

+ +

A Step Further

+ +

UI personalization and customization has made me curious on the limits to which we can go by giving users certain controls - and, more interestingly, when we don't have to. Most UI theming mechanisms provide a limited, strictly-defined amount of choices (as seen above). What if we could give users full control of the theme in an interface?

+ +

So I created a way for users to theme this site on their own. Check it out!

+ +

Theme Creator

+ +

I've been experimenting with ways to which we could give users fluid customization abilities, but provide them enough rails as to not degrade their experience.

+ +

I'm still playing around with this idea and gathering feedback, but by using Lyft Design's Colorbox tool and the algorithm they've crafted, we can create palettes on-the-fly using minimal inputs.

+ +

To provide some rails for the user, I included a way to gauge when parts of the theme aren't matching certain WCAG requirements as well.

+ +

I'd love to hear what you think, and if you have any feedback contact me using the details below!

]]>
+
+ + <![CDATA[From Figma to Code]]> + https://notes.ryanparag.com/notes/from-figma-to-code + Fri, 04 Sep 2020 00:00:00 GMT + +

In an effort to keep up with this site's ethos and show a bit more of how I design things, I thought I could share a Figma file and show the code used to produce what is contained in a Figma prototype.

+ +
+ +

TLDR:

+ +

Click here for the Figma file

+ +

Click here to go to the CodePen

+ +
+ +

What tools would I need?

+ +

Everything you need should be easily available ๐Ÿ˜€: +- Figma for quick static prototyping +- A code editor or CodePen to build what we design

+ +

Optional +- Pen & paper for ~~doodling~~ quick ideating

+ +
+ +

What are we going to build?

+ +

Let's build a few simple cards to track the prices of Bitcoin. ~~That way, we can see how much money we lost when we bought in during the hype.~~

+ +

We can also use Coindesk's open API when moving to code ๐Ÿ‘.

+ +

Let's start with a use case:

+ +

A user wants to be able to see the current price of Bitcoin and a few recent price trends.

+ +

For fun, we'll throw in a way to toggle between light and dark mode - for the night traders ๐Ÿฆ‰.

+ +

Let's also start by reverse engineering what I did:

+ +

Click here for the Figma file

+ +
+ +

Design Tokens

+ +

Let's get started by jumping into Figma. I think the most helpful takeaway from all of this, mostly for my own laziness, is to stay organized:

+ +
  • ๐Ÿ‘ฉโ€๐Ÿซ Name your frames/groups/components ...or else you're stuck with a bunch of things called Frame 107 or Group 69
  • ๐Ÿ—‚ Organize your pages - split components out of a prototype page and keep a separate page for research/documentation
+ +

Organized pages

+ +

Now that we've finished a bit of housekeeping, we can start building some of the base UI. Using Figma's method for creating text and color styles, I created a color palette (with shades and tints) and text styles (with multiple weights):

+ +

Color Styles

+ +

Text Styles

+ +
+ +

Components

+ +

Now that we have a few colors and text styles to use, we can start building components. Since we're incorporating a dark and light mode, we probably need two versions of a component to accomodate for each theme. We would start by building a .base component - prefixed with a period to avoid publishing when we publish a library.

+ +

Now that we have a .base, we would use that to build light/dark versions of the components:

+ +

Themed components

+ +

I'm designing using an 8pt grid and am using multiples/fractionals of 8 as spacing units. Let's take a look at a redline of a card component and see how we're using multiples of 8 as spacing values:

+ +

Redline

+ +

Once we finish building our components, we can start building the layout:

+ +

Layout

+ +

Since we have different versions of our components for light/dark mode, duplicating and switching out components should be easy:

+ +

Prototype

+ +

Code

+ +

Now that we have a functioning prototype in Figma, we can start crafting a bit of code. If you'd like to skip to the end:

+ +

Click here to go to the CodePen

+ +

Inserting our Design Tokens

+ +

By using CSS custom properties (variables) we can add in our color design tokens to help build out the light and dark theme of our UI.

+ +

`css +// colors from design tokens +--sail-color-black: #000; +--sail-color-white: #fff; +--sail-color-gray-50: #f7fafc; +--sail-color-gray-100: #e3e8ee; +--sail-color-gray-200: #c1c9d2; +--sail-color-gray-300: #a3acb9; +--sail-color-gray-400: #8792a2; +--sail-color-gray-500: #697386; +--sail-color-gray-600: #4f566b; +--sail-color-gray-700: #3c4257; +--sail-color-gray-800: #2a2f45; +--sail-color-gray-900: #1a1f36; +--sail-color-blue-50: #f5fbff; +--sail-color-blue-100: #d6ecff; +--sail-color-blue-200: #a4cdfe; +--sail-color-blue-300: #7dabf8; +--sail-color-blue-400: #6c8eef; +--sail-color-blue-500: #5469d4; +--sail-color-blue-600: #3d4eac; +--sail-color-blue-700: #2f3d89; +--sail-color-blue-800: #212d63; +--sail-color-blue-900: #131f41; +--sail-color-cyan-50: #edfdfd; +--sail-color-cyan-100: #c4f1f9; +--sail-color-cyan-200: #7fd3ed; +--sail-color-cyan-300: #4db7e8; +--sail-color-cyan-400: #3a97d4; +--sail-color-cyan-500: #067ab8; +--sail-color-cyan-600: #075996; +--sail-color-cyan-700: #06457a; +--sail-color-cyan-800: #093353; +--sail-color-cyan-900: #042235; +--sail-color-green-50: #efffed; +--sail-color-green-100: #cbf4c9; +--sail-color-green-200: #85d996; +--sail-color-green-300: #33c27f; +--sail-color-green-400: #1ea672; +--sail-color-green-500: #09825d; +--sail-color-green-600: #0e6245; +--sail-color-green-700: #0d4b3b; +--sail-color-green-800: #0b3733; +--sail-color-green-900: #082429; +--sail-color-yellow-50: #fcf9e9; +--sail-color-yellow-100: #f8e5b9; +--sail-color-yellow-200: #efc078; +--sail-color-yellow-300: #e5993e; +--sail-color-yellow-400: #d97917; +--sail-color-yellow-500: #bb5504; +--sail-color-yellow-600: #983705; +--sail-color-yellow-700: #762b0b; +--sail-color-yellow-800: #571f0d; +--sail-color-yellow-900: #3a1607; +--sail-color-orange-50: #fffaee; +--sail-color-orange-100: #fee3c0; +--sail-color-orange-200: #f8b886; +--sail-color-orange-300: #f5925e; +--sail-color-orange-400: #e56f4a; +--sail-color-orange-500: #c44c34; +--sail-color-orange-600: #9e2f28; +--sail-color-orange-700: #7e1e23; +--sail-color-orange-800: #5d161b; +--sail-color-orange-900: #420e11; +--sail-color-red-50: #fff8f5; +--sail-color-red-100: #fde2dd; +--sail-color-red-200: #fbb5b2; +--sail-color-red-300: #fa8389; +--sail-color-red-400: #ed5f74; +--sail-color-red-500: #cd3d64; +--sail-color-red-600: #a41c4e; +--sail-color-red-700: #80143f; +--sail-color-red-800: #5e1039; +--sail-color-red-900: #420828; +--sail-color-purple-50: #fff8fe; +--sail-color-purple-100: #fce0f6; +--sail-color-purple-200: #f0b4e4; +--sail-color-purple-300: #e28ddc; +--sail-color-purple-400: #c96ed0; +--sail-color-purple-500: #a450b5; +--sail-color-purple-600: #7b3997; +--sail-color-purple-700: #5b2b80; +--sail-color-purple-800: #401d6a; +--sail-color-purple-900: #2d0f55; +--sail-color-violet-50: #f8f9fe; +--sail-color-violet-100: #e6e6fc; +--sail-color-violet-200: #c7c2ea; +--sail-color-violet-300: #b0a1e1; +--sail-color-violet-400: #9c82db; +--sail-color-violet-500: #8260c3; +--sail-color-violet-600: #61469b; +--sail-color-violet-700: #4b3480; +--sail-color-violet-800: #352465; +--sail-color-violet-900: #1f184e;

+ +

// light mode +--bg: var(--sail-color-gray-50); +--color: var(--sail-color-gray-900); +--subtleColor: var(--sail-color-gray-400); +--successBg: var(--sail-color-green-100); +--successColor: var(--sail-color-green-500); +--dangerBg: var(--sail-color-red-100); +--dangerColor: var(--sail-color-red-500); +--cardBg: var(--sail-color-white); +--cardShadow: var(--sail-color-gray-100); +--cardBgHover: var(--sail-color-white); +--primary: var(--sail-color-blue-500); +--primarySubtle: var(--sail-color-blue-100); +--transparent: rgba(255,255,255,0); +--transparentSubtle: rgba(255,255,255,.6);

+ +

// dark mode - we can overwrite the utility variables by wrapping it in a class on the :root +--bg: var(--sail-color-gray-900); +--color: var(--sail-color-gray-100); +--successBg: var(--sail-color-green-700); +--successColor: var(--sail-color-green-200); +--dangerBg: var(--sail-color-red-700); +--dangerColor: var(--sail-color-red-200); +--cardBg: var(--sail-color-gray-900); +--cardShadow: var(--sail-color-gray-800); +--cardBgHover: var(--sail-color-gray-800); +--primarySubtle: var(--sail-color-blue-800); +--transparent: rgba(26,31,54,0); +--transparentSubtle: rgba(26,31,54,.6); +`

+ +

Now that we have our colors and themes, we need to add in our text styles and sizes that we defined in our text styles in Figma:

+ +

`css +--text-xxl: 2.8rem; +--text-xl: 2.4rem; +--text-lg: 1.8rem; +--text-md: 1.6rem; +--text-sm: 1.3rem; +--text-xs: 1.1rem;

+ +

--text-heavy: 900; +--text-bold: 700; +--text-normal: 400; +`

+ +

Switching Themes

+ +

Now for the theme switching part - we need a function to trigger when a toggle button is clicked. We can make a simple function to check if the root element on the page contains the dark mode class. Using a ternary operator, we can remove/add the class if that check returns as true/false:

+ +

`js +// Our toggle with id of button +const themeButton = document.getElementById('themeButton')

+ +

// theme switcher function +const changeTheme = () => { + let themeState = document.documentElement.classList.contains('theme--dark') + themeState ? themeButton.classList.remove('c-theme--active') : themeButton.classList.add('c-theme--active') + document.documentElement.classList.toggle('theme--dark') +} +`

+ +

And now we can add it to our toggle button:

+ +

html +<button id="themeButton" onclick="changeTheme()"> + <handle/> + <icon/> +</button> + +... and boom goes the dynamite ๐Ÿงจ +Theme switcher

+ +

Layout

+ +

Here's what the markup for my layout looks like so far:

+ +

html +<header class="l-header"> + <div class="u-pb--16 u-pt--16 u-display--flex u-align-items--center u-justify--space-between l-header__body u-pr--16 u-pl--16"> + <div class="u-text--left"> + <div class="u-text--md u-text--bold u-mb--4">Title</div> + <div class="u-text--xs u-text--subtle">Subtitle</div> + </div> + <button class="c-theme u-text--xs" id="themeButton" onclick="changeTheme()"> + <handle/> + <icon/> + </button> + </div> +</header> +<div class="l-wrapper u-pr--16 u-pl--16 u-pt--32" id="list"> + <!-- + This is where we can insert our Bitcoin card and table + --> +</div> +

+ +

You might be wondering what all of those classes mean. Most of my styles are utility classes and are prefixed with a letter using BEM. Utility classes are reusable, single-purpose style classes that I can add to web elements. For example, if I wanted to style text in our MD size, I could create a class named .u-text--md and add it to the web element. Utility classes help me build things quickly, but feel free to craft these styles in a different way - and don't forget to have fun homie ๐Ÿ˜Ž.

+ +

I'm also organizing these styles with prefixes and BEM naming conventions:

+ +
  • l = layout
  • u = utility
  • c = component
+ +

Learn more about utility classes

+ +

Learn more about BEM

+ +

Getting the Bitcoin Data

+ +

We also need to call the data from Coindesk. Since the current price data and historical data are two different endpoints, we need to make multiple calls.

+ +

By using Axios, we're able to do this easily in javascript:

+ +

js +axios.all([ + axios.get(current_url), + axios.get(history_url) +]) +.then(res => { + // do stuff with the data + // create and add HTML with data to the page +}) +

+ +

After a little bit of styling and data transformation, we should have something close to this:

+ +

Prototype

+ +

Conclusion

+ +

I'd love to know if this helps you design things and if shedding a bit of light on my process helps. If you have any ideas that could make this small project better, send me a message!

]]>
+
+ + <![CDATA[How I conduct UX research]]> + https://notes.ryanparag.com/notes/how-i-conduct-ux-research + Sun, 28 Feb 2021 00:00:00 GMT + +

Illustration from Alzea's Illustration Pack

+ +

TLDR:

+ +

Abdul Salam created a series of UX questions.

+ +

I collected them in Airtable as well and threw it on this site - find it here.

+ +
+ +

It's a bit difficult to find space for the planning, validating, and testing of new designs &mdash; especially when you're the solo designer or on very lean teams.

+ +

Finding time to gather answers from stakeholders, interview users, roadmap concepts can already be somewhat timestaking - inbetween actually keeping your head down and prototyping ideas. Figuring out how to build a process or framework for user research inside a rapid, agile, and lean team has been a goal of mine within my current team.

+ +

Early on, I came across GV's guide for a lean UX framework and begin incrementally adding in portions into our process - hoping to validate what works for us and build a bridge towards a formal UX framework for our team (and in the long-term, our entire organization).

+ +

Abdul Salam collected a ton of great questions to ask throughout a research process. I collected them in Airtable and threw it on this site - find it here

+ +
+ +

Before designing

+ +

Tip: keep a running doc of issues + feedback snippets from a users (past feedback, Twitter, App Store, etc.) and see which ones bubble up most often to help build priority.

+ +

To help find and prioritize what we need to design: +- What data are we currently collecting (e.g. analytics, A/B, customer support, surveys, usability)? +- What research is already available to us? +- Who do we need to speak with to gather feedback on the current status of our product?

+ +
+ +

Past research ๐Ÿ•ต๏ธโ€โ™‚๏ธ

+ +

Most probably, you know a few things before you're going off into desigining something new: +- Who are our users? +- What is the general product roadmap? (maybe even company roadmap) +- What are the short-term goals of the product? +- What are competitors doing to answer the problem we're trying to solve? What could they be doing better?

+ +
+ +

Aligning with stakeholders ๐Ÿ‘ฏโ€โ™€๏ธ

+ +

At this point, we've probably bubbled up a few friction points from our users &mdash; now we can start to think about solutions to these problems with a few methods. A few exercises we can try to figure out how to best answer a problem we're solving: +- Customer journey mapping to gauge and redesign product touchpoints +- Mind mapping to help align user and business goals with stakeholders +- Building user scenarios with PM's to pragmatically outline the new feature

+ +

There are a ton of tools to help share/build these docs, but a few popular ones are: Figma, Miro, Mural, Whimsical, Coda, or a good old-fashioned whiteboard.

+ +
+ +

Designing and prepping ๐Ÿ‘จโ€๐Ÿ’ป

+ +

This is the stage where we can finally begin building wireframes/prototypes ๐ŸŽ‰ based on the requirements, research, and assumptions we've gathered - we can put our heads down, open up Figma, Sketch, or whatever design tool of your choosing and begin designing a few iterations of this feature/product.

+ +

Before moving a feature into a development sprint, we try to frame what we need: +- Questions and assumptions of what we're designing +- A clickable prototype (Figma, InVision, Sketch Cloud, Framer, scrappy code) +- Qualitative research: 1-on-1 interviews (at least n=4) +- A way to measure the success of this feature

+ +
+ +

Conducting new research ๐Ÿ‘ฉโ€๐Ÿ”ฌ

+ +

Now that we have our new designs, scheduled participants, and questions to ask, we can begin conducting user interviews to quickly test our assumptions.

+ +

Through these user interviews, we can find where friction points may exist, if the new feature makes sense, and any other questions we may want to ask the participant. Just like in the GV guide and Jake Knapp's book, Sprint, we outline our interviews as such:

+ +
  1. Friendly welcome
  2. Context questions
  3. Introduction to the prototype
  4. Tasks
  5. Quick debrief
+ +
+ +

After the interviews ๐Ÿค 

+ +

After we gather the data we received from the user interviews, we find what changes we have to make and either go back to the drawing board or begin handing off to the development team. A few things to note: in my current process, there's typically a bit of overlap within these phases, but your team and process could very well function differently.

+ +
+ +

That's it! ๐Ÿ˜ฉ

+ +

Hopefully this helps your team, but I'd love to hear about how you manage working between researching and desigining - let me know through email or the form below.

]]>
+
+ + <![CDATA[Looking back through 2020]]> + https://notes.ryanparag.com/notes/looking-back-through-2020 + Sat, 02 Jan 2021 00:00:00 GMT + +

2020 - a year in which many of us reflected on who we are, how we spend our time, and what is our place in our communities.

+ +

One of the things I started this year was writing a bit - so why not write a year-in-review for 2020.

+ +

While many things this year were tumultuous, I was able to focus on a plethora of things that I may have never done. I spent alot of time walking/biking outside, listening to podcasts, learning new things, working on side projects, and spending time with those close to me.

+ +

Work

+ +

Professionally, this was a great year (and my 2nd year) at my day job. Although many companies and individuals went through difficult times at different times in 2020, we were lucky enough to be in an industry that didn't have a heavy, negative economic downturn.

+ +

At the beginning of this year, my team began a few new cutting-edge projects and ended the year shipping v1's on all of them ๐Ÿ™Œ! While starting the year off with a handful of high-impact projects, we also fulfilled the need to scale the size of our team by 1.5x. We spent a ton of time: +- Learning how digital tools can help bridge gaps between homeowners and contractors +- Understanding a user's relationship between an app and a hardware products +- Seeing how we can better inform/teach users on how to install home hardware products in the post-COVID-19 DIY age.

+ +

I chose not to freelance too much this year, but I did choose to help out designing for a company called SoleVenture. Learning how to ship a cross-platform product as well as find where freelancers see value in their benefits was eye-opening.

+ +

Portfolio and Blog

+ +

In late 2019, I began rebuilding my portfolio with React/Gatsby and was able to get it live in February - continuing to make small edits throughout the year!. Looking back on some of the quirks with Gatsby, I probably should have built it using Next.js from the start - but it's been a great learning experience!

+ +

At the beginning of this year, I wanted to start writing and sharing the things I'm working on in the open - so I built and iterated on this site. In fact, this site is not only a space for my writing, but also a place where I can experiment on new ideas I have: Theme Creator, Next.js, design system stuff, etc.

+ +

Using something like Next.js helps me iterate and deliver ideas more quickly and flexibly - easy-to-use, quick build times, package support, etc.

+ +

Side Projects

+ +

I spent a bit of time rebuilding Slack Themes this year and had a ton of fun implementing a few new, fun things into the updated version (Firebase, theme submission, Theme Admin, etc.). In fact, another designer jumped on board the project too. We've got some things cooking for this project this year!

+ +

The abrupt nature of this year caused a bit of disruption in how designers in the area could connect with each other - as well as how new designers in the area could connect with one of the many local design organizations. To help consolidate that information into a single space, I threw together TampaBay.design - designers could join the different Slack communities and see which virtual events were being hosted by the local design orgs.

+ +

I'll be iterating on these project alot more through 2021, and possibly starting a few others ๐Ÿ˜‰.

+ +

Learning

+ +

Every year I tell myself I'm going to learn something new to aid in my design skills. However, this year I couldn't use my usual excuse of not having the time ๐Ÿคฆโ€โ™€๏ธ. One of the benefits of 2020 was that I stayed at my desk a bit more, going through: +- 100 days of SwiftUI +- Learning Firebase +- Building a few Figma plugins to help with our design workflow at work +- Picking up a bit of 3D modeling with Blender and Spline. +- (Re)learning bits of Python

+ +

Some of these new learnings are leaking their way into some of my recent designs, but I fully expect the rest to make an entrance in 2021!

+ +

Extracurricular

+ +

Beside design/work stuff, I spent a ton of time picking up a few new things to help me spend my time more meaningfully, or even to help me "slow down and smell the roses". I picked up my dusty acoustic guitar more than I had in the past few years, and subsequently fell in love learning a bit of practical music theory.

+ +

One thing I never thought I would start is going to racing school ๐Ÿš—. Growing up as an avid Gran Turismo fan, the itch to get behind a steering wheel of a car on the track never seemed real. I had no idea what to expect, but I signed up for a 3-day racing school in January and it was more thrilling than I could've hoped. While I'm no Alain Prost in a Miata, it transformed the way I drive and helped me think about something as high-octane as racing in a more strategic way.

+ +

While stuck at home at the beginning of the year, I realized I could explore my locale further if I had a bike - so I went full-purist and picked up a fixed-gear bike. After doing of bit info digging before-hand, I thought to dive down the fixie route due to the DIY nature of it all - it's been great and I've been tracking my rides around town through Strava!

+ +

I also spent a bunch of time walking around town and listening to a ton of podcasts - while journeying from point A to point B. My podcast genres run fairly wide - from the music industry, to news, to design, to healthcare, etc. If you're curious, some of my favorite podcasts were: +- Marketplace +- Indie Hackers +- Ted Talks Daily +- NPR's Throughline +- Wall Street Journal +- Tradeoffs +- and a bunch more - ping me in the form below if you'd like to get the full list!

+ +

2020 also bit me with the coffee bug. Before this past year, I would drink 3 - 5 cups of coffee from wherever, keurig machines, coffee shops, the beat-up coffee machine. Around March, I decided to level-up my coffee game - you know the drill, chemex, gooseneck kettle, coffee grinder, local-roasted beans, etc. Who knows what 2021 has in store for my coffee addiction? Espresso machine โ˜•๏ธ?

+ +

So drinking more coffee, going on long bike rides, learning how to race cars - a strange way to sum up a few new hobbies I picked up in 2020.

+ +

What to expect for 2021

+ +

This year was a whirlwind for everyone of us, in many different ways - from working/learning from home to personal relationships, we all spent some much needed time recalibrating.

+ +

In 2021, I want to focus on adding more interactivity to this site, my portfolio, and my side projects. Having others interact with the content is way more interesting and meaningful than throwing together a simple, static website.

+ +

I also want to write (alot) more than I did this past year. Starting off with this post, I hope to increase my cadence to ~1 post week, and see which content connects.

+ +

As far as extracurricular stuff, I'll probably continue all of the awesome things I picked up in 2020. On top of that, I've been thinking about learning how to ride a motorcycle and possibly learning how to modify a cheap project bike!

+ +

I hope your 2020 was meaningful and that you have a great 2021 ๐ŸŽ‰!

]]>
+
+ + <![CDATA[Portfolio Redesign]]> + https://notes.ryanparag.com/notes/portfolio-redesign + Sat, 08 Aug 2020 00:00:00 GMT + +

A few months back, I began redesigning my portfolio - again ๐Ÿคฆโ€โ™€๏ธ. I not only wanted to add a couple of the newer projects I was working on, but I also wanted to address larger design issues and experiment with building it in some newer technologies.

+ +

Although the overall design might seem unchanged at a glance, I implemented/changed a ton of details in the design and under-the-hood.

+ +

Tools

+ +

If you're setting out to (re)build your portfolio, there are a few routes/tools you can choose from: +- Webflow: low-code / super popular +- Squarespace: WYSIWYG / easy-to-use / no-code +- Carrd: Simple / cheap / no-code +- Semplice: no-code / WYSIWYG +- Adobe Portfolio: free with Creative Suite / no code +- Code it yourself: HTML, CSS, JS / Jekyll / Gatsby / Next / etc.

+ +

I'm probably forgetting some, but I think that should cover what most designers use to build their portfolios.

+ +
+ +

I chose to code my previous portfolio. I also chose to code my new portfolio, but with newer technologies.

+ +

Old Portfolio

+ +

๐Ÿ‘†My old portfolio (~2016). Previously, I coded my portfolio using: +- Sketch for ideating/designing +- Pug +- SCSS +- JS, Jquery +- Drag-and-drop FTP for deploying

+ +
+ +

New Portfolio

+ +

๐Ÿ‘†My new portfolio โœจ. This time, I chose some more modern technologies: +- Figma for ideating/designing +- React and Gatsby +- Styled Components +- MDX for markdown-ish pages +- Vercel for deploying

+ +
+ +

Typography

+ +

Previously, I was using:

+ +

IBM Plex Sans: a tall x-height sans-serif that comes in a wide range of weights

+ +

Old Typography

+ +
+ +

I switched to:

+ +

Inter: an open-source sans-serif made for more-legible UI

+ +

New Typography

+ +
+ +

Why did I choose to change the typographic style?

+ +

Because ๐Ÿคทโ€โ™€๏ธ- but really, I wanted to implement a bit more minimalism and not have the typography intrude on the designs I would be showcasing.

+ +
+ +

Colors and Dark Mode

+ +

I love when I see websites/apps give me the option to choose to use dark/night modes. When I was building out my previous portfolio, I really wanted to implement the feature and give viewers the option to choose which to use.

+ +

Old: +Old Theme

+ +

`css +$grey-900: hsla(220, 24%, 7%, 1); +$grey-800: hsla(220, 21%, 13%, 1); +$grey-700: hsla(220, 18%, 21%, 1); +$grey-600: hsla(220, 15%, 29%, 1); +$grey-500: hsla(220, 12%, 37%, 1); +$grey-400: hsla(220, 9%, 68%, 1); +$grey-300: hsla(220, 6%, 76%, 1); +$grey-200: hsla(220, 3%, 91%, 1); +$grey-100: hsla(0, 0%, 96%, 1);

+ +

$color-green: #00d1b2; +$color-blue: #79cbca; +$color-pink: #e684ae; +`

+ +
+ +

New: +New Theme

+ +

js +colors: { + base: { + grey900: 'hsla(220, 24%, 7%, 1)', + grey800: 'hsla(220, 21%, 13%, 1)', + grey700: 'hsla(220, 18%, 21%, 1)', + grey600: 'hsla(220, 15%, 29%, 1)', + grey500: 'hsla(220, 12%, 37%, 1)', + grey400: 'hsla(220, 9%, 68%, 1)', + grey300: 'hsla(220, 6%, 76%, 1)', + grey200: 'hsla(220, 3%, 91%, 1)', + grey100: 'hsla(0, 0%, 96%, 1)', + grey0: 'hsla(0, 0%, 100%, 1)', + }, + states: { + green: 'hsla(171, 100%, 41%, 1)', + blue: 'hsla(179, 44%, 64%, 1)', + pink: 'hsla(334, 66%, 71%, 1)', + greenTransparent: 'hsla(171, 100%, 41%, .2)', + blueTransparent: 'hsla(179, 44%, 64%, .2)', + pinkTransparent: 'hsla(334, 66%, 71%, .2)', + visited: 'hsla(334, 86%, 43%, 1)', + greenDark: 'hsla(171, 100%, 35%, 1)', + blueDark: 'hsla(179, 44%, 40%, 1)', + } + } +

+ +
+ +

Things I changed in the color theme: +- Higher-contrast +- Transparency and variants

+ +
+ +

I also chose to change the UI for the toggle itself.

+ +

Could I make the theme toggle a simpler design?

+ +

Old: +Old Toggle

+ +
+ +

New: +New Toggle

+ +
+ +

About Page

+ +

I wanted to make the about page focus on the content more. Here are the things that changed:

+ +
  • Condensed the grid
  • Focus on the content by building hierarchies
  • Add in Spotify items through Spotify's API
  • Add things I'm currently enjoying
  • Reduced number of social media/contact items
+ +

About page

+ +
+ +

Auth and Private Projects

+ +

Alot of tools (Webflow, Squarespace) let designers password-protect projects using their CMS platform. Even previously, I needed to figure out a way to simply password protect selected projects.

+ +

Using a little JavaScript, I could hide routes and show users a password-protect screen on projects that required authorization. I also chose to add in some custom lettering for a bit of flourish when users came upon this page:

+ +

Old: +Old Auth

+ +
+ +

New: +New Auth

+ +
+ +

One of the most annoying things when looking at portfolios with password-protection is the fact that users must repeatedly input a password when entering password-protected projects.

+ +

What if I could make it so users only have to input the password once and saved a logged-in state?

+ +

In my new portfolio I have it so once users enter the password once, they no longer have to input a password again. A few other navigation items become available, as well as a nice little "+" next to the logo.

+ +
+ +

Pages

+ +

Layout was all over the place in my old portfolio. I wanted to change it so the layout was more Medium-like (single column) and was a less sporadic while scrolling through. On top of changing the page headers, I changed it so that each case study had defined sections:

+ +

New Auth

+ +
+ +

Conclusion

+ +

Hopefully some of that helps! I'm still making incremental changes, but if you have feedback or need help with your own portfolio, ping me using the form below.

]]>
+
+ + <![CDATA[Scaling Engagement and Interactivity]]> + https://notes.ryanparag.com/notes/scaling-engagement-and-interactivity + Wed, 27 Jan 2021 00:00:00 GMT + +

TLDR - check out the new site

+ +

One of the first posts I wrote about on this blog was about a community aggregate site for designers in the Tampa Bay area.

+ +

I quickly spun up a fairly static site using Next.js and a bunch of custom styles, linking to Google Forms to collect any info or updates.

+ +

Since the launch in Sprint 2020 โ†’ Jan 2021, I've had over 400 visitors and 5,000 sessions. While there aren't a ton of designers in the area, I wanted to increase engagement between each other, rather than exponentiate on the number of visitors.

+ +

My goal for the TampaBay.design is:

+ +

Increase engagement between the designers in the area and provide a simple, aggregate method to find resources, local-ish job openings, interviews, and other designers.

+ +

Here's what it looked like before:

+ +

The old TampaBay.design

+ +

Although the site was informative for finding groups in the area, it wasn't solving the problem designers (new and old) around the area were still experiencing.

+ +
  • Where can I find designers in the area?
  • What jobs are open for designers in Florida?
  • What resources are other local designers finding helpful?
  • Which design events are happening around me?
+ +

Separating Data from Design

+ +

Before, I simply listed organizations and slack groups statically - as well as linking to a few Google Forms for receving input for new events, slack groups, or organizations.

+ +

Why don't I just throw all of the data in Airtable?

+ +

I was using Airtable to list events and thought to add all of this data (and more) in Airtable rather than render statically.

+ +

Airtable data

+ +

I created an Airtable base and added a few different tables to store the data: +- events +- slack groups +- organizations +- designers +- and a few more

+ +

I was also really enjoying using TailwindCSS on another project and thought to rebuild this site with it as well.

+ +

To design/build the new site, we're going to use: +- Figma for ideating +- Next.js for the front-end +- TailwindCSS for styles +- Feather for icons +- Framer Motion for sweet animations +- Airtable to store data +- Netlify to deploy

+ +

Let's take a look at the new site

+ +

The new TampaBay.design

+ +

So we rebuilt the site, and to help increase regular engagement, we added more contextually informative sections:

+ +
  • Submit your portfolio if you're looking for a job?
  • Are you looking to hire a designer?
  • Read our weekly newsletter for new design resources!
+ +

I also added a light and dark mode, based of the user's OS theme.

+ +

Visually, I opted to create starker color contrast and spacing to allow for more breathing room for the increase in content.

+ +

On-site engagement

+ +

To help make the information updates more seamless, I converted the Google Form links to interactive forms that directly connected to Airtable:

+ +

Event Form

+ +

Using the same method, I wanted to provide a way for those on the job hunt to be added to a list and update their job search status:

+ +

Job Form

+ +

There's a ton more to go over, but rather than go through the detail of every change, check out the new site!

+ +

Hopefully that was helpful in case you want to do the same for your community, or even if you want to help contribute on this website. I'll keep updating this site with more features that could be helpful to other designers in the area, but if you have an idea, ping me using the form below - I'd love to hear about your ideas.

]]>
+
+ + <![CDATA[Showing my listening activity]]> + https://notes.ryanparag.com/notes/showing-my-listening-activity + Mon, 01 Mar 2021 00:00:00 GMT + +

Spotify icon can be found in my Figma Community file (as well as a bunch of other icons)

+ +
+ +

TLDR: +Grab your music and podcast info from Spotify using Next.js +- Lee Robinson's Spotify & Next.js tutorial ๐Ÿ‘ +- View this site's GitHub Repo to see how I did it ๐Ÿ‘จโ€๐Ÿ’ป +- View what I designed/built ๐ŸŽ‰

+ +
+ +

Sticking to this site's ethos - designing in the open - I thought I could share a few new things that I've been finding a ton of fun.

+ +

Either while I'm driving, walking, sitting at my desk, or falling asleep, I'm regularly listening to a song or podcast via Spotify. Using their Web API, you're able to access: +- Music (playlists, artists, songs, albums, etc.) +- Podcasts (episodes, shows) +- User metrics (currently listening, top listens) +- ...and probably a ton more

+ +

How to build

+ +

After a quick search, I came across Lee Robinson's Spotify & Next.js tutorial. Using the Spotify Web API and Next.js v10+, I threw together a way to grab my own Spotify data and pull it into this site.

+ +

Lee's tutorial is incredibly helpful in getting Spotify setup to show your own "Top Tracks", but I also wanted to have a way to show: +- if I'm currently listening to something +- if I'm my currently playing item is a podcast or song +- my last played song +- my recently subscribed podcasts

+ +

I also wanted to design this in a way that was compact and not overly informative. Let's start with a component that let's me see if I'm currently listening to something, and whether that item is a song or podcast. We'll need 2 endpoints for this:

+ +

`js +// spotify.js

+ +

const NOWPLAYINGENDPOINT = https://api.spotify.com/v1/me/player/currently-playing; +const NOWPLAYINGPODCAST_ENDPOINT = https://api.spotify.com/v1/me/player/currently-playing/?additional_types=episode; +`

+ +

If you've followed Lee's tutorial and completed the token & auth setup, we just have to add 2 more functions in order to fetch these endpoints:

+ +

`js +// spotify.js

+ +

export const getNowPlaying = async () => { + const { access_token } = await getAccessToken();

+ +

return fetch(NOWPLAYINGENDPOINT, { + headers: { + Authorization: Bearer ${access_token} + } + }); +};

+ +

export const getPodcastPlaying = async () => { + const { access_token } = await getAccessToken();

+ +

return fetch(NOWPLAYINGPODCAST_ENDPOINT, { + headers: { + Authorization: Bearer ${access_token}, + } + }); +}; +`

+ +

After that, we just need two routes to grab's playing. I kept the song and podcast routes separate in case I need them later. Using the route for grabbing the currently playing song as an example, we can import one of our new functions to grab the response from Spotify:

+ +

`js +// /api/spotify/now-playing.js

+ +

import { getNowPlaying } from '@utils/spotify';

+ +

export default async (_, res) => { + const response = await getNowPlaying();

+ +

if (response.status === 204 || response.status > 400 || response.status === 500) { + return res.status(200).json({ isPlaying: false, playing: null }); + }

+ +

const song = await response.json();

+ +

if(song.context === null) { + return res.status(200).json({ isPlaying: false, playing: 'podcast' }); + }

+ +

const isPlaying = song.isplaying; + const title = song.item.name; + const artist = song.item.artists.map((artist) => artist.name).join(', '); + const album = song.item.album.name; + const albumImageUrl = song.item.album.images[0].url; + const songUrl = song.item.externalurls.spotify;

+ +

res.setHeader( + 'Cache-Control', + 'public, s-maxage=60, stale-while-revalidate=30' + );

+ +

return res.status(200).json({ + album, + albumImageUrl, + artist, + isPlaying, + songUrl, + title + }); +}; +`

+ +

We would repeat the same function for the currently playing podcast - making slight changes in the object we send back and the endpoint function we're referencing. The above function accounts for 3 scenarios: +1. If a song is playing +2. If a song isn't playing, but a podcast is playing +3. If neither a song nor podcast is playing

+ +

Let's see how we can design a single component to account for all 3 of these states:

+ +

component states

+ +

This component can account for each of the scenarios our API response may give us - helping us only surface the correct information in a way that is a bit more seamless to the user.

+ +

What's next?

+ +

podcast subscriptions

I've been having some fun grabbing my recent top tracks and my recent podcast subscriptions - check it out! If you have feedback or ideas of what else could be a fun way to make this information more transparent and tangible, I'd love to know - let me know using the form below.

]]>