<?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" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Scott Scharl]]></title><description><![CDATA[Husband, Dad, Web Developer]]></description><link>https://scharl.click/</link><image><url>https://scharl.click/favicon.png</url><title>Scott Scharl</title><link>https://scharl.click/</link></image><generator>Ghost 4.2</generator><lastBuildDate>Sat, 04 Apr 2026 08:17:05 GMT</lastBuildDate><atom:link href="https://scharl.click/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[How I'm Using PocketBase with React Context]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>For several years now, I&apos;ve been building my side projects with a <a href="https://pocketbase.io">PocketBase</a> backend.</p>
<p>Here&apos;s the Context component (and custom hook) I&apos;m using to manage authentication in a React (Vite) app:</p>
<pre><code>import React from &quot;react&quot;;
import PocketBase from &quot;pocketbase&quot;;
import</code></pre>]]></description><link>https://scharl.click/how-im-using-pocketbase-with-react-context/</link><guid isPermaLink="false">67f311d369f13b05c7ab5d6f</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sun, 06 Apr 2025 23:48:49 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>For several years now, I&apos;ve been building my side projects with a <a href="https://pocketbase.io">PocketBase</a> backend.</p>
<p>Here&apos;s the Context component (and custom hook) I&apos;m using to manage authentication in a React (Vite) app:</p>
<pre><code>import React from &quot;react&quot;;
import PocketBase from &quot;pocketbase&quot;;
import { useInterval } from &quot;usehooks-ts&quot;;
import { jwtDecode } from &quot;jwt-decode&quot;;
import ms from &quot;ms&quot;;

const BASE_URL = import.meta.env.VITE_PB_URL;
const fiveMinutesInMs = ms(&quot;5 minutes&quot;);
const twoMinutesInMs = ms(&quot;2 minutes&quot;);

const PocketContext = React.createContext({});

export const PocketProvider = ({ children }) =&gt; {
  const pb = React.useMemo(() =&gt; new PocketBase(BASE_URL), []);
  pb.autoCancellation(false);

  const [token, setToken] = React.useState(pb.authStore.token);
  const [user, setUser] = React.useState(pb.authStore.record);

  React.useEffect(() =&gt; {
    return pb.authStore.onChange((token, record) =&gt; {
      setToken(token);
      setUser(record);
    });
  }, []);

  async function register(email, password) {
    try {
      // Create user
      const data = {
        email,
        password,
        passwordConfirm: password,
      };

      const newUser = await pb.collection(&quot;users&quot;).create(data);

      // Log them in
      await pb.collection(&quot;users&quot;).authWithPassword(email, password);

      return newUser;
    } catch (error) {
      console.error(&quot;Registration error:&quot;, error);
      throw error;
    }
  }

  async function login(email, password) {
    try {
      return await pb.collection(&quot;users&quot;).authWithPassword(email, password);
    } catch (error) {
      console.error(&quot;Login error:&quot;, error);
      // You might want to show this error to the user
      throw error; // Re-throw to handle in the UI
    }
  }

  function logout() {
    pb.authStore.clear();
    // No navigation here - we&apos;ll handle it in the component
  }

  async function refreshSession() {
    if (!pb.authStore.isValid) return;
    const decoded = jwtDecode(token);
    const tokenExpiration = decoded.exp;
    const expirationWithBuffer = (decoded.exp + fiveMinutesInMs) / 1000;
    if (tokenExpiration &lt; expirationWithBuffer) {
      await pb.collection(&quot;users&quot;).authRefresh();
    }
  }

  useInterval(refreshSession, token ? twoMinutesInMs : null);

  return (
    &lt;PocketContext.Provider
      value={{
        register,
        login,
        logout,
        user,
        token,
        pb,
      }}
    &gt;
      {children}
    &lt;/PocketContext.Provider&gt;
  );
};

