<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The Productive Dev]]></title><description><![CDATA[Welcome to my blog! I'm a Front-End Developer with a passion for learning! I write about Programming and Productivity Tips.]]></description><link>https://blog.alyssaholland.me</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 23:46:37 GMT</lastBuildDate><atom:link href="https://blog.alyssaholland.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Database GUI's to Boost Development Productivity]]></title><description><![CDATA[Although my experience is primarily with front-end development, my current job is affording me the opportunity to do more full-stack work. Naturally, this meant that I needed to explore the different database GUIs available on the market.
Here is a l...]]></description><link>https://blog.alyssaholland.me/database-guis-to-boost-development-productivity</link><guid isPermaLink="true">https://blog.alyssaholland.me/database-guis-to-boost-development-productivity</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Mon, 24 Nov 2025 15:00:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764597014420/db1a787d-fe36-4006-9ea0-11a3a5032780.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Although my experience is primarily with front-end development, my current job is affording me the opportunity to do more full-stack work. Naturally, this meant that I needed to explore the different database GUIs available on the market.</p>
<p>Here is a list of the top 3 database GUIs I found, along with some honorable mentions.</p>
<h2 id="heading-1-tableplus">1) TablePlus 🐘</h2>
<p>TablePlus is a modern, native, and friendly GUI tool for relational databases. It supports a myriad of different database connections, so it’s bound to work with whatever database you need. It includes features such as advanced filtering, the ability to export and import data, and a smart query filter, just to name a few.</p>
<p>Transparently, this is the database GUI that I choose to use, and I’ve found it to be very intuitive and easy to navigate. It also has a sleek design, that makes the app feel native and premium.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763861580398/d56e828a-7ff7-4d6b-8864-a1a329891238.jpeg" alt="Viewing a database table in TablePlus" class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://tableplus.com/">Check out TablePlus</a></p>
<h2 id="heading-2-dbeaver">2) DBeaver 🦫</h2>
<p>DBeaver is a free and open-source database management tool that allows you to manage and explore SQL databases like MySQL, PostgreSQL, SQLite, and more. "DBeaver Community" is the open-source and free version of the software, but there are also paid options available for enterprises and teams.</p>
<p>DBeaver allows you to manage data like a spreadsheet, create analytical reports, and export information. It also offers advanced features like a powerful SQL editor, administration tools, data and schema migration, and session monitoring.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763861883116/2f00f794-1080-4879-a84e-c9d2cbe94b6c.png" alt="Viewing a database table in DBeaver" class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://dbeaver.io/">Check out DBeaver</a></p>
<h2 id="heading-3-beekeeper-studio">3) Beekeeper Studio 🐝</h2>
<p>Beekeeper Studio touts itself as “<em>The SQL Editor and Database Manager Of Your Dreams”</em> and offers a polished interface and supports a wide range of SQL databases. Similar to DBeaver, it is available as a free and open-source community edition, with additional paid options for enhanced features.</p>
<p>Beekeeper Studio features an easy-to-use interface and includes tools like a powerful SQL editor with syntax highlighting. It allows you to write SQL queries with AI assistance and sync online and collaborate work across multiple devices.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763861643682/e5732b76-aeb7-479d-8d95-39f6724c3c1e.png" alt="Viewing a database table in Beekeeper Studio" class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://www.beekeeperstudio.io/">Check out Beekeeper Studio</a></p>
<h2 id="heading-honorable-mentions">Honorable Mentions</h2>
<p>Although the following options are not specifically designed as dedicated database GUI’s, they serve as alternatives for those who utilize these ORMs or prefer having a GUI integrated directly within VSCode. These tools can be particularly useful for developers who want to manage databases without leaving their coding environment.</p>
<h3 id="heading-1-prisma-and-drizzle-studio">1) Prisma and Drizzle Studio</h3>
<p>Prisma and Drizzle are two of the most popular ORMs in the JS/TS ecosystem. Both projects offer a "Studio," which is a ready-to-use solution that lets you easily interact with your databases through a clean interface. These tools read the respective config files, connect to your database, and allow you to perform actions based on your existing schemas.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763873320178/5c7ac963-1891-4d91-abdc-d4b5bba3f063.webp" alt="Viewing a database table in Drizzle." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://www.prisma.io/studio">Check out Prisma Studio</a> | <a target="_blank" href="https://orm.drizzle.team/drizzle-studio/overview">Check out Drizzle Studio</a></p>
<h3 id="heading-2-sqlite-viewer-for-vs-code">2) SQLite Viewer for VS Code</h3>
<p>As the name implies, SQLite Viewer is a quick and easy way to view your SQLite database inside VS Code. It has over 2.5 million downloads and integrates seamlessly with VSCode. Simply click on a <code>.sqlite</code> or <code>.db</code> file and the custom viewer opens.</p>
<p>In addition to the VSCode extensions, there is <a target="_blank" href="http://sqliteviewer.app">SQLite Viewer Web</a>, a free online tool that lets you open any SQLite file instantly in your browser. No installation or signup is required, and all data stays in your browser, remaining fully private.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763873203868/b64c9048-b7d4-4e63-8915-7185ad941a1d.png" alt="Viewing a database table in SQLite Viewer." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer">Check out SQLite Viewer for VS Code</a></p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Thanks for reading and I hope you found a new database GUI to add to your developer toolbox.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/0jX-VnFX5gE?si=k3wkj4Y-3e_r0-Tr">https://youtu.be/0jX-VnFX5gE?si=k3wkj4Y-3e_r0-Tr</a></div>
]]></content:encoded></item><item><title><![CDATA[How to Setup a Global gitignore File]]></title><description><![CDATA[A Note on the Series:
Fast Fridays 🏎 is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays.

In this Fast Friday tip, I'll explain how to crea...]]></description><link>https://blog.alyssaholland.me/how-to-setup-a-global-gitignore-file</link><guid isPermaLink="true">https://blog.alyssaholland.me/how-to-setup-a-global-gitignore-file</guid><category><![CDATA[Git]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 03 Oct 2025 11:00:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759374282345/4777a4ea-2983-471b-83b3-bfca1a6e6f8c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>A Note on the Series:</p>
<p>Fast Fridays 🏎 is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays.</p>
</blockquote>
<p>In this Fast Friday tip, I'll explain how to create a global <a target="_blank" href="https://git-scm.com/docs/gitignore">gitignore</a> file, showing you just how quick and easy it is to set up.</p>
<h2 id="heading-step-1-create-the-gitignore-file-and-add-content">Step 1: Create the gitignore file and add content</h2>
<p>This step is pretty obvious, but the first thing to do is create a file to store the global gitignore settings. You can name this file whatever you want, but a common practice is to name it <code>.gitignore_global</code>.</p>
<pre><code class="lang-bash">vim ~/.gitignore_global
</code></pre>
<p>Once the <code>.gitignore_global</code> file is created, you can add any files you want to be globally ignored. For example, I've been testing some AI tools that generate markdown files after analyzing your codebase, so I have a <code>GEMINI.md</code> file in my global ignore file.</p>
<h2 id="heading-step-2-configure-git-to-use-the-global-gitignore">Step 2: <strong>Configure Git to use the global gitignore</strong></h2>
<p>Now that we have a global gitignore file created, we need to tell Git where to find it. This can be set up in two different ways:</p>
<ol>
<li><p>Using the <a target="_blank" href="https://git-scm.com/docs/git-config"><code>git config</code></a> command.</p>
</li>
<li><p>Directly editing the git config files in your <code>$HOME/.gitconfig</code> file.</p>
</li>
</ol>
<p>I would recommend going with option #1. I find this to be the fastest way to set things up so that will be the method I discuss. However, feel free to do whatever works best for you.</p>
<p>Here is the command to configure Git to leverage the new ignore file:</p>
<pre><code class="lang-bash">git config --global core.excludesfile ~/.gitignore_global
</code></pre>
<h3 id="heading-verify-the-configuration"><strong>Verify the configuration</strong></h3>
<p>This is an optional step, but if you wanted to verify that the Git is properly referencing the new global ignore file that you just configured, you can run the following command:</p>
<pre><code class="lang-bash">git config --global core.excludesfile
</code></pre>
<p>If you see the file name outputted, then you know everything is set up correctly. On the other hand, if the output is empty, it means the setup wasn't successful. Of course another way to verify is to check that the files you added to your global ignore are no longer present, but this command is a convenient way to double-check.</p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Voilà! That's all you need to do. Just two simple steps to set up your global gitignore. Now, files will be properly ignored in any project you work on.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[7 Git GUI's to Boost Development Productivity]]></title><description><![CDATA[Over the past year, I’ve started incorporating a Git GUI into my workflow. I still reach for the terminal about 90% of the time, but I’ve found that a GUI can really shine when it comes to things like viewing diffs, checking stashes, or resolving mer...]]></description><link>https://blog.alyssaholland.me/7-git-guis-to-boost-development-productivity</link><guid isPermaLink="true">https://blog.alyssaholland.me/7-git-guis-to-boost-development-productivity</guid><category><![CDATA[Git]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 01 Aug 2025 03:27:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/4868b98eba6385b38e68efb5c50f87d2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past year, I’ve started incorporating a Git GUI into my workflow. I still reach for the terminal about 90% of the time, but I’ve found that a GUI can really shine when it comes to things like viewing diffs, checking stashes, or resolving merge conflicts. I heard someone describe their approach along the lines of <em>“using the terminal for running commands, and the GUI for viewing the status of things“</em> and that really resonated with me and how I approach using this tool.</p>
<p>This list includes the options I considered when choosing a git client. If you want to see a complete list of options, visit the <a target="_blank" href="https://git-scm.com/downloads/guis">official Git website</a>. With that said, here are a few Git GUI clients to explore.</p>
<h2 id="heading-1-fork">1) Fork 🍴</h2>
<p>Fork is a fast and friendly git client that offers a comprehensive range of features. Despite its lightweight design, it includes all the essential functionalities you would expect from a more extensive application. It supports advanced features like interactive rebase, merge conflict resolution, and a built-in merge tool, making it suitable for both beginners and experienced engineers. This is the git client that I personally use, and I have found it to be a helpful addition to my workflow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754012249215/97f541f0-f1e1-4628-b078-d89392084908.jpeg" alt="Fork GUI displaying an individual commit and its metadata." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://fork.dev/">Check out Fork</a></p>
<h2 id="heading-2-gitkraken">2) GitKraken 🦑</h2>
<p>GitKraken is a popular choice among developers and teams globally, known for its intuitive GUI and powerful terminal. It offers cross-platform support for Windows, Mac, and Linux, making it accessible to a wide range of users. By transforming the complex web of Git commands into a clear, navigable map, GitKraken simplifies version control, making it easier to track changes and understand the impact of each commit. Its extensive list of features and visual approach to project history have earned it a well-deserved reputation in the developer community.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754013643093/bc6950a9-002d-4d88-9432-0b4a84569da5.png" alt="Visualizing the commit history in the GitKraken Desktop app." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://www.gitkraken.com/git-client">Check out GitKraken</a></p>
<h2 id="heading-3-sourcetree">3) Sourcetree 🪾</h2>
<p>Sourcetree, created by the team at Atlassian, is a free Git client for Windows and Mac. It makes interacting with your Git repositories easier, allowing you to focus on coding and boost your productivity. With Sourcetree's simple Git GUI, you can visualize and manage your repositories, enabling you to commit with confidence.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754014096577/b606b6c9-e398-4383-90a5-91e14ce52d7e.png" alt="Sourcetree GUI showing a repository's commit history with details like commit hashes, authors, messages, branches, and dates. The graphical log on the left visualizes branch structures and merges." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://www.sourcetreeapp.com/">Check out Sourcetree</a></p>
<h2 id="heading-4-sublime-merge">4) Sublime Merge 🔀</h2>
<p>Sublime Merge is a fast, Git-native GUI client from the makers of Sublime Text. It features line-by-line staging, powerful search across commits and file history, and a merge tool that makes resolving conflicts straightforward. With syntax highlighting, a command palette, and the ability to see the exact Git commands being run, it bridges the gap between the command line and a modern GUI. Sublime Merge is a flexible and efficient choice for developers who want full control without sacrificing performance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754014393246/b71cc74a-059e-43c1-adba-f697bcf792c2.png" alt="Sublime Merge GUI showing a merge conflict." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://www.sublimemerge.com/">Check out Sublime Merge</a></p>
<h2 id="heading-5-sourcegit">5) SourceGit ♨️</h2>
<p>SourceGit is an open-source client that combines many features into a cross-platform interface. Available for Windows, macOS, and Linux, it supports a variety of Git actions, from basic operations like pull and push to advanced tools like interactive rebasing, bisect, and GitFlow. It offers a customizable theme system, multilingual support, a visual commit graph, and useful extras like image diffs and issue linking.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754014849591/fe656ef9-1fb6-4fd0-b3d5-f5354307fcbe.png" alt class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://sourcegit-scm.github.io/">Check out SourceGit</a></p>
<h2 id="heading-6-gitfox">6) Gitfox 🦊</h2>
<p>Gitfox touts itself as “the foxy Git Client for Mac” that helps you commit faster and improve your code quality with superior diffs. It’s a native app and some of its key features include the ability to compare anything, advanced diffs that show inline differences, git blame, and the ability to view diffs across images.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754015050735/44013dbf-599a-47f9-8691-a64cc0beeff7.png" alt="Gitfox GUI showing how you can compare any pair of branches, tags or commits." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://www.gitfox.app/">Check out Gitfox</a></p>
<h2 id="heading-7-gitnuro">7) Gitnuro 🥷🏽</h2>
<p>Gitnuro is a free and open-source (FOSS) multi-platform Git client designed to provide the best experience for both beginners and professionals. It prioritizes privacy, works quickly even with large repositories, and is customizable, allowing you to create your own theme and color palette. Like many other options on this list, it offers a wide range of features.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754015471073/5803a466-598f-4723-826d-6314c88f5ae7.png" alt="Gitnuro interface displaying a list of commits on several branches." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://gitnuro.com/">Check out Gitnuro</a></p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Thanks for reading and I hope you found a new Git GUI client to add to your developer toolbox.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Rurll6TuWm8?si=o2pzAFSe5sBw5itV">https://youtu.be/Rurll6TuWm8?si=o2pzAFSe5sBw5itV</a></div>
]]></content:encoded></item><item><title><![CDATA[Git Worktree]]></title><description><![CDATA[Introduction
Git worktree is a niche topic that I think doesn’t get enough attention. At first, the concept may seem a bit unnecessary, but once you find a great use case for it, it can be an immense improvement to your existing workflow. In this art...]]></description><link>https://blog.alyssaholland.me/git-worktree</link><guid isPermaLink="true">https://blog.alyssaholland.me/git-worktree</guid><category><![CDATA[Git]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Mon, 07 Jul 2025 11:00:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751835751257/c59da696-a8cc-4b00-bdd1-e21a80c8812d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>Git worktree is a niche topic that I think doesn’t get enough attention. At first, the concept <em>may</em> seem a bit unnecessary, but once you find a great use case for it, it can be an immense improvement to your existing workflow. In this article, you’ll learn what a working tree is, some common commands, and the benefits of using worktrees.</p>
<h2 id="heading-definition"><strong>Definition</strong></h2>
<p>The <code>git worktree</code> command allows you to manage and check out multiple “<strong>working trees</strong>” at the same time. A <strong>working tree</strong> in Git is essentially a directory that contains a checked-out version of your project files. When you clone a repository, Git sets up a working tree that holds the latest version of all files from that repository. As you make changes, these updates remain in your working tree until you choose to commit them.</p>
<p>Each working tree is linked to a specific branch in your repository, allowing you to work on different features or fixes simultaneously without interfering with each other. This means you can have separate environments for different branches, making it easier to manage multiple tasks at once.</p>
<blockquote>
<p><strong>TL;DR:</strong> The <code>git worktree</code> command allows you to checkout multiple branches at the same time.</p>
</blockquote>
<h2 id="heading-use-case-pr-reviews"><strong>Use case: PR reviews</strong></h2>
<p>I started using worktrees when reviewing pull requests (PRs) for team members. Often, I am working on a feature branch, but I need to pause to review a team member's PR. The typical process is to stash any changes I have, check out the PR branch, review the changes, then switch back to my feature branch and <code>apply</code> or <code>pop</code> the stashed changes. This often delays my PR reviews because I don't want to disrupt my current workspace. However, since I started using worktrees, I can review PRs much faster by checking them out into a separate worktree that doesn't interfere with my ongoing work. This change has reduced disruptions to my workflow and allows me to work more efficiently as both a reviewer and an individual contributor.</p>
<h2 id="heading-basic-commands"><strong>Basic Commands</strong></h2>
<p>Now that you have a better idea of what a worktree is and why you might want to use it, let’s go over some common commands.</p>
<ol>
<li><h3 id="heading-git-worktree-list"><code>git worktree list</code></h3>
</li>
</ol>
<p>This command outputs the details of each worktree. The main worktree is listed first, followed by each of the linked worktrees.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750884965240/ed9898cf-7b75-456c-8801-dd011942dbfb.png" alt="Terminal output of the  command." class="image--center mx-auto" /></p>
<ol start="2">
<li><h3 id="heading-git-worktree-add"><code>git worktree add &lt;path&gt;/&lt;branch-name&gt; &lt;remote-branch&gt;</code></h3>
</li>
</ol>
<blockquote>
<p>📌 <strong>Note:</strong> It is important to run this command from the <em>root of the cloned repo</em>.</p>
</blockquote>
<p>For example, let’s say that you cloned this <a target="_blank" href="https://github.com/Cool-Runningz/doggy-directory#">doggy-directory repo</a>. After cloning, you would <code>cd</code> into the <code>doggy-directory</code> folder (or w/e you called it when you cloned it). Essentially, you are running the command in the main repo so that git knows what worktrees are available for you to add in the specified location.</p>
<p>Now if you were to run the command <code>git worktree add ../worktrees/a11y-issues a11y-issues</code> it would create a new worktree for the remote <code>a11y-issues</code> branch in the specified <code>../worktrees/a11y-issues</code> directory allowing you to have a separate working environment for that branch.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750885521605/944b82a7-e3ad-40bb-a134-9846905ca2d6.png" alt="Terminal output of the  command." class="image--center mx-auto" /></p>
<ol start="3">
<li><h3 id="heading-git-worktree-remove"><code>git worktree remove &lt;worktree-path&gt;</code></h3>
</li>
</ol>
<p>As the name implies, this command is used to remove a worktree from the list of active worktrees. If Git encounters any issues while attempting to remove the worktree, you can use the <code>--force</code> flag to bypass these restrictions and ensure the worktree is forcibly removed.</p>
<ol start="4">
<li><h3 id="heading-git-worktree-prune"><code>git worktree prune</code></h3>
</li>
</ol>
<p>This command cleans up stale or orphaned worktrees that git still thinks exist but no longer do.</p>
<h2 id="heading-realistic-workflows">Realistic Workflows</h2>
<p>Worktrees are useful, but most of the time, you need additional environment variables and configurations for your project to run properly. While simple projects might work with worktrees out of the box, even moderately complex setups typically demand further customization. Although I can't address every project setup scenario, I'll share an example of how I automated this process for my job's setup.</p>
<h3 id="heading-automation-with-git-worktrees"><strong>Automation with Git Worktrees</strong></h3>
<p>To give you some background, the main repository at my job is a full-stack app using the Laravel PHP framework for the backend and React with Inertia for the frontend. For local development, we use Herd, a native Laravel and PHP environment for macOS.</p>
<p>For a new worktree environment to work properly, it requires:</p>
<ol>
<li><p>A copy of the <code>.env</code> file from the main repo</p>
</li>
<li><p>Updated environment variables based on the branch name</p>
</li>
<li><p>Frontend and backend dependencies installed</p>
</li>
<li><p>A new <a target="_blank" href="https://herd.laravel.com/docs/macos/getting-started/sites#sites">Herd site</a> set up to serve the app locally</p>
</li>
</ol>
<p>To avoid repeating these steps every time I review a pull request, I created two Bash scripts:</p>
<ol>
<li><p><code>setup-worktree.sh</code> to create the new worktree and configure the project requirements.</p>
</li>
<li><p><code>cleanup-worktree.sh</code> to remove the worktree when it's no longer needed.</p>
</li>
</ol>
<p>Now, whenever I need to review a PR quickly, I just run <code>./setup-worktree.sh &lt;feature-branch&gt;</code> and <code>./cleanup-worktree.sh &lt;feature-branch&gt;</code> to set up and take down the worktree.</p>
<p>I won't share the full script here, but the main idea is: <strong><em>identify the steps you do repeatedly and automate them in a script</em></strong> because worktrees work well with some shell scripting.</p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Git worktrees are a great way to manage multiple branches, making you more productive and smoothing out your workflow, especially when you're reviewing pull requests. By automating those repetitive setup tasks, you can make your development environment more streamlined and spend more time actually coding.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><p><a target="_blank" href="https://www.gitkraken.com/learn/git/git-worktree">GitKraken - How to use Git Worktree</a></p>
</li>
<li><p><a target="_blank" href="https://git-scm.com/docs/git-worktree">Git - git-worktree Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://graphite.dev/guides/git-working-tree">Graphite - What is a Git working tree?</a></p>
</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=9mTcpRmkTA4">https://www.youtube.com/watch?v=9mTcpRmkTA4</a></div>
]]></content:encoded></item><item><title><![CDATA[5 Speech-to-Text Apps to Boost Productivity]]></title><description><![CDATA[With all the AI tools popping up these days, I figured I'd try out some speech-to-text software in an attempt to boost my productivity. I've been hearing a lot about “developing at the speed of thought”, so here's a list of the top speech-to-text sof...]]></description><link>https://blog.alyssaholland.me/speech-to-text-apps-to-boost-productivity</link><guid isPermaLink="true">https://blog.alyssaholland.me/speech-to-text-apps-to-boost-productivity</guid><category><![CDATA[AI]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[#ai-tools]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 06 Jun 2025 02:00:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749170478767/2ea1c9ab-0f44-4449-b5f7-f9a86ad25c09.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With all the AI tools popping up these days, I figured I'd try out some speech-to-text software in an attempt to boost my productivity. I've been hearing a lot about “<em>developing at the speed of thought</em>”, so here's a list of the top speech-to-text software options I found on the market in 2025.</p>
<h2 id="heading-1-superwhisper">1) Superwhisper △</h2>
<p>Superwhisper is an AI-powered voice-to-text tool designed to significantly enhance your writing speed and efficiency. It boasts the ability to help you “<em>write 3x faster, without lifting a finger</em>” by converting your spoken words into written text swiftly and accurately. This tool is privacy-focused and ideal for power users who want full control and customization of their workflows.</p>
<p><em>Full transparency, Superwhisper is my voice dictation tool of choice. I started with a trial and ended up buying the lifetime version. Admittedly, the UI can be a bit unintuitive at first, but once you get the hang of things, it's really cool to see how much you can tailor Superwhisper to meet your needs. As a developer and tinkerer, I'm really enjoying the different workflows that I'm coming up with.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749163454566/cf85ca56-77e3-4d4c-b3ed-0e7c7215f22c.png" alt="Superwhisper logo. Below, the text reads, &quot;Write 3x faster, without lifting a finger. superwhisper AI powered voice to text.&quot;" class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://superwhisper.com/">Check out Superwhisper</a></p>
<h2 id="heading-2-wispr-flow">2) Wispr Flow 🔊</h2>
<p>Wispr Flow offers effortless voice dictation for every application, claiming it can help you write 4x faster than typing. Wispr Flow aims to reduce your typing by letting you speak naturally. It provides seamless speech-to-text functionality across all applications on your device. The AI automatically edits your speech instantly, turning rambling thoughts into clear and formatted text without typos or filler words.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749163961962/1a212b19-299a-49c6-ab1c-a5bc85f403bb.jpeg" alt="Flyer reads, &quot;Think it. Speak it. Send it.&quot; with the Flow logo above. On the right, there's a voice message transcription with a waveform." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://wisprflow.ai/">Check out Wispr Flow</a></p>
<h2 id="heading-3-macwhisper">3) MacWhisper 🎙️</h2>
<p>Known initially for its transcription features, MacWhisper is another good option for voice dictation. It allows you to quickly and easily turn audio files into text using OpenAI's advanced transcription technology, Whisper. Whether you're recording a meeting, lecture, or other important audio, MacWhisper transcribes your audio files into text quickly and accurately. It's also one of the more affordable options on this list.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749164316836/3e807e86-eec5-40a6-a6ed-a1f4e57ab52a.jpeg" alt="MacWhisper logo" class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://goodsnooze.gumroad.com/l/macwhisper">Check out MacWhisper</a></p>
<h2 id="heading-4-aiko">4) Aiko 🤖</h2>
<p>Aiko offers AI-powered audio transcription. The creator, <a target="_blank" href="https://sindresorhus.com/">Sindre Sorhus</a>, is a well-respected OSS developer, so you can trust that you're getting a well-made product. Although it has fewer features than some other options on this list, it provides high-quality on-device transcription, allowing you to easily convert speech to text from meetings, lectures, and more. The transcription is powered by OpenAI’s Whisper model running locally on your device.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749164917679/37b038d4-7512-477d-882b-3735a3d401b2.png" alt="Aiko logo - a smiling robot holding a piece of paper with binary code." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://sindresorhus.com/aiko">Check out Aiko</a></p>
<h2 id="heading-5-aqua-voice">5) Aqua Voice 💧</h2>
<p>Aqua allows you to type less and accomplish more by using advanced speech-to-text technology for everyday tasks. It seamlessly integrates with your existing workflows on apps, websites, and documents on both Windows and Mac. The text is automatically formatted to suit each specific application and document, making your work more efficient and streamlined.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749165337591/277a4bf3-1ea4-4d46-af9d-3851e97475c1.jpeg" alt="Blue spherical logo next to the word &quot;AQUA&quot; in bold letters." class="image--center mx-auto" /></p>
<p>🌐 <a target="_blank" href="https://withaqua.com/">Check out Aqua Voice</a></p>
<h2 id="heading-honorable-mentions">Honorable Mentions</h2>
<p>These next honorable mentions are great for those looking for AI speech tools designed specifically for bloggers and writers.</p>
<h3 id="heading-1-voicepal">1) Voicepal 👻</h3>
<p><a target="_blank" href="https://voicepal.me/">Voicepal</a> is like having a ghostwriter in your pocket, helping you generate ideas and go from concept to first draft in minutes. Created by popular productivity YouTuber Ali Abdaal, it claims users produce twice as much content in 70% less time. It aims to create a new way of writing that actually feels fun.</p>
<p>You can tap into your personalized shadow reader whenever you need guidance to explore your thoughts more deeply. Unlike general AI tools, Voicepal is trained to capture your tone and writing style, allowing you to easily turn your thoughts into first drafts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749166514140/934636f0-aa9b-46a3-9f18-a268bff5995e.png" alt="Voicepal logo" class="image--center mx-auto" /></p>
<h3 id="heading-2-blogrecorder">2) BlogRecorder 🎤</h3>
<p>I haven't personally used <a target="_blank" href="https://blogrecorder.com/">BlogRecorder</a>, but Kent C. Dodds, a respected figure in the tech community, mentioned it as a tool he uses, which piqued my interest. It aims to eliminate writer's block and encourages more frequent publishing. Audio is converted into structured blog posts that you can edit, export, and publish anywhere.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749167239492/4ba24d8b-aef5-40b6-9f3a-ab77d04fbcf8.png" alt="BlogRecorder logo" class="image--center mx-auto" /></p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>I hope you will find these voice dictation tools to be a helpful and productive addition to your workflow. While the speech-to-text market is flooded with options, I've highlighted just a subset of what's available.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[The AI Glossary for Humans: 30+ Essential Terms]]></title><description><![CDATA[Introduction
The AI field is buzzing with activity and it can be tough to keep track of all the acronyms and terms. This article will serve as a glossary of AI terms, acronyms and high-level definitions. I've also set up a GitHub repository, which I ...]]></description><link>https://blog.alyssaholland.me/ai-glossary</link><guid isPermaLink="true">https://blog.alyssaholland.me/ai-glossary</guid><category><![CDATA[AI]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Thu, 24 Apr 2025 03:37:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745204783133/1877f116-6f93-42e1-9cf0-a275a1940025.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>The AI field is buzzing with activity and it can be tough to keep track of all the acronyms and terms. This article will serve as a glossary of AI terms, acronyms and high-level definitions. I've also set up a <a target="_blank" href="https://github.com/Cool-Runningz/awesome-ai-glossary">GitHub repository</a>, which I will try to update regularly as the AI space evolves.</p>
<blockquote>
<p>Feel free to ⭐ star and contribute to the repo. PR’s are welcome!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/Cool-Runningz/awesome-ai-glossary">https://github.com/Cool-Runningz/awesome-ai-glossary</a></div>
<p> </p>
</blockquote>
<h2 id="heading-core-terms">🔠 Core Terms</h2>
<ol>
<li><p><a target="_blank" href="https://www.nasa.gov/what-is-artificial-intelligence/"><strong>Artificial Intelligence</strong></a> <strong>(AI)</strong> refers to the simulation of human intelligence processes by machines, especially computer systems. These processes include learning (the acquisition of information and rules for using the information), reasoning (using rules to reach approximate or definite conclusions), and self-correction. AI applications include expert systems, natural language processing, speech recognition, and machine vision.</p>
</li>
<li><p><a target="_blank" href="https://www.ibm.com/think/topics/machine-learning"><strong>Machine Learning</strong></a> <strong>(ML)</strong> is a subset of artificial intelligence that involves the use of algorithms and statistical models to enable computers to improve their performance on a specific task through experience. It allows systems to learn from data, identify patterns, and make decisions with minimal human intervention.</p>
</li>
<li><p><a target="_blank" href="https://research.ibm.com/blog/what-is-generative-AI"><strong>Generative AI</strong></a> refers to a type of artificial intelligence that is capable of creating new content, such as images, music, text, or videos, by learning patterns from existing data. Unlike traditional AI, which focuses on recognizing patterns and making predictions, generative AI models can produce original outputs that mimic the style and structure of the input data.</p>
</li>
<li><p><a target="_blank" href="https://aws.amazon.com/what-is/artificial-general-intelligence/"><strong>Artificial General Intelligence</strong></a> <strong>(AGI)</strong> refers to a form of artificial intelligence that possesses the ability to understand, learn, and apply knowledge across a wide range of tasks at a level comparable to human intelligence. Unlike <a target="_blank" href="https://www.datacamp.com/blog/what-is-narrow-ai">narrow AI</a>, which is designed for specific tasks, AGI aims to perform any intellectual task that a human can do, with the ability to transfer knowledge and skills from one domain to another. AGI remains a theoretical concept and is a major goal in the field of AI research.</p>
</li>
<li><p><a target="_blank" href="https://www.cloudflare.com/learning/ai/what-is-large-language-model/"><strong>Large Language Model</strong></a> <strong>(LLM)</strong> is a type of artificial intelligence model designed to understand and generate human-like text. These models are trained on vast amounts of text data and use deep learning techniques to predict and produce coherent and contextually relevant language. LLMs are capable of performing a variety of language tasks, such as translation, summarization, and conversation, by leveraging their extensive training on diverse datasets.</p>
</li>
<li><p><a target="_blank" href="https://www.databricks.com/glossary/retrieval-augmented-generation-rag"><strong>Retrieval-Augmented Generation</strong></a> <strong>(RAG)</strong> is a technique in artificial intelligence that combines retrieval-based methods with generative models to enhance the quality and accuracy of generated text. In RAG, the system retrieves relevant information from a large dataset or knowledge base and uses this information to inform and improve the text generation process. This approach allows the model to produce more informed and contextually accurate responses by incorporating external data into the generation process. RAG is particularly useful in applications where up-to-date or domain-specific information is required.</p>
</li>
<li><p><a target="_blank" href="https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them"><strong>Tokens</strong></a> are the smallest units of text that a model processes. A token can be a word, a part of a word, or even a character, depending on the tokenization method used. Tokenization is the process of breaking down text into these smaller units, which are then used by AI models to analyze and generate language.</p>
</li>
<li><p><a target="_blank" href="https://www.qodo.ai/glossary/ai-code-completion/"><strong>Completions</strong></a> refer to the output generated by a model in response to a given prompt. When a user provides an input, the AI model predicts and generates a continuation or completion of that text. Completions rely on the model's understanding of language patterns and context to produce meaningful and appropriate responses.</p>
</li>
<li><p><a target="_blank" href="https://www.ibm.com/think/topics/streaming-data"><strong>Streaming</strong></a>: the process of handling and processing data as it arrives continuously, rather than waiting for a complete dataset. This is particularly useful in applications requiring real-time insights or actions, such as live translations, real-time analytics, and interactive AI systems. Streaming ensures that data is processed with minimal latency, enabling timely and dynamic interactions.</p>
</li>
<li><p><a target="_blank" href="https://www.oracle.com/artificial-intelligence/ai-model-training/"><strong>Model Training</strong></a> involves the process of teaching an AI system to perform specific tasks by exposing it to large amounts of data. During training, the model learns to recognize patterns and make predictions based on the input data. This involves adjusting the model's parameters to minimize errors and improve accuracy. Training is a fundamental step in developing AI systems, enabling them to understand and generate text, recognize images, or perform other complex tasks.</p>
</li>
<li><p><a target="_blank" href="https://cloud.google.com/discover/what-are-ai-hallucinations"><strong>Hallucination</strong></a> refers to when an AI model creates content not based on input data or reality, often resulting in incorrect or made-up information. This can happen due to limitations in training data or when the model tries to fill gaps with plausible but wrong details.</p>
</li>
<li><p><a target="_blank" href="https://cloud.google.com/discover/what-is-prompt-engineering"><strong>Prompt Engineering</strong></a> is the practice of designing and refining the input prompts given to AI models, especially in natural language processing tasks. The goal of prompt engineering is to guide the model to produce the most accurate and relevant responses. By carefully crafting prompts, developers can influence the model's behavior and improve the quality of its outputs.</p>
</li>
<li><p><a target="_blank" href="https://www.ibm.com/think/topics/natural-language-processing"><strong>Natural Language Processing</strong></a> <strong>(NLP)</strong> is a part of artificial intelligence that enables computers to understand and respond to human language effectively. It uses computational linguistics along with machine learning and deep learning models to handle and analyze large amounts of natural language data.</p>
</li>
<li><p><a target="_blank" href="https://aws.amazon.com/what-is/neural-network"><strong>Neural Network</strong></a> a series of algorithms that aim to recognize underlying relationships in a set of data through a process that mimics the way the human brain operates. It consists of layers of interconnected nodes, or neurons, where each connection can transmit a signal to another neuron. These networks are used in various applications, including image and speech recognition, by learning from large amounts of data to make predictions or decisions.</p>
</li>
<li><p><a target="_blank" href="https://www.oracle.com/chatbots/what-is-a-chatbot/"><strong>Chatbots</strong></a> are interfaces designed to simulate human conversation. It uses artificial intelligence and natural language processing to understand and respond to user inputs, providing a conversational experience. Chatbots can be used in various applications, such as customer service, information retrieval, and personal assistance, to automate interactions and improve efficiency.</p>
</li>
<li><p><a target="_blank" href="https://blogs.nvidia.com/blog/what-is-agentic-ai/"><strong>Agentic AI</strong></a> refers to artificial intelligence systems that can make decisions and take actions independently, similar to how an agent would act on behalf of a user or organization. These systems are designed to perform tasks autonomously, often using machine learning and other AI technologies to adapt and respond to new situations without direct human intervention.</p>
</li>
</ol>
<h2 id="heading-tools">🛠️ Tools</h2>
<ol start="17">
<li><p><a target="_blank" href="https://docs.github.com/en/copilot/about-github-copilot/what-is-github-copilot"><strong>GitHub Copilot</strong></a> is an AI-powered code completion tool developed and designed to assist developers by suggesting code snippets and entire functions in real-time as they write code. By leveraging machine learning models trained on a vast amount of open-source code, GitHub Copilot aims to enhance productivity and streamline the coding process.</p>
</li>
<li><p><a target="_blank" href="https://www.jetbrains.com/ai/"><strong>JetBrains AI</strong></a> is built into JetBrains' development tools to make coding easier. It offers smart code suggestions, finds errors, and helps with automated refactoring. Using AI, JetBrains wants to boost developer productivity and simplify software development. It provides features for code completion, debugging, and project management in different programming languages.</p>
</li>
<li><p><a target="_blank" href="https://www.cursor.com/"><strong>Cursor</strong></a> is an AI code editor designed to help users become highly productive and claims to be the “<em>best way to code with AI</em>.” It allows writing code using natural language, obtains context based on the codebase, and facilitates easy changes by predicting the next edit.</p>
</li>
<li><p><a target="_blank" href="https://windsurf.com/"><strong>Windsurf</strong></a> (formerly Codeium) makes an AI code editor and plugin that can be installed in a variety editors. It includes Cascade, which is an agent designed to code, troubleshoot, and anticipate future steps. It helps maintain workflows by grasping intentions and managing intricate codebases.</p>
</li>
<li><p><a target="_blank" href="https://ollama.com/"><strong>Ollama</strong></a> is an open-source platform that allows users to run large language models (LLMs) on their own devices. It provides tools and infrastructure to easily deploy and manage these models locally, ensuring privacy and control over data. Ollama is designed to make it simple for developers to integrate AI capabilities into their applications without relying on external cloud services.</p>
<ol>
<li><a target="_blank" href="https://www.hostinger.com/tutorials/what-is-ollama">https://www.hostinger.com/tutorials/what-is-ollama</a></li>
</ol>
</li>
<li><p><a target="_blank" href="https://huggingface.co/"><strong>Hugging Face</strong></a> is a company known for its open-source platform that provides tools and models for natural language processing (NLP). It offers a library called “Transformers”, which includes pre-trained models for tasks like text classification, translation, and question answering. Hugging Face aims to make NLP accessible and user-friendly for developers and researchers.</p>
<ol>
<li><a target="_blank" href="https://zapier.com/blog/hugging-face/">https://zapier.com/blog/hugging-face/</a></li>
</ol>
</li>
</ol>
<h2 id="heading-models">🤖 Models</h2>
<ol start="23">
<li><p><a target="_blank" href="https://openai.com/index/chatgpt/"><strong>ChatGPT</strong></a> is an LLM developed by <a target="_blank" href="https://openai.com/">OpenAI</a>, designed to engage in human-like conversations. It is based on the GPT (Generative Pre-trained Transformer) architecture and is capable of understanding and generating text in response to user inputs.</p>
</li>
<li><p><a target="_blank" href="https://www.anthropic.com/claude"><strong>Claude</strong></a> is an LLM developed by <a target="_blank" href="https://www.anthropic.com/">Anthropic</a>, designed to engage in natural language understanding and generation. Similar to other advanced AI models, Claude is built to assist in tasks such as answering questions, providing explanations, and generating text based on user input.</p>
</li>
<li><p><a target="_blank" href="https://blog.google/technology/ai/google-gemini-ai/"><strong>Gemini</strong></a> is an LLM developed by <a target="_blank" href="https://deepmind.google/">Google DeepMind</a>. It is designed to perform a wide range of natural language processing tasks, including understanding and generating human-like text.</p>
</li>
<li><p><a target="_blank" href="https://www.midjourney.com/home"><strong>Midjourney</strong></a> is a popular AI-powered image generation tool that creates visually stunning artwork from text prompts. It's available as a Discord bot, allowing users to input text descriptions and receive generated images in various styles. Midjourney is developed by <a target="_blank" href="https://www.midjourney.com/"><strong>Midjourney, Inc.</strong></a>,</p>
</li>
<li><p><a target="_blank" href="https://www.llama.com/"><strong>Llama</strong></a> is a family of open-source, LLMs developed by Meta. Since it’s open-sourced, Llama allows developers and researchers to access, modify, and improve the models, fostering innovation and collaboration within the AI community.</p>
</li>
</ol>
<h2 id="heading-companies-amp-labs">🏢 Companies &amp; Labs</h2>
<ol start="28">
<li><p><a target="_blank" href="https://openai.com/about/"><strong>OpenAI</strong></a> is an AI research and deployment company whose mission is to ensure that artificial general intelligence benefits all of humanity. They popularized the concepts of chatbots with the invention of ChatGPT.</p>
</li>
<li><p><a target="_blank" href="https://www.deepseek.com/en"><strong>DeepSeek</strong></a> is a Chinese AI company developing and open-sourcing LLMs, including the DeepSeek-R1 model. It has gained attention for its claim of developing LLMs that match or surpass the performance of industry leaders like OpenAI, while reportedly costing less to create.</p>
</li>
<li><p><a target="_blank" href="https://www.anthropic.com/"><strong>Anthropic</strong></a> is an AI safety and research company focused on developing reliable and interpretable AI systems. They are the creators of Claude.</p>
</li>
<li><p><a target="_blank" href="https://deepmind.google/about/"><strong>Google DeepMind</strong></a> is a leading artificial intelligence research lab that aims to solve intelligence and use it to advance science and benefit humanity. They focus on developing advanced AI technologies and applying them to real-world problems, with a commitment to ethical and responsible AI development.</p>
</li>
</ol>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Automate Code Formatting with Prettier, ESLint, Husky, and lint-staged]]></title><description><![CDATA[Introduction
Maintaining a formatted and tidy codebase is the ideal scenario for projects. However, ensuring that codebases adhere to these standards requires some work to make this a reality. In this article, I’ll describe the libraries and automati...]]></description><link>https://blog.alyssaholland.me/automate-code-formatting</link><guid isPermaLink="true">https://blog.alyssaholland.me/automate-code-formatting</guid><category><![CDATA[Developer]]></category><category><![CDATA[Prettier]]></category><category><![CDATA[eslint]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 24 Jan 2025 12:30:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737247019977/d87bb74f-1314-458d-99b9-8342da2f3058.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Maintaining a formatted and tidy codebase is the ideal scenario for projects. However, ensuring that codebases adhere to these standards requires some work to make this a reality. In this article, I’ll describe the libraries and automations I’ve employed to achieve a polished and organized codebase a reality.</p>
<p>If you'd like to set up all these tools in a sample project, you can clone this <a target="_blank" href="https://github.com/Cool-Runningz/blog-automate-code-formatting">GitHub repository</a>. All the examples I provide will assume you're following along with the sample project unless I mention otherwise.</p>
<h2 id="heading-benefits">Benefits</h2>
<p>Before we dive into <em>how</em> to automate this process, let's first explore <em>why</em> you would want to automate code formatting and linting. Instead of bike-shedding over rules, you can automatically format or lint files using a small set of configuration files. Prettier is used for formatting, and linters are used for catching bugs. ESLint offers a wide range of options, including framework-specific checks and accessibility warnings, to name a few. Both Prettier and ESLint work together to improve the structure and quality of the codebase.</p>
<h2 id="heading-prettier">Prettier</h2>
<p><a target="_blank" href="https://prettier.io/">Prettier</a> is an opinionated code formatter that integrates with many code editors and supports a variety of programming languages. Prettier intentionally limits the amount of options you can customize so that you spend less time debating styles like whether to use tabs vs. spaces or single quotes vs. double quotes and more time focusing on important things. Simply put, Prettier saves you time and energy by allowing you to agree on a set of configuration options and move on with your day.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737250221884/b2017706-8643-47f8-9266-17b51568ae26.png" alt="Prettier logo" class="image--center mx-auto" /></p>
<h3 id="heading-installation-amp-configuration">Installation &amp; Configuration</h3>
<p>To install Prettier into your project run the following command:</p>
<pre><code class="lang-bash">npm install --save-dev --save-exact prettier
</code></pre>
<p>Then, create an empty config file at the root of your project:</p>
<pre><code class="lang-bash">touch .prettierrc.json
</code></pre>
<p>This <code>.prettierrc.json</code> file will house the configuration options for our project. To test out a few options, open the file and add the following text to enforce double quotes and semicolons.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"singleQuote"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"semi"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<h3 id="heading-testing">Testing</h3>
<p>Run the following command at the root of the project to utilize <a target="_blank" href="https://prettier.io/docs/en/cli">Prettier’s CLI</a> and check for any formatting issues in <code>src/counter.js</code>. :</p>
<pre><code class="lang-bash">npx prettier --check src/counter.js
</code></pre>
<p>After checking that file, Prettier will output the following to the terminal:</p>
<pre><code class="lang-bash">Checking formatting...
[warn] src/counter.js
[warn] Code style issues found <span class="hljs-keyword">in</span> the above file. Run Prettier with --write to fix.
</code></pre>
<p>To fix the issue, Prettier gives us a big clue here telling us to use the <code>—write</code> flag to address the issues it found so let’s try doing just that with this update:</p>
<pre><code class="lang-bash">npx prettier --write src/counter.js
</code></pre>
<p>Now if you run a diff against the <code>counter.js</code> you will notice that semicolons have been added to the end of statements and the single quotes have been replaced with double quotes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737259999456/74bcee35-26a1-4908-97df-598df277fa58.png" alt="git diff showing the quote and semicolon changes made by Prettier in ." class="image--center mx-auto" /></p>
<h2 id="heading-eslint">ESLint</h2>
<p><a target="_blank" href="https://eslint.org/">ESLint</a> is a powerful tool that performs static analysis of your code to identify potential issues and enforce coding standards. ESLint can detect syntax errors, potential bugs, and deviations from your defined coding style. This tool is widely integrated into many text editors, providing real-time feedback as you write code. Additionally, ESLint can be configured to run as part of your continuous integration pipeline, ensuring that all code changes adhere to your project's quality standards before they are merged. This helps maintain a consistent codebase and reduces the likelihood of introducing errors into your application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737250298794/aefcb3fc-0b12-4fc2-b8ff-cab98b5a9466.png" alt="ESLint logo" class="image--center mx-auto" /></p>
<h3 id="heading-installation-amp-configuration-1">Installation &amp; Configuration</h3>
<p>To install ESLint into your project run the following command:</p>
<pre><code class="lang-bash">npm install eslint --save-dev
</code></pre>
<p>You can configure ESLint using this command:</p>
<pre><code class="lang-bash">npm init @eslint/config@latest
</code></pre>
<p>This will prompt you with a series of questions and after installation is complete, create a <code>eslint.config.js</code> file that you can edit and configure to your liking. By default the recommended configuration will be applied and you can configure some rules so that the file looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// eslint.config.js</span>
<span class="hljs-keyword">import</span> globals <span class="hljs-keyword">from</span> <span class="hljs-string">"globals"</span>;
<span class="hljs-keyword">import</span> pluginJs <span class="hljs-keyword">from</span> <span class="hljs-string">"@eslint/js"</span>;