export const usePocket = () =&gt; React.useContext(PocketContext);
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[School Checklist App]]></title><description><![CDATA[<p>My kids&apos; school has a uniform which is varies on different days of the week for gym class, etc. I got tired of trying to remember what to lay out for them each morning, so I built a Vite React app and an Express API to serve up the</p>]]></description><link>https://scharl.click/school-morning-checklist-app/</link><guid isPermaLink="false">67c59d4c69f13b05c7ab5d40</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Mon, 03 Mar 2025 12:22:17 GMT</pubDate><content:encoded><![CDATA[<p>My kids&apos; school has a uniform which is varies on different days of the week for gym class, etc. I got tired of trying to remember what to lay out for them each morning, so I built a Vite React app and an Express API to serve up the checklist each morning. (The younger kid, on the bottom, is in preschool and doesen&apos;t have a uniform yet. Next year!)</p><p>Next steps are are to finish building another project, <a href="https://weather.scottyserver.net">Scott&apos;s Weather API</a>. This will enable the &quot;Snow Bag&quot; card appear only when it&apos;s appropriate based on previous or current days&apos; weather. </p><figure class="kg-card kg-image-card"><img src="https://scharl.click/content/images/2025/03/CleanShot-2025-03-03-at-07.15.55@2x.png" class="kg-image" alt loading="lazy" width="1250" height="1636" srcset="https://scharl.click/content/images/size/w600/2025/03/CleanShot-2025-03-03-at-07.15.55@2x.png 600w, https://scharl.click/content/images/size/w1000/2025/03/CleanShot-2025-03-03-at-07.15.55@2x.png 1000w, https://scharl.click/content/images/2025/03/CleanShot-2025-03-03-at-07.15.55@2x.png 1250w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Finishing My React course]]></title><description><![CDATA[<p>Today I used Claude.ai to help me generate a schedule to finish a React course I want to finish by June. </p><p>I want to finish 2 lessons per day until I complete the course. So, I had Claude parse the HTML page from the course to pull out the</p>]]></description><link>https://scharl.click/finishing-my-react-course/</link><guid isPermaLink="false">67c35e0a69f13b05c7ab5d02</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sat, 01 Mar 2025 19:32:15 GMT</pubDate><content:encoded><![CDATA[<p>Today I used Claude.ai to help me generate a schedule to finish a React course I want to finish by June. </p><p>I want to finish 2 lessons per day until I complete the course. So, I had Claude parse the HTML page from the course to pull out the 150+ lesson titles.</p><p>Then, I asked it to generate a node.js script to import each of the lessons as a Todoist task. I ran <code>npm i dotenv --save</code>, then added a <code>.env</code> file with my Todoist API key (accessible in Todoist at <em>Settings</em> &gt; <em>Integrations</em> &gt; <em>Developer</em>). </p><p>I ran the script, and it worked perfectly on the first try.</p><p>Now all I need to do is diligently follow the tasks laid out for each day in Todoist, and I&apos;ll be done with the course by the end of May.</p><!--kg-card-begin: markdown--><pre><code>require(&quot;dotenv&quot;).config();
const axios = require(&quot;axios&quot;);
const fs = require(&quot;fs&quot;);
const path = require(&quot;path&quot;);
const csv = require(&quot;csv-parser&quot;);

// Todoist API configuration
// Replace YOUR_API_TOKEN with your actual Todoist API token
const TODOIST_API_TOKEN = process.env.TODOIST_API_TOKEN;
const TODOIST_API_URL = &quot;https://api.todoist.com/rest/v2&quot;;