<span class="hljs-comment">/** @type {import('eslint').Linter.Config[]} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> [
  {<span class="hljs-attr">languageOptions</span>: { <span class="hljs-attr">globals</span>: globals.browser }},
  pluginJs.configs.recommended,
  {
    <span class="hljs-attr">rules</span>: {
      <span class="hljs-string">"no-console"</span>: <span class="hljs-string">"warn"</span>
    }
  }
];
</code></pre>
<p>The name <code>"no-console"</code> is an example of a <a target="_blank" href="https://eslint.org/docs/latest/rules">rule</a> in ESLint. When overriding rules the severity can be one of the following:</p>
<ul>
<li><p><code>0</code> or <code>"off"</code></p>
</li>
<li><p><code>1</code> or <code>"warn"</code></p>
</li>
<li><p><code>2</code> or <code>"error"</code></p>
</li>
</ul>
<p>The three severity levels provide you with fine-grained control over how ESLint applies rules (for more configuration options and details, see the <a target="_blank" href="https://eslint.org/docs/latest/use/configure/">configuration docs</a>).</p>
<details><summary>ℹ️ Bonus Tip</summary><div data-type="detailsContent">You can extend ESLint with <a target="_self" href="https://eslint.org/docs/latest/extend/ways-to-extend#plugins">plugins</a>. The most common ones I’ve used include: <code>eslint-plugin-jsx-a11y</code>, <code>eslint-plugin-react</code>, <code>eslint-plugin-react-hooks</code> and <code>eslint-plugin-storybook</code>.</div></details>

<h3 id="heading-eslint-config-prettier">eslint-config-prettier</h3>
<blockquote>
<p>ℹ️ Use Prettier for code formatting concerns, and linters for code-quality concerns.</p>
</blockquote>
<p>Linters often include rules for both code quality and style. However, most style rules aren't needed when using Prettier, so we need to install the <a target="_blank" href="https://github.com/prettier/eslint-config-prettier">eslint-config-prettier</a> package to prevent ESLint from conflicting with Prettier.</p>
<pre><code class="lang-bash">npm install eslint-config-prettier --save-dev
</code></pre>
<p>Then add <code>eslint-config-prettier</code> to the ESLint config so that it includes the following:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// eslint.config.js</span>
<span class="hljs-keyword">import</span> globals <span class="hljs-keyword">from</span> <span class="hljs-string">"globals"</span>;
<span class="hljs-keyword">import</span> pluginJs <span class="hljs-keyword">from</span> <span class="hljs-string">"@eslint/js"</span>;
<span class="hljs-keyword">import</span> eslintConfigPrettier <span class="hljs-keyword">from</span> <span class="hljs-string">"eslint-config-prettier"</span>; <span class="hljs-comment">//Import package</span>

<span class="hljs-comment">/** @type {import('eslint').Linter.Config[]} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> [
  {<span class="hljs-attr">languageOptions</span>: { <span class="hljs-attr">globals</span>: globals.browser }},
  pluginJs.configs.recommended,
  eslintConfigPrettier, <span class="hljs-comment">//Ensure this is the last option in the list</span>
  {
    <span class="hljs-attr">rules</span>: {
      <span class="hljs-string">"no-console"</span>: <span class="hljs-string">"warn"</span>
    }
  }
];
</code></pre>
<p>To ensure everything is set up correctly and that none of the ESLint rules conflict with Prettier, we can use the <a target="_blank" href="https://github.com/prettier/eslint-config-prettier?tab=readme-ov-file#cli-helper-tool">CLI helper tool</a> included with eslint-config-prettier. For example, if I run <code>npx eslint-config-prettier src/counter.js</code>, it will output, <em>“No rules that are unnecessary or conflict with Prettier were found.”</em></p>
<h3 id="heading-testing-1">Testing</h3>
<p>Run the following command at the root of the project to utilize <a target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface">ESLint’s CLI</a> and check for any formatting issues in <code>counter.js</code>:</p>
<pre><code class="lang-bash">npx eslint src/counter.js
</code></pre>
<p>You’ll notice that ESLint does not output anything, indicating that no issues were found. To purposely create an error and a warning, update <code>counter.js</code> to add a <code>console.log()</code> statement and create a new variable without referencing it anywhere.</p>
<p>If you’re using an editor like VSCode and have the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint">ESLint extension installed</a>, you might notice that the editor shows the following tooltips when you hover over the code associated with the issues:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737509903628/1ac88dff-2d09-4d1f-b445-058c89d0a7ce.png" alt="VSCode highlighting two ESLint warnings. One warns about an unexpected console statement, and the other notifies that a variable 'empty' is assigned a value but never used." /></p>
<p>However, you can use ESLint’s CLI tool to also check for any issues by running the following command:</p>
<pre><code class="lang-javascript">npx eslint src/counter.js
</code></pre>
<p>After running the command, the terminal should output the following error and warning messages:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737510280965/bcc28b8e-7da2-4b29-9123-4b33aad9bc58.png" alt="CLI output with two ESLint warnings. One warns about an unexpected console statement, and the other notifies that a variable 'empty' is assigned a value but never used." /></p>
<p>This indicates that ESLint is set up properly and correctly identifying issues.</p>
<h2 id="heading-husky">Husky</h2>
<p><a target="_blank" href="https://typicode.github.io/husky/">Husky</a> is a tool that allows you to manage Git hooks more easily. Git hooks are scripts that run automatically at certain points in the Git workflow, such as before a commit or push. Husky allows you to configure these hooks making it easier to enforce code quality standards, run tests, or perform other tasks automatically as part of the development process.</p>
<h3 id="heading-installation-amp-configuration-2"><strong>Installation &amp; Configuration</strong></h3>
<p>To install husky run the following command:</p>
<pre><code class="lang-bash">npm install --save-dev husky
</code></pre>
<p>Next, we need to run the <code>init</code> command which will simplify setting up husky in a project:</p>
<pre><code class="lang-bash">npx husky init
</code></pre>
<p>This creates a <code>pre-commit</code> script in <code>.husky/</code> and updates the <code>prepare</code> script in <code>package.json</code>. In the next section we’ll see how we can use this to run Prettier and ESLint whenever committing new files.</p>
<h2 id="heading-lint-staged">lint-staged</h2>
<p><a target="_blank" href="https://www.npmjs.com/package/lint-staged">lint-staged</a> enhances the code quality assurance process by running linters on files that are <strong><em>staged</em></strong> for commit, rather than the entire project. This approach ensures that only the files intended for the next commit are checked, preventing errors from entering the repository and enforcing consistent code style. By focusing on staged files, lint-staged significantly reduces the time and resources required for linting, making it a practical solution for maintaining code quality without unnecessary overhead. The tool provides the ability to execute custom shell tasks on these files, filtered by specified glob patterns, streamlining the pre-commit workflow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737600311186/59263b80-54b8-4db9-85e3-e30ad0d8923a.png" alt="Sample output from running git commit with a lint-staged configuration" /></p>
<h3 id="heading-installation-amp-configuration-3">Installation &amp; Configuration</h3>
<p>Run the following command to install <code>lint-staged</code></p>
<pre><code class="lang-bash">npm install --save-dev lint-staged
</code></pre>
<p>Next, we need to setup the <code>pre-commit</code> hook to run lint-staged. If you remember from the previous section, husky created this file for us so we can open the file directly and edit it or run the following command:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"npx lint-staged"</span> &gt; .husky/pre-commit
</code></pre>
<p>Now we need to instruct lint-staged to run Prettier and ESLint. We do this by creating a configuration file named <code>.lintstagedrc.cjs</code> and adding the following code to it:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//.lintstagedrc.cjs</span>
<span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-string">'src/**/*.js'</span>: [<span class="hljs-string">'eslint --fix'</span>, <span class="hljs-string">'prettier --write --ignore-unknown'</span>]
}
</code></pre>
<p>This file uses a glob pattern to match all JavaScript files in the <code>src</code> directory, and it lints and formats all staged files that are about to be committed.</p>
<h3 id="heading-testing-2">Testing</h3>
<p>Now if we add and commit <code>counter.js</code> with the intentional issues that were added earlier in the article, we will see the following output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737689398082/0431469a-8c26-4e86-8dd8-4ce097804100.png" alt="Command line output showing a failed ESLint and Prettier task during a pre-commit script run. The errors include an unused variable and an unexpected console statement, resulting in one error and one warning. " /></p>
<details><summary>ℹ️ Bonus Tip</summary><div data-type="detailsContent">If you want to test the lint-staged output and configuration without adding and undoing commits, you can run <code>npx lint-staged --debug</code></div></details>