// Create a new project for the Joy of React course
async function createTodoistProject() {
  try {
    const response = await axios.post(
      `${TODOIST_API_URL}/projects`,
      {
        name: &quot;Joy of React Course&quot;,
      },
      {
        headers: {
          Authorization: `Bearer ${TODOIST_API_TOKEN}`,
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
      }
    );

    console.log(&quot;Project created successfully:&quot;, response.data.name);
    return response.data.id;
  } catch (error) {
    console.error(
      &quot;Error creating project:&quot;,
      error.response ? error.response.data : error.message
    );
    throw error;
  }
}

// Parse the schedule data
function parseScheduleData() {
  return new Promise((resolve, reject) =&gt; {
    const results = [];

    // Create a readable stream from our CSV data
    const csvData = `Date,Day,Module,Lesson1,Lesson2
2025-03-01,Sat,Module 3: React Hooks,Running on Mount,On Mount Exercises
2025-03-02,Sun,Module 3: React Hooks,Cleanup,Cleanup Exercises
2025-03-03,Mon,Module 3: React Hooks,Stale Values,State-Setter Callback
2025-03-04,Tue,Module 3: React Hooks,Stale Values Exercises,Strict Mode
2025-03-05,Wed,Module 3: React Hooks,Custom Hooks,Custom Hook Exercises
2025-03-06,Thu,Module 3: React Hooks,Data Fetching,Fetching on Event
2025-03-07,Fri,Module 3: React Hooks,Fetching on Mount,Fetch Exercises
2025-03-08,Sat,Module 3: React Hooks,Async Effect Gotcha,Why React Re-Renders
2025-03-09,Sun,Module 3: React Hooks,Pure Components,The useMemo Hook
2025-03-10,Mon,Module 3: React Hooks,The useCallback Hook,Memo Exercises
2025-03-11,Tue,Module 3: React Hooks,Alternatives,Future of Memoization
2025-03-12,Wed,Module 4: Component API Design,Introduction,The Spectrum of Components
2025-03-16,Sun,Module 4: Component API Design,Spectrum Exercises,Component Libraries
2025-03-17,Mon,Module 4: Component API Design,Prop Delegation,Delegating Props Exercises
2025-03-18,Tue,Module 4: Component API Design,Conflicts,Delegating Styles
2025-03-19,Wed,Module 4: Component API Design,Forwarding Refs,ForwardRef Exercises
2025-03-20,Thu,Module 4: Component API Design,Polymorphism,Polymorphism Exercises
2025-03-21,Fri,Module 4: Component API Design,Compound Components,Slots
2025-03-22,Sat,Module 4: Component API Design,Slots Exercises,Context
2025-03-23,Sun,Module 4: Component API Design,The Problem,Syntax
2025-03-24,Mon,Module 4: Component API Design,Syntax Exercises,Provider Components
2025-03-25,Tue,Module 4: Component API Design,Performance,Modals
2025-03-25,Tue,Project: Toast Component,About This Project,Getting Started
2025-03-26,Wed,Module 4: Component API Design,Modal Exercises,Unstyled Component Libraries
2025-03-26,Wed,Project: Toast Component,Hints,Submit Your Project
2025-03-27,Thu,Module 4: Component API Design,Converting Our Modal,Library Exercises
2025-03-27,Thu,Project: Toast Component,Solution,Unguided Practice
2025-03-28,Fri,Module 5: Happy Practices,Introduction,Leveraging Keys
2025-03-29,Sat,Module 5: Happy Practices,Key Exercises,Elements Revisited
2025-03-30,Sun,Module 5: Happy Practices,Deriving State,Deriving Exercises
2025-03-31,Mon,Module 5: Happy Practices,Breaking the Rules,Lifting Content Up
2025-04-01,Tue,Module 5: Happy Practices,Lifting Content Up Exercises,Single Source of Truth
2025-04-02,Wed,Module 5: Happy Practices,SST Exercises,Principle of Least Privilege
2025-04-03,Thu,Module 5: Happy Practices,Least Privilege Exercises,useReducer
2025-04-04,Fri,Module 5: Happy Practices,Switch / Case,useReducer Exercises
2025-04-05,Sat,Module 5: Happy Practices,Immer,Immer 101
2025-04-06,Sun,Module 5: Happy Practices,Immer Exercises,The &quot;useImmer&quot; Hook
2025-04-07,Mon,Module 5: Happy Practices,Portals,Portal Exercises
2025-04-08,Tue,Module 5: Happy Practices,Refs Revisited,Error Boundaries
2025-04-09,Wed,Module 6: Full Stack React,Introduction,Client vs. Server Rendering
2025-04-10,Thu,Module 6: Full Stack React,Hydration,SSR Flavors
2025-04-11,Fri,Module 6: Full Stack React,React Server Components,Intro to Next.js
2025-04-12,Sat,Module 6: Full Stack React,Hello Next!,Initial Exercises
2025-04-13,Sun,Module 6: Full Stack React,Client Components,Client Components Exercises
2025-04-14,Mon,Module 6: Full Stack React,SSR Gotchas,SSR Exercises
2025-04-15,Tue,Module 6: Full Stack React,Routing,Page Transitions
2025-04-16,Wed,Module 6: Full Stack React,Programmatic Routing,Dynamic Segments
2025-04-17,Thu,Module 6: Full Stack React,Routing Exercises,Next&apos;s Metadata API
2025-04-18,Fri,Module 6: Full Stack React,Creating Building Deploying,Starting a New Project
2025-04-19,Sat,Module 6: Full Stack React,Import Aliases,Building for Production
2025-04-20,Sun,Module 6: Full Stack React,Deploying,Rendering Strategies
2025-04-21,Mon,Module 6: Full Stack React,Rendering Strategies Exercises,React Cache
2025-04-22,Tue,Module 6: Full Stack React,Suspense,An Exciting New World
2025-04-23,Wed,Module 6: Full Stack React,Graphing It Out,Understanding Suspense
2025-04-24,Thu,Module 6: Full Stack React,Putting It All Together,Using Suspense in Next
2025-04-25,Fri,Module 6: Full Stack React,Suspense Exercises,Lazy Loading
2025-04-26,Sat,Module 6: Full Stack React,Lazy Loading in Next,Baked-in Laziness
2025-04-27,Sun,Module 6: Full Stack React,Dark Mode,Module Review
2025-04-28,Mon,Bonus: Layout Animations,Intro to Framer Motion,Getting Started
2025-04-29,Tue,Bonus: Layout Animations,Basics Exercises,Layout Animations
2025-04-30,Wed,Bonus: Layout Animations,Layout Exercises,Shared Layout
2025-05-01,Thu,Bonus: Layout Animations,Working With Groups,Shared Layout Exercises
2025-05-02,Fri,Bonus: Layout Animations,One Last Exercise,Motion Accessibility
2025-05-03,Sat,Bonus: Layout Animations,Disabling Motion in Next,Troubleshooting
2025-05-04,Sun,Project: MDX Blog,About This Project,Intro to MDX
2025-05-05,Mon,Project: MDX Blog,MDX in Next.js,Getting Started
2025-05-06,Tue,Project: MDX Blog,Submit Your Project,Solution
2025-05-07,Wed,Project: MDX Blog,Suspense Investigations,Navigation Performance
2025-05-08,Thu,Project: MDX Blog,The Journey Continues,Project Review
2025-05-09,Fri,Bonus: Job Hunting Kit,Introduction,The Hiring Funnel
2025-05-10,Sat,Bonus: Job Hunting Kit,Cover Letters,Quantity vs. Quality
2025-05-11,Sun,Bonus: Job Hunting Kit,Portfolios,Building a Portfolio Site
2025-05-12,Mon,Bonus: Job Hunting Kit,Technical Interviews,FizzBuzz
2025-05-13,Tue,Bonus: Job Hunting Kit,Scroll Position,Todo App
2025-05-14,Wed,Bonus: Job Hunting Kit,Project Euler,Starfield Button
2025-05-15,Thu,Bonus: Job Hunting Kit,Multi-Step Signup,Trello Clone
2025-05-16,Fri,Bonus: Job Hunting Kit,Clicking Outside,Multi Checkbox
2025-05-17,Sat,Bonus: Job Hunting Kit,Fetch Race Condition,useEffectSkipMount
2025-05-18,Sun,Bonus: Job Hunting Kit,Retro Questions,Module Review
2025-05-19,Mon,Course Completion,Course Review,Celebration`;

    // Create a readable stream from the CSV string
    const Readable = require(&quot;stream&quot;).Readable;
    const s = new Readable();
    s.push(csvData);
    s.push(null); // Indicates EOF

    s.pipe(csv())
      .on(&quot;data&quot;, (data) =&gt; results.push(data))
      .on(&quot;end&quot;, () =&gt; {
        resolve(results);
      })
      .on(&quot;error&quot;, (error) =&gt; {
        reject(error);
      });
  });
}

// Create tasks in Todoist for each lesson
async function createTasks(projectId, scheduleData) {
  console.log(
    `Creating ${scheduleData.length * 2} tasks (2 lessons per day)...`
  );

  // Create a counter to track progress
  let tasksCreated = 0;

  // Process tasks in batches to avoid rate limiting
  const batchSize = 10;
  for (let i = 0; i &lt; scheduleData.length; i += batchSize) {
    const batch = scheduleData.slice(i, i + batchSize);

    // Create tasks for each item in the batch
    const promises = batch.flatMap((item) =&gt; {
      const dateStr = item.Date; // Format: 2025-03-01

      // Create two tasks - one for each lesson
      return [
        createTask(projectId, `${item.Module} - ${item.Lesson1}`, dateStr),
        createTask(projectId, `${item.Module} - ${item.Lesson2}`, dateStr),
      ];
    });

    // Wait for all tasks in this batch to be created
    await Promise.all(promises);

    // Update progress
    tasksCreated += batch.length * 2;
    console.log(
      `Progress: ${tasksCreated}/${scheduleData.length * 2} tasks created`
    );

    // Add a small delay between batches to avoid rate limiting
    if (i + batchSize &lt; scheduleData.length) {
      await new Promise((resolve) =&gt; setTimeout(resolve, 1000));
    }
  }

  console.log(&quot;All tasks created successfully!&quot;);
}

// Create a single task in Todoist
async function createTask(projectId, content, dueDate) {
  try {
    const response = await axios.post(
      `${TODOIST_API_URL}/tasks`,
      {
        project_id: projectId,
        content: content,
        due_date: dueDate,
      },
      {
        headers: {
          Authorization: `Bearer ${TODOIST_API_TOKEN}`,
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
      }
    );

    return response.data;
  } catch (error) {
    console.error(
      &quot;Error creating task:&quot;,
      error.response ? error.response.data : error.message
    );
    throw error;
  }
}

// Main function to run the script
async function main() {
  try {
    console.log(&quot;Starting Joy of React schedule import to Todoist...&quot;);

    // Create a new project in Todoist
    const projectId = await createTodoistProject();

    // Parse the schedule data
    const scheduleData = await parseScheduleData();

    // Create tasks for each lesson
    await createTasks(projectId, scheduleData);

    console.log(&quot;Import completed successfully!&quot;);
  } catch (error) {
    console.error(&quot;Error running the script:&quot;, error);
  }
}

// Run the script
main();
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[My First Next.js App - BuddyTask]]></title><description><![CDATA[<p>A few days ago, I deployed my first Next app to the world wide web.</p><p><a href="https://buddytask.app">BuddyTask</a> is now in production and in use by a friend and I. It&apos;s basically a partner accountability app. </p><p>Two users form a partnership, then each commit to completing a personal list of</p>]]></description><link>https://scharl.click/my-first-next-js-app/</link><guid isPermaLink="false">6736aedb94c35304d7b301b3</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Fri, 15 Nov 2024 03:18:41 GMT</pubDate><content:encoded><![CDATA[<p>A few days ago, I deployed my first Next app to the world wide web.</p><p><a href="https://buddytask.app">BuddyTask</a> is now in production and in use by a friend and I. It&apos;s basically a partner accountability app. </p><p>Two users form a partnership, then each commit to completing a personal list of tasks.</p><p>Each partner has visibility into the status of the other&apos;s list, so they can check in with each other when necessary. </p><p>As a self-taught developer, I have been trying for a LONG time to deploy something like this, and it feels good for it to be working and actually use it with a friend. More on the tech stack and roadmap bel0w.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scharl.click/content/images/2024/11/CleanShot-2024-11-14-at-22.12.51@2x.png" class="kg-image" alt loading="lazy" width="1370" height="1472" srcset="https://scharl.click/content/images/size/w600/2024/11/CleanShot-2024-11-14-at-22.12.51@2x.png 600w, https://scharl.click/content/images/size/w1000/2024/11/CleanShot-2024-11-14-at-22.12.51@2x.png 1000w, https://scharl.click/content/images/2024/11/CleanShot-2024-11-14-at-22.12.51@2x.png 1370w" sizes="(min-width: 720px) 720px"><figcaption>BuddyTask.app/user</figcaption></figure><p>Tech Stack: </p><ul><li>Next.js 15 (App Router)</li><li>PocketBase (database and auth)</li><li>shadcn/ui</li></ul><p>Next Steps: </p><ul><li>Build a marketing home page for it. </li><li>Build feature where buddies can invite each other to a partnership after they&apos;ve each created an account.</li><li>Implement email reminders to be sent to remind buddies to check in with each other. </li><li>Add Stripe payments so users can subscribe to the app.</li></ul><p>Stay tuned for more updates on this fun project!</p>]]></content:encoded></item><item><title><![CDATA[JavaScript function that builds a progress bar using emojis]]></title><description><![CDATA[<!--kg-card-begin: markdown--><pre><code>function getProgressBar(numerator: number, denominator: number) {
    const percentage = Math.round((numerator / denominator) * 100);
    const tenths = Math.round(percentage / 10);

    const barTenths = [
      &apos;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;</code></pre>]]></description><link>https://scharl.click/javascript-function-that-builds-a-progress-bar-using-emojis/</link><guid isPermaLink="false">6442e92494c35304d7b2ffdd</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Fri, 21 Apr 2023 20:03:32 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><pre><code>function getProgressBar(numerator: number, denominator: number) {
    const percentage = Math.round((numerator / denominator) * 100);
    const tenths = Math.round(percentage / 10);

    const barTenths = [
      &apos;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x2B1C;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#x1F7E6;&#xFE0F;&#x2B1C;&#xFE0F;&apos;,
      &apos;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&#x1F7E9;&apos;,
    ];

    return barTenths[tenths];
  };
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Zendash v2.0 is live]]></title><description><![CDATA[<h2 id="new-features">New features:</h2><ul><li>Dynamic username displayed in the top nav bar</li><li><code>Settings</code> page w/ user-configurable <code>Daily Goal</code> and <code>Timezone</code></li><li>Dynamic counter in the app title, visible in the browser tab showing <br><code>Public Comments Today</code> (followed <a href="https://dev.to/luispa/how-to-add-a-dynamic-title-on-your-react-app-1l7k">this helpful post</a>)</li><li>Converted from single- to multi-page app using <code>react-router</code></li></ul><p>&#x2192; <a href="https://zendash.vercel.app">zendash.vercel.app</a> (user:</p>]]></description><link>https://scharl.click/zendash-version-2-is-out/</link><guid isPermaLink="false">6437300e94c35304d7b2ff6c</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Thu, 13 Apr 2023 17:00:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="new-features">New features:</h2><ul><li>Dynamic username displayed in the top nav bar</li><li><code>Settings</code> page w/ user-configurable <code>Daily Goal</code> and <code>Timezone</code></li><li>Dynamic counter in the app title, visible in the browser tab showing <br><code>Public Comments Today</code> (followed <a href="https://dev.to/luispa/how-to-add-a-dynamic-title-on-your-react-app-1l7k">this helpful post</a>)</li><li>Converted from single- to multi-page app using <code>react-router</code></li></ul><p>&#x2192; <a href="https://zendash.vercel.app">zendash.vercel.app</a> (user: <code>test</code> pw: <code>zendash</code>)</p><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://scharl.click/content/images/2023/04/CleanShot-2023-04-12-at-18.33.38@2x.png" width="996" height="984" loading="lazy" alt srcset="https://scharl.click/content/images/size/w600/2023/04/CleanShot-2023-04-12-at-18.33.38@2x.png 600w, https://scharl.click/content/images/2023/04/CleanShot-2023-04-12-at-18.33.38@2x.png 996w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://scharl.click/content/images/2023/04/CleanShot-2023-04-12-at-18.33.46@2x.png" width="1004" height="1144" loading="lazy" alt srcset="https://scharl.click/content/images/size/w600/2023/04/CleanShot-2023-04-12-at-18.33.46@2x.png 600w, https://scharl.click/content/images/size/w1000/2023/04/CleanShot-2023-04-12-at-18.33.46@2x.png 1000w, https://scharl.click/content/images/2023/04/CleanShot-2023-04-12-at-18.33.46@2x.png 1004w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://scharl.click/content/images/2023/04/CleanShot-2023-04-12-at-18.35.02@2x.png" width="702" height="452" loading="lazy" alt srcset="https://scharl.click/content/images/size/w600/2023/04/CleanShot-2023-04-12-at-18.35.02@2x.png 600w, https://scharl.click/content/images/2023/04/CleanShot-2023-04-12-at-18.35.02@2x.png 702w"></div></div></div></figure>]]></content:encoded></item><item><title><![CDATA[Announcing My First Web App: Zendash]]></title><description><![CDATA["This is ridiculous", I thought. "Surely there must be a way to fix this with software!"]]></description><link>https://scharl.click/announcing-my-first-web-app-zendash/</link><guid isPermaLink="false">64287ea694c35304d7b2fde4</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sat, 01 Apr 2023 19:38:05 GMT</pubDate><content:encoded><![CDATA[<p>I&#x2019;m happy to announce that I&apos;ve reached a major personal milestone--I finished &amp; deployed the beta version of my side project, <a href="https://zendash.vercel.app/" rel="noopener noreferrer">Zendash</a>.</p><h2 id="the-problem">The Problem</h2><p>I&apos;m about 18 months into my role in Customer Support at IFTTT. About 6 months ago, I was meeting with my manager to set performance goals for the upcoming year. With my growing knowledge of IFTTT&apos;s product, I wanted to identify a stretch goal to shoot for in terms of number of tickets responded to per day.</p><p>I was frustrated because during my workday, Zendesk doesn&apos;t show me how many tickets I&apos;ve responded to in the current day. I can go back and view the previous day&apos;s total, but no real-time counter is provided. To track my efficiency on a particular workday, I&apos;d be forced to scratch tally marks on a notepad next to my Mac, an easily-forgotten action that breaks a good workflow by forcing my hand away from the keyboard.</p><p>&quot;This is ridiculous&quot;, I thought. &quot;Surely there must be a way to fix this with software!&quot;</p><p>As I reviewed Zendesk&apos;s API documentation, I realized it would be possible to build a simple web app to solve my problem, and that I could probably make it work for all of my colleagues on the Support team.</p><p>This project would be a great learning opportunity, because it&apos;d require me t0 learn three topics I hadn&apos;t built before:</p><ul><li>fetching and periodically refreshing data on the client-side</li><li>configuring a database</li><li>user authentication</li></ul><p>So, I dove in! Six months of hacking in my spare time on nights and weekends, here&apos;s what I made.</p><h2 id="the-solution">The Solution</h2><p>Zendash is a realtime dashboard that tells you how many Zendesk tickets you&#x2019;ve responded to today (specifically the number of &quot;public comments&quot; you&#x2019;ve added since midnight Pacific time). The dashboard also displays how many tickets are in some of the Zendesk queues that we use.</p><figure class="kg-card kg-image-card"><img src="https://scharl.click/content/images/2023/04/CleanShot-2023-03-20-at-10.33.56.png" class="kg-image" alt loading="lazy" width="442" height="481"></figure><h2 id="tech-stack">Tech Stack</h2><h3 id="frontend">Frontend</h3><ul><li>React.js (Create-React-App, deployed to Vercel)</li><li>Bootstrap</li></ul><h3 id="backend">Backend</h3><ul><li><a href="https://pocketbase.io">PocketBase</a> (deployed to Fly.io, following <a href="https://github.com/pocketbase/pocketbase/discussions/537">this guide</a>)</li><li>Google Cloud Schedulers &amp; Cloud Functions (running daily cron jobs to reset each user&#x2019;s &#x201C;Public Comments Today&#x201D; counter, and to wrap the Zendesk API to simplify queries)</li></ul><h2 id="test-it-out">Test it out!</h2><!--kg-card-begin: markdown--><p>&#x2192; <a href="https://zendash.vercel.app">zendash.vercel.app</a><br>
user: <code>test</code><br>
password: <code>zendash</code></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adding Firebase Auth and CRUD Operations and a new name!]]></title><description><![CDATA[<p>Most of the UI is now built for my app. I now am learning how to signup and log in users via Firebase, and how to create/read/update/destroy data in their accounts with Cloud Firestore database.</p><p>I also renamed the app: BirthdayBot is now BirthdayBird!</p>]]></description><link>https://scharl.click/learning-firebase-auth/</link><guid isPermaLink="false">62951ba494c35304d7b2fd63</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Mon, 30 May 2022 19:33:40 GMT</pubDate><content:encoded><![CDATA[<p>Most of the UI is now built for my app. I now am learning how to signup and log in users via Firebase, and how to create/read/update/destroy data in their accounts with Cloud Firestore database.</p><p>I also renamed the app: BirthdayBot is now BirthdayBird!</p>]]></content:encoded></item><item><title><![CDATA[Remaking the UI]]></title><description><![CDATA[<p>I&apos;m proud of how this is starting to look! I&apos;m learning <a href="https://tailwindcss.com">TailwindCSS</a> for the design. </p><figure class="kg-card kg-image-card"><img src="https://scharl.click/content/images/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png" class="kg-image" alt loading="lazy" width="1350" height="982" srcset="https://scharl.click/content/images/size/w600/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png 600w, https://scharl.click/content/images/size/w1000/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png 1000w, https://scharl.click/content/images/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png 1350w" sizes="(min-width: 720px) 720px"></figure>]]></description><link>https://scharl.click/remaking-the-ui/</link><guid isPermaLink="false">626d627094c35304d7b2fd41</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sat, 30 Apr 2022 16:26:21 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;m proud of how this is starting to look! I&apos;m learning <a href="https://tailwindcss.com">TailwindCSS</a> for the design. </p><figure class="kg-card kg-image-card"><img src="https://scharl.click/content/images/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png" class="kg-image" alt loading="lazy" width="1350" height="982" srcset="https://scharl.click/content/images/size/w600/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png 600w, https://scharl.click/content/images/size/w1000/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png 1000w, https://scharl.click/content/images/2022/04/Screen-Shot-2022-04-30-at-10.23.42-AM.png 1350w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[I'm Back]]></title><description><![CDATA[<p>It&apos;s time to reboot this blog! </p><p>The past year has been the most challenging of my life. My family was forced to move (across town) on short notice, I started a new job, and my mother-in-law died. </p><p>But, thankfully, though I neglected to post on this blog, I</p>]]></description><link>https://scharl.click/im-back/</link><guid isPermaLink="false">6256495294c35304d7b2fc9f</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Wed, 13 Apr 2022 04:43:17 GMT</pubDate><content:encoded><![CDATA[<p>It&apos;s time to reboot this blog! </p><p>The past year has been the most challenging of my life. My family was forced to move (across town) on short notice, I started a new job, and my mother-in-law died. </p><p>But, thankfully, though I neglected to post on this blog, I kept going with learning web development. I&apos;m going to keep posting weekly updates on here as I continue to build BirthdayBot. </p><p>My buddy Dave continues to be a wonderful mentor, and in my new job at IFTTT I&apos;m learning a ton during my 9-5, too. It&apos;s an exciting time! </p><p>Thanks for following along as I continue to push my career forward. </p>]]></content:encoded></item><item><title><![CDATA[Week 9: More Refactoring and Learning About State]]></title><description><![CDATA[<p>This week I&apos;m doing a bunch more refactoring of my file structure to make it easier to work in. I&apos;m also STILL tackling state and making some cards in the center of my screen editable.</p>]]></description><link>https://scharl.click/untitled-eek/</link><guid isPermaLink="false">60f63156936ed35870be0ee1</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sun, 18 Jul 2021 02:15:00 GMT</pubDate><content:encoded><![CDATA[<p>This week I&apos;m doing a bunch more refactoring of my file structure to make it easier to work in. I&apos;m also STILL tackling state and making some cards in the center of my screen editable.</p>]]></content:encoded></item><item><title><![CDATA[Week 8: Building Similar Components, JavaScript Events]]></title><description><![CDATA[<!--kg-card-begin: markdown--><ul>
<li>Using Javascript and props to read an array and build multiple components of the same type.</li>
<li>Intro to Javascript Events using the example of <code>onClick=</code></li>
</ul>
<!--kg-card-end: markdown-->]]></description><link>https://scharl.click/week-8/</link><guid isPermaLink="false">60edfb3f936ed35870be0eb6</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sat, 10 Jul 2021 20:50:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><ul>
<li>Using Javascript and props to read an array and build multiple components of the same type.</li>
<li>Intro to Javascript Events using the example of <code>onClick=</code></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Week 7: Refactoring & Reusing Components]]></title><description><![CDATA[<!--kg-card-begin: markdown--><ul>
<li>
<p>Broke out a number of the portions of my files into separate files.</p>
</li>
<li>
<p>Built a single <code>&lt;Button /&gt;</code> class, with props relating to different CSS styling for several types:</p>
<ul>
<li><code>Primary</code></li>
<li><code>Secondary</code></li>
<li><code>Transparent</code></li>
<li><code>Danger</code></li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown-->]]></description><link>https://scharl.click/week-7/</link><guid isPermaLink="false">60edf899936ed35870be0e96</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sat, 03 Jul 2021 20:42:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><ul>
<li>
<p>Broke out a number of the portions of my files into separate files.</p>
</li>
<li>
<p>Built a single <code>&lt;Button /&gt;</code> class, with props relating to different CSS styling for several types:</p>
<ul>
<li><code>Primary</code></li>
<li><code>Secondary</code></li>
<li><code>Transparent</code></li>
<li><code>Danger</code></li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Week 6: (independent work)]]></title><description><![CDATA[<p>Because of emergency travel out of state, I was not able to meet with my friend this week.</p><p>I&apos;m working on building out additional React components for my site. </p>]]></description><link>https://scharl.click/week-6-independent-work_/</link><guid isPermaLink="false">60d8d5fb936ed35870be0e35</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sun, 27 Jun 2021 19:51:22 GMT</pubDate><content:encoded><![CDATA[<p>Because of emergency travel out of state, I was not able to meet with my friend this week.</p><p>I&apos;m working on building out additional React components for my site. </p>]]></content:encoded></item><item><title><![CDATA[Week 5: State]]></title><description><![CDATA[<p>Today we introduced state in React. It was challenging to try to understand such an abstract concept. </p><p>My task is to continue building out different views within my app. They still aren&apos;t connected to any data or backend. </p>]]></description><link>https://scharl.click/week-5/</link><guid isPermaLink="false">60d8889a936ed35870be0db4</guid><dc:creator><![CDATA[Scott Scharl]]></dc:creator><pubDate>Sat, 19 Jun 2021 14:23:00 GMT</pubDate><content:encoded><![CDATA[<p>Today we introduced state in React. It was challenging to try to understand such an abstract concept. </p><p>My task is to continue building out different views within my app. They still aren&apos;t connected to any data or backend. </p>]]></content:encoded></item></channel></rss>