<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Hopefully, you can start to see the benefits this combination of tools offers. Prettier formats files, ESLint helps you catch potential bugs, Husky makes it easier to create pre-commit hooks, and lint-staged is the glue that keeps this automation process consistent.</p>
<p>By leveraging these tools, you can automate code formatting and maintain a clean, efficient codebase. These tools not only enhance code quality but also streamline the development process, allowing you to focus more on building and less on manual formatting.</p>
<p>As always, thank you for reading, and happy coding!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/tIO9cBRm4hc?si=2YMCppoX6T80tf63">https://youtu.be/tIO9cBRm4hc?si=2YMCppoX6T80tf63</a></div>
]]></content:encoded></item><item><title><![CDATA[7 More Mac Apps to Boost Development Productivity]]></title><description><![CDATA[I wrote a similarly titled article back in 2020 including a list of the main apps I use as a full-time developer. Since that was one of my most popular articles and the list of apps I currently use has changed a bit, I thought it would be helpful to ...]]></description><link>https://blog.alyssaholland.me/7-more-mac-apps-to-boost-development-productivity</link><guid isPermaLink="true">https://blog.alyssaholland.me/7-more-mac-apps-to-boost-development-productivity</guid><category><![CDATA[General Programming]]></category><category><![CDATA[macbook]]></category><category><![CDATA[macOS]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Tue, 26 Nov 2024 12:00:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Im7lZjxeLhg/upload/459d18b3c11f5c939c39b8ef15c300bc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I wrote a <a target="_blank" href="https://blog.alyssaholland.me/7-free-mac-apps-to-boost-development-productivity">similarly titled article back in 2020</a> including a list of the main apps I use as a full-time developer. Since that was one of my most popular articles and the list of apps I currently use has changed a bit, I thought it would be helpful to write an updated blog post on this topic. Hope you enjoy!</p>
<h2 id="heading-1-raycast">1) Raycast ⚡</h2>
<p>Raycast provides a collection of powerful productivity tools all within an extendable launcher. With its fast and customizable interface, you can search files, run commands, manage apps, and even extend functionality with community-built plugins.</p>
<p>I’ve known about Raycast for a while but was an avid Alfred user for years. I finally made the switch this year and have been happy with the results.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731984948249/1d6a972d-d909-4700-a5e3-edc30aa61185.png" alt="A dark-themed command bar with the input &quot;Buy milk.&quot; Below, there are various options including &quot;Create To-Do,&quot; &quot;Create Snippet,&quot; and &quot;Create AI Command.&quot;" class="image--center mx-auto" /></p>
<p>🆓 Free | 🌐 <a target="_blank" href="https://www.raycast.com/">Check out Raycast</a></p>
<h2 id="heading-2-shottr">2) Shottr 📸</h2>
<p>Shottr is a lightweight screenshot tool designed specifically for macOS. Packed with features like pixel-perfect zoom, text recognition (OCR), and annotation tools, it’s perfect for developers and designers who need to capture and share ideas quickly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731984309960/d4dade53-2b47-4025-b0c5-4da2ffa93bdf.png" alt="Promotional graphic for Shottr, a screenshot measurement and annotation app for Mac, featuring a user interface with annotation tools and measurement display." class="image--center mx-auto" /></p>
<p>🆓 Free | 🌐 <a target="_blank" href="https://shottr.cc/">Check out Shottr</a></p>
<h2 id="heading-3-runjs">3) RunJS ▶️</h2>
<p>RunJS is a fast and efficient tool for prototyping JavaScript and TypeScript. It serves as a playground or scratchpad, allowing you to write and execute code seamlessly. The output of your code is displayed side-by-side with the editor, providing instant feedback.</p>
<p>RunJS allows you to install npm packages, making it a good fit for testing and experimenting with libraries. It also allows you to save and organize snippets into a collection for quick and easy access.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731983955534/d56cfaa5-e0d4-4992-9593-79dd310073c6.png" alt="Promotional graphic for RunJS, labeled as &quot;The JavaScript playground for your desktop,&quot; featuring a code editor with JavaScript code that generates a tree pattern." class="image--center mx-auto" /></p>
<p>🆓 💰 Freemium | 🌐 <a target="_blank" href="https://runjs.app/">Check out RunJS</a></p>
<h2 id="heading-4-rectangle">4) Rectangle 🟦</h2>
<p>Rectangle is an open-source, window management tool based on Spectacle (the deprecated window manager I mentioned in my last rendition of this post). Rectangle allows you to move and resize windows using keyboard shortcuts or snap areas. Macs still do not have this feature natively, making this a very handy tool.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732548592920/9827e43f-0531-4bc1-b779-7b1783314826.png" alt="Modal displaying a list of keyboard shortcuts for Rectangle" class="image--center mx-auto" /></p>
<p>🆓 Free | 🌐 <a target="_blank" href="https://rectangleapp.com/">Check out Rectangle</a></p>
<h2 id="heading-5-eagle">5) Eagle 🦅</h2>
<p>Eagle is a versatile digital asset management tool designed to help developers, designers, and creatives stay organized. With features like smart folders, tagging, and powerful search capabilities, it lets you effortlessly manage and categorize images, videos, fonts, and other design resources. Eagle streamlines your workflow, ensuring your inspiration and assets are always at your fingertips.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731892036275/169c7144-1eda-472c-b84f-29da651a1f19.webp" alt="A collection in Eagle showing various 404 error page designs shown as thumbnails." class="image--center mx-auto" /></p>
<p>💰 Paid | 🌐 <a target="_blank" href="https://eagle.cool/">Check out Eagle</a></p>
<h2 id="heading-6-bruno">6) Bruno 🐶</h2>
<p>Bruno is a lightweight, open-source, Postman alternative that aims to reinvent the API client. Some of its differentiators includes a native Git integration and offline-only capabilities.</p>
<p><img src="https://www.usebruno.com/features-product.svg" alt="Bruno interface with a sample GET request and corresponding JSON response." class="image--center mx-auto" /></p>
<p>🆓 Free | 🌐 <a target="_blank" href="https://www.usebruno.com/">Check out Bruno</a></p>
<p><em>P.S.</em> <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client"><em>Thunder Client</em></a> <em>is another alternative if you’re looking for an API client that you can use directly inside VS Code.</em></p>
<h2 id="heading-7-pixelsnap-2">7) PixelSnap 2 📏</h2>
<p>PixelSnap is a precision measurement tool that makes it effortless to measure anything on your screen. Ideal for developers and designers, it lets you quickly find distances between objects, dimensions of UI elements, or even gaps in layouts. With its smart snapping and intuitive interface, PixelSnap saves time and ensures pixel-perfect accuracy in your work.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731983541681/bd7b1ae8-b428-4ab7-900e-15b1fd1c3a02.png" alt="A laptop screen displaying a measurement tool with an overlay showing &quot;176x17 px.&quot; On the right, there's an icon with measuring tools and the text &quot;Measure anything on your screen.&quot;" class="image--center mx-auto" /></p>
<p>💰 Paid | 🌐 <a target="_blank" href="https://getpixelsnap.com/">Check out PixelSnap 2</a></p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>I hope you will find these apps to be a helpful and productive addition to your workflow. Remember to check out my related article, <a target="_blank" href="https://blog.alyssaholland.me/7-free-mac-apps-to-boost-development-productivity">7 Free Mac Apps to Boost Development Productivity</a>, for a list of other apps that you may want to add to your toolkit.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[How to bypass aliases that conflict with bash commands]]></title><description><![CDATA[A Note on the Series:
Fast Fridays 🏎 is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays.

It's common to have aliases in dotfiles that offe...]]></description><link>https://blog.alyssaholland.me/bypass-bash-aliases</link><guid isPermaLink="true">https://blog.alyssaholland.me/bypass-bash-aliases</guid><category><![CDATA[Bash]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 22 Nov 2024 15:00:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731114498907/36af77ce-d0af-4bed-843c-86a3b532c716.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>A Note on the Series:</p>
<p>Fast Fridays 🏎 is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays.</p>
</blockquote>
<p>It's common to have aliases in dotfiles that offer shortcuts for running frequent commands. A useful trick is to create an alias for an existing bash command and use that instead of the actual command. Occasionally, you might need to run the real command instead of the alias. In this Fast Friday tip, you'll learn how to easily bypass an aliased command.</p>
<h2 id="heading-simplifying-commands-with-aliases">Simplifying Commands with Aliases</h2>
<p>I have an alias defined in my <code>.zshrc</code> file that maps <a target="_blank" href="https://en.wikipedia.org/wiki/Rm_\(Unix\)"><code>rm</code></a> to the <code>trash</code> command, a <a target="_blank" href="https://github.com/sindresorhus/macos-trash">third-party package</a> that moves files and folders to the trash. Essentially, it’s a safer alternative to the <a target="_blank" href="https://docstore.mik.ua/orelly/unix3/upt/ch14_03.htm">dangers of the default <code>rm</code> command</a> as it moves anything you delete to the trash folder instead of removing it entirely from your computer without any way to retrieve it.</p>
<p><em>P.S. This was a tip that I got from the following Wes Bos video years ago:</em></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/dPYm0tb25O4?si=S-gDy5qH1HgtBBYD">https://youtu.be/dPYm0tb25O4?si=S-gDy5qH1HgtBBYD</a></div>
<p> </p>
<p>Although it’s rare that I find myself removing something unintentionally, it surely came in handy the one time I fat-fingered a command and deleted an entire directory of projects. Thankfully, all I had to do was <kbd>CTRL</kbd> + <kbd>Z</kbd> to undo the action, and all my folders and files were returned to their original location. Phew! 😥 Crisis averted!</p>
<h2 id="heading-bypassing-aliases">Bypassing Aliases</h2>
<p>Even though I have those safeguards in place, there are times when I <em>do</em> actually want to run the default <code>rm</code> command. A common example of this is when I’m copying and pasting a command from a work repo and it includes the <code>-rf</code> flags. If I were to run a command that included those flags I would get an output that reads <em>“The file “-rf” doesn’t exist.”</em> That’s because the aliased version runs <code>trash</code> which doesn’t accept those flags.</p>
<pre><code class="lang-bash">&gt; rm -rf blog-temp
&gt; The file “-rf” doesn’t exist.
</code></pre>
<p>In order to get around this, you have two options:</p>
<ol>
<li><p><code>\</code></p>
</li>
<li><p><code>command</code></p>
</li>
</ol>
<p>By prefixing with the <code>\</code> (backslash) or <code>command</code> keyword, you instruct the shell to bypass any alias and execute the original command.</p>
<p>For example, if I were to run either of the following commands it would correctly remove the specified directory:</p>
<pre><code class="lang-bash">&gt; <span class="hljs-built_in">command</span> rm -rf blog-temp
&gt; \rm -rf blog-temp
</code></pre>
<p>This Fast Friday tip is a bit niche, but it's super handy when needed!</p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Principles for Building Successful Component Libraries]]></title><description><![CDATA[ℹ️ Introduction
Reflecting on my journey with component libraries, I've gathered a myriad of insights that I believe can guide others in creating successful component libraries of their own. Working across several libraries in various capacities, eac...]]></description><link>https://blog.alyssaholland.me/component-library-principles</link><guid isPermaLink="true">https://blog.alyssaholland.me/component-library-principles</guid><category><![CDATA[React]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[component libraries]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Mon, 18 Nov 2024 12:30:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731812725393/30b67213-7d8e-4dfe-90e0-bbd10765c182.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">ℹ️ Introduction</h2>
<p>Reflecting on my journey with component libraries, I've gathered a myriad of insights that I believe can guide others in creating successful component libraries of their own. Working across several libraries in various capacities, each project has taught me something new about what works and what doesn't. Your mileage may vary, but these are some of the lessons I’ve learned being a component library maintainer and contributor.</p>
<h2 id="heading-avoid-the-kitchen-sink">🚰 Avoid the Kitchen Sink</h2>
<p>It's tempting to create a component for every conceivable need, but this can lead to an overwhelming array of choices for developers. For instance, deciding between a <code>HeaderSection</code> or <code>AppSectionHeader</code> can complicate rather than simplify development. The key is to focus on essential components, ensuring that developers aren't bogged down by unnecessary options. Help a dev out and prevent a scenario where it makes their job harder because they have too many components to choose from.</p>
<p>Not every UI element needs to be included in a component library. Create components only when they are needed multiple times and justify the effort of abstraction. Complex components with unique logic should remain presentational, avoiding the inclusion of business logic. This focus ensures that the library remains manageable and relevant in a wide range of scenarios.</p>
<h2 id="heading-carefully-craft-the-props-api">🔡 <strong>Carefully Craft the Props API</strong></h2>
<p>Defining the props that individual components accept are the backbone of any component library. If the initial props structure is too rigid, it becomes challenging to make changes without causing disruption. To mitigate this, it's vital to design a flexible and robust API from the start. Changes should be additive, not subtractive, to avoid breaking existing implementations. This foresight ensures that your components remain adaptable and easy to integrate with third-party libraries, without being tied to specific implementations.</p>
<p>Another thing to keep in mind when crafting props is to <a target="_blank" href="https://kentcdodds.com/blog/make-impossible-states-impossible">make impossible states impossible</a>. This means structuring your component's API so that it inherently disallows invalid or contradictory configurations. By doing so, you reduce the likelihood of bugs and make your components more robust.</p>
<h2 id="heading-prioritize-accessibility">♿ Prioritize Accessibility</h2>
<p>Accessibility should always be a priority. By implementing accessible components from the beginning, applications can make meaningful strides in creating a more inclusive user experience (UX). This ensures that even developers with limited accessibility expertise can effectively use these components. Such an approach not only enhances UX but also simplifies development by offering accessible building blocks.</p>
<p>Check out my course, <a target="_blank" href="https://www.newline.co/courses/the-approachable-guide-to-accessible-components"><strong><em>The Approachable Guide to Accessible Components</em></strong></a>, to learn more about this topic.</p>
<p><a target="_blank" href="https://www.newline.co/courses/the-approachable-guide-to-accessible-components"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731823904645/5178ff3c-afb1-4378-9272-01cb88ec27ee.jpeg" alt="'The Approachable Guide to Accessible Components' course artwork. " class="image--center mx-auto" /></a></p>
<h2 id="heading-emphasize-flexibility-and-extensibility">🤸🏽‍♀️ Emphasize Flexibility and Extensibility</h2>
<p>Components should be developed with a focus on flexibility and extensibility. Avoid imposing fixed dimensions unless absolutely necessary, as this allows components to adapt to different layouts and design changes.</p>
<p>For example, if the initial design calls for a component to be centered within a container, but later the design changes so that it needs to grow and take up the full width of said container, this change becomes trivial if the component was built in such a way that it was unopinionated about its dimensions from the start.</p>
<p>To prevent components from causing unintended layout shifts, avoid applying margins to the outermost container of the component. Using padding is acceptable as it only affects the internal spacing of the parent component. Ensuring that components can expand to fit their container makes them adaptable to various applications.</p>
<h2 id="heading-document-thoroughly">📝 Document Thoroughly</h2>
<p>When it comes to component libraries, developers are your audience. Help them out by writing comprehensive documentation. Tools like <a target="_blank" href="https://storybook.js.org/">Storybook</a> and <a target="_blank" href="https://react-styleguidist.js.org/">React Styleguidist</a> can help with creating detailed documentation, making it easier for developers to effectively utilize components.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731825627109/2b3e616f-ea95-4ac3-9c96-a77e6049bfd7.png" alt="Sample documentation for Storybook’s Avatar component." class="image--center mx-auto" /></p>
<h2 id="heading-extend-html-element-attributes">📶 Extend HTML Element Attributes</h2>
<p>When implementing components that have native HTML equivalents, it's helpful to expose props that extend the element's attributes. Common HTML elements added to component libraries include the <code>&lt;button&gt;</code>, <code>&lt;textarea&gt;</code>, and <code>&lt;input&gt;</code>.</p>
<p>For example, if you were to create a custom <code>TextInput</code> component in React, you might define props that looks something like the following code block:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">"./TextInput.css"</span>;
<span class="hljs-keyword">import</span> PropTypes <span class="hljs-keyword">from</span> <span class="hljs-string">"prop-types"</span>;

TextInput.propTypes = {
  <span class="hljs-attr">id</span>: PropTypes.string.isRequired,
  <span class="hljs-attr">label</span>: PropTypes.string.isRequired,
  <span class="hljs-attr">hideLabel</span>: PropTypes.bool,
  <span class="hljs-attr">disabled</span>: PropTypes.bool, <span class="hljs-comment">// &lt;input&gt; attribute</span>
  <span class="hljs-attr">error</span>: PropTypes.string,
  <span class="hljs-attr">type</span>: PropTypes.string, <span class="hljs-comment">// &lt;input&gt; attribute</span>
  <span class="hljs-attr">name</span>: PropTypes.string, <span class="hljs-comment">// &lt;input&gt; attribute</span>
  <span class="hljs-attr">placeholder</span>: PropTypes.string, <span class="hljs-comment">// &lt;input&gt; attribute</span>
  <span class="hljs-attr">value</span>: PropTypes.string.isRequired, <span class="hljs-comment">// &lt;input&gt; attribute</span>
  <span class="hljs-attr">onChange</span>: PropTypes.func.isRequired 
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TextInput</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">const</span> { id, label, disabled, hideLabel, error, name, value,
          type = <span class="hljs-string">"text"</span>, placeholder, onChange } = props;

  <span class="hljs-keyword">const</span> hasError = error?.length &gt; <span class="hljs-number">0</span>;
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">{id}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{hideLabel</span> ? "<span class="hljs-attr">visually-hidden</span>" <span class="hljs-attr">:</span> <span class="hljs-attr">null</span>}&gt;</span>
        {label}
      <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">{id}</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">{type}</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">{placeholder}</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">{name}</span>
        <span class="hljs-attr">disabled</span>=<span class="hljs-string">{disabled}</span>
        <span class="hljs-attr">value</span>=<span class="hljs-string">{value}</span>
        <span class="hljs-attr">aria-describedby</span>=<span class="hljs-string">{hasError</span> ? "<span class="hljs-attr">error-message</span>" <span class="hljs-attr">:</span> <span class="hljs-attr">null</span>}
        <span class="hljs-attr">aria-invalid</span>=<span class="hljs-string">{hasError</span> ? "<span class="hljs-attr">true</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">false</span>"}
        <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(event)</span> =&gt;</span> onChange?.(event.target.value)}
      /&gt;
      {hasError &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"error-message"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"error"</span>&gt;</span>
          {error}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>By using the same names as the equivalent HTML elements, developers can more easily adopt the custom component without needing to learn an entirely new API.</p>
<h2 id="heading-leverage-headless-libraries">🔝Leverage Headless Libraries</h2>
<p><a target="_blank" href="https://blog.alyssaholland.me/headless-components">Headless components</a> offer a powerful abstraction by providing logic and accessibility without styling. This allows developers to apply their own branding while benefiting from pre-built functionality. These libraries are particularly useful for complex components, enabling developers to focus on meeting design requirements rather than reinventing the wheel.</p>
<p>Check out this YouTube video to learn more about how to implement an accessible <code>Tabs</code> component using Radix:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/prRRVkuzm7A?si=BntyPwupr3KwLG6z">https://youtu.be/prRRVkuzm7A?si=BntyPwupr3KwLG6z</a></div>
<p> </p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Building a successful component library involves thoughtful architecture, striking a balance of restraint and flexibility, and a strong emphasis on accessibility and documentation. By adhering to these best practices, you can develop a library that is both practical and extensible, effectively meeting the needs of developers throughout your company.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[How I Automate the Process of Setting up a New Mac]]></title><description><![CDATA[1️⃣ Introduction
If you’re anything like me, then the thought of getting a new computer invokes two emotions: excitement and dread. You’re excited to get a brand new computer that is an upgrade to your current machine or maybe you’re starting a new j...]]></description><link>https://blog.alyssaholland.me/dotfiles-management</link><guid isPermaLink="true">https://blog.alyssaholland.me/dotfiles-management</guid><category><![CDATA[Bash]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Homebrew]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Mon, 26 Aug 2024 14:00:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724531267507/d8ce72c6-1247-48a0-87e6-fb4eb0bb74da.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-1-introduction">1️⃣ Introduction</h2>
<p>If you’re anything like me, then the thought of getting a new computer invokes two emotions: excitement and dread. You’re excited to get a brand new computer that is an upgrade to your current machine or maybe you’re starting a new job and need to get things setup quickly. However, you’re also dreading the hassle of having to go through the tedious process of configuring your computer to feel comfy like your last one. After going through this song and dance multiple times, I got really interested in automating this process via dotfiles and bash scripts.</p>
<p>Throughout this article, I will go over the automated process that I finally landed on after copious amounts of research.</p>
<h2 id="heading-the-why">The Why ❓</h2>
<p>Let's start with why I decided to invest so much time into automating this process. If you've ever looked into this topic, you'll quickly find that there are many ways to manage dotfiles. Some methods use ready-made tools that handle it for you (check out <a target="_blank" href="https://dotfiles.github.io/utilities/">this site</a> for a comprehensive list of options). Other solutions are more customized and tailor-made.</p>
<p>One thing I learned from all my research is that it all depends on what works best for you and your configurations. Interestingly enough, it seems like there is no universally accepted solution for managing dotfiles in the developer community. After scoping out the different options, I decided to go the tailor-made route.</p>
<h2 id="heading-choosing-the-right-approach">🤔 Choosing the Right Approach</h2>
<p>Narrowing the choice down to the customized route ruled out a large subset of options, but then came the hard part of figuring out how to manage these dotfiles in an elegant way. Thankfully, there are wonderful people on the web who share their setups, and it seemed that the two most popular ways to manage this were:</p>
<ol>
<li><p><a target="_blank" href="https://www.atlassian.com/git/tutorials/dotfiles">git bare repo</a></p>
</li>
<li><p><a target="_blank" href="https://www.gnu.org/software/stow/manual/stow.html">GNU stow</a></p>
</li>
</ol>
<p>My initial research led me to the git bare repo method, and I began implementing it. However, I encountered some difficulties and decided to take a break. Months later, I revisited the project and noticed several references to using GNU Stow. Naturally, I explored this option and eventually decided that Stow was the best way to go.</p>
<p>Although there were many articles written about the git bare repo process, I found it a bit too confusing. I also worried I might screw things up if I missed a step. On the other hand, GNU Stow was more intuitive for me from the start, and I felt it would be easier to maintain long term.</p>
<h2 id="heading-scripting">👩🏽‍💻Scripting</h2>
<p>In addition to managing dotfiles, I also wanted to automate the installation of my applications and Mac settings. Now that I had a plan for managing my dotfiles, I needed to write scripts to automate this process.</p>
<h3 id="heading-installing-applications-via-homebrew">🍺 Installing Applications via Homebrew</h3>
<p><a target="_blank" href="https://brew.sh/">Homebrew</a> touts itself as <em>"The Missing Package Manager for macOS"</em> and it's an amazing tool that allows you to install apps and packages via the command line. The first thing I did for this process was perform an audit of all the apps that I use on my work and personal computer and add them to a spreadsheet.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724536213073/9da6f638-31c8-4ddf-a67c-1a54012efc58.png" alt="Spreadsheet sorted by alphabetical order that lists out all the mac applications used" class="image--center mx-auto" /></p>
<p>Then I labeled which ones required a manual install and which ones could be installed via Homebrew. Thankfully a majority of the apps could be installed via the command line so I created a <code>brew-install.sh</code> script that ended up looking something like this:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>
<span class="hljs-comment"># Check for Homebrew to be present, install if it's missing</span>
<span class="hljs-keyword">if</span> <span class="hljs-built_in">test</span> ! $(<span class="hljs-built_in">which</span> brew); <span class="hljs-keyword">then</span>
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"🍺 Installing Homebrew"</span>
   /bin/bash -c <span class="hljs-string">"<span class="hljs-subst">$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)</span>"</span>  
   brew update
 <span class="hljs-keyword">else</span>
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"Homebrew is already installed."</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-function"><span class="hljs-title">checkForResponse</span></span>() {
  <span class="hljs-built_in">read</span> -p <span class="hljs-string">"Install <span class="hljs-variable">$1</span> [y/n]? "</span> response
}

checkForResponse <span class="hljs-string">"Applications"</span>
<span class="hljs-keyword">if</span> [[ <span class="hljs-variable">$response</span> == <span class="hljs-string">"y"</span> || <span class="hljs-variable">$response</span> == <span class="hljs-string">"Y"</span> ]]; <span class="hljs-keyword">then</span>
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"🍱 Installing Applications"</span>
    brew install --cask figma
    brew install --cask itsycal
    brew install --cask notion
    <span class="hljs-comment"># List out other apps here...</span>
<span class="hljs-keyword">fi</span>

checkForResponse <span class="hljs-string">"Packages"</span>
<span class="hljs-keyword">if</span> [[ <span class="hljs-variable">$response</span> == <span class="hljs-string">"y"</span> || <span class="hljs-variable">$response</span> == <span class="hljs-string">"Y"</span> ]]; <span class="hljs-keyword">then</span>
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"📦 Installing Packages"</span>
   brew install bat
   brew install git
   brew install stow
   <span class="hljs-comment"># List out other packages here...</span>
<span class="hljs-keyword">fi</span>

checkForResponse <span class="hljs-string">"Fonts"</span>
<span class="hljs-keyword">if</span> [[ <span class="hljs-variable">$response</span> == <span class="hljs-string">"y"</span> || <span class="hljs-variable">$response</span> == <span class="hljs-string">"Y"</span> ]]; <span class="hljs-keyword">then</span>
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"🔠 Installing Fonts"</span>
    brew install --cask font-fira-code
    brew install --cask font-cascadia-code
    brew install --cask font-inconsolata-for-powerline
   <span class="hljs-comment"># List out other fonts here...</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"✅ Installation using Homebrew is complete"</span>
</code></pre>
<h3 id="heading-configuring-mac-settings">🖥️ Configuring Mac settings</h3>
<p>Another thing I wanted to automate was configuring my Mac settings. These are always the "set it and forget it" aspects of owning a computer. However, you really notice the differences when you switch to a new computer and have the feeling that something is <em>off.</em></p>
<p>I had seen some other people's dotfiles using a strange syntax like this: <code>domains.&lt;something&gt;.&lt;else&gt;</code>. After some Googling, I discovered it was a low-level way to configure Mac preferences. The only problem was that I didn't have a list of all the different options. Then, I found this <a target="_blank" href="https://macos-defaults.com/">list of macOS defaults</a>. This site was extremely helpful in figuring out what settings I could automate, and it explained the syntax well. It may not be an exhaustive list of all the Mac settings you can set from the command line, but it gave me plenty to work with.</p>
<p>To handle this, I created a new file called <code>mac-settings.sh</code>, and the final output looked something like this:</p>
<pre><code class="lang-bash"><span class="hljs-comment">#======================Dock settings =================</span>
defaults write com.apple.dock <span class="hljs-string">"orientation"</span> -string <span class="hljs-string">"left"</span> <span class="hljs-comment"># Set dock to the left</span>
defaults write com.apple.dock <span class="hljs-string">"show-recents"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Do not display recent apps in the Dock</span>
defaults write com.apple.dock <span class="hljs-string">"autohide"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Always display the dock (default)</span>
defaults write com.apple.dock <span class="hljs-string">"mineffect"</span> -string <span class="hljs-string">"genie"</span> <span class="hljs-comment"># Genie effect for minimizing windows (default)</span>

<span class="hljs-comment">#======================Screencapture settings =================</span>
mkdir -p <span class="hljs-variable">$HOME</span>/Desktop/Screenshots <span class="hljs-comment"># Create "Screenshots" folder on the desktop and set it as the default location </span>
defaults write com.apple.screencapture <span class="hljs-string">"location"</span> <span class="hljs-variable">$HOME</span>/Desktop/Screenshots  
defaults write com.apple.screencapture <span class="hljs-string">"show-thumbnail"</span> -bool <span class="hljs-literal">true</span> <span class="hljs-comment"># Display the thumbnail after taking a screenshot (default)</span>
defaults write com.apple.screencapture <span class="hljs-string">"include-date"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Don't include date in the screenshot file name</span>

<span class="hljs-comment">#======================Finder settings =================</span>
defaults write NSGlobalDomain <span class="hljs-string">"AppleShowAllExtensions"</span> -bool <span class="hljs-literal">true</span> <span class="hljs-comment"># Show filename extensions </span>
defaults write com.apple.finder <span class="hljs-string">"ShowPathbar"</span> -bool <span class="hljs-literal">true</span> <span class="hljs-comment"># Show path bar in the bottom of the Finder windows</span>
defaults write com.apple.finder <span class="hljs-string">"FXPreferredViewStyle"</span> -string <span class="hljs-string">"Nlsv"</span> <span class="hljs-comment"># Set the default view style for folders without custom setting to list view</span>
defaults write com.apple.finder <span class="hljs-string">"FXDefaultSearchScope"</span> -string <span class="hljs-string">"SCcf"</span> <span class="hljs-comment"># Set the default search scope when performing a search in Finder to the current folder</span>
defaults write NSGlobalDomain <span class="hljs-string">"NSDocumentSaveNewDocumentsToCloud"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Save to disk (not to iCloud) by default</span>

<span class="hljs-comment">#======================Trackpad settings =================</span>
defaults write com.apple.AppleMultitouchTrackpad <span class="hljs-string">"FirstClickThreshold"</span> -int 1
defaults write com.apple.AppleMultitouchTrackpad <span class="hljs-string">"DragLock"</span> -bool <span class="hljs-literal">false</span>
defaults write com.apple.AppleMultitouchTrackpad <span class="hljs-string">"Dragging"</span> -bool <span class="hljs-literal">false</span>
defaults write com.apple.AppleMultitouchTrackpad <span class="hljs-string">"TrackpadThreeFingerDrag"</span> -bool <span class="hljs-literal">false</span>

<span class="hljs-comment">#======================Mission Control settings =================</span>
defaults write com.apple.dock <span class="hljs-string">"mru-spaces"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Do not rearrange spaces based on most recent use</span>
defaults write com.apple.dock <span class="hljs-string">"expose-group-apps"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Do not group windows by application (default)</span>

<span class="hljs-comment">#======================Text Edit settings =================</span>
defaults write com.apple.TextEdit <span class="hljs-string">"RichText"</span> -bool <span class="hljs-literal">false</span> <span class="hljs-comment"># Set default document format as plain text (.txt)</span>
</code></pre>
<h3 id="heading-stowing-dotfiles">⚙️ Stowing dotfiles</h3>
<p>One vital aspect of this new dotfiles workflow was to create a portable and reusable system that could be stored in version control. I wanted a way to easily update my dotfiles without having to manually copy them over in an ad hoc manner.</p>
<p>As I mentioned in the introduction, I chose GNU Stow because it felt more intuitive to me. GNU Stow describes itself as a "<em>symlink farm manager</em>". Simply put, it manages the creation and deletion of <a target="_blank" href="https://en.wikipedia.org/wiki/Symbolic_link#:~:text=A%20symbolic%20link%20contains%20a,exists%20independently%20of%20its%20target.">symbolic links</a>. To stow something, you type <code>stow</code> followed by the name of the package(s) you want to symlink. Here's what the final <code>stow</code> command I used looks like:</p>
<pre><code class="lang-bash">stow -v --dir=<span class="hljs-variable">$HOME</span>/dotfiles/<span class="hljs-variable">$LAPTOP_TYPE</span> --target=<span class="hljs-variable">$HOME</span> zsh git vim
</code></pre>
<h4 id="heading-breaking-down-the-code">🛠️ Breaking down the code</h4>
<ul>
<li><p><code>-v</code> = Enables verbose mode, which outputs detailed information about what Stow is doing.</p>
</li>
<li><p><code>--dir</code> = Specifies the directory where the dotfiles are located. If not specified, this defaults to the current directory.</p>
</li>
<li><p><code>--target</code> = Sets the target directory where the symbolic links will be created. In this case, it is the <code>$HOME</code> directory. If not specified, this defaults to the parent of the stow directory.</p>
</li>
</ul>
<h4 id="heading-package-structure">📦 Package Structure</h4>
<p>When using GNU Stow, the package structure is crucial because it determines how the symbolic links are created and organized. Each package (e.g., <code>zsh</code>, <code>git</code>, <code>vim</code>) are a subdirectory within the main directory specified by <code>--dir</code>. Inside each package directory, the files and subdirectories mirror the structure of the target directory where the symlinks will be created. This ensures that the symbolic links are placed correctly in the target directory.</p>
<p>Maintaining a consistent structure across different packages helps in managing and updating dotfiles. For example, the <code>.zshrc</code> file in the <code>zsh</code> package will be placed in <code>zsh/.zshrc</code> so that Stow can link it to <code>$HOME/.zshrc</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724616271408/8c6fba07-2323-47c9-954f-0f5a92c17de2.png" alt="A terminal window displays the output of the  with arrows representing the symlinks created by GNU Stow.." class="image--center mx-auto" /></p>
<h2 id="heading-testing">🧪 Testing</h2>
<p>It’s all good and fun to have these scripts, but I needed an environment to test them in. It’s difficult to get access to a clean slate of an operating system so I looked around for options. Plus, I wanted an environment that I could test out these bash scripts without borking my own machine. At some point I heard about the idea of using Docker and I proceeded down that route.</p>
<h3 id="heading-docker">🐳 Docker</h3>
<p>Even though I have <a target="_blank" href="https://blog.alyssaholland.me/gentle-introduction-to-docker">written about Docker before</a>, I am by no means an expert. However, I understood that the containerization aspect of Docker would make it an ideal environment to use as my testing ground.</p>
<p>The first thing I did was write a <code>Dockerfile</code> to create a simple container that could run bash scripts. When the container runs, the <code>Dockerfile</code> is set up to <code>sh</code> into the working directory. This setup allowed me to run the scripts and fix any issues. It also let me test CLI commands before adding them to the final scripts.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Grab base image from Docker Hub</span>
<span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-comment"># Update and install necessary packages</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install -y git vim curl zsh bash build-essential procps curl file language-pack-en</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /root/dotfiles/</span>

<span class="hljs-comment"># Copy the scripts files from the current directory into the container's working directory</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-comment"># Make the brew-install.sh script executable</span>
<span class="hljs-keyword">RUN</span><span class="bash"> chmod +x brew-install.sh setup.sh</span>

<span class="hljs-comment"># Start bash</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"sh"</span>]</span>
</code></pre>
<p>The great thing about using a Docker container was that I could experiment with many different approaches. I don't write a lot of bash scripts, so this was especially helpful. If I messed something up, no problem—I could just delete the container, make some changes, and then restart and try again. It turned out to be a good sandbox, and I recommend using one if you want to fine-tune your own dotfiles workflow.</p>
<h2 id="heading-putting-it-all-together">🔠 Putting it all Together</h2>
<p>With all those steps in place, I finally had a workflow that I could run. Here’s a breakdown of how it all pans out:</p>
<h4 id="heading-setupsh"><code>setup.sh</code></h4>
<p>This file is the main driver that executes all the complimentary scripts and functions.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-comment"># Source the helpers file to get access to the functions</span>
<span class="hljs-built_in">source</span> ./helpers.sh

<span class="hljs-comment">#======================Run setup scripts===============  </span>
./brew-install.sh
setup-stow
setup-oh-my-zsh
setup-cobalt2-theme
./mac-settings.sh
</code></pre>
<h4 id="heading-brew-installsh"><code>brew-install.sh</code></h4>
<p>The first script called from <code>setup.sh</code> is the <code>brew-install.sh</code> script that I mentioned earlier. This script checks if Homebrew is installed. If it's not, it will install it and then ask if I want to install three types of items: applications, packages, and fonts.</p>
<h4 id="heading-setup-stow"><code>setup-stow()</code></h4>
<p>Once the Homebrew script finishes, the next step is to run the <code>setup-stow</code> function. As the name suggests, this function takes care of setting up the correct Stow environment.</p>
<pre><code class="lang-bash">setup-<span class="hljs-function"><span class="hljs-title">stow</span></span>() {
  <span class="hljs-comment"># Check if stow is present, install if it's missing</span>
  <span class="hljs-keyword">if</span> <span class="hljs-built_in">test</span> ! $(<span class="hljs-built_in">which</span> stow); <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Installing GNU Stow"</span>
    brew install stow
  <span class="hljs-keyword">else</span>
   <span class="hljs-comment"># Get the laptop type</span>
   LAPTOP_TYPE=$(get-laptop-type)
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"💻 Laptop type: <span class="hljs-variable">$LAPTOP_TYPE</span>"</span>
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"🐐 Stowing dotfiles..."</span>
   stow -v --dir=<span class="hljs-variable">$HOME</span>/dotfiles/<span class="hljs-variable">$LAPTOP_TYPE</span> --target=<span class="hljs-variable">$HOME</span> zsh git vim
   <span class="hljs-built_in">echo</span> <span class="hljs-string">"🚀 Stow complete!"</span>
  <span class="hljs-keyword">fi</span>
}
</code></pre>
<p>The function prompts me for the "laptop type" since I maintain separate directories for work and personal files to stay organized. This approach allows me to stow work-related files on my company laptop and personal files on my own Mac. While the differences between the two sets of files are minimal, this separation enables me to customize the experience for each device, ensuring that each setup is tailored to its specific use case.</p>
<h4 id="heading-setup-oh-my-zsh"><code>setup-oh-my-zsh()</code></h4>
<p>The third item run in the main script is the <code>setup-oh-my-zsh()</code> function. This handles installing oh my zsh and is passed two important flags:</p>
<ol>
<li><p><code>--keep-zshrc</code> - This flag tells oh my zsh to respect the existing <code>.zshrc</code> file so that any pre-existing settings will be reflected.</p>
</li>
<li><p><code>--unattended</code> - This flag uses the “<strong>unattended install</strong>” to prevent oh my zsh from exiting the script early.</p>
</li>
</ol>
<pre><code class="lang-bash">setup-oh-my-<span class="hljs-function"><span class="hljs-title">zsh</span></span>() {
<span class="hljs-comment"># Check if oh-my-zsh is present, install if it's missing</span>
<span class="hljs-keyword">if</span> [ ! -d <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/.oh-my-zsh"</span>  ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Installing oh my zsh..."</span> 
    sh -c <span class="hljs-string">"<span class="hljs-subst">$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>"</span> <span class="hljs-string">""</span> --keep-zshrc --unattended
  <span class="hljs-keyword">else</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"oh my zsh is already installed."</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<p>Having the <code>setup-oh-my-zsh</code> function run <em>after</em> the Stow setup is intentional. It's crucial that the <code>.zshrc</code> file is in the <code>$HOME</code> directory so that when the flag checks for it, it can find it and apply the existing settings. This wasn't immediately obvious to me, but I realized the order of operations was important after testing it in the Docker container. This is why I'm a big fan of having a sandbox environment that gives you the opportunity to experiment with these scripts.</p>
<h4 id="heading-mac-settingssh"><code>mac-settings.sh</code></h4>
<p>The last part of the script runs the <code>mac-settings.sh</code> script that I shared earlier. This script handles running the CLI commands to update some Mac settings, such as moving the dock to the left and setting the default folder for screenshots.</p>
<h2 id="heading-el-fin">👋🏽 El Fin</h2>
<p>After a lot of trial and error and even more research, I finally feel happy with my dotfiles workflow. Now instead of grappling with the dichotomy of emotions when setting up a new computer, I’m prepared that the workflow I configured will make getting up and running with a new machine a more enjoyable process</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><p><a target="_blank" href="https://www.jakewiesler.com/blog/managing-dotfiles">Jake Wiesler - Manage Your Dotfiles Like a Superhero</a></p>
</li>
<li><p><a target="_blank" href="https://systemcrafters.net/managing-your-dotfiles/using-gnu-stow/">System Crafters - Using GNU Stow to Manager Symbolic Links for your Dotfiles</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Music Maestro: Use AI to Curate a Playlist and Discover Music on Spotify]]></title><description><![CDATA[I am excited to announce my hackathon project called Music Maestro 🎵

Music Maestro is a site that helps you craft custom playlists. The architecture uses AI to create tailored playlists based on any search criteria

🌐 View the live version of the ...]]></description><link>https://blog.alyssaholland.me/music-maestro</link><guid isPermaLink="true">https://blog.alyssaholland.me/music-maestro</guid><category><![CDATA[AIForTomorrow]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 26 Jul 2024 09:00:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721949081391/df12bf3d-a0c1-4854-a5f8-5443a7b1a711.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I am excited to announce my hackathon project called <a target="_blank" href="https://musicmaestro.io/">Music Maestro</a> 🎵</p>
<blockquote>
<p><strong>Music Maestro</strong> is a site that helps you craft custom playlists. The architecture uses AI to create tailored playlists based on any search criteria</p>
</blockquote>
<p>🌐 View the live version of the site at <a target="_blank" href="https://musicmaestro.io/">https://musicmaestro.io/</a></p>
<p>💻 Check out the GitHub repo at <a target="_blank" href="https://github.com/Cool-Runningz/music-maestro">https://github.com/Cool-Runningz/music-maestro</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/ug1DSCcwcqU">https://youtu.be/ug1DSCcwcqU</a></div>
<p> </p>
<h2 id="heading-how-it-works">✨How it Works</h2>
<p>When you arrive at the landing page, you will need to login to Spotify. After you've successfully logged in, you will be presented with a search input and some sample prompts like "90's nostalgia" or "Jazz vibes for a rainy day".</p>
<p>Search based on any criteria and in a matter of seconds the UI will display a custom generated playlist. If you have a Spotify premium account, you will be able to play the songs directly in the browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721952300721/ae3bd542-d1c2-4359-ab30-2b66818a1134.gif" alt="Performing a search in Music Maestro" class="image--center mx-auto" /></p>
<h2 id="heading-how-i-came-up-with-the-idea"><strong>💡 How I Came up with the Idea</strong></h2>
<p>I waffled back and forth on ideas for this hackathon. With AI, the possibilities are endless, so I almost had too many options to choose from. Some potential ideas ranged from an image alt text generator, since I'm all about accessibility, to an app that generates timestamps for your podcast since my sister and I co-host a podcast together called <a target="_blank" href="https://cultureinbetween.com/">Culture in Between</a>.</p>
<p>Eventually, I had a lightbulb moment when I thought back to a time when I couldn't decide what songs I wanted to play. I've been a Spotify premium member for years, and although their app has a great search feature, searching for something vague like "capture the vibe of 'Feels like Summer' by Childish Gambino" isn't going to yield the best results.</p>
<p>At the time I had that fleeting thought, I didn't think of creating this myself. It wasn't until I saw the "AI for Tomorrow" hackathon pop up on my feed and noodled on myriads of ideas for a few days that I finally realized this might be a feasible (and interesting!) project.</p>
<h2 id="heading-tech-stack"><strong>🛠️ Tech Stack</strong></h2>
<ol>
<li><p><a target="_blank" href="https://nextjs.org/"><strong>Next.js</strong></a> (Full-stack web framework)</p>
</li>
<li><p><a target="_blank" href="https://tailwindcss.com/"><strong>Tailwind CSS</strong></a> (Utility-first CSS framework)</p>
</li>
<li><p><a target="_blank" href="https://tailwindui.com/"><strong>Tailwind UI</strong></a> (Premium UI components designed with Tailwind CSS)</p>
</li>
<li><p><a target="_blank" href="https://catalyst.tailwindui.com/docs"><strong>Catalyst</strong></a> (Modern UI Kit written by the Tailwind team)</p>
</li>
<li><p><a target="_blank" href="https://developer.spotify.com/documentation/web-api"><strong>Spotify API</strong></a> (Enables the creation of applications that can interact with Spotify's streaming service)</p>
</li>
<li><p><a target="_blank" href="https://openai.com/api/"><strong>OpenAI</strong></a> (Platform for building AI products)</p>
</li>
<li><p><a target="_blank" href="https://heroicons.com/"><strong>Heroicons</strong></a> (SVG icons)</p>
</li>
<li><p><a target="_blank" href="https://swr.vercel.app/"><strong>SWR</strong></a> (React Hooks for Data Fetching)</p>
</li>
<li><p><a target="_blank" href="https://authjs.dev/"><strong>NextAuth v5</strong></a> (Authentication for Next.js)</p>
</li>
<li><p><a target="_blank" href="https://www.typescriptlang.org/"><strong>TypeScript</strong></a> (JavaScript with syntax for types)</p>
</li>
</ol>
<h2 id="heading-implementation-details"><strong>👷🏽‍♀️ Implementation Details</strong></h2>
<p>In the following sections, I will describe how I went about implementing this project and will provide a deeper dive into the technical details. The first detail I will share is that this project is still a work in progress. <strong><mark>By default, Spotify apps have to get approved before they can be used by a wider audience so if you're interested in using this, let me know and I will try my best to get you early beta access.</mark></strong></p>
<h3 id="heading-phase-1-authentication">🔐 Phase 1: Authentication</h3>
<p>Connecting to Spotify is the crux of this project so I started with auth. I don't have previous experience working with authentication and I seldom work on the backend so this was all new territory. In the interest of time and ensuring I didn't screw this process up, I went with <code>next-auth</code> since it has a Spotify Provider that made authenticating with Spotify more manageable.</p>
<h3 id="heading-phase-2-scaffolding-the-ui">🎨 Phase 2: Scaffolding the UI</h3>
<p>The second major thing I focused on was building out the UI. Design is not my strong suit so I leveraged one of the templates from <strong>Tailwind UI</strong> and tweaked it to suit the projects needs. Since the site consists of a single page, I went with a theme that would allow me to showcase the two main sections of the app: the search widget and the playlist.</p>
<p>I also leveraged <strong>Catalyst</strong>, which I would describe as Tailwinds take on a component library. I honestly forgot that I had access to that when I paid for the Tailwind UI package so being able to leverage pre-made, accessible, and aesthetically pleasing components, really expedited the UI process.</p>
<h3 id="heading-phase-3-the-core-functionality">🚂 Phase 3: The Core Functionality</h3>
<p>Now that user could login and view hardcoded results on the UI, I tackled the engine of the app - the generate functionality.</p>
<p>Once a user enters in a prompt and clicks 'generate', the UI makes an API call that hits the Next.js route handler. This is my first time working with the new Next.js app router so I learned a lot of terms and patterns along the way - like working with server components in React!</p>
<p>Anyway, the first thing the route handles does is initiate a call to <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI's chat completions API</a>. I have a specific prompt that I pass to the <code>system</code> role and a longer more descriptive prompt that I pass to the <code>user</code> role. This whole process felt very similar to how one would interact with ChatGPT in the browser.</p>
<p>In addition to prompt engineering, I leveraged <a target="_blank" href="https://platform.openai.com/docs/guides/function-calling">function calling</a> to improve the output from OpenAI. I wanted a very specific JSON response but for some reason OpenAI has issues with returning properly formatted JSON. Before implementing function calling, there was approximately a 40% - 50% chance of receiving JSON. After the addition of functional calling, the consistency of JSON output improved to about 80% - 90%.</p>
<p>Once the app gets back a list of <code>recommendations</code> from OpenAI, the list is sanitized to remove any faulty JSON. Then it extracts out the info needed to search for the proper tracks in the <a target="_blank" href="https://developer.spotify.com/documentation/web-api/reference/search">Spotify search endpoint</a>. Once the list of tracks is returned from Spotify, I flatten the response to only contain the necessary fields and return that back to the UI to interact with <a target="_blank" href="https://developer.spotify.com/documentation/web-playback-sdk">Spotify's Web Playback SDK</a>. User's with a Spotify Premium account are then able to interact with the playback controls within their browser.</p>
<p>Phew! That's quite a few moving parts so here is a sketch of the general architecture of the app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721967920941/0a779764-fd7b-45ce-a809-fa3a6904044c.png" alt="Excalidraw sketch of the Music Maestro Architecture" class="image--center mx-auto" /></p>
<h2 id="heading-mvp-and-beyond"><strong>🔮 MVP and Beyond</strong></h2>
<p>I am super proud of what I’ve been able to accomplish for the hackathon, however, there are many more improvements that I would like to make in the future. Below is a list of some items that I would like to add post-MVP.</p>
<ol>
<li><p><strong>Refresh tokens</strong> - Spotify's access tokens expire every hour so I manually set the session to expire at the same interval. Ideally, I would like the token to be refreshed in the background so that the user has a seamless experience without having to login and logout so often.</p>
</li>
<li><p><strong>Save &amp; Shuffle Playlists</strong> - It would be cool to expand the playlist functionality so that users could save a playlist if they liked what was generated. Inversely, if the user doesn't love the generated playlist but wants to keep the same prompt, they could "shuffle" the songs to get different results.</p>
</li>
<li><p><strong>Train a model to generate more accurate results</strong> - The current suggestions that OpenAI provides are decent but it can lack variety and diversity. Utilizing a model that is more tailored to music recommendations would allow for better playlists.</p>
</li>
</ol>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Thanks for reading about the journey to create this project. This has been a great learning experience and has taught me a lot about what it takes to implement a legit MVP.</p>
<p>As always, thank you for reading, and happy coding! Again, feel free to check out the site and view the GitHub repository.</p>
<ul>
<li><p><a target="_blank" href="https://musicmaestro.io/">https://musicmaestro.io/</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Cool-Runningz/music-maestro">https://github.com/Cool-Runningz/music-maestro</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Short Scripts for Small Wins]]></title><description><![CDATA[A Note on the Series:
Fast Fridays 🏎 is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays.

In this Fast Friday tip, I’ll cover a short bash ...]]></description><link>https://blog.alyssaholland.me/short-scripts-for-small-wins</link><guid isPermaLink="true">https://blog.alyssaholland.me/short-scripts-for-small-wins</guid><category><![CDATA[Bash]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Fri, 24 May 2024 14:16:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716573182206/31ba5917-a294-44d7-93ab-a9045c18eecb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>A Note on the Series:</p>
<p>Fast Fridays 🏎 is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays.</p>
</blockquote>
<p>In this Fast Friday tip, I’ll cover a short bash script I created that has allowed me to start developing new components within our reports library in seconds instead of minutes. I’m always looking for little hacks I can employ to help improve my productivity as a developer and I hope this helps you as well!</p>
<h2 id="heading-the-friction-point">The Friction Point</h2>
<p>Throughout my career, I’ve maintained a few component libraries and there has been a common procedure that I’ve had to follow in each of them.</p>
<ul>
<li><p><strong>Step 1:</strong> Create a folder and corresponding <code>*.jsx</code> <em>or</em> <code>*.tsx</code> file for housing React component X.</p>
</li>
<li><p><strong>Step 2</strong>: Create a styles file for component X.</p>
</li>
<li><p><strong>Step 3:</strong> Create Storybook files for component X.</p>
</li>
<li><p><strong>Step 4:</strong> Create a tests folder and files for component X.</p>
</li>
<li><p><strong>Step 5:</strong> Forget all the boilerplate that you have to add for steps 1 -4 that this process takes longer than it should and ends up being annoying 😣</p>
</li>
</ul>
<p>One day, while going through this monotonous cycle, I thought, "There has to be a better way to do this." I got so frustrated with the process that I finally decided to do something about it.</p>
<h2 id="heading-the-solution">The Solution</h2>
<p>After thinking about the problem for a bit, I decided I would tinker with creating a bash script to help automate these steps. I had only ever written a handful of bash scripts, so this gave me a good opportunity to learn more about developing in bash.</p>
<p>My development workflow for this was fairly straightforward, I took steps 1- 4 that I outlined in the previous section, turned those into bash commands, and then filled in any missing gaps.</p>
<p>Here's the final code:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-comment"># Prompt for component name</span>
<span class="hljs-built_in">read</span> -p <span class="hljs-string">"Enter component name: "</span> COMPONENT_NAME
TOP_LEVEL_DIR=<span class="hljs-string">"src/components/<span class="hljs-variable">$COMPONENT_NAME</span>"</span>

<span class="hljs-comment"># Check if folder already exists. If it does, abort the process.</span>
<span class="hljs-keyword">if</span> [ -d <span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>"</span>  ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Folder already exists so aborting the process..."</span>
    <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Create a new directory for the component</span>
mkdir -p <span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📂 Created new directory: /<span class="hljs-variable">$COMPONENT_NAME</span>"</span>

<span class="hljs-comment"># Create component file</span>
COMPONENT_FILEPATH=<span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>/<span class="hljs-variable">$COMPONENT_NAME</span>.tsx"</span>
<span class="hljs-built_in">printf</span> <span class="hljs-string">"%s"</span> <span class="hljs-string">"import React from 'react';
import { StyledH1 } from './<span class="hljs-variable">$COMPONENT_NAME</span>.styled';

export default function <span class="hljs-variable">$COMPONENT_NAME</span>(): JSX.Element {
    return &lt;StyledH1&gt;New <span class="hljs-variable">$COMPONENT_NAME</span> Component&lt;/StyledH1&gt;;
}
"</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$COMPONENT_FILEPATH</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📄 Created <span class="hljs-variable">$COMPONENT_NAME</span>.tsx"</span>

<span class="hljs-comment"># Create styled component file</span>
SC_FILEPATH=<span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>/<span class="hljs-variable">$COMPONENT_NAME</span>.styled.ts"</span>
<span class="hljs-built_in">printf</span> <span class="hljs-string">"%s"</span> <span class="hljs-string">"import styled from 'styled-components';
import { color } from '@/styles/variables';

export const StyledH1 = styled.h1\`
    color: \${color.blue[700].value};
\`;
"</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$SC_FILEPATH</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📄 Created <span class="hljs-variable">$COMPONENT_NAME</span>.styled.ts file"</span>

<span class="hljs-comment"># Create stories file</span>
STORIES_FILEPATH=<span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>/<span class="hljs-variable">$COMPONENT_NAME</span>.stories.tsx"</span>
<span class="hljs-built_in">printf</span> <span class="hljs-string">"%s"</span> <span class="hljs-string">"import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';

import <span class="hljs-variable">$COMPONENT_NAME</span> from './<span class="hljs-variable">$COMPONENT_NAME</span>';

export default {
    title: 'Components/<span class="hljs-variable">$COMPONENT_NAME</span>',
    component: <span class="hljs-variable">$COMPONENT_NAME</span>,
    parameters: {
        design: {
            type: 'figma',
            url: '',
        },
    },
} as ComponentMeta&lt;typeof <span class="hljs-variable">$COMPONENT_NAME</span>&gt;;

const Template: ComponentStory&lt;typeof <span class="hljs-variable">$COMPONENT_NAME</span>&gt; = (args) =&gt; &lt;<span class="hljs-variable">$COMPONENT_NAME</span> {...args} /&gt;;

export const Default = Template.bind({});
Default.args = {};
"</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$STORIES_FILEPATH</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📄 Created <span class="hljs-variable">$COMPONENT_NAME</span>.stories.tsx"</span>

<span class="hljs-comment"># Create test folder and file</span>
TEST_FILEPATH=<span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>/__tests__/<span class="hljs-variable">$COMPONENT_NAME</span>.test.tsx"</span>
mkdir -p <span class="hljs-string">"<span class="hljs-variable">$TOP_LEVEL_DIR</span>/__tests__"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📂 Created __tests__ folder"</span>

<span class="hljs-built_in">printf</span> <span class="hljs-string">"%s"</span> <span class="hljs-string">"import React from 'react';
import { render, screen } from '@testing-library/react';

import <span class="hljs-variable">$COMPONENT_NAME</span> from '../<span class="hljs-variable">$COMPONENT_NAME</span>';

describe('<span class="hljs-variable">$COMPONENT_NAME</span> Component', () =&gt; {
    test('renders default view', () =&gt; {
        render(&lt;<span class="hljs-variable">$COMPONENT_NAME</span> /&gt;);
    });
});

"</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$TEST_FILEPATH</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📄 Created <span class="hljs-variable">$COMPONENT_NAME</span>.test.tsx"</span>
</code></pre>
<p>Now, all you have to do to create a new component is run the command. You will be prompted to name the new component, and the script will handle creating a new folder with all the necessary files, folders, and boilerplate code.</p>
<h2 id="heading-measurable-improvements">Measurable Improvements</h2>
<p>Whenever I take time away from my assigned tasks to implement things like this, I try to think about the measurable improvements that were made. Here are a few reasons why I believe it was time well spent:</p>
<ol>
<li><p>There is now a streamlined process for a repeatable task. Automation works best in these situations, making this the perfect task for a script.</p>
</li>
<li><p>Before, adding all the boilerplate for a new component would probably take around 5 minutes. Now, the time was reduced to just a few seconds.</p>
</li>
<li><p>Having an automated process for creating new components ensured code consistency in terms of file structure and organization.</p>
</li>
<li><p>To make this process even simpler, I added an npm script to the projects <code>package.json</code> file so that any developer on the team could easily run this script by running <code>yarn:new component</code> in their terminal.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716558582821/4a31acc9-4dae-4beb-9435-3ebf2fdc8c28.png" alt="Terminal output after running the bash script" class="image--center mx-auto" /></p>
<h1 id="heading-el-fin">El Fin 👋🏽</h1>
<p>Overall, It was cool that a bash script of less than 100 lines of code could speed up my development process considerably. The next time you have a friction point when developing, consider if there’s a way that you can automate the process.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Harnessing Creativity in the Digital Age]]></title><description><![CDATA[Introduction
With the heavy infiltration of AI into society and software applications, I found myself reflecting on a book I read a few years back. “Steal Like an Artist” by Austin Kleon is a manifesto for creativity in the digital age.
Although the ...]]></description><link>https://blog.alyssaholland.me/steal-like-an-artist</link><guid isPermaLink="true">https://blog.alyssaholland.me/steal-like-an-artist</guid><category><![CDATA[books]]></category><category><![CDATA[creativity]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Wed, 26 Apr 2023 12:30:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681851245686/18302703-aff2-4822-a5d1-5c12601bbe08.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>With the heavy infiltration of AI into society and software applications, I found myself reflecting on a book I read a few years back. <a target="_blank" href="https://austinkleon.com/steal/">“Steal Like an Artist” by Austin Kleon</a> is a manifesto for creativity in the digital age.</p>
<p>Although the book was written over a decade ago, I think its message is as relevant as ever in this digital age where the lines between AI-generated content and human-generated content are becoming blurred.</p>
<p>With the sheer number of tools and resources at our fingertips, it's arguably easier than it's ever been to render content. However, let's make sure to remember what keeps us human and whole and always set aside time to be creative.</p>
<h2 id="heading-chapter-1-steal-like-an-artist">Chapter 1: Steal Like an Artist</h2>
<h3 id="heading-i-how-to-look-at-the-world-like-an-artist">i) <strong>How to Look at the world (Like an artist)</strong></h3>
<ul>
<li>Look at the world like an artist would. When you do this you stop putting things in a "good" or "bad" box and instead think in terms of what ideas, concepts, designs, etc are worth stealing and what aren't.</li>
</ul>
<h3 id="heading-ii-genealogy-of-ideas">ii) Genealogy of ideas</h3>
<ul>
<li><p>Your ideas stem from your upbringing, experiences, and content you let into your life and are the mashup or sum of these influences.</p>
</li>
<li><p>Take ideas from anything that resonates with you. Pick things that make you feel something and are influential as this will keep your product authentic and true to you.</p>
</li>
</ul>
<h3 id="heading-iii-school-yourself">iii) School Yourself</h3>
<ul>
<li>Always be learning. Use your curiosity and thirst for knowledge to grow. It'll help you get ahead in life.</li>
</ul>
<p><strong>Quote of the Section 💬</strong></p>
<blockquote>
<p>"If we're free from the burden of trying to be completely original, we can stop trying to make something out of nothing, and we can embrace influence instead of running away from it." - Austin Kleon</p>
</blockquote>
<hr />
<h2 id="heading-chapter-2-dont-wait-until-you-know-who-you-are-to-get-started">Chapter 2: Don’t wait until you know who you are to get started</h2>
<h3 id="heading-i-make-things-know-thyself">i) <strong>Make things, Know thyself</strong></h3>
<ul>
<li><p>If you wait to do something until you think you're qualified, you'll be waiting your whole life. This is where <a target="_blank" href="https://en.wikipedia.org/wiki/Impostor_syndrome">imposter syndrome</a> runs rampant and tries to convince you that you can't do something. Try your best to fight against those thoughts.</p>
</li>
<li><p>There's a ▶️ <a target="_blank" href="https://youtu.be/PbC4gqZGPSY">gap</a> between where you are and where you want to be. Make progress towards your goal by doing the things you want to be doing now. Don't start when you think you're ready. Sure, it may be intimidating but it will slowly inch you closer to your goal.</p>
</li>
</ul>
<h3 id="heading-ii-fake-it-til-you-make-it">ii) <strong>Fake it ‘til you Make it</strong></h3>
<ul>
<li><p>There are two ways in which you can interpret this common phrase:</p>
<ol>
<li><p>Pretend to be the thing you want to be until you are that thing and you are successful in it and people recognize that you're good at said thing.</p>
</li>
<li><p>Pretend that you are creating the type of content you want to be creating until that comes to fruition.</p>
</li>
</ol>
</li>
</ul>
<hr />
<h2 id="heading-chapter-3-write-the-book-you-want-to-read">Chapter 3: Write the Book you want to Read</h2>
<h3 id="heading-i-write-what-you-know-like">i) <strong>Write what you <s>know</s> Like</strong></h3>
<ul>
<li>Create the content that YOU want to experience. Don't only create things you know or see, but rather take what you know and like and make it into something that you would enjoy. Don't create something just because you think that's what people expect you to make. But instead, focus on what you want or like, which will be your unique creation.</li>
</ul>
<hr />
<h2 id="heading-chapter-4-use-your-hands">Chapter 4: Use Your Hands</h2>
<h3 id="heading-i-step-away-from-the-screen">i) Step Away from the Screen</h3>
<ul>
<li><p>Something engaging happens when you aren't doing all your thinking and creating in front of a computer screen.</p>
</li>
<li><p>The computer is great when you have a finalized copy of your creation that you want to share with the world or just reference. Oh, and it's good for editing ideas. However, computers aren't so good when it comes to <em>generating ideas.</em></p>
</li>
<li><p>By stepping away from the screen and using other methods to express and create, we engage more senses and therefore can create more freely.</p>
</li>
</ul>
<p><strong>Quote of the Section 💬</strong></p>
<blockquote>
<p>"The computer brings out the uptight perfectionist in us -- we start editing ideas before we have them." - Austin Kleon</p>
</blockquote>
<hr />
<h2 id="heading-chapter-5-side-projects-and-hobbies-are-important">Chapter 5: Side Projects and Hobbies Are Important</h2>
<h3 id="heading-i-practice-productive-procrastination">i) <strong>Practice Productive Procrastination</strong></h3>
<ul>
<li>Stop trying to be so damn productive all the time. Take the time to unwind and not think every once in a while. Just be in the moment and let your mind wander and occasionally try and experience boredom. You may just come up with your next big idea.</li>
</ul>
<h3 id="heading-ii-dont-throw-any-of-yourself-away">ii) <strong>Don't Throw Any of Yourself Away</strong></h3>
<p><em>Note: I typically paraphrase or summarize but this section had so many good quotes, that I just have to use them directly.</em></p>
<p><strong>Quote #1 💬</strong> (This is one of my favorites throughout the book 🔥)</p>
<blockquote>
<p>"It's so important to have a hobby. A hobby is something creative that's just for you. You don't try to make money or get famous off it, you just do it because it makes you happy. A hobby is something that gives but doesn't take."</p>
</blockquote>
<p><strong>Quote #2 💬</strong></p>
<blockquote>
<p>"If you have two or three real passions, don't feel like you have to pick and choose between them. Don't discard. Keep all your passions in your life"</p>
</blockquote>
<p><strong>Quote #3</strong> 💬</p>
<blockquote>
<p>"Don't throw any of yourself away. Don't worry about a grand scheme or unified vision for your work. Don't worry about unity—what unifies your work is the fact that you made it. One day, you'll look back and it will all make sense."</p>
</blockquote>
<hr />
<h2 id="heading-chapter-6-the-secret-do-good-work-and-share-it-with-people">Chapter 6: The Secret: Do Good Work and Share It with People</h2>
<h3 id="heading-i-in-the-beginning-obscurity-is-good">i) <strong>In the beginning, obscurity is good</strong></h3>
<ul>
<li>Embrace obscurity in the beginning, as it allows you to experiment, have fun, and do what you want without pressure. Attention is ideal only after you're producing really good work.</li>
</ul>
<h3 id="heading-ii-the-not-so-secret-formula">ii) <strong>The Not-So-Secret Formula</strong></h3>
<p>$$ not-so-secret-formula = "Do-good-work-and-share-it-with-people" $$</p><p><strong>Two-step process:</strong></p>
<ol>
<li><p>Wonder at something</p>
</li>
<li><p>Invite others to wonder with you</p>
</li>
</ol>
<p><strong>Benefits of posting your content online:</strong></p>
<ul>
<li><p>Find your voice</p>
</li>
<li><p>Incubate ideas that aren't fully formed</p>
</li>
<li><p>Share what's on your mind for others to learn and benefit from</p>
</li>
</ul>
<p><strong>Some ideas of what to share:</strong></p>
<ul>
<li><p>A little bit of what you're working on.</p>
</li>
<li><p>Sketch or a doodle or a code snippet</p>
</li>
<li><p>The process you use to do things</p>
</li>
<li><p>A tip you learned while working</p>
</li>
<li><p>Links to interesting articles</p>
</li>
<li><p>Book summaries</p>
</li>
<li><p>A piece of advice or words of wisdom based on past experiences</p>
</li>
</ul>
<p><strong>Quote of the Section 💬</strong></p>
<blockquote>
<p>"Think about what you have to share that could be of some value to people...If you're worried about giving your secrets away, you can share your dots without connecting them." - Austin Kleon</p>
</blockquote>
<hr />
<h2 id="heading-chapter-7-geography-is-no-longer-our-master">Chapter 7: Geography is no longer our Master</h2>
<ul>
<li><p>Create a world that allows you to take time to be creative. Your physical location does not restrict your ability to create great things.</p>
</li>
<li><p>Leave home every once in a while to experience different things.</p>
</li>
</ul>
<hr />
<h2 id="heading-chapter-8-be-nice-the-world-is-a-small-town">Chapter 8: Be Nice. (The World is a Small Town)</h2>
<h3 id="heading-i-stand-next-to-the-talent">i) Stand next to the Talent</h3>
<ul>
<li>Surround yourself with influential people who inspire you, and learn from them. Observe their methods, the topics they discuss, and the individuals they engage with.</li>
</ul>
<h3 id="heading-ii-write-fan-letters">ii) Write Fan Letters</h3>
<ul>
<li><p>Instead of writing a literal letter or message to someone praising them for their work, try the approach of <strong><em>public fan letters</em></strong>. (Examples of this include writing a blog post about someone's work you admire and providing a link to their website or product.)</p>
</li>
<li><p>The person you admire may never get a chance to see your work or respond to it. The important part to glean from this is that you showed your appreciation without expecting a response in return. Plus there's the bonus of creating new work out of that appreciation.</p>
</li>
</ul>
<h3 id="heading-iii-keep-a-praise-file">iii) Keep a Praise File</h3>
<ul>
<li><p>A praise file is a collection of positive feedback, thank you notes, and other expressions of appreciation that you've received over time. It's a way to archive and revisit the kind words that people have shared with you.</p>
<ul>
<li>Examples of this can include things like nice Slack messages or emails, favorable tweets, or appreciative comments on a blog post.</li>
</ul>
</li>
<li><p>This praise file should be relatively easy to access so that when those inevitable days roll around when you feel like 💩, you have something to reference that can bring a smile to your face.</p>
</li>
</ul>
<p><strong>Quote of the Section 💬</strong></p>
<blockquote>
<p>"The best way to get approval is to not need it" - Hugh MacLeod</p>
</blockquote>
<hr />
<h2 id="heading-chapter-9-be-boring-its-the-only-way-to-get-work-done">Chapter 9: Be Boring (It’s the only way to get work done)</h2>
<h3 id="heading-i-take-care-of-yourself">i) <strong>Take Care of Yourself</strong></h3>
<ul>
<li>It takes a lot of energy to be creative so make sure to take care of yourself.</li>
</ul>
<h3 id="heading-ii-get-yourself-a-calendar">ii) Get Yourself a Calendar</h3>
<ul>
<li>Having a calendar helps you keep on track and plan out what you're gonna do so that you can have a path to achieve your goals.</li>
</ul>
<h3 id="heading-iii-keep-a-logbook">iii) Keep a Logbook</h3>
<ul>
<li><p>A logbook isn’t a journal or diary, it’s just a place where you can log the things you do every day.</p>
<ul>
<li>Examples are projects you worked on, people you visited, places you ate, etc.</li>
</ul>
</li>
</ul>
<p><strong>Quote of the Section 💬</strong></p>
<blockquote>
<p>“Be regular and orderly in your life, so that you may be violent and original in your work” - Gustave Flaubert</p>
</blockquote>
<hr />
<h2 id="heading-chapter-10-creativity-is-subtraction">Chapter 10: Creativity is Subtraction</h2>
<h3 id="heading-i-choose-what-to-leave-out">i) <strong>Choose What to Leave Out</strong></h3>
<ul>
<li><p>You can’t do it all. Having abundant information at your fingertips can sometimes stifle growth because you don’t know where to start.</p>
</li>
<li><p>Accept the fact that you can’t learn it all and limit what you let in so that you can start doing something.</p>
</li>
<li><p>Make use of the resources in front of you and start creating. Don’t make excuses.</p>
</li>
</ul>
<hr />
<h1 id="heading-el-fin">El Fin 👋🏽</h1>
<p>I highly recommend reading <a target="_blank" href="https://austinkleon.com/steal/">Steal Like an Artist by Austin Kleon</a> as it's a great reminder to take a step back, embrace our creativity, and make the most of the resources, people, and environment around us.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Initial Thoughts on ChatGPT and Generative AI]]></title><description><![CDATA[Introduction
ChatGPT and the rise of generative AI are trending topics at the moment. Since AI affects developers I figured I'd write a piece about my early reflections on ChatGPT. This blog post will mostly be a stream of consciousness of my thought...]]></description><link>https://blog.alyssaholland.me/chatgpt-ai</link><guid isPermaLink="true">https://blog.alyssaholland.me/chatgpt-ai</guid><category><![CDATA[chatgpt]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[AI]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[D3.js]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Tue, 14 Feb 2023 13:30:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676344361384/1c8125e4-766f-41da-bd84-72dfc3629daf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p><a target="_blank" href="https://openai.com/blog/chatgpt/">ChatGPT</a> and the rise of <a target="_blank" href="https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-is-generative-ai">generative AI</a> are trending topics at the moment. Since AI affects developers I figured I'd write a piece about my early reflections on ChatGPT. This blog post will mostly be a stream of consciousness of my thoughts, opinions, and predictions on this topic so take it with a grain of salt 😅 AI and its effects in the workforce are still to be seen but here are my early thoughts.</p>
<p>Let's start at the very beginning...</p>
<h2 id="heading-fear">😨 Fear</h2>
<p>When I first heard about ChatGPT and watched numerous videos on what it was capable of, I was terrified. That is not hyperbole either. I remember sitting back in my chair and sulking over how AI would take over my job. I felt dejected and that I would be obsolete in five years' time.</p>
<p><a target="_blank" href="https://github.com/features/copilot">GitHub's Copilot</a> had caused some anxiety when it was first announced, but this didn't compare to the panic that I felt when I saw ChatGPT work its magic. <em>"What was the point of trying?"</em> I thought to myself. This pity party lasted about a week before I snapped out of my funk and realized that I needed to face my fear.</p>
<h2 id="heading-taking-the-bull-by-the-horns">🐂 Taking the Bull by the Horns</h2>
<p>Instead of avoiding ChatGPT, I decided that I would give it a try for myself. Rather than dodging ChatGPT and trying to act like it didn't exist, I would "take the bull by the horns" and see what the hype was all about.</p>
<p>ChatGPT's conversational aspect is the feature that frightened me the most; however, it was also the part that intrigued me the most. Stack Overflow and Googling for an answer are some of a developer's top choices when it comes to finding answers to pesky bugs or solving a particularly difficult problem, so I was curious if ChatGPT would improve the feedback loop for workflows like this.</p>
<p>Surely it was at least worth a try...</p>
<h2 id="heading-work-work-work-work-work">🏦 Work, work, work, work, work</h2>
<p><em>(🎵 kudos to those who get the Rihanna song reference in the title)</em></p>
<p>Around the same time that I decided to create an <a target="_blank" href="https://openai.com/about/">OpenAI</a> account, I was assigned a task at work to build out a chart to display a new finance metric. <a target="_blank" href="https://www.tableau.com/learn/articles/data-visualization">Data visualization</a> (data viz) is not something that I had previous experience in, so, although it was an exciting task to get to work on, it was also a bit intimidating.</p>
<p>The only thing I really knew about data viz was that <a target="_blank" href="https://d3js.org/">D3</a> was the de facto library that most people reached for when building charts and graphs. I also remembered hearing that D3 had a bit of a steep learning curve. <strong><em>Sigh</em></strong> 😔 With no prior knowledge of data viz and somewhat of a deadline on when I needed to complete this task, I crammed hours of Frontend Masters courses at 2x speed into my brain.</p>
<p>After my crash course, I was now able to read D3 code a bit better than before, but writing it was a different story. I looked through numerous D3 examples with charts that resembled the one I needed to build, but the examples were so complex that I had a hard time adapting them to my use case. If only I could see a simplified example of a chart similar to the one I needed to create, then I could take that sample and tweak it to match my requirements.</p>
<p>If only I could generate an example...</p>
<h2 id="heading-chatting-with-robots">🤖 Chatting with Robots</h2>
<p>I was faced with a challenging task, so I figured this was as good a time as any to utilize ChatGPT. I had tinkered around ChatGPT a little beforehand, mainly asking it a few basic prompts, but nothing too serious. Finally confronted with a real problem, I was curious if it could help me get unstuck.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676346323431/e530a150-5122-4507-a549-b1d3ea8ee771.png" alt="Prompt and response after asking ChatGPT to render a bar chart" class="image--center mx-auto" /></p>
<p>The first couple of prompts I produced didn't yield promising results. The charts either looked wonky or fell apart once I modified the data it accepted. Eventually, I realized that the prompts you provide ChatGPT take some finessing. I needed to be less vague and more specific about what I wanted.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676347219664/6c78edc8-2805-4a65-af9b-10231475baa4.png" alt class="image--center mx-auto" /></p>
<p>After some trial and error, I was finally able to get a stacked, bar chart that I could work with, albeit an ugly bar chart that didn't meet the design requirements by any stretch of the imagination. It was a starting point though! I was confident that once I had a code example that I could wrap my head around I would be off to the races and surely enough that was the case.</p>
<p>Once I learned that I could import the <a target="_blank" href="https://github.com/d3/d3-shape">d3-shape</a> package and leverage <a target="_blank" href="https://github.com/d3/d3-shape#stacks">stacks</a> to help with generating the chart, then I was able to move on to ask other prompts about how to set up the tick marks along the x-axis and even have it explain the code it provided.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676347109045/6a55d1e2-2f08-4ce9-b25b-0ac6f77c0c66.png" alt="ChatGPT explanation of generated code" class="image--center mx-auto" /></p>
<p>This back and forth of taking the simplified code example and breaking it down into smaller pieces that I could consult with ChatGPT about continued until I was able to successfully build the chart and complete the task. I still wrote a majority of the code myself but being able to ask <strong><em>how to approach</em></strong> a particular problem in D3 for my specific use case was immensely helpful.</p>
<blockquote>
<p>It was at this point that I realized ChatGPT wasn't so scary after all. In fact, I was finding that it made my job a bit easier. I felt more productive and soon found myself reaching for ChatGPT before initiating a Google search.</p>
</blockquote>
<p>Talk about a 180. I went from being terrified of ChatGPT and wanting to avoid it to embracing it and incorporating it into my dev workflow. I'm still a bit shocked that I made such a big transition, but overall I'm glad I gave it a try.</p>
<h2 id="heading-my-thoughts-so-far">💭 My Thoughts so Far</h2>
<p>Here are some hot takes after using ChatGPT for almost two months:</p>
<ol>
<li><p><strong>It's not as scary as I initially thought.</strong></p>
</li>
<li><p><strong>Technologies like ChatGPT will be a tool in a developer's toolbox and help them be more productive.</strong></p>
</li>
<li><p><strong>Conversing with ChatGPT is nuanced and providing the right prompt is key to getting desired results.</strong></p>
</li>
<li><p><strong>Vet the code that is produced.</strong> Some of the code that gets generated is kinda jank, straight-up incorrect, or outdated (like the time it suggested I use floats to position elements in my CSS). Being able to decipher quality vs. not-so-bueno code is crucial.</p>
</li>
<li><p><strong>It's only a matter of time before AI tools seamlessly integrate into developers' environments</strong>. I think this will be similar to how IDE's, features like autocomplete and text formatting have weaved themselves into most developers' code editors.</p>
</li>
</ol>
<h2 id="heading-will-ai-replace-developer-jobs">❓ Will AI Replace Developer Jobs?</h2>
<p>This is the controversial question I see pop up all over and it's one that I have been wondering myself. My short and simple answer is: <strong>No, at this point in time, I don't think AI and tools like Chat GPT will replace developer jobs</strong>.</p>
<p>The world of AI and its impact on society is still new so who knows what the future holds. All I can say is after some early exposure to AI tools like ChatGPT I don't think developer jobs are in jeopardy at the moment. However, if the robots ever figure out how to create apps and build projects on their own without prompting, then I think we're screwed 😅. But if it ever reaches that point and AI got that smart, we would have larger problems on our hands.</p>
<p>Anyway, I guess this is a long-winded way of saying that I have no idea what the future holds for AI+ developers. All I know is that right now I'm excited to use tools like ChatGPT to improve my development workflow and move quicker through challenging tasks.</p>
<p>I went from being apprehensive of this tool to looking forward to using it in my daily dev work. I recommend you try it out for yourself to see what workflows it may help improve for you too.</p>
<h2 id="heading-el-fin">👋🏽 El Fin</h2>
<p>That's a wrap folks! I'd love to know your opinions on ChatGPT and generative AI in general so feel free to leave a comment.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
<p><em>P.S. The cover image was AI-generated using</em> <a target="_blank" href="https://openai.com/dall-e-2/"><em>DALL-E 2</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Headless Components]]></title><description><![CDATA[Headless components are a trend that I've seen emerge over the past couple of years. With veteran libraries like Downshift and ReachUI and newer libraries like Radix and Headless UI, it seems to be a trend that is here to stay. Throughout this articl...]]></description><link>https://blog.alyssaholland.me/headless-components</link><guid isPermaLink="true">https://blog.alyssaholland.me/headless-components</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[components]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Tue, 17 Jan 2023 15:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/VBNb52J8Trk/upload/7b9daecab9fabf37201bdd7fabc18dd4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Headless components are a trend that I've seen emerge over the past couple of years. With veteran libraries like <a target="_blank" href="https://github.com/downshift-js/downshift">Downshift</a> and <a target="_blank" href="https://reach.tech/">ReachUI</a> and newer libraries like <a target="_blank" href="https://www.radix-ui.com/">Radix</a> and <a target="_blank" href="https://headlessui.com/">Headless UI</a>, it seems to be a trend that is here to stay. Throughout this article, I will provide a more thorough definition of <strong><em>what</em></strong> headless components are, <strong><em>why</em></strong> they are beneficial, and <strong><em>how</em></strong> they can be incorporated into your component libraries.</p>
<h2 id="heading-what-are-headless-components">What are Headless Components?</h2>
<p>Headless components are unopinionated, unstyled, components that handle a majority of the tricky implementation details so that you can build out components faster. Headless components do not have preset styles but instead provide full, interactive functionality for a particular component pattern.</p>
<h3 id="heading-benefits">Benefits</h3>
<ol>
<li><p><strong>Built-in Accessibility</strong>: Creating accessible components that meet the <a target="_blank" href="https://www.w3.org/WAI/ARIA/apg/patterns/">WAI-ARIA standards</a> and properly handle aspects like keyboard navigation can be a large undertaking. Headless component libraries aim to simplify this often difficult process by providing a set of flexible and extensible components that are prebuilt with accessibility and developer experience in mind.</p>
</li>
<li><p><strong>Reusable &amp; Flexible</strong>: Headless components are typically flexible enough to be able to handle most business requirements and implementation details. Because these components don't apply any preset CSS, they can make great candidates for component libraries as you can tailor and style them to meet most design systems. In addition, headless components tend to handle things like keyboard interactions and focus management providing one less thing for developers to have to worry about.</p>
</li>
<li><p><strong>Inversion of Control</strong>: Headless components put a majority of the power in the developer's hands via "<a target="_blank" href="https://kentcdodds.com/blog/inversion-of-control">inversion of control</a>". This pattern obscures enough of the implementation details and provides you with an API that you can control and style to meet most needs.</p>
<blockquote>
<p><strong>"</strong><a target="_blank" href="https://en.wikipedia.org/wiki/With_great_power_comes_great_responsibility"><strong><em>With great power comes great responsibility</em></strong></a><strong>"</strong>: As with most things in software development, there is a trade-off to using headless components as it does put more responsibility in the developer's hands when it comes to styling and certain rendering logic. However, I personally think the benefits outweigh the cons especially when it comes to implementing tricky component patterns for non-native elements like date pickers and autocomplete inputs.</p>
</blockquote>
</li>
</ol>
<h2 id="heading-headless-component-libraries">Headless Component Libraries</h2>
<p>This section will contain a list of some of the most popular component libraries available on the market today. Some of the options are better suited for certain scenarios but I've provided a variety of choices so that you can choose the library that works best for your use case.</p>
<h3 id="heading-1-headless-ui">1) Headless UI</h3>
<p><a target="_blank" href="https://headlessui.com/">Headless UI</a> is built by the <a target="_blank" href="https://github.com/tailwindlabs">Tailwind Labs</a> team making it an obvious option for projects that use Tailwind CSS. The library provides components for both React and Vue making it usable across multiple JS frameworks. When building out my <a target="_blank" href="https://github.com/Cool-Runningz/roadtrip-fm">RoadTrip.FM project</a> I reached for the <code>Listbox</code> and <code>Disclosure</code> components. I've found that Headless UI coupled with <a target="_blank" href="https://heroicons.com/">Heroicons</a>, another open-source project by the Tailwind Labs team, make for a great combo!</p>
<p><strong>Headless UI Tabs Component Example:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Tab } <span class="hljs-keyword">from</span> <span class="hljs-string">'@headlessui/react'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">HeadlessUITabs</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Tab.Group</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Tab.List</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span>&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span>&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Tab.List</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Tab.Panels</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab.Panel</span>&gt;</span>Content 1<span class="hljs-tag">&lt;/<span class="hljs-name">Tab.Panel</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab.Panel</span>&gt;</span>Content 2<span class="hljs-tag">&lt;/<span class="hljs-name">Tab.Panel</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Tab.Panels</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Tab.Group</span>&gt;</span></span>
  )
}
</code></pre>
<h3 id="heading-2-reach-ui">2) Reach UI</h3>
<p><a target="_blank" href="https://reach.tech/">Reach UI</a> has been around for a while and is one of the O.G's in the headless component library space. Reach UI seeks to become the accessible foundation of your React-based design system and has solid accessibility support as it claims that each component is tested with Safari + VoiceOver, Firefox + NVDA, and Edge + JAWS. Reach UI was initially created by <a target="_blank" href="https://twitter.com/ryanflorence">Ryan Florence</a> and <a target="_blank" href="https://twitter.com/mjackson">Michael Jackson</a>, the duo behind <a target="_blank" href="https://remix.run/">Remix</a> and <a target="_blank" href="https://reactrouter.com/en/main">React Router</a>.</p>
<p><strong>Reach UI Tabs Component Example:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Tabs, TabList, Tab, TabPanels, TabPanel } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reach/tabs"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ReachUITabs</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Tabs</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TabList</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span>&gt;</span>One<span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span>&gt;</span>Two<span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">TabList</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TabPanels</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">TabPanel</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>one!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">TabPanel</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">TabPanel</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>two!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">TabPanel</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">TabPanels</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Tabs</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-3-radix">3) Radix</h3>
<p><a target="_blank" href="https://www.radix-ui.com/">Radix Primitives</a> is the library’s flagship product and is described as a collection of <em>“unstyled, accessible components for building high‑quality design systems and web apps in React.”</em> Instead of reinventing the wheel, Radix Primitives handle a lot of the tricky parts when it comes to building out common component patterns.</p>
<p>They already have a reputable number of primitives and have plans to add to that list. I've written about Radix before in my "<a target="_blank" href="https://blog.logrocket.com/building-design-system-radix/"><strong>Building a design system with Radix</strong></a><strong>"</strong> article for LogRocket and I found the documentation and developer experience to be quite good.</p>
<p><strong>Radix Tabs Component Example:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Tabs <span class="hljs-keyword">from</span> <span class="hljs-string">'@radix-ui/react-tabs'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> () =&gt; (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Tabs.Root</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">"tab1"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Tabs.List</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"tabs example"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Tabs.Trigger</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"tab1"</span>&gt;</span>One<span class="hljs-tag">&lt;/<span class="hljs-name">Tabs.Trigger</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Tabs.Trigger</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"tab2"</span>&gt;</span>Two<span class="hljs-tag">&lt;/<span class="hljs-name">Tabs.Trigger</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Tabs.List</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Tabs.Content</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"tab1"</span>&gt;</span>Tab one content<span class="hljs-tag">&lt;/<span class="hljs-name">Tabs.Content</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Tabs.Content</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"tab2"</span>&gt;</span>Tab two content<span class="hljs-tag">&lt;/<span class="hljs-name">Tabs.Content</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Tabs.Root</span>&gt;</span></span>
);
</code></pre>
<h3 id="heading-4-react-aria">4) React Aria</h3>
<p><a target="_blank" href="https://react-spectrum.adobe.com/react-aria/index.html">React Aria</a> is built by the team at Adobe and uses a hook-based approach to implementing components. According to their <a target="_blank" href="https://react-spectrum.adobe.com/react-aria/getting-started.html">documentation</a>:</p>
<blockquote>
<p><strong>React Aria</strong> is a library of <a target="_blank" href="https://reactjs.org/docs/hooks-intro.html">React Hooks</a> that provides accessible UI primitives for your design system. It provides <a target="_blank" href="https://react-spectrum.adobe.com/react-aria/accessibility.html">accessibility</a> and behavior for many common UI components so you can focus on your unique design and styling. It implements <a target="_blank" href="https://react-spectrum.adobe.com/react-aria/interactions.html">adaptive interactions</a> to ensure the best experience possible for all users, including support for mouse, touch, keyboard, and screen readers.</p>
</blockquote>
<p>Check out the <a target="_blank" href="https://react-spectrum.adobe.com/react-aria/useTabList.html"><code>useTabList</code> hook</a> 🪝 to see how to implement tabs using React Aria.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://react-spectrum.adobe.com/react-aria/">https://react-spectrum.adobe.com/react-aria/</a></div>
<p> </p>
<h3 id="heading-5-reakit">5) Reakit</h3>
<p><a target="_blank" href="https://reakit.io/">Reakit</a> is a lower-level component library for building accessible high-level UI libraries, design systems, and applications with React. Reakit strictly follows <a target="_blank" href="https://www.w3.org/TR/wai-aria/"><strong>WAI-ARIA 1.1</strong></a> standards, is customizable, and is built with composition in mind.</p>
<p><strong>Reakit Tabs Component Example:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useTabState, Tab, TabList, TabPanel } <span class="hljs-keyword">from</span> <span class="hljs-string">"reakit/Tab"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ReakitTabs</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> tab = useTabState();
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TabList</span> {<span class="hljs-attr">...tab</span>} <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"My tabs"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span> {<span class="hljs-attr">...tab</span>}&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span> {<span class="hljs-attr">...tab</span>}&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">TabList</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TabPanel</span> {<span class="hljs-attr">...tab</span>}&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">TabPanel</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TabPanel</span> {<span class="hljs-attr">...tab</span>}&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">TabPanel</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-honorable-mentions">Honorable Mentions</h2>
<p>The items in this section are additional libraries that can be leveraged that also use a "headless" approach to their implementations.</p>
<h3 id="heading-1-downshift">1) Downshift 🏎</h3>
<p><a target="_blank" href="https://www.downshift-js.com/">Downshift</a> is a great choice if you need an autocomplete, combobox, or a select input in your application and you want it to be accessible and flexible. The library provides a robust API that leverages interesting patterns like <a target="_blank" href="https://kentcdodds.com/blog/compose-render-props">render props</a> and <a target="_blank" href="https://kentcdodds.com/blog/the-state-reducer-pattern-with-react-hooks">state reducers</a>. The library <a target="_blank" href="https://kentcdodds.com/blog/introducing-downshift-for-react">initially created by Kent. C Dodds</a> provides a nice solution for these notoriously hard to implement components.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.npmjs.com/package/downshift">https://www.npmjs.com/package/downshift</a></div>
<p> </p>
<h3 id="heading-2-mui-base">2) MUI Base</h3>
<p><a target="_blank" href="https://mui.com/base/getting-started/overview/">MUI Base</a> is built by the same team behind MUI (the artist <a target="_blank" href="https://mui.com/blog/material-ui-is-now-mui/">formerly known as Material UI</a>). According to the <a target="_blank" href="https://mui.com/base/getting-started/overview/">MUI Docs</a>:</p>
<blockquote>
<p>MUI Base is a library of unstyled React UI components. These components were extracted from <a target="_blank" href="https://mui.com/material-ui/getting-started/overview/">Material UI</a>, and are now available as a standalone package. They feature the same robust engineering but without implementing Material Design.</p>
<p>MUI Base includes prebuilt components with production-ready functionality, along with low-level hooks for transferring that functionality to other components.</p>
<p>With MUI Base, you can rapidly build on top of our foundational components using any styling solution you choose—no need to override any default style engine or theme.</p>
</blockquote>
<p>At the time of this writing, MUI Base is still in alpha which is why it's included in this section and not part of the larger component library list.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://mui.com/base/getting-started/overview/">https://mui.com/base/getting-started/overview/</a></div>
<p> </p>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Headless components are a relatively new pattern that has been gaining popularity in the frontend space and I'm loving the flexibility, extensibility, and accessibility benefits that this pattern provides. I highly recommend trying out a headless component library the next time you have a use case for them.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Create a React Ecommerce Store with Medusa]]></title><description><![CDATA[Introduction
This guide will explain how to set up Medusa in a React application. Medusa is an open source headless commerce engine built for developers. Medusa's composable architecture allows for endless customization and provides a friendly develo...]]></description><link>https://blog.alyssaholland.me/react-ecommerce-store-medusa</link><guid isPermaLink="true">https://blog.alyssaholland.me/react-ecommerce-store-medusa</guid><category><![CDATA[React]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[ecommerce]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Wed, 11 Jan 2023 13:25:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673410110518/a111b62e-a13c-4d9b-a9ee-36978b987910.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>This guide will explain how to set up <a target="_blank" href="https://github.com/medusajs/medusa">Medusa</a> in a React application. Medusa is an open source headless commerce engine built for developers. Medusa's composable architecture allows for endless customization and provides a friendly developer experience. React is a popular JavaScript library that provides a declarative and component-based architecture for building user interfaces.</p>
<p>Throughout this tutorial, you will learn how to build a React ecommerce store with Medusa. You will learn how to set up the Medusa server and admin panel and build out some pages to make up the storefront.</p>
<p>The code for the project developed in this article can be found in this <a target="_blank" href="https://github.com/aholland-work/react-medusa-storefront">GitHub repository</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673409863541/7b56d209-05c1-41e9-ad80-734663918634.gif" alt="Demo of the completed project" class="image--center mx-auto" /></p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To complete this tutorial, you will need:</p>
<ul>
<li><p><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment/#nodejs">Node.js</a> version 14.18+ or 16+ installed on your local machine.</p>
</li>
<li><p>One of the following package managers: <code>npm</code>, <code>yarn</code>, or <code>pnpm</code> (This tutorial will specifically use <code>npm</code>.)</p>
</li>
</ul>
<h2 id="heading-set-up-the-medusa-server"><strong>Set up the Medusa Server</strong></h2>
<p>In order to set up the server, you first need to install Medusa's CLI with the following command:</p>
<pre><code class="lang-bash">npm install @medusajs/medusa-cli -g
</code></pre>
<p>Next, run the following command to install the Medusa server:</p>
<pre><code class="lang-bash">medusa new medusa-server --seed
</code></pre>
<p>The command above installs the necessary packages and adds the Medusa server to the <code>medusa-server</code> directory. The <code>--seed</code> command populates an SQLite database with sample data that can be referenced from the storefront and admin panel.</p>
<h3 id="heading-test-the-medusa-server"><strong>Test the Medusa Server</strong></h3>
<p>Navigate to the <code>medusa-server</code> folder and start the server:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> medusa-server
medusa develop
</code></pre>
<p>The command above launches the server on <a target="_blank" href="http://localhost:9000"><code>localhost:9000</code></a>.</p>
<p>You now have a complete commerce engine running locally. You can verify that the server is running by going to <a target="_blank" href="http://localhost:9000/store/products"><code>http://localhost:9000/store/products</code></a> in your browser. You should see a JSON blob that has the following response:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673398752571/8e492109-6214-48b5-80e2-a8e53c96efaf.jpeg" alt="JSON response of sample products data" class="image--center mx-auto" /></p>
<p>If you want the ability to upload product images to your Medusa server, you will need to install and configure a file service plugin like <a target="_blank" href="https://docs.medusajs.com/add-plugins/s3">S3</a> or <a target="_blank" href="https://docs.medusajs.com/add-plugins/spaces">DigitalOcean Spaces</a>.</p>
<h2 id="heading-setup-the-medusa-admin"><strong>Setup the Medusa Admin</strong></h2>
<p>Medusa provides an admin dashboard with numerous functionalities that equip you to manage your store, including order and product management.</p>
<p>To set up the Medusa Admin, clone the <a target="_blank" href="https://github.com/medusajs/admin">Admin GitHub repository</a>:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/medusajs/admin medusa-admin
</code></pre>
<p>Change into the cloned directory and then install the dependencies:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> medusa-admin
npm install
</code></pre>
<h3 id="heading-test-the-medusa-admin"><strong>Test the Medusa Admin</strong></h3>
<p>Before testing the Medusa admin, ensure that the Medusa server is up and running.</p>
<p>Once the installation of dependencies is complete and the Medusa server is running, execute the following command to start the Medusa admin:</p>
<pre><code class="lang-bash">npm run start
</code></pre>
<p>You can now view the Medusa Admin in the browser at <a target="_blank" href="http://localhost:7000/"><code>http://localhost:7000/</code></a> where you will be prompted with the following login screen:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673398977069/f5b5cabf-37db-4c31-9ccb-55c56740eb20.png" alt="Medusa Admin Login Screen" class="image--center mx-auto" /></p>
<p>To log in, you’ll need to input the credentials of the demo user that was created when the server’s database was seeded. For the email field, enter <a target="_blank" href="mailto:admin@medusa-test.com"><code>admin@medusa-test.com</code></a>, and for the password field, enter <code>supersecret</code>.</p>
<p>Once logged in, click on “Products” in the sidebar menu. Here, you will see a list of demo products and you can add a few products by clicking on the “New Product” button in the upper right-hand corner.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673399050872/26ab6a60-3da3-4da2-aef7-5c25d1daba72.png" alt="Medusa Admin Products Page" class="image--center mx-auto" /></p>
<p>To learn more about the features and functionalities of the Medusa Admin, check out the <a target="_blank" href="https://docs.medusajs.com/user-guide/">user guide</a>.</p>
<h2 id="heading-setup-the-react-app"><strong>Setup the React App</strong></h2>
<p>In this section, you will scaffold a React application with <a target="_blank" href="https://vitejs.dev/guide/">Vite</a>. Vite is a build tool that aims to provide a faster and leaner development experience for modern web projects and is an alternative to tools like <a target="_blank" href="https://create-react-app.dev/docs/getting-started">Create React App</a>.</p>
<p>Run one of the following commands to create a Vite app. The command will vary slightly depending on the npm version that you have installed:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># npm version 6.x</span>
npm create vite@latest react-medusa-storefront --template react
​
<span class="hljs-comment"># npm version 7+, extra double-dash is needed</span>
npm create vite@latest react-medusa-storefront -- --template react
</code></pre>
<p>From here, change into the new directory and install the project dependencies:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> react-medusa-storefront
npm install
</code></pre>
<p>Start the dev server locally:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Navigate to <a target="_blank" href="http://localhost:5173/"><code>http://localhost:5173/</code></a> in your browser and you should now see a screen similar to the one below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673399167254/b452c50c-83a7-4e7c-8e94-acd6e717d727.jpeg" alt="Vite default landing page" class="image--center mx-auto" /></p>
<h3 id="heading-install-react-bootstrap"><strong>Install React Bootstrap</strong></h3>
<p>To assist with styling and building the components for the storefront you will use <a target="_blank" href="https://react-bootstrap.github.io/getting-started/introduction/">React Bootstrap</a>. To install, run the following command:</p>
<pre><code class="lang-bash">npm install react-bootstrap bootstrap
</code></pre>
<p>In order to apply the styles needed for the React Bootstrap library, you need to add the following import to the top of <code>src/main.jsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">'bootstrap/dist/css/bootstrap.min.css'</span>;
</code></pre>
<h3 id="heading-update-the-css"><strong>Update the CSS</strong></h3>
<p>Importing the React Bootstrap stylesheet in <code>src/main.jsx</code> applies a myriad of base styles therefore the stylesheet can be trimmed down.</p>
<p>Open <code>src/index.css</code> and replace its content with the following CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">font-family</span>: Inter, Avenir, Helvetica, Arial, sans-serif;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>;
  <span class="hljs-attribute">line-height</span>: <span class="hljs-number">24px</span>;
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">400</span>;
  <span class="hljs-attribute">font-synthesis</span>: none;
  <span class="hljs-attribute">text-rendering</span>: optimizeLegibility;
  <span class="hljs-attribute">-webkit-font-smoothing</span>: antialiased;
  <span class="hljs-attribute">-moz-osx-font-smoothing</span>: grayscale;
  <span class="hljs-attribute">-webkit-text-size-adjust</span>: <span class="hljs-number">100%</span>;
}
​
<span class="hljs-selector-tag">a</span> {
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
}
<span class="hljs-selector-tag">a</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">cursor</span>: pointer;
}
​
<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">min-height</span>: <span class="hljs-number">100vh</span>;
}
</code></pre>
<h3 id="heading-install-react-router"><strong>Install React Router</strong></h3>
<p>In order to enable <a target="_blank" href="https://reactrouter.com/en/main/start/overview#client-side-routing">client-side routing</a> in our app, we will need to install and configure <a target="_blank" href="https://reactrouter.com/en/main">React Router</a> by running the following command:</p>
<pre><code class="lang-bash">npm install react-router-dom
</code></pre>
<p>Since <code>src/main.jsx</code> is our entry point, we need to import <a target="_blank" href="https://reactrouter.com/en/main/router-components/browser-router"><code>BrowserRouter</code></a> and wrap the <code>App</code> component within it. Doing so will enable client-side routing for our entire web app.</p>
<p>Open <code>src/main.jsx</code> and replace its contents with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>
<span class="hljs-keyword">import</span> { BrowserRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'./index.css'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'bootstrap/dist/css/bootstrap.min.css'</span>;
​
ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)).render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">BrowserRouter</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">BrowserRouter</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>
)
</code></pre>
<p>With React Router installed and configured, we can begin to build out the other pages for the storefront.</p>
<h2 id="heading-connect-the-react-storefront-to-the-medusa-server"><strong>Connect the React Storefront to the Medusa Server</strong></h2>
<p>The <a target="_blank" href="https://www.npmjs.com/package/@medusajs/medusa-js">Medusa JS Client</a> is an <a target="_blank" href="https://www.ibm.com/cloud/blog/sdk-vs-api">SDK</a> that provides an easy way to access the Medusa API from a web application. This Medusa JS client is an alternative to interacting with the <a target="_blank" href="https://docs.medusajs.com/api/store">REST APIs</a>.</p>
<p>Run the following command to install the Medusa JS Client:</p>
<pre><code class="lang-bash">npm install @medusajs/medusa-js
</code></pre>
<p>Medusa uses CORS to only allow specific origins to access the server. By default, the Medusa server is configured to allow access to the storefront on port <code>8000</code>. Because the default port for Vite is <code>5713</code>, you need to <a target="_blank" href="https://vitejs.dev/config/">configure Vite</a> to point to the desired port number.</p>
<p>To address this, add the <a target="_blank" href="https://vitejs.dev/config/server-options.html#server-port"><code>server.port</code></a> option to the <code>vite.config.js</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>
​
<span class="hljs-comment">// https://vitejs.dev/config/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">plugins</span>: [react()],
  <span class="hljs-attr">server</span>: {
    <span class="hljs-attr">port</span>: <span class="hljs-number">8000</span>,
  },
})
</code></pre>
<p>Restart the Vite dev server to reflect the changes made in the config file, and you can now open a new browser tab at <a target="_blank" href="http://localhost:8000/"><code>http://localhost:8000/</code></a>.</p>
<h3 id="heading-create-medusa-js-client-utility"><strong>Create Medusa JS Client Utility</strong></h3>
<p>Create a new file at the root of the project called <code>.env</code> and add the following environment variable that points to the URL of the Medusa server:</p>
<pre><code class="lang-bash">VITE_MEDUSA_SERVER_URL=<span class="hljs-string">"http://localhost:9000"</span>
</code></pre>
<p>Vite requires all <a target="_blank" href="https://vitejs.dev/guide/env-and-mode.html#env-files">env variables</a> that need to be exposed to the client source code to be prefixed with <code>VITE_</code> and exposes the environment variable to the client via <code>import.meta.env.VITE_MEDUSA_SERVER_URL</code>.</p>
<p>Create the file <code>src/utils/client.js</code> and add the following code to create a utility that will make accessing the instance of the Medusa JS Client reusable across the application:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Medusa <span class="hljs-keyword">from</span> <span class="hljs-string">"@medusajs/medusa-js"</span>
​
<span class="hljs-keyword">const</span> medusaClient = <span class="hljs-keyword">new</span> Medusa({ <span class="hljs-attr">baseUrl</span>: <span class="hljs-keyword">import</span>.meta.env.VITE_MEDUSA_SERVER_URL })
​
<span class="hljs-keyword">export</span> { medusaClient }
</code></pre>
<h2 id="heading-create-the-storefront-components"><strong>Create the Storefront Components</strong></h2>
<p>This section will cover building out the components that will compose the home page of product listings. Here is a list of the components that we will be building in this section:</p>
<ol>
<li><p><strong>NavHeader</strong> - Displays the logo, links, and shopping cart info.</p>
</li>
<li><p><strong>ProductCard</strong> - Displays metadata about the products.</p>
</li>
</ol>
<p>To start, create a new <code>components</code> folder in the <code>/src</code> directory to house all the components we will build for the storefront.</p>
<h3 id="heading-navheader-component"><strong>NavHeader Component</strong></h3>
<p>Create the file <code>src/components/NavHeader.jsx</code> and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>
<span class="hljs-keyword">import</span> Container <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Container'</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Navbar'</span>;
<span class="hljs-keyword">import</span> Nav <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Nav'</span>;
<span class="hljs-keyword">import</span> Badge <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Badge'</span>;
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NavHeader</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> cartCount = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'cartCount'</span>) ?? <span class="hljs-number">0</span>
​
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> <span class="hljs-attr">bg</span>=<span class="hljs-string">"dark"</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"dark"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Container</span> <span class="hljs-attr">fluid</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-25 d-flex align-items-center justify-content-between"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
                        <span class="hljs-attr">alt</span>=<span class="hljs-string">"Medusa logo"</span>
                        <span class="hljs-attr">src</span>=<span class="hljs-string">"https://raw.githubusercontent.com/aholland-work/react-medusa-storefront/main/src/assets/logo-dark.svg"</span>
                        <span class="hljs-attr">width</span>=<span class="hljs-string">"150"</span>
                    /&gt;</span>
​
                    <span class="hljs-tag">&lt;<span class="hljs-name">Navbar.Text</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-light fw-bold"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-decoration-none"</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/"</span>&gt;</span> 🛍️ Products<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Navbar.Text</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Nav</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Nav</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Navbar.Text</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-light fw-bold"</span>&gt;</span>
                        Cart
                        <span class="hljs-tag">&lt;<span class="hljs-name">Badge</span> <span class="hljs-attr">bg</span>=<span class="hljs-string">"success"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"ms-2"</span>&gt;</span>{cartCount}<span class="hljs-tag">&lt;/<span class="hljs-name">Badge</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"visually-hidden"</span>&gt;</span>{cartCount} items in the cart<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Navbar.Text</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Nav</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Container</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Navbar</span>&gt;</span></span>
    )
}
</code></pre>
<p>The <code>NavHeader</code> component contains a link back to the home page via the “Products” link and a total count of the number of items that have been added to the cart.</p>
<h3 id="heading-productcard-component"><strong>ProductCard Component</strong></h3>
<p>In the <code>components</code> directory, create a new file called <code>ProductCard.jsx</code> and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>
<span class="hljs-keyword">import</span> PropTypes <span class="hljs-keyword">from</span> <span class="hljs-string">'prop-types'</span>;
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Card'</span>;
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ProductCard</span>(<span class="hljs-params">props</span>) </span>{
    <span class="hljs-keyword">const</span> formattedPrice = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">'en-US'</span>, { <span class="hljs-attr">style</span>: <span class="hljs-string">'currency'</span>, <span class="hljs-attr">currency</span>: <span class="hljs-string">'USD'</span> }).format(props.price / <span class="hljs-number">100</span>)

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Card</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Card.Img</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"top"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{props.thumbnail}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">{props.title}</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Card.Body</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Card.Title</span>&gt;</span>{props.title}<span class="hljs-tag">&lt;/<span class="hljs-name">Card.Title</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Card.Text</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-success fw-bold'</span>&gt;</span>
                    {formattedPrice}
                <span class="hljs-tag">&lt;/<span class="hljs-name">Card.Text</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">products</span>/${<span class="hljs-attr">props.productId</span>}`}&gt;</span>View Details<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Card.Body</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Card</span>&gt;</span></span>
    )
}
​
ProductCard.propTypes = {
    <span class="hljs-attr">title</span>: PropTypes.string.isRequired,
    <span class="hljs-attr">thumbnail</span>: PropTypes.string.isRequired,
    <span class="hljs-attr">price</span>: PropTypes.number.isRequired,
    <span class="hljs-attr">productId</span>: PropTypes.string.isRequired
};
</code></pre>
<p>This component uses <a target="_blank" href="https://react-bootstrap.github.io/components/cards/">React Bootstrap’s Card component</a> to display metadata about each product.</p>
<h2 id="heading-create-the-home-page"><strong>Create the Home Page</strong></h2>
<p>The home page is the root route that uses the <a target="_blank" href="https://docs.medusajs.com/references/js-client/classes/ProductsResource#list">Medusa JS Client’s <code>list</code> method</a> to pull in a list of products and display information about them using the <code>ProductCard</code> component that was created earlier.</p>
<p>Create a new file in<code>src/routes/Home.jsx</code> and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Col <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Col'</span>;
<span class="hljs-keyword">import</span> Row <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Row'</span>;
<span class="hljs-keyword">import</span> ProductCard <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/ProductCard'</span>
<span class="hljs-keyword">import</span> { medusaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/client.js'</span>
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [products, setProducts] = useState([])
​
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> getProducts = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> medusaClient.products.list();
            setProducts(results.products)
        }
​
        getProducts()
    }, []);
​
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"my-4"</span>&gt;</span>All Products<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Row</span> <span class="hljs-attr">xs</span>=<span class="hljs-string">{1}</span> <span class="hljs-attr">sm</span>=<span class="hljs-string">{2}</span> <span class="hljs-attr">md</span>=<span class="hljs-string">{3}</span> <span class="hljs-attr">lg</span>=<span class="hljs-string">{4}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"g-4"</span>&gt;</span>
                    {products.map((product) =&gt; (
                        <span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{product.id}</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ProductCard</span>
                                <span class="hljs-attr">title</span>=<span class="hljs-string">{product.title}</span>
                                <span class="hljs-attr">productId</span>=<span class="hljs-string">{product.id}</span>
                                <span class="hljs-attr">price</span>=<span class="hljs-string">{product.variants[0].prices[1].amount}</span>
                                <span class="hljs-attr">thumbnail</span>=<span class="hljs-string">{product.thumbnail}</span> /&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
                    ))}
                <span class="hljs-tag">&lt;/<span class="hljs-name">Row</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    )
}
</code></pre>
<h3 id="heading-register-the-home-page-route"><strong>Register the Home Page Route</strong></h3>
<p>Next, leverage React Routers <a target="_blank" href="https://reactrouter.com/en/main/components/routes"><code>Routes</code></a> and <a target="_blank" href="https://reactrouter.com/en/main/route/route"><code>Route</code></a> components in order to register the pages needed for the react webshop.</p>
<p>Open up <code>src/App.jsx</code> and update it to include the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>
<span class="hljs-keyword">import</span> NavHeader <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/NavHeader'</span>
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/Home'</span>
​
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">NavHeader</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>} /&gt;
      <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App
</code></pre>
<p>With this setup, the <code>NavHeader</code> component will be visible across all our routes and the list of all products can be viewed at the root route.</p>
<h3 id="heading-test-the-home-page"><strong>Test the Home Page</strong></h3>
<p>Ensure that the Medusa server and the storefront are running and navigate to <a target="_blank" href="http://localhost:8000/"><code>http://localhost:8000/</code></a> to see a preview of the home page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673399826780/e57dee42-872f-4f03-afe2-f41f22031987.jpeg" alt="List of all products on the home page" class="image--center mx-auto" /></p>
<h2 id="heading-create-the-product-page"><strong>Create the Product Page</strong></h2>
<p>In the <code>routes</code> folder, create a new file called <code>Product.jsx</code> and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { useParams } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>
<span class="hljs-keyword">import</span> Container <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Container'</span>;
<span class="hljs-keyword">import</span> Row <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Row'</span>;
<span class="hljs-keyword">import</span> Col <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Col'</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Button'</span>;
<span class="hljs-keyword">import</span> { medusaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/client.js'</span>
​
<span class="hljs-keyword">const</span> getFormattedPrice = <span class="hljs-function">(<span class="hljs-params">amount</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">'en-US'</span>, { <span class="hljs-attr">style</span>: <span class="hljs-string">'currency'</span>, <span class="hljs-attr">currency</span>: <span class="hljs-string">'USD'</span> }).format(amount / <span class="hljs-number">100</span>);
}
​
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Add functions to handle the add to cart functionality</span>
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Product</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> { id } = useParams();
   <span class="hljs-keyword">const</span> [product, setProduct] = useState({})
​
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> getIndividualProduct = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> medusaClient.products.retrieve(id);
            setProduct(results.product)
        }
​
        getIndividualProduct()
    }, []);
​
    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-5"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Container</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Row</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">Col</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"500px"</span>
                          <span class="hljs-attr">alt</span>=<span class="hljs-string">{product.title}</span>
                          <span class="hljs-attr">src</span>=<span class="hljs-string">{product.thumbnail}</span> /&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-flex justify-content-center flex-column"</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{product.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-4 text-success fw-bold"</span>&gt;</span>{getFormattedPrice(product.variants?.[0]?.prices?.[1]?.amount)}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-5"</span>&gt;</span>{product.description}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"success"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"lg"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> { console.log("Add to cart") }}&gt;Add to cart<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">Row</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">Container</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
    )
}
</code></pre>
<p>This code takes the ID in the URL’s query param and uses the <a target="_blank" href="https://docs.medusajs.com/references/js-client/classes/ProductsResource#retrieve">Medusa JS Client’s <code>retrieve</code> method</a> to obtain information about an individual product.</p>
<h3 id="heading-register-the-product-page-route"><strong>Register the Product Page Route</strong></h3>
<p>Re-open <code>src/App.jsx</code>, import the <code>Product</code> page component, and add a new React Router <code>Route</code> that will point to the individual product page:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>
<span class="hljs-keyword">import</span> NavHeader <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/NavHeader'</span>
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/Home'</span>
<span class="hljs-keyword">import</span> Product <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/Product'</span>
​
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">NavHeader</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>} /&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"products/:id"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Product</span> /&gt;</span>} /&gt;
      <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App
</code></pre>
<h3 id="heading-test-the-product-page"><strong>Test the Product Page</strong></h3>
<p>To test this out, click on the “View Details” button for the “Medusa Sweatpants” and you will be directed to the product's details page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673399911546/2a653a28-196d-46d4-8aed-b4aac4a94c84.jpeg" alt="Product page for the “Medusa Sweatpants”" class="image--center mx-auto" /></p>
<h2 id="heading-create-add-to-cart-functionality"><strong>Create Add to Cart Functionality</strong></h2>
<p>To wire up the logic for the add-to-cart functionality, you need to check if a cart was previously created or not.</p>
<p>For this example, if the <code>cartID</code> is present, then we call the <a target="_blank" href="https://docs.medusajs.com/guides/carts-in-medusa/#add-line-item-to-the-cart">add the line item to the cart</a> and store the cart’s item count in local storage under the <code>cartCount</code> field. If the <code>cartID</code> is not present, then we <a target="_blank" href="https://docs.medusajs.com/guides/carts-in-medusa/#create-a-cart">create a new cart</a> and store the newly generated ID in local storage. The <code>cartCount</code> is used to update the display count in the navigation header.</p>
<p>Re-open <code>Product.jsx</code> in the <code>routes</code> folder and update the file to include the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { useParams } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>
<span class="hljs-keyword">import</span> Container <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Container'</span>;
<span class="hljs-keyword">import</span> Row <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Row'</span>;
<span class="hljs-keyword">import</span> Col <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Col'</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">'react-bootstrap/Button'</span>;
<span class="hljs-keyword">import</span> { medusaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/client.js'</span>
​
<span class="hljs-keyword">const</span> getFormattedPrice = <span class="hljs-function">(<span class="hljs-params">amount</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.NumberFormat(<span class="hljs-string">'en-US'</span>, { <span class="hljs-attr">style</span>: <span class="hljs-string">'currency'</span>, <span class="hljs-attr">currency</span>: <span class="hljs-string">'USD'</span> }).format(amount / <span class="hljs-number">100</span>);
}
​
<span class="hljs-keyword">const</span> addProduct = <span class="hljs-keyword">async</span> (cartId, product) =&gt; {
    <span class="hljs-keyword">const</span> { cart } = <span class="hljs-keyword">await</span> medusaClient.carts.lineItems.create(cartId, {
        <span class="hljs-attr">variant_id</span>: product.variants[<span class="hljs-number">0</span>].id, <span class="hljs-comment">//For simplicities sake only adding the first variant</span>
        <span class="hljs-attr">quantity</span>: <span class="hljs-number">1</span>
    })
    <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'cartCount'</span>, cart.items.length)
    <span class="hljs-built_in">window</span>.location.reload()
}
​
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Product</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Get the product ID param from the URL.</span>
    <span class="hljs-keyword">const</span> { id } = useParams();
    <span class="hljs-keyword">const</span> [product, setProduct] = useState({})
    <span class="hljs-keyword">const</span> [regionId, setRegionId] = useState(<span class="hljs-string">""</span>)
​
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> getIndividualProduct = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> medusaClient.products.retrieve(id);
            setProduct(results.product)
        }
​
        <span class="hljs-keyword">const</span> getRegions = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> medusaClient.regions.list()
            setRegionId(results.regions[<span class="hljs-number">1</span>].id)
        }
​
        getIndividualProduct()
        getRegions()
    }, []);
​
    <span class="hljs-keyword">const</span> handleAddToCart = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">const</span> cartId = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'cartId'</span>);
​
        <span class="hljs-keyword">if</span> (cartId) {
            <span class="hljs-comment">//A cart was previously created so use the cartId found in localStorage</span>
            addProduct(cartId, product)
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">//Create a cart if there isn't a pre-existing one</span>
            <span class="hljs-keyword">const</span> { cart } = <span class="hljs-keyword">await</span> medusaClient.carts.create({ <span class="hljs-attr">region_id</span>: regionId })
            <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'cartId'</span>, cart.id);
​
            <span class="hljs-comment">//Use the newly generated cart's ID</span>
            addProduct(cart.id, product)
        }
    }
​
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-5"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Container</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Row</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Col</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"500px"</span>
                            <span class="hljs-attr">alt</span>=<span class="hljs-string">{product.title}</span>
                            <span class="hljs-attr">src</span>=<span class="hljs-string">{product.thumbnail}</span> /&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Col</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"d-flex justify-content-center flex-column"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{product.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-4 text-success fw-bold"</span>&gt;</span>{getFormattedPrice(product.variants?.[0]?.prices?.[1]?.amount)}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-5"</span>&gt;</span>{product.description}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"success"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"lg"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleAddToCart}</span>&gt;</span>Add to Cart<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Col</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Row</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Container</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
    )
}
</code></pre>
<p>The code above adds the following functionality:</p>
<ol>
<li><p><code>addProduct</code> async function that uses the <code>medusaClient</code> utility to add a product to a cart.</p>
</li>
<li><p><code>getRegions</code> function to grab a <a target="_blank" href="https://docs.medusajs.com/references/js-client/classes/RegionsResource#list">list of all the regions</a> and set it to the US region found at the first index in the <code>regions</code> array. This ID then gets stored in the <code>regionId</code> state variable.</p>
</li>
<li><p><code>handleAddToCart</code> function that is invoked when the “Add to Cart” button is clicked.</p>
</li>
</ol>
<p>For simplicities sake, the code adds the first variant of a product. This means that a product can only be added to a cart once. In addition, a page reload is initiated in order to pick up the new cart count. This decision was intentional to keep the project simple but know that a real-world app could allow the ability to select different variants and add more advanced capabilities.</p>
<h3 id="heading-test-add-to-cart-functionality"><strong>Test Add to Cart Functionality</strong></h3>
<p>To test out the functionality, click on the “View Details” link for any item to navigate to the product details page. From there, click on the “Add to Cart” button which will initiate a page refresh and update the cart’s badge count in the navigation header.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673399968727/d7aa0971-2267-4c5b-8eab-f21df1d56c1a.png" alt="Cart count increment" class="image--center mx-auto" /></p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Throughout this tutorial, you learned about the <a target="_blank" href="https://docs.medusajs.com/admin/quickstart">Medusa Admin</a>, <a target="_blank" href="https://docs.medusajs.com/quickstart/quick-start">Medusa Server</a>, and how you can use the <a target="_blank" href="https://docs.medusajs.com/js-client/overview/">Medusa JS Client</a> to create a sample ecommerce store with React. A lot of material was covered, however, there is still so much more that you can generate with Medusa such as:</p>
<ul>
<li><p>Expanding upon the “add to cart” functionality and building a shopping cart screen that <a target="_blank" href="https://docs.medusajs.com/guides/carts-in-medusa/#retrieve-a-cart">retrieves a cart</a> and displays a list of all the products that have been added to it.</p>
</li>
<li><p>Payment integrations with <a target="_blank" href="https://docs.medusajs.com/add-plugins/stripe">Stripe</a>.</p>
</li>
<li><p><a target="_blank" href="https://docs.medusajs.com/advanced/storefront/how-to-implement-checkout-flow/">Implementing a Checkout Flow</a></p>
</li>
</ul>
<p>Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via <a target="_blank" href="https://discord.gg/F87eGuwkTp">Discord</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Dev Retro 2022]]></title><description><![CDATA[Introduction 🎬
When I read the Dev Retro announcement in Hashnode's Townhall and how it was an opportunity to "reflect on your journey as a developer and share your experiences from the past year", I was super excited because I had been waffling on ...]]></description><link>https://blog.alyssaholland.me/dev-retro-2022</link><guid isPermaLink="true">https://blog.alyssaholland.me/dev-retro-2022</guid><category><![CDATA[#DevRetro2022]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Mon, 19 Dec 2022 16:45:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670989826840/_r6O_o7hV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction 🎬</h2>
<p>When I read the <a target="_blank" href="https://townhall.hashnode.com/announcing-dev-retro-2022">Dev Retro announcement</a> in Hashnode's Townhall and how it was an opportunity to "<strong><em>reflect on your journey as a developer and share your experiences from the past year"</em></strong>, I was super excited because I had been waffling on writing a reflection blog post and this was the catalyst I needed.</p>
<p>With the hustle and bustle of life, I think it's very important to pause and take the time to look back to appreciate all that was accomplished and experienced throughout the year. Although this post will be a personal recollection of thoughts and milestones, I hope this article will encourage you to take the time to write a retrospective of your own 🙂</p>
<p>Without further ado, here goes the 2022 rendition of my developer retrospective!</p>
<h2 id="heading-guest-writing">Guest Writing ✍🏽</h2>
<p>Hands down, guest writing has been my biggest objective this year. Since I had a solid year of publishing regularly under my belt, I figured it was time to try and venture out into writing for some of the top tech publications.</p>
<p>It all started with my first-ever guest post with Smashing Magazine last December where I wrote about a niche topic: "<a target="_blank" href="https://www.smashingmagazine.com/2021/12/create-custom-range-input-consistent-browsers/">Creating A Custom Range Input That Looks Consistent Across All Browsers</a>". From there, I went on to write for DigitalOcean in May on "<a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-test-a-react-app-with-jest-and-react-testing-library">How To Test a React App with Jest and React Testing Library</a><strong>".</strong> Last but not least, I wrote an article for LogRocket in October on "<a target="_blank" href="https://blog.logrocket.com/building-design-system-radix/">Building a design system with Radix</a><strong>".</strong></p>
<p>In addition to writing for three different tech publications, I've also written guides for a form backend service called <a target="_blank" href="https://usebasin.com/guides">Basin</a>, and will soon have a tutorial published for a popular open-source project. That makes the total tally for guest writing to 5 companies. Out of those 5, I only applied for writing opportunities with Smashing Magazine and DigitalOcean. The remainder of the opportunities all presented themselves due to my prior writing history.</p>
<p>Experiencing these different writing opportunities has taught me that the process can be pretty extensive because you have to adhere to the tone and style guide of the individual company. In addition, you typically need to have an accompanying project that shows how to use a certain aspect of technology so the planning and creating process of all that takes quite a bit of time. Except for the guides I wrote for Basin, I would say that every article I've written for a tech publication has taken a minimum of ~20 hours each 🤯</p>
<p>Overall, I'm extremely appreciative of these guest writing opportunities as I've learned a lot throughout the process and have gained confidence in myself as a technical writer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671244309915/8GSm_SDTN.png" alt="Cover photos for the three articles written for tech publications" class="image--center mx-auto" /></p>
<h2 id="heading-personal-blog">Personal Blog 📜</h2>
<p>Because my primary focus was on guest writing, the frequency at which I was posting on my blog took a bit of a dip. Even though I would've liked to have posted more personal content, I'm ok with this statistic because it was an opportunity cost and I think it was worth it. Although the total <strong><em>quantity</em></strong> of articles published may have been less than I originally wanted, the <strong><em>quality</em></strong> of the content within the articles I did publish was high so I'm pleased with the outcome.</p>
<h3 id="heading-stats-and-analytics">Stats and Analytics 📊</h3>
<ul>
<li><p><strong>Total # of Articles Written:</strong> 12</p>
</li>
<li><p><strong>Highest Viewed Article:</strong> <a target="_blank" href="https://blog.alyssaholland.me/7-terminal-tools-and-emulators-to-boost-development-productivity">7 Free Terminal Tools and Emulators to Boost Development Productivity</a></p>
<ul>
<li>~7.5K views at the time of writing this retro 😲</li>
</ul>
</li>
<li><p><strong>Article with the most engagement:</strong> <a target="_blank" href="https://blog.alyssaholland.me/show-your-work">The Book that Encouraged me to Start my Blog</a></p>
<ul>
<li><p><strong>Total # of Comments</strong>: 7</p>
</li>
<li><p><strong>Total # of Reactions</strong>: 151</p>
</li>
</ul>
</li>
<li><p><strong>Total # of Pageviews in 2022:</strong> ~15,000</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671247749150/SbqyqAhds.jpeg" alt="Hashnode advanced analytics view for this year" class="image--center mx-auto" /></p>
<p><cite>Hashnode's "Advanced Analytics" view for this year</cite></p>
<h2 id="heading-work-life">Work Life 🏦</h2>
<p>July marked my first full year working as a <strong>Senior Software Engineer</strong> and I can finally say that I feel (fairly) comfortable with that title. Imposter Syndrome is something that I struggle with and when I initially got hired as a Senior Engineer I was worried I wouldn't be able to live up to the job requirements. Thankfully, I work for a company that cultivates an encouraging and supportive culture so it has been a great place to work as I have been getting my bearings in my new senior role.</p>
<h3 id="heading-lessons-learned">Lessons Learned 👩🏽‍🎓</h3>
<p>I find I'm happiest in my job when I'm continually learning new things and being intellectually challenged. Based on that here are some of the top things I learned throughout the year:</p>
<ul>
<li><p>I learned that my accessibility fundamentals are pretty solid. I worked on a ticket and inadvertently identified areas where we could add some accessibility improvements to our app's workflow.</p>
</li>
<li><p>I found a use case for the <a target="_blank" href="https://reactjs.org/docs/hooks-reference.html#usereducer">useReducer hook</a> when dealing with more complex state management while implementing an autocomplete search feature.</p>
</li>
<li><p>Scheduled meetings with a Principal Engineer on the team to learn more about authentication and authorization. Since I work primarily as a Front-End Engineer, these meetings have sort of turned into a mentorship as I look to gain a larger breadth of knowledge about general backend architecture topics.</p>
</li>
<li><p>Currently, working on building a component library for a reports service and I've learned a plethora of things including but not limited to:</p>
<ul>
<li><p><a target="_blank" href="https://storybook.js.org/">Storybook</a></p>
</li>
<li><p>Setting up a project with <a target="_blank" href="https://eslint.org/">ESLint</a>, <a target="_blank" href="https://prettier.io/">Prettier</a>, <a target="_blank" href="https://stylelint.io/">Stylelint</a>, and <a target="_blank" href="https://www.npmjs.com/package/lint-staged">lint-staged</a></p>
</li>
<li><p>Bundling a component library into a consumable package</p>
</li>
</ul>
</li>
<li><p><a target="_blank" href="https://kentcdodds.com/blog/compound-components-with-react-hooks">Compound Components</a> in React. This is a really cool pattern as it allows for more rendering flexibility and extensibility. It's definitely a pattern I might want to try and leverage more in the future.</p>
</li>
</ul>
<h2 id="heading-side-projects">Side Projects 👩🏽‍💻</h2>
<p>The main side project I completed this year was my <a target="_blank" href="https://github.com/Cool-Runningz/roadtrip-fm">RoadTrip.FM</a> 🚗 📻 project for the <a target="_blank" href="https://blog.alyssaholland.me/roadtripfm">Netlify x Hashnode Hackathon</a>. Building this app was a lot of fun as it's something I've wanted to build for years. <a target="_blank" href="https://roadtripfm.live/">RoadTrip.FM</a> was the first full-stack app I'd ever built and I learned a lot along the way. Ironically, I've found that the hackathon timelines apply a healthy pressure that gives me the extra boost to finally bring my project ideas to fruition.</p>
<p>Although not an official side project, I also participated in <a target="_blank" href="https://blog.alyssaholland.me/hacktoberfest-2021-2022">Hacktoberfest</a> for the second year in a row so that was nice to be able to contribute to the open-source community.</p>
<p>All my other side projects were demos that I used within my guest writing posts. This included <a target="_blank" href="https://github.com/Cool-Runningz/doggy-directory">Doggy Directory</a> which is a sample app for my <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-test-a-react-app-with-jest-and-react-testing-library">DigitalOcean article</a> on React Testing Library and my sample <a target="_blank" href="https://github.com/Cool-Runningz/radix-component-library">Radix component library</a> that I built for my <a target="_blank" href="https://blog.logrocket.com/building-design-system-radix/">LogRocket article</a>.</p>
<p>Oh! I'd be remiss if I didn't mention that building LEGO has been a new "side project" for me. I know this doesn't <em>technically</em> meet the criteria for a development side project but it's close enough and I needed some excuse to integrate my recent LEGO <s>obsession</s> hobby 😆</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671314526392/jHn1Na9z4.jpeg" alt="LEGO Sonic the Hedgehog build" class="image--center mx-auto" /></p>
<p><cite>LEGO Sonic the Hedgehog build</cite></p>
<h2 id="heading-aspirations-for-2023">Aspirations for 2023 🆕</h2>
<p>As I prepare to wrap up 2022, I've already begun thinking about what I want to accomplish in 2023. I think it's important to have a healthy drive for what you want to attain in the future. I don't expect to complete all these items, but here's a list of some goals I would like to achieve next year:</p>
<ul>
<li><p><strong>Create a course</strong> 👩🏽‍🏫 - Component Libraries and Accessibility are two things I'm very passionate about so it would be exciting to try and create a course that combined both topics. I've already pitched an outline to a company on this concept so stay tuned for more information 👀.</p>
</li>
<li><p><strong>Start a newsletter</strong> 🗞️ - Technically, I already have a default newsletter setup within Hashnode, however, I think I'm ready for something more custom and tailored. I'm not sure what platform I will go with but my plans are to research options like <a target="_blank" href="https://www.getrevue.co/">Revue</a>, <a target="_blank" href="https://convertkit.com/">ConvertKit</a>, and <a target="_blank" href="https://substack.com/">Substack</a>.</p>
</li>
<li><p><strong>Start a podcast</strong> 🎙️ - My sister and I have been talking about starting a podcast for a while now so maybe next year will finally be when we start one. My sister isn't technical so the topics we cover wouldn't be developer related but we're still ironing out the details on what the exact subject would be.</p>
<ul>
<li>My ultimate goal is to create a technical podcast, however, I would need a co-host so until I find that person, this particular podcast will have to wait until a later time.</li>
</ul>
</li>
<li><p><strong>Develop more side projects</strong> - I have a large backlog of projects that I want to build but haven't had the time to work on. I hope that I can bring some of these ideas to fruition while still maintaining a regular writing schedule.</p>
</li>
</ul>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>Thanks for taking the time to read about my journey in 2022. It was a fun experience to go back down memory lane and reflect on all the things I learned and accomplished throughout the year.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[How to Test npm Packages Locally]]></title><description><![CDATA[Currently, I'm developing a component library at work that will be consumed by our company's internal applications. Part of this process requires bundling the library and making sure that it can be installed in other applications. Before publishing t...]]></description><link>https://blog.alyssaholland.me/npm-yarn-link</link><guid isPermaLink="true">https://blog.alyssaholland.me/npm-yarn-link</guid><category><![CDATA[npm]]></category><category><![CDATA[Yarn]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Mon, 12 Dec 2022 13:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670459102663/W-ecurFZ6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Currently, I'm developing a component library at work that will be consumed by our company's internal applications. Part of this process requires bundling the library and making sure that it can be installed in other applications. Before publishing the package, however, I wanted to test importing the components from a local version of the bundled code.</p>
<p>Turns out that testing a local version of a package is a fairly straightforward process with <a target="_blank" href="https://classic.yarnpkg.com/lang/en/docs/cli/link/">yarn link</a> (There is also an <a target="_blank" href="https://docs.npmjs.com/cli/v8/commands/npm-link">npm link</a> equivalent). In this blog post, you will learn what <code>npm/yarn link</code> is, how it works, and how you can incorporate it into your own projects.</p>
<blockquote>
<p><strong>Note:</strong> This tutorial assumes that you already have a bundled version of your code using a module bundler like <a target="_blank" href="https://rollupjs.org/guide/en/">rollup.js</a> or <a target="_blank" href="https://webpack.js.org/">Webpack</a>.</p>
</blockquote>
<h2 id="heading-what-is-yarn-link">What is <code>yarn link</code>? 🔗</h2>
<p><code>yarn link</code> is a CLI command that allows developers to <strong><em>link</em></strong> a package in one project to other separate projects. This is particularly useful when you are developing a package in one repository but want to test a local version of that package in another application before publishing it. <strong>When a package is linked, you are able to test and build iteratively as revisions are automatically synced whenever a new bundle is generated.</strong></p>
<p>Package linking is a two-step process:</p>
<p><strong>Step 1</strong></p>
<ul>
<li>Run <code>yarn link</code> in the root directory of the package that you want to link. This creates a global <a target="_blank" href="https://www.digitalocean.com/community/tutorials/workflow-symbolic-links">symbolic link</a> (symlink) for the package and provides a reference point for the consuming project.</li>
</ul>
<p><strong>Step 2</strong></p>
<ul>
<li>Run <code>yarn link &lt;package-name&gt;</code> in the root directory of the consuming project. This will create a symlink in the consuming projects <code>node_modules</code> folder that points to the globally-installed <code>&lt;package-name&gt;</code>.</li>
</ul>
<h2 id="heading-package-linking-example">Package Linking Example 📦</h2>
<p>Assume you have two projects: <code>my-cool-library</code> and <code>consuming-webapp</code>.</p>
<p><code>my-cool-library</code> provides a helper function, <code>myCoolFunction</code>, that you want to import into the <code>consuming-webapp</code> project.</p>
<ol>
<li><p>In the root directory of <code>my-cool-library</code> run the following command to create the global symlink:</p>
<pre><code class="lang-bash">yarn link
</code></pre>
</li>
<li><p>In the root directory of <code>consuming-webapp</code> run the following command to link to <code>my-cool-library</code> locally:</p>
<pre><code class="lang-bash">yarn link my-cool-library
</code></pre>
<ul>
<li>Now if you were to navigate to the <code>node_modules</code> folder in <code>consuming-webapp</code>, you would see the symlink point to the local version of <code>my-cool-library</code>.</li>
</ul>
</li>
<li><p>With the linking process successfully completed, you could now import the <code>myCoolFunction</code> helper function in <code>consuming-webapp</code> like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { myCoolFunction } <span class="hljs-keyword">from</span> <span class="hljs-string">"my-cool-library"</span>;
</code></pre>
</li>
</ol>
<blockquote>
<p><strong>Note:</strong> By default, package dependencies linked in this way are <strong><em>not</em></strong> saved to <code>package.json</code>. This is based on the assumption that the intention is to have a link stand in for a regular non-link dependency. This means that your IDE may show an error when attempting to import from the package, however, you can ignore this and your code should render just fine.</p>
</blockquote>
<h2 id="heading-unlinking-a-package">Unlinking a Package 🔓</h2>
<p><a target="_blank" href="https://classic.yarnpkg.com/en/docs/cli/unlink">yarn unlink</a> is an aptly named command that handles removing (unlinking) the symlinked package. Similar to the linking method, unlinking a package is also a two-step process that is essentially a reversal of the linking workflow:</p>
<p><strong>Step 1:</strong></p>
<ul>
<li>Run <code>yarn unlink</code> in the root directory of the package that you want to dissociate.</li>
</ul>
<p><strong>Step 2</strong></p>
<ul>
<li>Run <code>yarn unlink &lt;package-name&gt;</code> in the root directory of the consuming project.</li>
</ul>
<p>Based on the example in the previous section, that would mean running <code>yarn unlink</code> in the <code>my-cool-library</code> project and <code>yarn unlink my-cool-library</code> in the <code>consuming-webapp</code> project.</p>
<p>Both steps combined remove the global symlink for the package and delete the reference point from the consuming project, therefore completing the unlinking process. Unlinking is important to remember as a cleanup mechanism when you're done performing local testing.</p>
<h1 id="heading-el-fin">El Fin 👋🏽</h1>
<p>Hopefully, you learned something new about how you can leverage package linking to test packages locally.</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
<p><strong>Resources</strong></p>
<ul>
<li><p><a target="_blank" href="https://docs.npmjs.com/cli/v8/commands/npm-link">npm link</a></p>
</li>
<li><p><a target="_blank" href="https://classic.yarnpkg.com/lang/en/docs/cli/link/">yarn link</a> and <a target="_blank" href="https://classic.yarnpkg.com/en/docs/cli/unlink">yarn unlink</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=VuysNccCnEQ">Brad Garropy - 🔗 developing npm packages locally</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=6R699AQYH74">LevelUp Tuts - How to Use a Local Javascript Package in a Real Project</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Hacktoberfest  2021 & 2022]]></title><description><![CDATA[Hacktoberfest is an annual event that has been hosted by DigitalOcean for almost a decade. The purpose of Hacktoberfest is to encourage people to contribute to open-source projects throughout the month of October. Much of the technology and applicati...]]></description><link>https://blog.alyssaholland.me/hacktoberfest-2021-2022</link><guid isPermaLink="true">https://blog.alyssaholland.me/hacktoberfest-2021-2022</guid><category><![CDATA[Open Source]]></category><category><![CDATA[hacktoberfest2022]]></category><category><![CDATA[hacktoberfest]]></category><category><![CDATA[hacktoberfest2021]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Alyssa Holland]]></dc:creator><pubDate>Tue, 08 Nov 2022 15:00:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667667535141/OHfJ4tZGw.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://hacktoberfest.com/">Hacktoberfest</a> is an annual event that has been hosted by DigitalOcean for almost a decade. The purpose of Hacktoberfest is to encourage people to contribute to open-source projects throughout the month of October. Much of the technology and applications used in the world rely heavily on the hard work done throughout open-source projects. Hacktoberfest is a way to give back to the open-source community that has given so much to us. </p>
<p>My inaugural Hacktoberfest was in 2021, and it was a lot of fun to participate in the event. I liked the idea of jumping into a random codebase and seeing how quickly I could get up to speed and contribute a successful pull request (PR). I've found that participating is a great way to practice and sharpen your development skills. Since this is my second year participating, I figured I would document the process for the past two events and share some of the lessons I learned and cool projects I worked on along the way.</p>
<h2 id="heading-hacktoberfest-2022">Hacktoberfest 2022</h2>
<p>With a little more experience under my belt from my first Hacktoberfest, I entered this year's event excited to contribute to some new projects. Here is a depiction of October's events.</p>
<h3 id="heading-awesome-uses">Awesome Uses 🕶</h3>
<p><a target="_blank" href="https://twitter.com/wesbos">Wes Bos</a> hosts a popular site, <a target="_blank" href="https://uses.tech/">uses.tech</a>, that contains a massive list of developers' "uses" pages. A "uses" page is a place to share information showcasing developer setups, equipment, software, and configurations. I have a <a target="_blank" href="https://blog.alyssaholland.me/developer-toolbox">dedicated article</a> that covers the items contained on my "uses" page. My contribution was straightforward as I added a reference to my page on the <a target="_blank" href="https://uses.tech/">uses.tech</a> site and <a target="_blank" href="https://github.com/wesbos/awesome-uses#readme">README</a>. </p>
<p>It's cool to have a fun repo like this that provides a site where developers can share their developer setup with others. I highly recommend adding it to this list if you have a "uses" page of your own.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667508963467/cV70EUN4p.jpeg" alt="awesome-uses-card.jpeg" class="image--center mx-auto" /></p>
<p><cite>Card generated on the uses.tech site</cite></p>
<ul>
<li>🛠️ <strong>Technologies Used:</strong> JavaScript</li>
<li>📦 <strong>GitHub Repo:</strong> https://github.com/wesbos/awesome-uses</li>
<li>⬇️ <strong>Pull Request:</strong> https://github.com/wesbos/awesome-uses/pull/1448</li>
</ul>
<h3 id="heading-json-hero">JSON Hero 🦸‍♂️</h3>
<p><a target="_blank" href="https://jsonhero.io/">JSON Hero</a> is a JSON explorer for the web that makes reading and understanding JSON files easier. It achieves this by providing a clean interface packed with extra features like automatically inferring the contents of strings to provide previews and the ability to view JSON in multiple views (Column View, Tree View, and more).</p>
<p>While perusing the repo, I came across <a target="_blank" href="https://github.com/apihero-run/jsonhero-web/issues/38">this issue</a> asking to add a skeleton loader to the preview section to prevent content jumping. <a target="_blank" href="https://blog.prototypr.io/skeleton-loader-an-overview-purpose-usage-and-design-173b5340d0e1">Skeleton loaders</a> are a popular loading strategy used across a lot of sites and, having implemented them on my own website I felt confident in my ability to resolve this issue. After getting some clarification about the placement of the loader, I was off to the races. Conveniently the JSON Hero codebase felt very familiar since it was built with Remix and used Tailwind CSS, the same technologies I used in my <a target="_blank" href="https://github.com/Cool-Runningz/roadtrip-fm">roadtrip-fm</a> project.</p>
<p>Tailwind conveniently has an <a target="_blank" href="https://tailwindcss.com/docs/animation#pulse">animate-pulse</a> class that simulates the look and feel of a skeleton loader, so I leveraged that and, without too much effort, I was able to add a successful loader and got my second PR merged in! </p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Light Mode</td><td>Dark Mode</td></tr>
</thead>
<tbody>
<tr>
<td><img src="https://user-images.githubusercontent.com/6391149/194773263-98ffc2b4-5467-433d-b806-cd9f7a433be4.gif" alt="Screen Cast 2022-10-09 at 2 08 08 PM" /></td><td><img src="https://user-images.githubusercontent.com/6391149/194773317-9f9982d7-4de1-4aa3-acaf-7e193bd80de4.gif" alt="Screen Cast 2022-10-09 at 2 08 45 PM" /></td></tr>
</tbody>
</table>
</div><p><cite>Skeleton loader screenshots in light and dark mode</cite></p>
<ul>
<li>🛠️ <strong>Technologies used:</strong> 💿 <a target="_blank" href="https://remix.run/">Remix</a>, React, <a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a></li>
<li>📦 <strong>GitHub Repo:</strong> https://github.com/apihero-run/jsonhero-web</li>
<li>⬇️ <strong>Pull Request:</strong> https://github.com/apihero-run/jsonhero-web/pull/119</li>
</ul>
<h3 id="heading-fig">Fig 🍐</h3>
<p><a target="_blank" href="https://fig.io/">Fig</a> is an awesome tool that provides autocomplete for the terminal. I've <a target="_blank" href="https://blog.alyssaholland.me/7-terminal-tools-and-emulators-to-boost-development-productivity#heading-1-fig">written about Fig</a> before and it's easily one of my favorite tools in my 🧰 <a target="_blank" href="https://www.alyssaholland.com/uses/">developer toolbox</a>.</p>
<p>One of the interesting parts about Fig's architecture is that they've set things up so that the autocompletion commands or "specs" can be added from the open-source community. There is extensive <a target="_blank" href="https://fig.io/docs/getting-started">documentation</a> on this process, and I figured it would be a good opportunity to give back by contributing two new specs since I use this tool all the time.</p>
<p>The first spec I created was for the <a target="_blank" href="https://en.wikipedia.org/wiki/Seq_(Unix)"><code>seq</code> command</a>. The second spec was for the <code>mdls</code> command. These tasks got me more familiar with using the <a target="_blank" href="https://en.wikipedia.org/wiki/Man_page">man pages</a> to identify specific information about a CLI command. It was also interesting to see how the <a target="_blank" href="https://blog.alyssaholland.me/declarative-vs-imperative-programming#declarative-approach">declarative</a> syntax of Figs completion spec worked. </p>
<p>It took me a little while to get familiar with the completion spec syntax; however, by the second pull request, I was able to debug issues more quickly. Thanks to this work, I have a better understanding of the syntax which will help me with future contributions. </p>
<ul>
<li>🛠️ <strong>Technologies Used:</strong> JavaScript</li>
<li>📦 <strong>GitHub Repo:</strong> https://github.com/withfig/autocomplete</li>
</ul>
<p><strong>Pull Request #1</strong>: </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/withfig/autocomplete/pull/1603">https://github.com/withfig/autocomplete/pull/1603</a></div>
<p><strong>Pull Request #2:</strong></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/withfig/autocomplete/pull/1647">https://github.com/withfig/autocomplete/pull/1647</a></div>
<h2 id="heading-hacktoberfest-2021">Hacktoberfest 2021</h2>
<p>Since I didn't write about my experience in my inaugural Hacktoberfest, I wanted to include some blurbs about last year's contributions since I'm hoping to make this an annual tradition. </p>
<h3 id="heading-appsmith">Appsmith 🔒</h3>
<p><a target="_blank" href="https://www.appsmith.com/">Appsmith</a> is an open-source, low-code platform to build, ship, and maintain internal tools. On a side note, I've interacted with a few members of the team, and they were all super nice and friendly, so that's a nice plus 🙂</p>
<p>For last year's Hacktoberfest, I contributed two different PRs. The <a target="_blank" href="https://github.com/appsmithorg/appsmith/pull/8134">first PR</a> was a simple one around adding a tooltip to display when hovering over the "Scroll Content" label. The <a target="_blank" href="https://github.com/appsmithorg/appsmith/pull/8140">second PR</a> addressed a shifting layout display issue that would occur when clicking through the sorting options within the Table header. It also addressed adding a low-contrast icon color for better accessibility. </p>
<ul>
<li><p>🛠️ <strong>Technologies Used:</strong> React and 💅🏽<a target="_blank" href="https://www.styled-components.com/">styled-components</a></p>
</li>
<li><p>📦 <strong>GitHub Repo:</strong> https://github.com/appsmithorg/appsmith</p>
</li>
</ul>
<p><strong>Pull Request #1:</strong> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/appsmithorg/appsmith/pull/8134">https://github.com/appsmithorg/appsmith/pull/8134</a></div>
<p><strong>Pull Request #2:</strong> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/appsmithorg/appsmith/pull/8140">https://github.com/appsmithorg/appsmith/pull/8140</a></div>
<hr />

<h3 id="heading-datenanfragen">Datenanfragen 🇩🇪</h3>
<p><a target="_blank" href="https://www.datarequests.org/">Datenanfragen</a> is a non-profit association from Germany whose mission is to help you exercise your right to privacy. I also contributed two separate PRs to this project. The <a target="_blank" href="https://github.com/datenanfragen/website/pull/732">first PR</a> I addressed was around alphabetically sorting the countries and categories on the <a target="_blank" href="https://www.datarequests.org/suggest/">"Suggest" page</a>. This work involved using my knowledge of the <a target="_blank" href="https://blog.alyssaholland.me/the-basics-of-sorting-in-javascript">basics of sorting in JavaScript</a>.</p>
<p>The <a target="_blank" href="https://github.com/datenanfragen/website/pull/782">second PR</a> involved modifying the footnotes on the Hugo site (example <a target="_blank" href="https://www.datarequests.org/blog/honey-data-collection/#fnref:1">here</a>) to make them more accessible and user-friendly. Honestly, out of all the Hacktoberfest issues I have worked on so far, this has been my favorite because it involved using my knowledge of accessibility and provided a fun challenge for my brain. The final solution for this problem took some digging and research into Hugo and Preact. Overall it was fun to solve the problem and come up with a viable solution! There was also <a target="_blank" href="https://github.com/datenanfragen/website/pull/782#issue-1025855297">constructive communication</a> within the PR, and it was a nice end to my last PR for Hacktoberfest 2021. </p>
<ul>
<li>🛠️ <strong>Technologies Used:</strong> <a target="_blank" href="https://gohugo.io/">Hugo</a> and <a target="_blank" href="https://preactjs.com/">Preact</a></li>
<li>📦 <strong>GitHub Repo:</strong> https://github.com/datenanfragen/website</li>
</ul>
<p><strong>Pull Request #1:</strong> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/datenanfragen/website/pull/732">https://github.com/datenanfragen/website/pull/732</a></div>
<p><strong>Pull Request #2:</strong></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/datenanfragen/website/pull/782">https://github.com/datenanfragen/website/pull/782</a></div>
<h2 id="heading-el-fin">El Fin 👋🏽</h2>
<p>I hope this blog post encourages you to consider giving Hacktoberfest a try. The satisfaction you get from pouring back into the open-source community that has given developers so much is gratifying. If you have been a part of Hacktoberfest in the past, feel free to leave a comment and share some of your experiences 🙂</p>
<p>If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.</p>
<p>As always, thank you for reading, and happy coding!</p>
]]></content:encoded></item></channel></rss>