<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Fatuma Abdullahi - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Fatuma Abdullahi - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 16:29:46 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/HijabiCoder/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Secure SSR Authentication with Supabase, Astro, and Cloudflare Turnstile ]]>
                </title>
                <description>
                    <![CDATA[ In this guide, you'll build a full server-side rendered (SSR) authentication system using Astro, Supabase, and Cloudflare Turnstile to protect against bots. By the end, you'll have a fully functional authentication system with Astro actions, magic li... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-secure-ssr-authentication-with-supabase-astro-and-cloudflare-turnstile/</link>
                <guid isPermaLink="false">685594145aea0dba325c37e1</guid>
                
                    <category>
                        <![CDATA[ supabase ss ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Astro ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase auth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ magic links ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloudflare ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloudflare Turnstile ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSR ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Fri, 20 Jun 2025 17:02:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750438909287/d36c0c01-e779-4eea-aa41-b797fcbb05f6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this guide, you'll build a full server-side rendered (SSR) authentication system using Astro, Supabase, and Cloudflare Turnstile to protect against bots.</p>
<p>By the end, you'll have a fully functional authentication system with Astro actions, magic link authentication using Supabase, bot protection via Cloudflare Turnstile, protected routes and middleware, and secure session management.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-technologies">Understanding the Technologies</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-astro">What is Astro?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-astro-actions">What are Astro Actions?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-supabase">What is Supabase?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-cloudflare-turnstile">What is Cloudflare Turnstile?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-ssr-authentication">Understanding SSR Authentication</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-ssr-vs-spa-authentication">SSR vs. SPA Authentication</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-protect-auth-forms">Why Protect Auth Forms?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-part-1-how-to-set-up-the-backend">Part 1: How to Set Up the Backend</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-set-up-supabase-backend">Set Up Supabase Backend</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-cloudflare-turnstile">Set Up Cloudflare Turnstile</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-2-how-to-set-up-the-frontend">Part 2: How to Set Up the Frontend</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-the-astro-project">Create the Astro Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-astro-for-ssr">Configure Astro for SSR</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-supabase-dependencies">Install Supabase Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-environment-variables">Configure Environment Variables</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-3-how-to-set-up-supabase-ssr">Part 3: How to Set Up Supabase SSR</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-the-supabase-client">Create the Supabase Client</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-middleware-for-route-protection">Create Middleware for Route Protection</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-4-how-to-build-the-user-interface">Part 4: How to Build the User Interface</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-update-the-layout">Update the Layout</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-sign-in-page">Create the Sign-In Page</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-protected-page">Create the Protected Page</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-5-how-to-set-up-astro-actions">Part 5: How to Set Up Astro Actions</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-the-authentication-actions">Create the Authentication Actions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-code-exchange-api-route">Create the Code Exchange API Route</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-6-how-to-test-your-application">Part 6: How to Test Your Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-notes-and-additional-resources">Notes and Additional Resources</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-useful-documentation">Useful Documentation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-complete-code-repository">Complete Code Repository</a></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This tutorial assumes you are familiar with:</p>
<ul>
<li><p>Web development frameworks</p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/set-up-authentication-in-apps-with-supabase/">Basic authentication flows</a></p>
</li>
<li><p>Basic Backend-as-a-Service (BaaS) concepts</p>
</li>
</ul>
<h2 id="heading-understanding-the-technologies">Understanding the Technologies</h2>
<h3 id="heading-what-is-astro">What is Astro?</h3>
<p><a target="_blank" href="https://docs.astro.build/en/getting-started/">Astro</a> is a UI-agnostic web framework that renders <a target="_blank" href="https://docs.astro.build/en/concepts/why-astro/#server-first">server-first</a> by default. It <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/#official-integrations">can be used with any UI framework</a>, including <a target="_blank" href="https://docs.astro.build/en/guides/client-side-scripts/">Astro client components</a>.</p>
<h3 id="heading-what-are-astro-actions">What are Astro Actions?</h3>
<p><a target="_blank" href="https://docs.astro.build/en/guides/actions/">Astro actions</a> allow you to write server-side functions that can be called without explicitly setting up API routes. They provide many useful utilities that simplify the process of running server logic and can be called from both client and server environments.</p>
<h3 id="heading-what-is-supabase">What is Supabase?</h3>
<p><a target="_blank" href="https://supabase.com/docs">Supabase</a> is an open-source Backend-as-a-Service that builds upon <a target="_blank" href="https://www.postgresql.org/docs/">Postgres</a>. It provides key features such as authentication, real-time capabilities, edge functions, storage, and more. Supabase offers both a hosted version for easy scaling and a self-hostable version for full control.</p>
<h3 id="heading-what-is-cloudflare-turnstile">What is Cloudflare Turnstile?</h3>
<p>Turnstile is <a target="_blank" href="https://www.cloudflare.com/en-gb/application-services/products/turnstile/">Cloudflare's replacement for CAPTCHAs</a>, which are visual puzzles used to differentiate between genuine users and bots. Unlike traditional CAPTCHAs, which are visually clunky, annoying, and <a target="_blank" href="https://blog.cloudflare.com/turnstile-ga/">sometimes difficult to solve</a>, Turnstile detects malicious activity without requiring users to solve puzzles, while providing a better user experience.</p>
<h2 id="heading-understanding-ssr-authentication">Understanding SSR Authentication</h2>
<p>Server-side rendered (SSR) auth refers to handling authentication on the server using a <a target="_blank" href="https://www.freecodecamp.org/news/set-up-authentication-in-apps-with-supabase/#how-does-authentication-work">cookie-based authentication method</a>.</p>
<p>The flow works as follows:</p>
<ol>
<li><p>The server creates a session and stores a session ID in a cookie sent to the client</p>
</li>
<li><p>The browser receives the cookie and automatically includes it in future requests</p>
</li>
<li><p>The server uses the cookie to determine if the user is authenticated</p>
</li>
</ol>
<p>Since browsers cannot modify HTTP-only cookies and servers cannot access local storage, SSR authentication requires careful management to prevent security risks such as session hijacking and stale sessions.</p>
<h3 id="heading-ssr-vs-spa-authentication">SSR vs. SPA Authentication</h3>
<p>Single-Page Applications (SPAs), like traditional React apps, handle authentication on the client side because they don't have direct access to a server. SPAs typically use JWTs stored in local storage, cookies, or session storage, sending these tokens in HTTP headers when communicating with servers.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/HdE3dk8VkRU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-why-protect-auth-forms">Why Protect Auth Forms?</h2>
<p>Authentication protects sensitive resources from unauthorized access, making auth forms primary targets for bots and malicious actors. Taking extra precautions is important for maintaining security.</p>
<h2 id="heading-part-1-how-to-set-up-the-backend">Part 1: How to Set Up the Backend</h2>
<h3 id="heading-set-up-supabase-backend">Set Up Supabase Backend</h3>
<p>First, you'll need <a target="_blank" href="https://supabase.com/dashboard/">a Supabase account</a>. Create a project, then:</p>
<ol>
<li><p>Go to the Authentication tab in the sidebar</p>
</li>
<li><p>Click the Sign In / Up tab under Configuration</p>
</li>
<li><p>Enable user sign-ups</p>
</li>
<li><p>Scroll down to Auth Providers and enable email (disable email confirmation for this tutorial)</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742054137964/a379192b-4eaf-491f-bcf4-a0e1e0deef94.png" alt="Supabase authentication configuration interface showing user signup options and email provider enabled" width="2480" height="1448" loading="lazy"></p>
<h3 id="heading-set-up-cloudflare-turnstile">Set Up Cloudflare Turnstile</h3>
<ol>
<li><p><a target="_blank" href="https://dash.cloudflare.com/login">Log in or register for a Cloudflare account</a></p>
</li>
<li><p>Click the Turnstile tab in the sidebar</p>
</li>
<li><p>Click the "Add widget" button</p>
</li>
<li><p>Name your widget and add "localhost" as the hostname</p>
</li>
<li><p>Leave all other settings as default, and create the widget</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750260766060/95ec02e5-8ee7-4438-a66c-76866ec068c1.png" alt="Cloudflare Turnstile widget creation interface" width="2200" height="1796" loading="lazy"></p>
<p>After creating the widget, copy the secret key and add it to your Supabase dashboard:</p>
<ol>
<li><p>Go back to Supabase Authentication settings</p>
</li>
<li><p>Navigate to the Auth Protection tab under Configuration</p>
</li>
<li><p>Turn on Captcha protection</p>
</li>
<li><p>Choose Cloudflare as the provider</p>
</li>
<li><p>Paste your secret key</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750260776990/56ef5fc1-3321-45f0-ab9a-878679a08e88.png" alt="Supabase Attack Protection settings with Turnstile configuration" width="2302" height="986" loading="lazy"></p>
<h2 id="heading-part-2-how-to-set-up-the-frontend">Part 2: How to Set Up the Frontend</h2>
<h3 id="heading-create-the-astro-project">Create the Astro Project</h3>
<p>Next, you will need to create an Astro project. Open your preferred IDE or Text editor’s integrated terminal and run the following command to scaffold an Astro project in a folder named “ssr-auth.” Feel free to use any name you like.</p>
<pre><code class="lang-bash">npm create astro@latest ssr-auth
</code></pre>
<p>Follow the provided prompts and choose a basic template to start with. When it’s done, change into the folder, then run <code>npm install</code> to install dependencies, followed by <code>npm run dev</code> to start the server, and your site will be available at <a target="_blank" href="http://localhost:4321"><code>localhost:4321</code></a>.</p>
<h3 id="heading-configure-astro-for-ssr">Configure Astro for SSR</h3>
<p>Set Astro to run in SSR mode by adding <code>output: "server",</code> to the <code>defineConfig</code> function found in the <code>astro.config.mjs</code> file at the root of the folder.</p>
<p>Next, <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/node/">add an adapter</a> to create a server runtime. For this, use the Node.js adapter by running this command in a terminal: <code>npx astro add node</code>. This will add it and automatically make all relevant changes.</p>
<p>Finally, add Tailwind for styling. Run this command in a terminal window: <code>npx astro add tailwind</code>. Follow the prompts, and it will make any changes necessary.</p>
<p>At this stage, your <code>astro.config.mjs</code> should look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @ts-check</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;
<span class="hljs-keyword">import</span> node <span class="hljs-keyword">from</span> <span class="hljs-string">"@astrojs/node"</span>;
<span class="hljs-keyword">import</span> tailwindcss <span class="hljs-keyword">from</span> <span class="hljs-string">"@tailwindcss/vite"</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  output: <span class="hljs-string">"server"</span>,
  adapter: node({
    mode: <span class="hljs-string">"standalone"</span>,
  }),
  vite: {
    plugins: [tailwindcss()],
  },
});
</code></pre>
<h3 id="heading-install-supabase-dependencies">Install Supabase Dependencies</h3>
<p>You can do this by running the following command:</p>
<pre><code class="lang-bash">npm install @supabase/supabase-js @supabase/ssr
</code></pre>
<h3 id="heading-configure-environment-variables">Configure Environment Variables</h3>
<p>Create a <code>.env</code> file in the project root and add the following. Remember to replace with your actual credentials:</p>
<pre><code class="lang-bash">SUPABASE_URL=&lt;YOUR_URL&gt;
SUPABASE_ANON_KEY=&lt;YOUR_ANON_KEY&gt;
TURNSTILE_SITE_KEY=&lt;YOUR_TURNSTILE_SITE_KEY&gt;
</code></pre>
<p>You can get the Supabase values from the dashboard:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742054292788/8aeec326-259c-49bd-a6f8-b885e9a9e6ea.png" alt="Supabase project connection interface showing environment variables" width="2132" height="802" loading="lazy"></p>
<p><strong>💡Note:</strong> In Astro, environment variables accessed on the client side must be prefixed with 'PUBLIC'. But since we're using Astro actions that run on the server, the prefix is not required.</p>
<h2 id="heading-part-3-how-to-set-up-supabase-ssr">Part 3: How to Set Up Supabase SSR</h2>
<h3 id="heading-create-the-supabase-client">Create the Supabase Client</h3>
<p>Create <code>src/lib/supabase.ts</code>:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { createServerClient, parseCookieHeader } <span class="hljs-keyword">from</span> <span class="hljs-string">"@supabase/ssr"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { AstroCookies } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createClient</span>(<span class="hljs-params">{
    request,
    cookies,
}: {
    request: Request;
    cookies: AstroCookies;
}</span>) </span>{
    <span class="hljs-keyword">const</span> cookieHeader = request.headers.get(<span class="hljs-string">"Cookie"</span>) || <span class="hljs-string">""</span>;

    <span class="hljs-keyword">return</span> createServerClient(
        <span class="hljs-keyword">import</span>.meta.env.SUPABASE_URL,
        <span class="hljs-keyword">import</span>.meta.env.SUPABASE_ANON_KEY,
        {
            cookies: {
                getAll() {
                    <span class="hljs-keyword">const</span> cookies = parseCookieHeader(cookieHeader);
                    <span class="hljs-keyword">return</span> cookies.map(<span class="hljs-function">(<span class="hljs-params">{ name, value }</span>) =&gt;</span> ({
                        name,
                        value: value ?? <span class="hljs-string">""</span>,
                    }));
                },
                setAll(cookiesToSet) {
                    cookiesToSet.forEach(<span class="hljs-function">(<span class="hljs-params">{ name, value, options }</span>) =&gt;</span>
                        cookies.set(name, value, options)
                    );
                },
            },
        }
    );
}
</code></pre>
<p>This sets up Supabase to handle <a target="_blank" href="https://supabase.com/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&amp;framework=astro&amp;queryGroups=environment&amp;environment=astro-browser">cookies in a server-rendered application</a> and exports a function that takes the request and cookies object as input. The function is set up like this because Astro has three ways to access request and cookie information:</p>
<ul>
<li><p>Through Astro’s global object, which is only available on Astro pages.</p>
</li>
<li><p>Through <code>AstroAPIContext</code> object, which is only available in Astro actions.</p>
</li>
<li><p>Through <code>APIContext</code> which is a subset of the global object and is available through API routes and middleware.</p>
</li>
</ul>
<p>So the <code>createClient</code> function accepts the <code>request</code> and <code>cookies</code> objects separately to make it flexible and applicable in the various contexts in which it may be used.</p>
<h3 id="heading-create-middleware-for-route-protection">Create Middleware for Route Protection</h3>
<p>Next, create a <code>middleware.ts</code> file in the <code>src</code> folder and paste this into it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { defineMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:middleware"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> onRequest = defineMiddleware(<span class="hljs-keyword">async</span> (context, next) =&gt; {
    <span class="hljs-keyword">const</span> { pathname } = context.url;

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Middleware executing for path:"</span>, pathname);

    <span class="hljs-keyword">const</span> supabase = createClient({
        request: context.request,
        cookies: context.cookies,
    });

    <span class="hljs-keyword">if</span> (pathname === <span class="hljs-string">"/protected"</span>) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Checking auth for protected route"</span>);

        <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.auth.getUser();

        <span class="hljs-comment">// If no user, redirect to index</span>
        <span class="hljs-keyword">if</span> (!data.user) {
            <span class="hljs-keyword">return</span> context.redirect(<span class="hljs-string">"/"</span>);
        }
    }

    <span class="hljs-keyword">return</span> next();
});
</code></pre>
<p>This middleware checks for an active user when accessing the protected route and redirects unauthenticated users to the index page.</p>
<h2 id="heading-part-4-how-to-build-the-user-interface">Part 4: How to Build the User Interface</h2>
<h3 id="heading-update-the-layout">Update the Layout</h3>
<p>First, update <code>src/layouts/Layout.astro</code> to include the Turnstile script. Add this just above the closing <code>&lt;/head&gt;</code> tag:</p>
<pre><code class="lang-typescript">&lt;script
    src=<span class="hljs-string">"https://challenges.cloudflare.com/turnstile/v0/api.js"</span>
    <span class="hljs-keyword">async</span>
    defer&gt;
&lt;/script&gt;
</code></pre>
<h3 id="heading-create-the-sign-in-page">Create the Sign-In Page</h3>
<p>Replace the contents of <code>src/pages/index.astro</code>:</p>
<pre><code class="lang-typescript">---
<span class="hljs-keyword">import</span> Layout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/Layout.astro"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/supabase"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/global.css"</span>;

<span class="hljs-keyword">const</span> supabase = createClient({
    request: Astro.request,
    cookies: Astro.cookies,
});

<span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.auth.getUser();

<span class="hljs-keyword">if</span> (data.user) {
    <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/protected"</span>);
}

<span class="hljs-keyword">const</span> apiKey = <span class="hljs-keyword">import</span>.meta.env.TURNSTILE_SITE_KEY;
---

&lt;Layout&gt;
    &lt;section <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex flex-col items-center justify-center m-30"</span>&gt;
        &lt;h1 <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-4xl text-left font-bold mb-12"</span>&gt;Sign In to Your Account&lt;/h1&gt;
        &lt;form id=<span class="hljs-string">"signin-form"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex flex-col gap-2 w-1/2"</span>&gt;
            &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"email"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">""</span>&gt;Enter your email&lt;/label&gt;
            &lt;input
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
                name=<span class="hljs-string">"email"</span>
                id=<span class="hljs-string">"email"</span>
                placeholder=<span class="hljs-string">"youremail@example.com"</span>
                <span class="hljs-keyword">class</span>=<span class="hljs-string">"border border-gray-500 rounded-md p-2"</span>
                required
            /&gt;
            &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"cf-turnstile"</span> data-sitekey={apiKey}&gt;&lt;/div&gt;
            &lt;button
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
                id=<span class="hljs-string">"sign-in"</span>
                <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-gray-600 hover:bg-gray-700 p-2 rounded-md text-white font-bold w-full cursor-pointer disabled:bg-gray-500 disabled:hover:bg-gray-500 disabled:cursor-not-allowed"</span>
                &gt;Sign In&lt;/button
            &gt;
        &lt;/form&gt;
    &lt;/section&gt;
&lt;/Layout&gt;
</code></pre>
<p>Here, the frontmatter creates a Supabase server client and then uses it to check if we have an active user. It redirects based on this information. This works because the front matter runs on the server side, and the project is set to server output.</p>
<p>The template displays a simple form with an email input. To complete it, add this below the closing <code>&lt;/Layout&gt;</code> tag:</p>
<pre><code class="lang-typescript">
&lt;script&gt;
    <span class="hljs-keyword">import</span> { actions } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:actions"</span>;

    <span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
        <span class="hljs-keyword">interface</span> Window {
            turnstile?: {
                reset: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
            };
        }
    }

    <span class="hljs-keyword">const</span> signInForm = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#signin-form"</span>) <span class="hljs-keyword">as</span> HTMLFormElement;
    <span class="hljs-keyword">const</span> formSubmitBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"sign-in"</span>) <span class="hljs-keyword">as</span> HTMLButtonElement;

    signInForm?.addEventListener(<span class="hljs-string">"submit"</span>, <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        formSubmitBtn.disabled = <span class="hljs-literal">true</span>;
        formSubmitBtn.textContent = <span class="hljs-string">"Signing in..."</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> turnstileToken = (
                <span class="hljs-built_in">document</span>.querySelector(
                    <span class="hljs-string">"[name='cf-turnstile-response']"</span>
                ) <span class="hljs-keyword">as</span> HTMLInputElement
            )?.value;

            <span class="hljs-keyword">if</span> (!turnstileToken) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"verification_missing"</span>);
            }

            <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData(signInForm);
            formData.append(<span class="hljs-string">"captchaToken"</span>, turnstileToken);

            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> actions.signIn(formData);

            <span class="hljs-keyword">if</span> (!results.data?.success) {
                <span class="hljs-keyword">if</span> (results.data?.message?.includes(<span class="hljs-string">"captcha protection"</span>)) {
                    alert(<span class="hljs-string">"Verification failed. Please try again."</span>);
                    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.turnstile) {
                        <span class="hljs-built_in">window</span>.turnstile.reset();
                    }
                    formSubmitBtn.disabled = <span class="hljs-literal">false</span>;
                    formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
                    <span class="hljs-keyword">return</span>;
                } <span class="hljs-keyword">else</span> {
                    alert(<span class="hljs-string">"Oops! Could not sign in. Please try again"</span>);
                    formSubmitBtn.disabled = <span class="hljs-literal">false</span>;
                    formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
                    <span class="hljs-keyword">return</span>;
                }
            }

            formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
            alert(<span class="hljs-string">"Please check your email to sign in"</span>);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.turnstile) {
                <span class="hljs-built_in">window</span>.turnstile.reset();
            }
            formSubmitBtn.disabled = <span class="hljs-literal">false</span>;
            formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
            <span class="hljs-built_in">console</span>.log(error);
            alert(<span class="hljs-string">"Something went wrong. Please try again"</span>);
        }
    });
&lt;/script&gt;
</code></pre>
<p>This adds some vanilla JavaScript that calls the <code>SignIn</code> Upon form submission. This action provides user feedback through alerts and manages the button’s text and disabled state. This effectively adds client-side interactivity to the page.</p>
<h3 id="heading-create-the-protected-page">Create the Protected Page</h3>
<p>Create <code>src/pages/protected.astro</code>:</p>
<pre><code class="lang-typescript">---
<span class="hljs-keyword">import</span> Layout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/Layout.astro"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/supabase"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/global.css"</span>;

<span class="hljs-keyword">const</span> supabase = createClient({
    request: Astro.request,
    cookies: Astro.cookies,
});

<span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.auth.getUser();
---

&lt;Layout&gt;
    &lt;section <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex flex-col items-center justify-center m-30"</span>&gt;
        &lt;h1 <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-4xl text-left font-bold mb-12"</span>&gt;You are logged <span class="hljs-keyword">in</span>!&lt;/h1&gt;
        &lt;p <span class="hljs-keyword">class</span>=<span class="hljs-string">"mb-6"</span>&gt;Your user Id: {data.user?.id}&lt;/p&gt;
        &lt;button
            id=<span class="hljs-string">"sign-out"</span>
            <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-md text-white font-bold cursor-pointer disabled:bg-gray-500 disabled:hover:bg-gray-500 disabled:cursor-not-allowed"</span>
            &gt;Sign Out&lt;/button
        &gt;
    &lt;/section&gt;
&lt;/Layout&gt;

&lt;script&gt;
    <span class="hljs-keyword">import</span> { actions } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:actions"</span>;
    <span class="hljs-keyword">const</span> signOutBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"sign-out"</span>) <span class="hljs-keyword">as</span> HTMLButtonElement;

    signOutBtn?.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        signOutBtn!.disabled = <span class="hljs-literal">true</span>;
        signOutBtn!.textContent = <span class="hljs-string">"Signing out..."</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> actions.signOut();

            <span class="hljs-keyword">if</span> (!results.data?.success) {
                signOutBtn!.disabled = <span class="hljs-literal">false</span>;
                signOutBtn!.textContent = <span class="hljs-string">"Sign Out"</span>;
                <span class="hljs-keyword">return</span> alert(<span class="hljs-string">"Oops! Could not sign Out. Please try again"</span>);
            }
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">window</span>.location.reload();
        } <span class="hljs-keyword">catch</span> (error) {
            signOutBtn.disabled = <span class="hljs-literal">false</span>;
            signOutBtn.textContent = <span class="hljs-string">"Sign Out"</span>;
            <span class="hljs-built_in">console</span>.log(error);
            <span class="hljs-keyword">return</span> alert(<span class="hljs-string">"Something went wrong. Please try again"</span>);
        }
    });
&lt;/script&gt;
</code></pre>
<p>This page retrieves the user data server-side in the front matter and displays it in the template, along with a sign-out button.</p>
<p>The JavaScript in the <code>script</code> tags handle calling the sign-out action, user feedback, and button state, as in the <code>index.astro</code> page.</p>
<h2 id="heading-part-5-how-to-set-up-astro-actions">Part 5: How to Set Up Astro Actions</h2>
<h3 id="heading-create-the-authentication-actions">Create the Authentication Actions</h3>
<p>Finally, add an <code>actions</code> folder in the <code>src</code> folder and create an <code>index.ts</code> file to hold our logic. Paste the following into it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { defineAction, <span class="hljs-keyword">type</span> ActionAPIContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:actions"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:schema"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/supabase"</span>;

<span class="hljs-keyword">const</span> emailSignUp = <span class="hljs-keyword">async</span> (
    {
        email,
        captchaToken,
    }: {
        email: <span class="hljs-built_in">string</span>;
        captchaToken: <span class="hljs-built_in">string</span>;
    },
    context: ActionAPIContext
) =&gt; {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sign up action"</span>);
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> supabase = createClient({
            request: context.request,
            cookies: context.cookies,
        });

        <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> supabase.auth.signInWithOtp({
            email,
            options: {
                captchaToken,
                emailRedirectTo: <span class="hljs-string">"http://localhost:4321/api/exchange"</span>,
            },
        });

        <span class="hljs-keyword">if</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Sign up error"</span>, error);
            <span class="hljs-keyword">return</span> {
                success: <span class="hljs-literal">false</span>,
                message: error.message,
            };
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sign up success"</span>, data);
            <span class="hljs-keyword">return</span> {
                success: <span class="hljs-literal">true</span>,
                message: <span class="hljs-string">"Successfully logged in"</span>,
            };
        }
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"SignUp action other error"</span>, err);
        <span class="hljs-keyword">return</span> {
            success: <span class="hljs-literal">false</span>,
            message: <span class="hljs-string">"Unexpected error"</span>,
        };
    }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> server = {
    signIn: defineAction({
        accept: <span class="hljs-string">"form"</span>,
        input: z.object({
            email: z.string().email(),
            captchaToken: z.string(),
        }),
        handler: <span class="hljs-keyword">async</span> (input, context) =&gt; {
            <span class="hljs-keyword">return</span> emailSignUp(input, context);
        },
    }),
    signOut: defineAction({
        handler: <span class="hljs-keyword">async</span> (_, context) =&gt; {
            <span class="hljs-keyword">const</span> supabase = createClient({
                request: context.request,
                cookies: context.cookies,
            });
            <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signOut();
            <span class="hljs-keyword">if</span> (error) {
                <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Sign out error"</span>, error);
                <span class="hljs-keyword">return</span> {
                    success: <span class="hljs-literal">false</span>,
                    message: error.message,
                };
            }
            <span class="hljs-keyword">return</span> {
                success: <span class="hljs-literal">true</span>,
                message: <span class="hljs-string">"Successfully signed out"</span>,
            };
        },
    }),
};
</code></pre>
<p>This action handles both sign-in and sign-out methods. A Supabase server instance is created during the sign-in method, and the magic link method is used for sign-in. It passes a redirect URL, which we have yet to create, and handles any errors that may occur.</p>
<p>It also passes the token verification, allowing Supabase to perform verification on our behalf, eliminating the need to call <a target="_blank" href="https://developers.cloudflare.com/turnstile/get-started/server-side-validation/">Cloudflare’s verify APIs</a> directly.</p>
<p>The sign-out method calls Supabase’s sign-out method and handles any potential errors.</p>
<p>The redirect URL refers to an API route that exchanges the code from the email Supabase sends for a session that Supabase handles.</p>
<h3 id="heading-create-the-code-exchange-api-route">Create the Code Exchange API Route</h3>
<p>Create <code>src/pages/api/exchange.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> GET: APIRoute = <span class="hljs-keyword">async</span> ({ request, cookies, redirect }) =&gt; {
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url);
    <span class="hljs-keyword">const</span> code = url.searchParams.get(<span class="hljs-string">"code"</span>);

    <span class="hljs-keyword">if</span> (!code) {
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"/"</span>);
    }

    <span class="hljs-keyword">const</span> supabase = createClient({ request, cookies });
    <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.exchangeCodeForSession(code);

    <span class="hljs-keyword">if</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error exchanging code for session:"</span>, error);
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"/404"</span>);
    }

    <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"/protected"</span>);
};
</code></pre>
<p>This grabs the code from the URL in the magic link sent, creates a server client, and calls the <code>exchangeCodeForSession</code> method with the code. It handles any error by redirecting to Astro’s built-in not-found page.</p>
<p>Otherwise, it will redirect to the protected page as Supabase handles the session implementation details.</p>
<h2 id="heading-part-6-how-to-test-your-application">Part 6: How to Test Your Application</h2>
<p>Start your development server: <code>npm run dev</code></p>
<p>Visit the provided localhost URL. You should see the sign-in page with the Turnstile widget:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750267075336/66ad5f39-67c6-458a-96ea-4dfe1123b015.png" alt="Sign-in page with Turnstile verification and email input field" width="2356" height="956" loading="lazy"></p>
<p>If you try to access the <code>/protected</code> page, it will redirect you back to this view until you sign in. Now, sign in, and you should get an email with a link that will redirect you to the <code>/protected</code> page. This is what you should see:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750335131827/f85cde2f-f9bb-46b0-a09e-6ae6456cd49f.png" alt="Text reads: &quot;You are logged in!&quot; with a field labeled &quot;Your user Id&quot; and a &quot;Sign Out&quot; button below." width="1200" height="502" loading="lazy"></p>
<p>And with that, you've successfully built a comprehensive auth system that leverages Astro actions, Supabase auth, and Cloudflare Turnstile's bot protection. This setup provides a secure, user-friendly authentication experience while protecting your application from malicious actors.</p>
<h2 id="heading-notes-and-additional-resources">Notes and Additional Resources</h2>
<h3 id="heading-useful-documentation">Useful Documentation</h3>
<ul>
<li><p><a target="_blank" href="https://supabase.com/docs/guides/auth/server-side/advanced-guide">Supabase's advanced guide to SSR</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/supabase/ssr">Supabase SSR package</a></p>
</li>
<li><p><a target="_blank" href="https://docs.astro.build/en/reference/api-reference/#cookies">Astro Cookies documentation</a></p>
</li>
<li><p><a target="_blank" href="https://supabase.com/docs/guides/auth/sessions/pkce-flow">Supabase PKCE flow documentation</a></p>
</li>
<li><p><a target="_blank" href="https://docs.astro.build/en/guides/actions/">Astro Actions documentation</a></p>
</li>
<li><p><a target="_blank" href="https://developers.cloudflare.com/turnstile/get-started/">Get started with Turnstile</a></p>
</li>
</ul>
<h3 id="heading-complete-code-repository">Complete Code Repository</h3>
<p>The complete code for this project is available on GitHub:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/FatumaA/supa-ssr">Base authentication setup</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/FatumaA/supa-ssr/tree/add-cloudflare">With Cloudflare Turnstile</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Move Figma Components to Penpot ]]>
                </title>
                <description>
                    <![CDATA[ Penpot is an open-source design tool for creating complete design systems. It is free, self-hostable, and allows multiple projects. Penpot supports reusable components and assets and allows files and libraries to be shared across projects. Penpot fil... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-recreate-figma-components-in-penpot/</link>
                <guid isPermaLink="false">67e48bc10d600bc3806ccf54</guid>
                
                    <category>
                        <![CDATA[ Design Systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Penpot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ figma ]]>
                    </category>
                
                    <category>
                        <![CDATA[ components ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Wed, 26 Mar 2025 23:20:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743028701920/ef200f8f-888d-4658-a5b0-ffa483edbdcf.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://penpot.app/">Penpot</a> is an open-source design tool for creating complete design systems. It is free, self-hostable, and allows multiple projects. Penpot supports reusable components and assets and allows files and libraries to be shared across projects. Penpot files can be exported in various formats to enhance collaboration.</p>
<p>Penpot designs directly translate to code that meets web standards, facilitating collaboration and hand-off between designers and developers.</p>
<p>This tutorial will walk you through recreating or moving existing <a target="_blank" href="https://www.figma.com/">Figma</a> components to Penpot. It will include a rundown of the Penpot tool and how to use it, a brief comparison of the two tools, and important resources.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-recreate-existing-figma-files-in-penpot">How to Recreate Existing Figma Files in Penpot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-penpot-user-interface">Understanding the Penpot User Interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-penpot-layouts">Understanding Penpot Layouts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-use-penpot-components">How to Create and Use Penpot Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-reusable-assets-in-penpot">How to Create Reusable Assets in Penpot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-and-use-icons-in-penpot">How to Add and Use Icons in Penpot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-figma-vs-penpot-my-experience">Figma Vs. Penpot - My experience</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resources-and-notes">Resources and Notes</a></p>
</li>
</ul>
<h2 id="heading-how-to-recreate-existing-figma-files-in-penpot">How to Recreate Existing Figma Files in Penpot</h2>
<p>There are two ways to move or recreate existing Codex components from Figma to Penpot: By using the community-supported migration tool or by manually recreating the components and files.</p>
<h3 id="heading-using-the-community-supported-migration-plugin">Using the Community-Supported Migration Plugin</h3>
<p>You can use <a target="_blank" href="https://www.figma.com/community/plugin/1219369440655168734/penpot-exporter">this plugin</a> to move your Figma designs to Penpot. It is a straightforward way to migrate from Figma, especially if you have many components.</p>
<p>To do so, add it as a Figma plugin and find the option in the context menu. Then, click on the "Penpot Exporter" option to activate the plugin. The plugin will then create a zip file for your component and download it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742571496007/da97ad1b-6939-4bb9-9000-6c367a711797.gif" alt="A screenrecording illustrating how to add and use the &quot;Penpot Exporter&quot; plugin." class="image--center mx-auto" width="800" height="450" loading="lazy"></p>
<p>Over in Penpot, in your Projects dashboard, click on the three dots context menu and select "Import Penpot files" to open a file picker window. Click on the zip file downloaded from Figma, and Penpot will create a new project with the same name as the zip file. This new project will contain your Figma component translated to the Penpot environment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742571779644/1ebeb54d-ce27-4987-b630-f101a60ee8d3.gif" alt="An illustration of how to import files to Penpot" class="image--center mx-auto" width="800" height="450" loading="lazy"></p>
<p>Looking at the new component, you'll notice that the migration plugin correctly picks up any child components and typography variables while keeping things fairly organized. But it does not pick up color variables.</p>
<h3 id="heading-manually-recreating-figma-components">Manually Recreating Figma components</h3>
<p>Another way to move Figma components to Penpot is to recreate them manually. While this option is more tedious than the previous one, it gives you fine-tuned control over how the component looks, how the files are organized, and how the assets are carried over.</p>
<p>It is an excellent excuse to spruce up the existing components, especially if there are few. It is also a good exercise for first-time users to grasp Penpot.</p>
<p>To effectively move components using this method, you will need to understand how Penpot works. The following concepts will help make this process more efficient.</p>
<h2 id="heading-understanding-the-penpot-user-interface">Understanding the Penpot User Interface</h2>
<p>Penpot's UI is pretty similar to Figma's, except that Penpot defaults to Dark Mode. You have your controls on the right and your files, assets, and layers on the left, with a canvas in the middle.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742811019699/ea5336e3-50a7-4771-bfa6-07a225390320.png" alt="Alt: Screenshot of a Penpot UI with a left panel labeled &quot;Layers&quot; showing file structure, a central canvas labeled &quot;Canvas,&quot; and a right panel with various design tools and settings." class="image--center mx-auto" width="1600" height="620" loading="lazy"></p>
<p>Both have a toolbar that enables you to interact with the canvas. The toolbars have slightly different icons and names but have the same functionality. Notably, in Penpot, the Frame tool is called Board.</p>
<p>Penpot supports most of Figma's features, except for Design Tokens, which will be added soon.</p>
<h2 id="heading-understanding-penpot-layouts">Understanding Penpot Layouts</h2>
<p>Penpot supports Flex and Grid layouts. These layouts allow you to position items precisely and simplify creating fluid designs. For those familiar with CSS Grid and Flex, they work in the same way.</p>
<p>Flex layout allows you to organize the elements within a parent container, like a board or an encompassing shape, across one dimension, width, or height. It enables you to determine the spacing and positioning of the child elements via visual controls with labels on the right side panel.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742571904634/c8bf8fc0-79a7-415f-916b-42da7c881ed8.gif" alt="An illustration on how to use Penpot Flex Layout" class="image--center mx-auto" width="800" height="450" loading="lazy"></p>
<p>Grid layouts allow the same controls as Flex layouts but with the added flexibility of simultaneously spanning a child element across both dimensions. This makes Grid layouts quite helpful in creating complex and nuanced designs.</p>
<h2 id="heading-how-to-create-and-use-penpot-components">How to Create and Use Penpot Components</h2>
<p>Creating a component in Penpot is relatively straightforward. You select the elements that will make up the component, then right-click and click "Create component," turning your selection into a component.</p>
<p>Your new component will be available under the "Assets" tab on the left side panel. To use, drag it onto the canvas.</p>
<p>You can also create copies of the component. By default, the copies are tied to the main component, and any changes to the main will be reflected in all copies. You can override this by detaching the copies from the context menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742571991986/6dd4820b-0f3a-4d41-895d-cb9f5e7d4f52.gif" alt="A speedrun of how to create a Penpot Component" class="image--center mx-auto" width="800" height="450" loading="lazy"></p>
<h2 id="heading-how-to-create-reusable-assets-in-penpot">How to Create Reusable Assets in Penpot</h2>
<p>Penpot has three asset types: Typography, Colors, and Components, with <a target="_blank" href="https://penpot.app/collaboration/design-tokens">Penpot design tokens</a> coming very soon. Typography refers to information on font types and weights that make up text styles. Colors enable you to create and sort your repeated colors. Components refer to reusable design pieces and, in this context, include graphical items like icons and images.</p>
<p>To create a color or typography asset, click the "Assets" tab and the plus icon on the relevant accordion item. For color, it will open a color picker. Paste the hex value in the appropriate place, then save it. The color will show up in the accordion drop-down. You can click on it to give it a semantic name if desired.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742809861347/9ae3b0fb-798b-4363-ab90-873c18a16793.png" alt="Screenshot of a Penpot UI showing a color palette with hex codes, a color picker, and local library assets." class="image--center mx-auto" width="976" height="800" loading="lazy"></p>
<p>For typography, click on the plus icon at the relevant accordion. This will open a panel where you can specify the font, font weight, letter spacing, and line height. You can also change the asset's name after it's saved.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742810318737/daf0e24f-7c4a-4c00-bb73-ccca7575bcf5.png" alt="daf0e24f-7c4a-4c00-bb73-ccca7575bcf5" class="image--center mx-auto" width="443" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-and-use-icons-in-penpot">How to Add and Use Icons in Penpot</h2>
<p>You can add icon packs and libraries in Penpot from the Project dashboard. At the bottom of the page, a banner containing a list of commonly used libraries is displayed. These libraries usually include a few icon packs. You can scroll through to choose one or click on the explore more option at the end of the slider to open up the dedicated Penpot templates page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742810534992/83b2eb72-4877-49b3-9d2e-02c37e0cb894.png" alt="Screenshot of Penpot UI's lower bottom showing &quot;Penpot - Design System v2.0&quot;, &quot;New Project 1&quot;, &quot;Avataaars&quot;, &quot;UX Notes&quot;, &quot;Whiteboarding Kit&quot;, &quot;Open Color Scheme&quot;, and &quot;Flex Layout Playground&quot;. A sidebar labeled &quot;Libraries &amp; Templates&quot; is visible on the right." class="image--center mx-auto" width="900" height="282" loading="lazy"></p>
<p>If you find a relevant library from the bottom banner, click the download icon. Penpot will prompt you to add the selected library. Once you accept it, it will be added to the "Libraries" menu item on the left side panel.</p>
<p>If you click through to the dedicated templates page, clicking the download icon will download a Penpot file. You can then drag it to the projects page, and it will also be available in the "Libraries" section of the projects page.</p>
<p>To use the libraries within your project file, click the "Assets" tab on the left side panel, then click the "Libraries" button below it. This will open a modal with all your libraries listed under "Shared Libraries."</p>
<p>Click on the plus icon on the ones you want to use in your project. This will link that library to your project and be available in the left side panel. You can then search for any particular icon from the search bar and drag it onto the canvas.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742571300138/3dbbc937-f727-4c03-987b-43ddb71b1608.gif" alt="A gif illustrating how to link and delink libraries in the Penpot User Interface" class="image--center mx-auto" width="800" height="450" loading="lazy"></p>
<p>To remove a library from your project, click on the libraries button. In the modal, under "libraries in this file," all used libraries are listed. Click on the link icon next to the library you want to remove. This will delink it and remove it from the side panel.</p>
<h2 id="heading-figma-vs-penpot-my-experience">Figma Vs. Penpot – My experience</h2>
<p>As a first-time user of Penpot and a long-term casual user of Figma, I noticed some differences and encountered some teething problems.</p>
<h3 id="heading-swapping-out-icons">Swapping Out Icons</h3>
<p>You can <a target="_blank" href="https://help.penpot.app/user-guide/components/#component-swap">swap out components</a> in Penpot! It's too bad that I discovered this after I had finished my first component migration.</p>
<h3 id="heading-renaming-files-and-folders">Renaming Files and Folders</h3>
<p>I struggled to find how to rename files and folders in Penpot. I kept looking for the shortcut or a menu item in the context menu. It turns out you have to double-click on the title to change it. You can rename files in the same way in Figma, but I just never did it like that.</p>
<h3 id="heading-adherence-to-web-standards">Adherence to Web Standards</h3>
<p>I was pleasantly surprised at the code quality that Penpot provides, along with your designs. It meets the <a target="_blank" href="https://www.w3.org/standards/">w3c standards</a>, but the HTML could be more <a target="_blank" href="https://web.dev/learn/html/semantic-html">semantic</a>.</p>
<h3 id="heading-penpot-design-system">Penpot Design System</h3>
<p>Penpot avails its <a target="_blank" href="https://community.penpot.app/t/pencil-the-penpot-design-system/7152">design system</a> along with lots of little libraries that help you understand how the tool works. This is very nice as it allows you to get a good understanding of design language and how to organize design systems.</p>
<h3 id="heading-limited-shape-options-in-the-toolbar">Limited Shape Options in the Toolbar</h3>
<p>The Penpot toolbar lacks essential shapes such as pen, arrow, and triangle. While you can recreate this with the path tool or use a library to import them, it is definitely something I missed that was readily available in Figma.</p>
<p>It took me a few tries to get comfortable with Penpot and in the end, I really liked it. Plus, its open-source nature makes it even more endearing.</p>
<h2 id="heading-resources-and-notes">Resources and Notes</h2>
<p>Using Penpot has a learning curve, even if you are familiar with other design tools like Figma. These resources can help you get up to speed faster and make you more efficient with Penpot.</p>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/live/64O8qi51Jqc?si=A6AVp1m8se3gl1ut">Penpot video on layouts</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/live/aW0LNHLEI_Y?si=HATAWee8tH29Sgrq&amp;t=1604">Penpot live on design tokens</a></p>
</li>
<li><p><a target="_blank" href="https://help.penpot.app/user-guide/">Penpot documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.figma.com/community/plugin/1219369440655168734/penpot-exporter">Penpot migration plugin</a></p>
</li>
<li><p><a target="_blank" href="https://penpot.app/libraries-templates">Penpot templates and libraries</a></p>
</li>
<li><p><a target="_blank" href="https://penpot.app/blog/penpot-for-design-systems-101/">Penpot design systems</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Quickly Add Auth to your Flutter Apps with Supabase Auth UI ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you will learn how to use Supabase's auth package to quickly and efficiently add authentication functionality to your Flutter apps. We will go through the entire process, from setting up a Flutter project to configuring Email/Passwor... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/add-auth-to-flutter-apps-with-supabase-auth-ui/</link>
                <guid isPermaLink="false">66b999a5c39234149cf01130</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Mon, 03 Jun 2024 19:55:22 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/supa-auth-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you will learn how to use Supabase's <a target="_blank" href="https://pub.dev/packages/supabase_auth_ui">auth package</a> to quickly and efficiently add authentication functionality to your Flutter apps. We will go through the entire process, from setting up a Flutter project to configuring Email/Password, OAuth, and Magic link flows.</p>
<p>In the end, you'll have a complete authentication system with theming, localisation, and native support.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li>
        <a href="#prerequisites">Prerequisites</a>
    </li>
    <li>
        <a href="#what-is-supabase-auth-ui">What is Supabase Auth UI</a>
    </li>
    <li>
        <a href="#supported-authentication-methods
        ">Supported Authentication Methods
        </a>
    </li>
    <li>
        <a href="#how-to-set-up-a-flutter-project
        ">How to Set Up a Flutter Project
        </a>
    </li>

    <li>
        <a href="#how-to-connect-to-a-supabase-project
        ">How to Connect to a Supabase Project
        </a>
    </li>
    <li>
        <a href="#how-to-implement-auth-in-a-flutter-app
        ">How to Implement Auth in a Flutter App
        </a>
    </li>

    <li>
        <a href="#how-to-set-up-supabase-email-and-oauth-provider-authentication
                 ">How to Set Up Supabase Email and OAuth Provider Authentication
        </a>
        <ul>
            <li><a href="#how-to-set-up-github-as-an-oauth-provider">How to Set Up GitHub as an OAuth Provider</a>
            </li>
            <li><a href="#how-to-set-up-google-as-an-oauth-provider">How to Set Up Google as an OAuth Provider</a>
            </li>
        </ul>
</li>


<li><a href="#how-to-sign-in-a-user-using-the-auth-ui-package
    ">How to Sign in a User using the Auth UI Package
    </a></li>

<li><a href="#how-to-add-magic-link-auth
    ">How to Add Magic Link Auth
    </a></li>
<li><a href="#native-auth-support-how-to-sign-in-with-google
    ">Native Auth Support - How to Sign in with Google
    </a></li>

   <li>
        <a href="#how-to-theme-your-supabase-auth-ui
                 ">How to Theme Your Supabase Auth UI
        </a>
        <ul>
            <li><a href="#flexible-layout-options">Flexible Layout Options</a>
            </li>
            <li><a href="#localization-and-translation-ready">Localization and Translation Ready</a>
            </li>
        </ul>
</li>



    <li>
        <a href="#summary">
            Summary
        </a>
    </li>
  <li>
        <a href="#resources">
            Resources
        </a>
    </li>

</ul>

<p>## </p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This article assumes that you have:</p>
<ul>
<li>A basic understanding of <a target="_blank" href="https://docs.flutter.dev/">Flutter</a></li>
<li>Flutter <a target="_blank" href="https://docs.flutter.dev/get-started/install">installed</a> and ready to go</li>
<li>A basic understanding of <a target="_blank" href="https://www.cloudflare.com/en-gb/learning/serverless/glossary/backend-as-a-service-baas/">Backend-as-a-Service concepts</a></li>
<li>A basic understanding of <a target="_blank" href="https://www.freecodecamp.org/news/set-up-authentication-in-apps-with-supabase/">authentication</a></li>
<li>An IDE (Integrated Developer Environment) or a <a target="_blank" href="https://code.visualstudio.com/download">text editor</a> to work in</li>
</ul>
<h2 id="heading-what-is-supabase-auth-ui">What is Supabase Auth UI?</h2>
<p>Supabase Auth UI is an open-source community-supported Flutter package that offers pre-configured, theme-able widgets to simplify the process of creating authentication forms.</p>
<p>What's more? It's translation ready.</p>
<h2 id="heading-supported-authentication-methods">Supported Authentication Methods</h2>
<p>The Supabase auth UI supports the following authentication methods out of the box:</p>
<ul>
<li>Magic links</li>
<li>Email and Password auth</li>
<li>OAuth/Social login auth</li>
<li>Sign in with Google</li>
<li>Sign in with Apple</li>
</ul>
<h2 id="heading-how-to-set-up-a-flutter-project">How to Set Up a Flutter Project</h2>
<p>The first thing you will need is a Flutter project set up. Open your preferred text editor to the location you'd like to keep the Flutter project, then open up the integrated terminal and run <code>flutter create auth_example</code>. This will create a folder called "auth_example" in the same location.</p>
<p>Open the newly created folder and paste the following in the <code>pubspec.yaml</code> file as part of the dependencies: <code>supabase_auth_ui: ^0.4.4</code>. </p>
<p>The file should look similar to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716931528734/b4e92507-4427-4945-a74e-de8edd0c1107.png" alt="Image" width="1696" height="776" loading="lazy">
_A code snippet showing a Flutter <code>pubspec.yaml</code> file with dependencies. The <code>supabase_auth_ui</code> dependency is highlighted with a pink arrow, indicating version <code>^0.4.4</code>._</p>
<p>Back in the integrated terminal, run <code>cd auth_example</code> to change into the correct folder then run <code>flutter pub get</code> to get the auth dependency into your project.</p>
<h2 id="heading-how-to-connect-to-a-supabase-project">How to Connect to a Supabase Project</h2>
<p>Go to your Supabase dashboard or <a target="_blank" href="https://supabase.com/">create an account</a> if you don't have one. In the dashboard, go to Project settings at the bottom then click on API under configuration. Copy the URL and the project anon key from the right hand side of the page as illustrated below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716932440241/1f5c49a5-5ef8-449f-8bec-557e0492e950.png" alt="Image" width="2822" height="1332" loading="lazy">
<em>A screenshot of the API settings page in Supabase dashboard. The left sidebar shows various menu options under "Project Settings," "Configuration," and "Billing." The "API" option under "Configuration" is highlighted. The main section displays fields for "Project URL" and "Project API keys," with options to copy or reveal the keys.</em></p>
<p>Back in your Flutter app, create a <code>.env</code> file at the root of the folder and paste the following, replacing with the values you copied above:</p>
<pre><code class="lang-bash">SUPABASE_URL=your_url
SUPABASE_ANON_KEY=your_project_anon_key
</code></pre>
<p>Add the environment file to the .<code>gitignore</code> file to keep it out of version control, then add the <code>flutter_dotenv</code> package to the list of dependencies right below <code>supabase_auth_ui</code> dependency. Finally add the <code>.env</code> file as a path under the assets key in the pubspec.yaml file. </p>
<p>The file should look like so:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716933569314/2beb83cc-f8fc-406a-ac39-dedb5d32d42d.png" alt="Image" width="1674" height="1066" loading="lazy">
_A screenshot of a code editor displaying a <code>pubspec.yaml</code> file for a Flutter project. The file lists dependencies such as <code>flutter</code>, <code>supabase_auth_ui</code>, and <code>flutter_dotenv</code>. Two pink arrows point to the <code>supabase_auth_ui: ^0.4.4</code> and <code>flutter_dotenv: ^5.1.0</code> dependencies. The file also includes sections for <code>dev_dependencies</code>, <code>flutter_lints</code>, and <code>assets</code>._</p>
<p>In the <code>main.dart</code> file, replace the <code>main</code> function with the following code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">await</span> dotenv.load(fileName: <span class="hljs-string">".env"</span>);
  <span class="hljs-keyword">await</span> Supabase.initialize(
      url: dotenv.env[<span class="hljs-string">'SUPABASE_URL'</span>]!,
      anonKey: dotenv.env[<span class="hljs-string">'SUPABASE_ANON_KEY'</span>]!);
  runApp(
    <span class="hljs-keyword">const</span> MyApp(),
  );
}
</code></pre>
<p>This loads the <code>.env</code> file and initialises Supabase.</p>
<h2 id="heading-how-to-implement-auth-in-a-flutter-app">How to Implement Auth in a Flutter App</h2>
<p>Replace the rest of the code below the <code>main</code> function with the following:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyApp({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Supabase Auth UI'</span>,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
      ),
      initialRoute: <span class="hljs-string">'/'</span>,
      routes: {
        <span class="hljs-string">'/'</span>: (context) =&gt; <span class="hljs-keyword">const</span> SplashScreen(),
        <span class="hljs-string">'/auth'</span>: (context) =&gt; AuthScreen(),
        <span class="hljs-string">'/home'</span>: (context) =&gt; HomeScreen(),
      },
    );
  }
}
</code></pre>
<p>Create a <code>splash_screen.dart</code> file in the <code>lib</code> folder and paste the following in it:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:auth_ui_example/auth_screen.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:auth_ui_example/home_screen.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:supabase_auth_ui/supabase_auth_ui.dart'</span>;

<span class="hljs-keyword">final</span> activeSession = Supabase.instance.client.auth.currentSession;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SplashScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> SplashScreen({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: Center(child: activeSession == <span class="hljs-keyword">null</span> ? AuthScreen() : HomeScreen()),
    );
  }
}
</code></pre>
<p>This redirects the user to a different screen depending on whether or not the user has an active session.</p>
<p>Now create a new file in the <code>lib</code> folder called <code>home_screen.dart</code> and paste the following into it:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:supabase_auth_ui/supabase_auth_ui.dart'</span>;

<span class="hljs-keyword">final</span> supabase = Supabase.instance.client;
<span class="hljs-keyword">final</span> activeSession = supabase.auth.currentSession;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomeScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;HomeScreen&gt; createState() =&gt; _HomeScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomeScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomeScreen</span>&gt; </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    <span class="hljs-keyword">if</span> (activeSession == <span class="hljs-keyword">null</span>) {
      Navigator.pushNamed(context, <span class="hljs-string">'/auth'</span>);
    }
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Home'</span>)),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              <span class="hljs-string">'You are home - <span class="hljs-subst">${activeSession?.user.id}</span>'</span>,
              style: <span class="hljs-keyword">const</span> TextStyle(fontWeight: FontWeight.bold, fontSize: <span class="hljs-number">18</span>),
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">24.0</span>),
            ElevatedButton(
              onPressed: () <span class="hljs-keyword">async</span> {
                <span class="hljs-keyword">await</span> supabase.auth.signOut();
                Navigator.pushNamed(context, <span class="hljs-string">'/'</span>);
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Sign out'</span>),
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p>This creates a stateful widget class that checks for an active session and redirects to the authentications screen if there is no active session. It also displays some text and a button that allows the user to sign out of the application.</p>
<p>Finally, create <code>auth_screen.dart</code> in the <code>lib</code> folder and paste the following into it:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:supabase_auth_ui/supabase_auth_ui.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> AuthScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView(
        padding: <span class="hljs-keyword">const</span> EdgeInsets.fromLTRB(<span class="hljs-number">24.0</span>, <span class="hljs-number">96.0</span>, <span class="hljs-number">24.0</span>, <span class="hljs-number">24.0</span>),
        children: [
          Column(
            children: [
              <span class="hljs-keyword">const</span> Text(
                <span class="hljs-string">'Supabase Auth UI'</span>,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: <span class="hljs-number">18</span>,
                ),
              ),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">24.0</span>),
              SupaEmailAuth(
                redirectTo:
                    kIsWeb ? <span class="hljs-keyword">null</span> : <span class="hljs-string">"myapptest://com.example.auth_ui_example"</span>,
                onSignInComplete: (res) =&gt; Navigator.pushNamed(context, <span class="hljs-string">'/home'</span>),
                onSignUpComplete: (res) =&gt; Navigator.pushNamed(context, <span class="hljs-string">'/home'</span>),
                onError: (error) =&gt; SnackBar(content: Text(error.toString())),
              ),
              SupaSocialsAuth(
                socialProviders: <span class="hljs-keyword">const</span> [
                  OAuthProvider.google,
                  OAuthProvider.github,
                ],
                redirectUrl:
                    kIsWeb ? <span class="hljs-keyword">null</span> : <span class="hljs-string">"myapptest://com.example.auth_ui_example"</span>,
                onSuccess: (session) =&gt; Navigator.pushNamed(
                  context,
                  <span class="hljs-string">'/home'</span>,
                ),
                onError: (error) =&gt; SnackBar(
                  content: Text(
                    error.toString(),
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
</code></pre>
<p>This displays some text and special widgets from the <code>supabase_auth_ui</code> package that display a sign up/in form and some social login options.</p>
<p>In the integrated terminal, run <code>flutter run</code> to start the application. It will give you several platforms to run on, so just choose chrome for this case. You should see this beautiful interface out of the box 🎉:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716935543736/501f4324-0bde-4aac-bc07-a54301d049a7.png" alt="Image" width="2090" height="1116" loading="lazy">
<em>Supabase Auth UI login screen with fields for email and password, options to sign in, reset password, sign up, and buttons to continue with Google or GitHub.</em></p>
<p>Taking a closer look at the code in the auth screen file, you will see that we are using <code>SupaEmailAuth</code> and <code>SupaSocialsAuth</code> widgets to achieve this. The email auth widget takes in a callback telling it what to do on failure and on success of the authentication action. The social login widget takes the same, plus a list of OAuth providers you wish to use in your application.</p>
<h2 id="heading-how-to-set-up-supabase-email-and-oauth-provider-authentication">How to Set Up Supabase Email and OAuth Provider Authentication</h2>
<p>To have Supabase auth UI work properly on both mobile and web, you will need to set up deep links and a site URL.</p>
<p>Under authentication &gt; URL configurations, add "<a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a>" as the site URL.</p>
<p>Then under "redirect URLs" add "YOUR_SCHEME://YOUR_HOSTNAME" as the value, where YOUR_SCHEME is a unique identifier that you decide for your app and YOUR_HOSTNAME is the package name in your <code>build.gradle</code> file as applicationID under android &gt; src &gt; build.gradle. Something like this: "myapptest://com.example.auth_ui_example".</p>
<p>Complete configuring Android by going to the <code>AndroidManifest</code> file under android &gt; src &gt; main &gt; AndroidManifest.xml, and adding this code below the existing <code>&lt;intent-filter&gt;</code>, above the closing <code>&lt;/activity&gt;</code> tag:</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">intent-filter</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">action</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.action.VIEW"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">category</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.category.DEFAULT"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">category</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.category.BROWSABLE"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">data</span>
                <span class="hljs-attr">android:scheme</span>=<span class="hljs-string">"myapptest"</span>
                <span class="hljs-attr">android:host</span>=<span class="hljs-string">"com.example.auth_ui_example"</span> /&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">intent-filter</span>&gt;</span>
</code></pre>
<p>Complete the iOS configuration by pasting the following code at the bottom of the <code>info.plist</code> file, just above the closing <code>&lt;/dict&gt;</code> tag, under ios &gt; Runner &gt; info.plist:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>CFBundleURLTypes<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">array</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dict</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>CFBundleTypeRole<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>Editor<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>CFBundleURLSchemes<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">array</span>&gt;</span>
            <span class="hljs-comment">&lt;!-- Add your custom URL scheme here --&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>myapptest<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">array</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dict</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">array</span>&gt;</span>
</code></pre>
<p>That completes the set up across web and mobile.</p>
<p>To set up email auth, in the Supabase dashboard, go to authentication &gt; providers, and look for email. Make sure it is enabled. That's all for setting up email.</p>
<h3 id="heading-how-to-set-up-github-as-an-oauth-provider">How to Set Up GitHub as an OAuth Provider</h3>
<p>To enable GitHub as an authentication provider, scroll down the list of providers until you get to GitHub. Open the dropdown, enable it, and copy the callback URL. Then follow these steps:</p>
<ol>
<li>Create a new OAuth App <a target="_blank" href="https://github.com/settings/developers">here</a>, fill in the required information, and add the copied URL under the authorization URL field.</li>
<li>Add the homepage URL as "<a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a>".</li>
<li>Copy the clientID from GitHub and add it to the Supabase GitHub provider settings.</li>
<li>Generate a new secret and paste it into the client secret field in the Supabase GitHub provider settings.</li>
</ol>
<p>Save it, and you are done with setting up GitHub as a provider.</p>
<h3 id="heading-how-to-set-up-google-as-an-oauth-provider">How to Set Up Google as an OAuth Provider</h3>
<p>Go back to the Supabase dashboard and enable the Google provider. Copy the callback URL, then follow these steps:</p>
<ol>
<li>Create a new <a target="_blank" href="https://www.cloud.google.com/">Google cloud project</a>.</li>
<li>Go to Credentials, click on the create credentials button, and then choose OAuth client ID.</li>
<li>Go on and configure the consent screen to external users.</li>
<li>Go back to credentials, click create credentials, and choose web application in the application type field.</li>
<li>Copy the callback URL from the Supabase settings, paste it in under the authorised URLs field, and leave the rest as is and save.</li>
<li>A screen will pop up with the client ID and secret. Copy those over and paste them in the relevant fields in the Supabase Google provider settings.</li>
</ol>
<p>Save it, and you are done with setting up Google as a provider.</p>
<h2 id="heading-how-to-sign-in-a-user-using-the-auth-ui-package">How to Sign in a User using the Auth UI Package</h2>
<p>To test that auth is working so far, go back to your application, run <code>flutter run -d chrome --web-port=3000</code> in the integrated terminal window, and click one of the Social login buttons.</p>
<p>You should be presented with a window asking you to confirm permissions, after which the app will sign you in. Signing in via email/password should also work.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717072136518/6aed0f14-595a-49f4-9999-f04977d5463f.png" alt="Image" width="2032" height="1198" loading="lazy">
<em>Authorization screen for "Test Flutter auth ui" requesting access to a GitHub account. Options to "Cancel" or "Authorize FatumaA" are available.</em></p>
<p>Now log out of the application and try to manually navigate to the home screen. It will redirect you back to the auth screen.</p>
<h2 id="heading-how-to-add-magic-link-auth">How to Add Magic Link Auth</h2>
<p>If you only want to sign in a user using magic link, replace the contents in the <code>auth_screen</code> file with this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:supabase_auth_ui/supabase_auth_ui.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> AuthScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView(
        padding: <span class="hljs-keyword">const</span> EdgeInsets.fromLTRB(<span class="hljs-number">24.0</span>, <span class="hljs-number">96.0</span>, <span class="hljs-number">24.0</span>, <span class="hljs-number">24.0</span>),
        children: [
          Column(
            children: [
              <span class="hljs-keyword">const</span> Text(
                <span class="hljs-string">'Supabase Auth UI'</span>,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: <span class="hljs-number">18</span>,
                ),
              ),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">24.0</span>),
              SupaMagicAuth(
                redirectUrl:
                    kIsWeb ? <span class="hljs-keyword">null</span> : <span class="hljs-string">"myapptest://com.example.auth_ui_example"</span>,
                onSuccess: (session) =&gt; Navigator.pushNamed(context, <span class="hljs-string">'/home'</span>),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
</code></pre>
<p>Run the app and you should see this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-06-01-at-00.16.25.png" alt="Image" width="600" height="400" loading="lazy">
<em>Supabase authentication interface with an email input field and a 'Continue with magic Link' button.</em></p>
<p>Sign in and you should receive an email and be redirected back to the application.</p>
<h2 id="heading-native-auth-support-how-to-sign-in-with-google">Native Auth Support – How to Sign in with Google</h2>
<p>To add native sign in with Google support across Android, Web, and iOS, you'll need to add a few more configurations.</p>
<p>Go back to your Google cloud project and create a new credential. Choose OAuth client ID. Click on Android from the application type dropdown.</p>
<p>Next, add the package name. You can find this in the <code>AndroidManifest.xml</code> file under android &gt; src &gt; main &gt; AndroidManifest.xml or in the <code>build.gradle</code> file as applicationID under android &gt; src &gt; build.gradle.</p>
<p>To generate the SHA1 certificate fingerprint, paste the following code into a terminal window.</p>
<pre><code class="lang-bash">keytool -list -v \
-<span class="hljs-built_in">alias</span> androiddebugkey -keystore ~/.android/debug.keystore
</code></pre>
<p>When prompted for a password, enter "android".  Copy the generated SHA-1 certificate and paste it into the relevant field in your OAuth client ID set up above and create the project. Close the pop up with the Android client ID.</p>
<p>Go back and create a new OAuth client ID, but this time set it as an iOS app. Provide it the same value as the package name from before and create it. Close the pop up as before.</p>
<p>In your Flutter application, paste the following code above the custom URL scheme you previously added in the <code>info.plist</code> file. Replace the part in caps with the first part of the iOS client ID you created above minus the apps.google... part.</p>
<pre><code class="lang-xml">
            <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>com.googleusercontent.apps.FIRST_PART_OF_IOS_CLIENT_ID_MINUS_apps.googleusercontent.com_PART<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
            <span class="hljs-comment">&lt;!-- Leave rest untouched --&gt;</span>           
            <span class="hljs-comment">&lt;!-- Add your custom URL scheme here --&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>myapptest<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
</code></pre>
<p>In the <code>auth_screen</code> file, replace the <code>SupaSocialsAuth</code> widget with this:</p>
<pre><code class="lang-dart">              SupaSocialsAuth(
                redirectUrl:
                    kIsWeb ? <span class="hljs-keyword">null</span> : <span class="hljs-string">"myapptest://com.example.auth_ui_example"</span>,
                nativeGoogleAuthConfig: NativeGoogleAuthConfig(
                    iosClientId: dotenv.env[<span class="hljs-string">'IOS_CLIENT_ID'</span>]!,
                    webClientId: dotenv.env[<span class="hljs-string">'WEB_CLIENT_ID'</span>]!),
                socialProviders: <span class="hljs-keyword">const</span> [
                  OAuthProvider.google,
                  OAuthProvider.github,
                ],
                onSuccess: (session) =&gt; Navigator.pushNamed(
                  context,
                  <span class="hljs-string">'/home'</span>,
                ),
                onError: (error) =&gt; SnackBar(
                  content: Text(
                    error.toString(),
                  ),
                ),
              ),
</code></pre>
<p>Add the iOS and Web client secret from Google cloud to your <code>.env</code> file and you are ready to test.</p>
<p>Start the Android or iOS emulator. Then back in your Flutter project, run <code>flutter run</code> in your integrated terminal. Something like this should show up:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-06-01-at-00.19.35.png" alt="Image" width="600" height="400" loading="lazy">
<em>A mobile app screen displaying the Supabase Auth UI on an iOS emulator. The interface includes fields for entering an email and password, a "Sign In" button, options for password recovery, account sign-up, and login buttons for Google and GitHub.</em></p>
<p>Click on the Google icon and you should be logged in.</p>
<h2 id="heading-how-to-theme-your-supabase-auth-ui">How to Theme Your Supabase Auth UI</h2>
<p>Now that you have confirmed that the package actually works, it's time for the fun part. Supabase auth UI allows you to customise the look and the layout of the widgets.</p>
<p>In <code>main.dart</code>, replace the <code>ThemeData</code> widget under the <code>MaterialApp</code> with the following:</p>
<pre><code class="lang-dart">ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
        inputDecorationTheme: <span class="hljs-keyword">const</span> InputDecorationTheme(
          border: OutlineInputBorder(),
          focusedBorder: OutlineInputBorder(
            borderSide: BorderSide(color: Colors.purple, width: <span class="hljs-number">2.0</span>),
          ),
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.deepPurple,
            foregroundColor: Colors.white,
          ),
        ),
      ),
</code></pre>
<p>Notice how the sign up form responds to the theme changes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716941053665/46606dcf-386f-4abf-98e1-464bfd4b2955.png" alt="Image" width="2566" height="748" loading="lazy">
<em>A login interface for Supabase Auth UI. It includes fields for entering an email and password, with icons indicating their respective purposes. Below the fields is a purple "Sign In" button. Links for "Forgot your password?" and "Don't have an account? Sign up" are located at the bottom.</em></p>
<h3 id="heading-flexible-layout-options">Flexible Layout Options</h3>
<p>Supabase auth UI allows you to layout the social login buttons vertically and horizontally. To see this in action, add the following line in the <code>SupaSocialsAuth</code> widget: <code>socialButtonVariant: SocialButtonVariant.icon,</code>. The layout should go from this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717081837364/49584d9b-aac2-412c-b197-bbd2edeac8b1.png" alt="Image" width="1015" height="581" loading="lazy">
<em>A login interface for Supabase Auth UI with fields for email and password, a "Sign In" button, options to reset the password or sign up, and buttons to continue with Google or GitHub layout vertically with text and icons.</em></p>
<p>To this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717081856997/ec454669-c11c-4861-83cc-9cd7ca805140.png" alt="Image" width="1966" height="892" loading="lazy">
<em>Login screen for Supabase Auth UI with fields for email and password, a "Sign In" button, options for password recovery, account creation, and login via Google or GitHub in circular icons layout horizontally</em></p>
<h3 id="heading-localization-and-translation-ready">Localization and Translation Ready</h3>
<p>Supabase auth UI is <a target="_blank" href="https://github.com/supabase-community/flutter-auth-ui/pull/76">translation ready</a>, and you can change the labels to any language you wish.</p>
<p>Replace the code in <code>auth_screen</code> with the following:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_dotenv/flutter_dotenv.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:supabase_auth_ui/supabase_auth_ui.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> AuthScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView(
        padding: <span class="hljs-keyword">const</span> EdgeInsets.fromLTRB(<span class="hljs-number">24.0</span>, <span class="hljs-number">96.0</span>, <span class="hljs-number">24.0</span>, <span class="hljs-number">24.0</span>),
        children: [
          Column(
            children: [
              <span class="hljs-keyword">const</span> Text(
                <span class="hljs-string">'Supabase Auth UI'</span>,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: <span class="hljs-number">18</span>,
                ),
              ),
              <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">24.0</span>),
              SupaEmailAuth(
                redirectTo:
                    kIsWeb ? <span class="hljs-keyword">null</span> : <span class="hljs-string">"myapptest://com.example.auth_ui_example"</span>,
                localization: <span class="hljs-keyword">const</span> SupaEmailAuthLocalization(
                    enterEmail: <span class="hljs-string">"Ingiza barua pepe yako"</span>,
                    validEmailError: <span class="hljs-string">"'الرجاء إدخال عنوان بريد إلكتروني صالح"</span>,
                    enterPassword: <span class="hljs-string">"Ingresa tu contraseña"</span>,
                    passwordLengthError:
                        <span class="hljs-string">'Tafadhali ingiza nenosiri lenye herufi angalau 6'</span>,
                    signIn: <span class="hljs-string">'تسجيل الدخول'</span>,
                    signUp: <span class="hljs-string">'Registrarse'</span>,
                    forgotPassword: <span class="hljs-string">'Umesahau nenosiri lako?'</span>,
                    dontHaveAccount: <span class="hljs-string">'لا تملك حساب؟ سجل'</span>,
                    haveAccount: <span class="hljs-string">'¿Ya tienes una cuenta? Inicia sesión'</span>,
                    sendPasswordReset:
                        <span class="hljs-string">'Tuma barua pepe ya kurekebisha nenosiri'</span>,
                    backToSignIn: <span class="hljs-string">'العودة إلى تسجيل الدخول'</span>,
                    unexpectedError: <span class="hljs-string">'Se produjo un error inesperado'</span>),
                onSignInComplete: (e) =&gt; Navigator.pushNamed(context, <span class="hljs-string">'/home'</span>),
                onSignUpComplete: (e) =&gt; Navigator.pushNamed(context, <span class="hljs-string">'/home'</span>),
                onError: (error) =&gt; SnackBar(content: Text(error.toString())),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
</code></pre>
<p>The screen is now translated to Swahili, Arabic, and Spanish. Run <code>flutter run -d chrome --web-port=3000</code> and you should see this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717083375350/55e1129c-3216-4997-af9d-bf4c3a1e1828.png" alt="Image" width="2030" height="802" loading="lazy">
<em>A login interface titled "Supabase Auth UI" with fields for entering email and password in different languages. The email field is labeled "Ingiza barua pepe yako" and the password field is labeled "Ingresa tu contraseña." There is a purple login button with Arabic text. Below the button, there are links for forgotten password and account registration in Swahili and Arabic.</em></p>
<p>Type a wrong email and press sign in button to trigger the error messages. Your app should now be a polyglot, as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717083621863/93ebfce6-c6ff-4d2b-9709-46e429a08f3f.png" alt="Image" width="1986" height="830" loading="lazy">
<em>A login interface titled "Supabase Auth UI" with fields for email and password. The email field contains "ggg" and has an error message in multiple languages. The password field is empty with a placeholder in Spanish. Below, there is a purple login button with text in Arabic. Additional text in Swahili and Arabic is present below the button.</em></p>
<h2 id="heading-summary">Summary</h2>
<p>Supabase auth UI is a package that makes it exceptionally easy to get started with adding authentication flows in your flutter apps. It provides customizable, translation -ready widgets out of the box.</p>
<p>It is open-source and always looking for more contributions. Remember to leave a star on the <a target="_blank" href="https://github.com/supabase-community/flutter-auth-ui">repository</a>.</p>
<h2 id="heading-resources">Resources</h2>
<p>Here are some links that might be useful:</p>
<ul>
<li><a target="_blank" href="https://supabase.com/docs/guides/auth/native-mobile-deep-linking?platform=flutter&amp;queryGroups=platform&amp;queryGroups=os&amp;os=android#setting-up-deep-linking">Supabase docs on native deep linking</a></li>
<li><a target="_blank" href="https://supabase.com/docs/guides/auth/auth-helpers/flutter-auth-ui">Supabase Flutter auth UI docs</a></li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=utMg6fVmX0U">Supabase video on Google Sign In</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an AI-enhanced Task App with React and Appwrite ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you'll build a task manager application that has some artificial intelligence capabilities and is voice-enabled, sortable, and searchable.  As an extra, the application will have dark mode support that respects the users' system pref... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-task-app/</link>
                <guid isPermaLink="false">66b999a9d9d170feecefbbc5</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Appwrite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Wed, 13 Mar 2024 09:21:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/Group-3--20-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you'll build a task manager application that has some artificial intelligence capabilities and is voice-enabled, sortable, and searchable. </p>
<p>As an extra, the application will have dark mode support that respects the users' system preferences.</p>
<p>The application will be able to create, read, update and delete (CRUD) tasks as well as the ability to view a given task.  </p>
<p>You'll build this application using Appwrite as a backend, React on the frontend, Typescript for type safety and Tailwind CSS for styling.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
    <li><a href="#prerequisites">Prerequisites</a></li>
    <li><a href="#what-is-appwrite">What is Appwrite?</a></li>
    <li><a href="#how-to-set-up-the-appwrite-backend">How to Set Up the Appwrite Backend</a></li>
    <li><a href="#how-to-set-up-the-react-frontend">How to Set up the React Frontend</a></li>
    <li><a href="#how-to-connect-to-the-appwrite-project">How to Connect to the Appwrite Project</a></li>
    <li><a href="#how-to-build-the-task-manager-application">How to Build the Task Manager Application</a>
        <ul>
            <li><a href="#how-to-set-up-routing-with-react-router-v6">How to Set up Routing with React Router V6</a></li>
            <li><a href="#how-to-create-the-form-component">How to Create the Form Component</a></li>
            <li><a href="#how-to-set-up-form-to-create-task">How to Set up Form to Create Task</a></li>
            <li><a href="#how-to-make-the-tasks-editable">How to Make the Tasks Editable</a></li>
            <li><a href="#how-to-enable-viewing-of-tasks">How to Enable Viewing of Tasks</a></li>
            <li><a href="#how-to-auto-generate-descriptions-with-vercel-s-ai-sdk">How to Auto Generate Descriptions with Vercel's AI SDK</a></li>
            <li><a href="#voice-enable-the-application-with-the-react-speech-recognition-package">Voice-enable the Application with the React Speech Recognition Package</a></li>
            <li><a href="#how-to-add-search-functionality-to-the-application">How to Add Search Functionality to the Application</a></li>
            <li><a href="#how-to-add-ability-to-sort-tasks-via-due-date-and-priorityadd-ability-to-sort-tasks-via-due-date-and-priority">How to Add Ability to Sort Tasks via Due Date and Priority</a></li>
            <li><a href="#bonus-add-dark-mode-support">Bonus: Add Dark Mode Support</a></li>

        </ul>
    </li>
    <li><a href="#notes">Notes</a></li>
    <li><a href="#limitations">Limitations</a></li>
</ul>



<h2 id="heading-prerequisitesheading-prerequisites-1"><a class="post-section-overview" href="#heading-prerequisites-1">Prerequisites</a></h2>
<p>You will need the following to be able to build along with this article:</p>
<ul>
<li>Basic programming knowledge</li>
<li>Basic understanding of React, Typescript and Tailwind</li>
<li><a target="_blank" href="https://appwrite.io/">An Appwrite account</a></li>
<li>And a text editor to code along</li>
</ul>
<h2 id="heading-what-is-appwrite">What is Appwrite?</h2>
<p>Appwrite is an <a target="_blank" href="https://opensource.com/resources/what-open-source">open source</a> Backend-as-a-Service (BaaS) platform. A BaaS is a cloud service that packages backend tasks that are typically needed for most applications. </p>
<p>Appwrite offers both a managed database, authentication, functions and storage services and the ability to self-host the entire platform on your own. </p>
<p>Appwrite recently announced a host of new features that makes developers building on their platform lives more straightforward. You can read on that <a target="_blank" href="https://appwrite.io/init">here</a>. </p>
<h2 id="heading-how-to-set-up-the-appwrite-backend">How to Set Up the Appwrite Backend</h2>
<p>Before starting to build the application and interacting with Appwrite, you'll need an Appwrite account and to set up the project.</p>
<p>Once you have the account ready, you will need to create an organization, then create a project within that organization. You can name the project "Tasks App" or any other name you see fit. </p>
<p><strong>Note:</strong> Appwrite cloud restricts you to one organization per account on the hobby/free plan. If you already had an organization, you can go straight to creating a project within your existing organization.</p>
<p>In your Tasks App project, add a web platform and follow the prompts. For the hostname, add "localhost" for now. This is to allow the frontend to bypass <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> when interacting with the Appwrite backend.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-14.44.11.png" alt="Image" width="600" height="400" loading="lazy">
<em>A picture of the Appwrite Console showing the "Add a platform" section</em></p>
<p>Copy the installation instructions as you complete setting up the web platform for the project. Keep these safe, you will need them when setting up the frontend. </p>
<p>You should now be in the Appwrite cloud console. Click on the "Databases" on the left sidebar. Then click on the pink "Create database" button. Name your database and leave the autogenerated ID as is.  </p>
<p>Now, click on the "Create collection" button, name your collection "tasks" and leave the autogenerated ID as is. Now, click on the grey "Create attribute" button as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-15.00.42.png" alt="Image" width="600" height="400" loading="lazy">
<em>A picture of the Appwrite Console showing the "Create attribute" button</em></p>
<p>Add the following attributes: </p>
<ul>
<li>title of type String, give it a size of 49 and make it required</li>
<li>description of type String, give it a size of 200</li>
<li>due_date of type Datetime, make it required</li>
<li>done of type Boolean, give it a default of False</li>
<li>priority of type String, give it a size of 10</li>
</ul>
<p>Finally, you need to set permissions in order for your React frontend to interact with Appwrite services. In this case, allow any one to have access. This is not ideal for production and you can read more about Appwrite permissions <a target="_blank" href="https://appwrite.io/docs/advanced/platform/permissions">here</a>.</p>
<p>Go to the console, click on databases, then your task database and then your tasks collection, then click on settings and scroll down to permissions. Add permissions for "Any" role and give them full CRUD access.</p>
<p>You are now ready to start setting up the frontend and to connect it to the Appwrite project you just completed prepping.</p>
<h2 id="heading-how-to-set-up-the-react-frontend">How to Set up the React Frontend</h2>
<p>Open your text editor to your preferred location. Then open the integrated terminal and run the following command to create a Vite-based application: </p>
<pre><code class="lang-terminal">
//taskwrite is the name of the application
npm create vite@latest taskwrite
</code></pre>
<p>Choose React and then plain Typescript when prompted. This will create a React application with Typescript already set up for you. </p>
<p>Change folders into the newly created "taskwrite" one by running <code>cd taskwrite</code> from the terminal. Run the following command in the same terminal window to add Tailwind to the application:</p>
<pre><code class="lang-terminal">
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
</code></pre>
<p>Then in your <strong>tailwind.config.js</strong> file which is in the root of the Taskwrite application, replace the "content" key with <code>content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],</code>. The file should look like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    <span class="hljs-attr">content</span>: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
    <span class="hljs-attr">theme</span>: {
        <span class="hljs-attr">extend</span>: {},
    },
    <span class="hljs-attr">plugins</span>: [],
};
</code></pre>
<p>This tells Tailwind that it should look for its classes in the index.html file at the root and in files within the <strong>src</strong> folder that end with either <code>.js</code>, <code>.ts</code>, <code>.jsx</code> or <code>.tsx</code> extensions.</p>
<p>Then open the <strong>src</strong> folder and delete the "App.css" file. Open the <strong>index.css</strong> file and replace its contents with the following: </p>
<pre><code class="lang-css">
<span class="hljs-keyword">@import</span> url(<span class="hljs-string">'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&amp;family=Quicksand:wght@300..700&amp;display=swap'</span>);
<span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;

<span class="hljs-keyword">@layer</span> base {
    <span class="hljs-selector-pseudo">:root</span>{
        <span class="hljs-attribute">--base-bg</span>: <span class="hljs-number">#ffffff</span>;
        <span class="hljs-attribute">--btn-bg-primary</span>: <span class="hljs-number">#be185d</span>;
        <span class="hljs-attribute">--btn-bg-primary-hover</span>: <span class="hljs-number">#9d174d</span>;
        <span class="hljs-attribute">--btn-icon-main</span>: <span class="hljs-number">#1e293b</span>;
        <span class="hljs-attribute">--btn-bg-ok</span>: <span class="hljs-number">#4ade80</span>;
        <span class="hljs-attribute">--btn-bg-light-ok</span>: <span class="hljs-number">#bbf7d0</span>;
        <span class="hljs-attribute">--btn-bg-light</span>: <span class="hljs-number">#e5e7eb</span>;
        <span class="hljs-attribute">--low-priority</span>: <span class="hljs-number">#facc15</span>;
        <span class="hljs-attribute">--medium-priority</span>: <span class="hljs-number">#fb923c</span>;
        <span class="hljs-attribute">--high-priority</span>: <span class="hljs-number">#f87171</span>;
        <span class="hljs-attribute">--text-error</span>: <span class="hljs-number">#dc2626</span>;
        <span class="hljs-attribute">--text-ok</span>: <span class="hljs-number">#16a34a</span>;
        <span class="hljs-attribute">--text-main</span>: <span class="hljs-number">#262626</span>;
        <span class="hljs-attribute">--border-container</span>: <span class="hljs-number">#9ca3af</span>;
        <span class="hljs-attribute">--border-input</span>: <span class="hljs-number">#1e293b</span>;
        <span class="hljs-attribute">--border-error</span>: <span class="hljs-number">#dc2626</span>;
    }

    <span class="hljs-selector-tag">body</span>{
        <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--base-bg);
        <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--text-main);    
    }

    <span class="hljs-selector-id">#date</span><span class="hljs-selector-pseudo">::-webkit-calendar-picker-indicator</span> {
        <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--btn-bg-light); 
    }
}
</code></pre>
<p>This adds some custom css variables to the application. The variables map to Tailwind colors. </p>
<p>Next, paste the following into the <strong>tailwind.config.js</strong> at the root of the application:</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    content: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
    theme: {
        extend: {
            textColor: {
                error: <span class="hljs-string">"var(--text-error)"</span>,
                ok: <span class="hljs-string">"var(--text-ok)"</span>,
                main: <span class="hljs-string">"var(--text-main)"</span>,
                iconColor: <span class="hljs-string">"var(--btn-icon-main)"</span>,
            },
            backgroundColor: {
                base: <span class="hljs-string">"var(--base-bg)"</span>,
                primary: <span class="hljs-string">"var(--btn-bg-primary)"</span>,
                primaryHover: <span class="hljs-string">"var(--btn-bg-primary-hover)"</span>,
                ok: <span class="hljs-string">"var(--btn-bg-ok)"</span>,
                lightOk: <span class="hljs-string">"var(--btn-bg-light-ok)"</span>,
                light: <span class="hljs-string">"var(--btn-bg-light)"</span>,
                lowPriority: <span class="hljs-string">"var(--low-priority)"</span>,
                mediumPriority: <span class="hljs-string">"var(--medium-priority)"</span>,
                highPriority: <span class="hljs-string">"var(--high-priority)"</span>,
            },
            borderColor: {
                container: <span class="hljs-string">"var(--border-container)"</span>,
                input: <span class="hljs-string">"var(--border-input)"</span>,
                error: <span class="hljs-string">"var(--border-error)"</span>,
            },
        },
    },
    plugins: [],
};
</code></pre>
<p>This ties the CSS variables to the tailwind config and makes them available to use in our application.</p>
<p>Now Taskwrite is set up with React, Typescript and Tailwind.</p>
<h2 id="heading-how-to-connect-to-the-appwrite-project">How to Connect to the Appwrite Project</h2>
<p>Firstly, you need to add the Appwrite dependency to the React application. Run the following command in the terminal window to do that: <code>npm i appwrite</code>.</p>
<p>Next thing is to set up the Appwrite keys we need as environment variables. In the <strong>.gitignore</strong> file at the root of the application, add <code>*.env</code> at the top of the file then save. This will ensure that the <strong>.env</strong> file you'll create is not added to version control. </p>
<p>Now, create a <strong>.env</strong> file at the root of the React application and paste the following variables in it:</p>
<pre><code class="lang-env">
//replace the right hand side of the equal sign with the correct values from your Appwrite project.
VITE_APPWRITE_URL=YOUR-APPWRITE-API-ENDPOINT
VITE_APPWRITE_PROJ_ID=YOUR-APPWRITE-PROJECT-ID
</code></pre>
<p>You can get the necessary values in your Appwrite console. Click on the settings tab at the bottom of the left sidebar and copy API credentials. </p>
<p>Next, create a utils folder in the <strong>src</strong> folder of the React application. Add a file called <strong>appwrite.ts</strong> within it and paste the following config information:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Client, Databases } <span class="hljs-keyword">from</span> <span class="hljs-string">"appwrite"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> Client();

client
    .setEndpoint(<span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_URL)
    .setProject(<span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_PROJ_ID);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> databases = <span class="hljs-keyword">new</span> Databases(client);

<span class="hljs-keyword">export</span> { ID } <span class="hljs-keyword">from</span> <span class="hljs-string">"appwrite"</span>;
</code></pre>
<p>You are ready to test that the React application is connected to the Appwrite project. Replace everything in the <strong>App.tsx</strong> file within the <strong>src</strong> folder with the following code:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { client } <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils/appwrite"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Appwrite"</span>, client);
    <span class="hljs-keyword">return</span> &lt;div className=<span class="hljs-string">"text-purple-500 text-center font-bold text-      5xl"</span>&gt;App&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Then open an integrated terminal window and run the following command: <code>npm run dev</code>. This will run your React application at this URL: <a target="_blank" href="http://localhost:5173/">http://localhost:5173/</a>. Open the URL in a browser window and open the browser console. </p>
<p>You should see a large purple text "App" in the center of the screen and the Appwrite client logged in the console like so: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-16.36.36.png" alt="Image" width="600" height="400" loading="lazy">
<em>The web app running in the browser</em></p>
<p>Now you need to grab the database ID and the collection ID from the Appwrite console. Click on the databases tab on the left sidebar, hover on the database ID value and click to copy it. </p>
<p>Go back to your <strong>.env</strong> file and add an entry like so:</p>
<pre><code class="lang-env">
//replace the right hand side of the equal sign with the correct values from your Appwrite project.
VITE_APPWRITE_URL=YOUR-APPWRITE-API-ENDPOINT
VITE_APPWRITE_PROJ_ID=YOUR-APPWRITE-PROJECT-ID
//new entry below
VITE_APPWRITE_DB_ID=YOUR-APPWRITE-DB-ID
</code></pre>
<p>Lastly, go back to the console and click through the database to get to the collections. Hover and copy collection ID like before, then add it just below the database ID in your env file like so: </p>
<pre><code class="lang-env">
VITE_APPWRITE_DB_ID=YOUR-APPWRITE-DB-ID
VITE_APPWRITE_COLLECTION_ID=YOUR-APPWRITE-COLLECTION-ID
</code></pre>
<p>With that, the set up part of building Taskwrite is complete.</p>
<h2 id="heading-how-to-build-the-task-manager-application">How to Build the Task Manager Application</h2>
<p>To make it easier to work with Typescript, you will need to add interfaces that correspond with the shape of the Appwrite database response. </p>
<p>In your <strong>src</strong> folder, create a folder called <strong>models</strong> and in it, create a file called <strong>interface.ts</strong>. Paste the following in the file:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Models } <span class="hljs-keyword">from</span> <span class="hljs-string">"appwrite"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IPayload {
    title: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    due_date: <span class="hljs-built_in">Date</span>;
    priority?: <span class="hljs-built_in">string</span>;
    done?: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ITask <span class="hljs-keyword">extends</span> Models.Document {
    title: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    due_date: <span class="hljs-built_in">Date</span>;
    priority?: <span class="hljs-built_in">string</span>;
    done: <span class="hljs-built_in">boolean</span>;
}
</code></pre>
<p>Here, you are defining an interface called "IPayload" with the same attributes as the task we defined in the Appwrite project. Then you are defining another interface called "ITask" that extends the built in base Model from Appwrite. </p>
<p>This means that ITask has both the attributes of the task we defined before and the built in base attributes that Appwrite collections come with.</p>
<p>Next, in your <strong>utils</strong> folder add a file called <strong>db.ts</strong> and paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { ID, databases } <span class="hljs-keyword">from</span> <span class="hljs-string">"./appwrite"</span>;
<span class="hljs-keyword">import</span> { IPayload } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;

<span class="hljs-keyword">const</span> dbID: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_DB_ID;
<span class="hljs-keyword">const</span> collectionID: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">import</span>.meta.env.VITE_APPWRITE_COLLECTION_ID;

<span class="hljs-keyword">const</span> createDocument = <span class="hljs-keyword">async</span> (payload: IPayload) =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.createDocument(dbID, collectionID, ID.unique(), {
        ...payload,
    });

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">const</span> readDocuments = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID);

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">const</span> updateDocument = <span class="hljs-keyword">async</span> (payload: IPayload, id: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.updateDocument(dbID, collectionID, id, {
        ...payload,
    });

    <span class="hljs-keyword">return</span> res;
};
<span class="hljs-keyword">const</span> deleteDocument = <span class="hljs-keyword">async</span> (id: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.deleteDocument(dbID, collectionID, id);

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">export</span> { createDocument, readDocuments, updateDocument, deleteDocument };
</code></pre>
<p>This file defines four functions corresponding with the CRUD operations. The naming of the functions map to which operation it performs. For all functions, you pass the collection and database IDs so that Appwrite knows which resources to operate on.</p>
<p>To create a task on the Appwrite database, you pass an object with the shape of a task to the function and ask it to create a unique ID for each new task it creates. </p>
<p>To update a task, you pass it a task object similar to create but we also pass it the unique ID of the task to be updated.</p>
<p>To read all tasks from the Appwrite, you call the "listDocuments" function and to delete a task you pass the ID corresponding to the task to be deleted.</p>
<h3 id="heading-how-to-set-up-routing-with-react-router-v6">How to Set Up Routing with React Router V6</h3>
<p>The Taskwrite application will have two routes and a navigation menu to help with that. To add navigation, open an integrated terminal and run the following command to install the React Router library: <code>npm i react-router-dom</code>.</p>
<p>Now, go to the <strong>main.tsx</strong> file in the <strong>src</strong> folder and paste the following in it: </p>
<pre><code class="lang-typescript">
<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.tsx"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)!).render(
    &lt;React.StrictMode&gt;
        &lt;BrowserRouter&gt;
            &lt;App /&gt;
        &lt;/BrowserRouter&gt;
    &lt;/React.StrictMode&gt;
);
</code></pre>
<p>Then go to the <strong>App.tsx</strong> file in <strong>src</strong> folder and paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> { Route, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> Task <span class="hljs-keyword">from</span> <span class="hljs-string">"./routes/Task"</span>;
<span class="hljs-keyword">import</span> Index <span class="hljs-keyword">from</span> <span class="hljs-string">"./routes/Index"</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Navbar"</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> (
        &lt;&gt;
            &lt;Navbar/&gt;
            &lt;Routes&gt;
                &lt;Route path=<span class="hljs-string">"/"</span> element={&lt;Index /&gt;} /&gt;
                &lt;Route path=<span class="hljs-string">"/tasks"</span> element={&lt;Task /&gt;} /&gt;
            &lt;/Routes&gt;
        &lt;/&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>You now need to create the referenced component above. In the <strong>src</strong> folder, create a folder called <strong>routes</strong> and within it create two files called <strong>Index.tsx</strong> and <strong>Task.tsx</strong>.  </p>
<p>In <strong>Index.tsx</strong>, paste the following: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> Index = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
            &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
                    AI-enhanced, Voice-enabled, Searchable Task Manager
                &lt;/h1&gt;
            &lt;/section&gt;
        &lt;/main&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Index;
</code></pre>
<p>And in <strong>Task.tsx</strong>, paste the following:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> Task = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
            &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
                    Your Tasks
                &lt;/h1&gt;
            &lt;/section&gt;
        &lt;/main&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Task;
</code></pre>
<p>Now, create a components folder in the <strong>src</strong> folder and add a file within it called <strong>Navbar.tsx</strong>. Paste the following in that file: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> { PencilIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/24/solid"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> navigate = useNavigate();

    <span class="hljs-keyword">return</span> (
        &lt;nav className=<span class="hljs-string">"py-4 border-b-2 border-container shadow-md shadow-gray-400 w-full fixed top-0 bg-base"</span>&gt;
            &lt;ul className=<span class="hljs-string">"flex items-center justify-between  w-11/12 mx-auto"</span>&gt;
                &lt;Link to=<span class="hljs-string">"/"</span>&gt;
                    &lt;Button
                        content={{
                            text: <span class="hljs-string">"Taskwrite"</span>,
                            icon: PencilIcon,
                        }}
                        textClasses=<span class="hljs-string">"font-semibold text-main"</span>
                        iconClasses=<span class="hljs-string">"text-main"</span>
                    /&gt;
                &lt;/Link&gt;
                &lt;/Link&gt;
                &lt;div className=<span class="hljs-string">"flex items-center justify-between gap-6"</span>&gt;
                    &lt;Link
                        to=<span class="hljs-string">"/tasks"</span>
                        className=<span class="hljs-string">"font-semibold hover:scale-105 transition duration-300 ease-in-out"</span>
                    &gt;
                        View Tasks
                    &lt;/Link&gt;
                &lt;/div&gt;
            &lt;/ul&gt;
        &lt;/nav&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<p>This file contains navigation menu that switches between the two pages. You will need to create the <code>Button</code> component referenced above and add the Hero icons package.   </p>
<p>In an integrated terminal, run the following to add Hero icons: <code>npm i @heroicons/react</code> . Next, add a new file called <strong>Button.tsx</strong> in the components folder. Paste the following within that file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> ButtonProps {
    extraBtnClasses?: <span class="hljs-built_in">string</span>;
    textColor?: <span class="hljs-built_in">string</span>;
    handleClick?: <span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent&lt;HTMLButtonElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    title?: <span class="hljs-built_in">string</span>;
    disable?: <span class="hljs-built_in">boolean</span>;
    <span class="hljs-keyword">type</span>?: <span class="hljs-string">"button"</span> | <span class="hljs-string">"submit"</span> | <span class="hljs-string">"reset"</span>;
    children: ReactNode;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Button</span>(<span class="hljs-params">{
    extraBtnClasses,
    textColor,
    handleClick,
    title,
    disable,
    <span class="hljs-keyword">type</span> = "button",
    children,
}: ButtonProps</span>) </span>{
    <span class="hljs-keyword">const</span> handleClickProp = <span class="hljs-keyword">type</span> === <span class="hljs-string">"submit"</span> ? <span class="hljs-literal">undefined</span> : handleClick;

    <span class="hljs-keyword">return</span> (
        &lt;button
            <span class="hljs-keyword">type</span>={<span class="hljs-keyword">type</span>}
            title={title ?? <span class="hljs-string">""</span>}
            onClick={handleClickProp}
            disabled={disable}
            className={<span class="hljs-string">`flex gap-2 items-center text-iconColor <span class="hljs-subst">${extraBtnClasses}</span> <span class="hljs-subst">${
                textColor ?? <span class="hljs-string">""</span>
            }</span> rounded-md px-2 py-1 hover:scale-105 transition duration-300 ease-in-out`</span>}
        &gt;
            {children}
        &lt;/button&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Button;
</code></pre>
<p>This file describes a shared button component and defines the props that it will accept.</p>
<p>Go back and fix any import errors and re-run the application by running <code>npm run dev</code>, you should see something like this: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-18.23.45.png" alt="Image" width="600" height="400" loading="lazy">
<em>app running with npm run dev command</em></p>
<h3 id="heading-how-to-create-the-form-component">How to Create the Form Component</h3>
<p>Add a new file called <strong>AddTask.tsx</strong> in the components folder and paste the following into it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Select <span class="hljs-keyword">from</span> <span class="hljs-string">"./Select"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">const</span> AddTask = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [titleVal, setTitleVal] = useState(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [textAreaVal, setTextAreaVal] = useState(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [dueDate, setDueDate] = useState(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());

    <span class="hljs-keyword">const</span> priorityArray = [<span class="hljs-string">"low"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"high"</span>];

    <span class="hljs-keyword">const</span> [priority, setPriority] = useState(priorityArray[<span class="hljs-number">0</span>]);

    <span class="hljs-keyword">return</span> (
        &lt;form id=<span class="hljs-string">"form"</span> className=<span class="hljs-string">"m-8"</span>&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"title"</span>&gt;Task Title&lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                    id=<span class="hljs-string">"title"</span>
                    placeholder=<span class="hljs-string">"Title of your task"</span>
                    value={titleVal}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTitleVal(e.target.value)}
                    className=<span class="hljs-string">"bg-inherit border rounded-sm p-2 focus:outline-none focus:ring-1 border-input focus:ring-slate-900"</span>
                /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"description"</span> className=<span class="hljs-string">"mb-1"</span>&gt;
                    Task Description
                &lt;/label&gt;
                &lt;textarea
                    id=<span class="hljs-string">"description"</span>
                    placeholder=<span class="hljs-string">"Describe your task"</span>
                    maxLength={<span class="hljs-number">200</span>}
                    value={textAreaVal}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTextAreaVal(e.target.value)}
                    className=<span class="hljs-string">"bg-inherit border rounded-sm p-2 h-32 resize-none focus:outline-none focus:ring-1 border-input focus:ring-slate-900"</span>
                /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"description"</span> className=<span class="hljs-string">"mb-1"</span>&gt;
                    Task Priority
                &lt;/label&gt;
                &lt;Select
                    defaultSelectValue={priority}
                    selectOptions={priorityArray}
                    handleSelectChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setPriority(e.target.value)}
                /&gt;
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;
                &lt;label htmlFor=<span class="hljs-string">"description"</span> className=<span class="hljs-string">"mb-1"</span>&gt;
                    Task Due <span class="hljs-built_in">Date</span>
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">"date"</span>
                    id=<span class="hljs-string">"date"</span>
                    value={dueDate!.toISOString().split(<span class="hljs-string">"T"</span>)[<span class="hljs-number">0</span>]}
                    min={<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString().split(<span class="hljs-string">"T"</span>)[<span class="hljs-number">0</span>]}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDueDate(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e.target.value))}
                    className=<span class="hljs-string">"bg-inherit border rounded-sm border-input p-2 focus:outline-none focus:ring-1 focus:ring-slate-900 invalid:focus:ring-red-600"</span>
                /&gt;
            &lt;/div&gt;
            &lt;Button
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
                content={{
                    text: <span class="hljs-string">"Add Task"</span>,
                }}
                extraBtnClasses=<span class="hljs-string">"bg-pink-700 justify-center text-white font-semibold px-4 py-2 outline-1 hover:bg-pink-800 focus:ring-1 focus:ring-pink-800 w-full"</span>
            /&gt;
        &lt;/form&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AddTask;
</code></pre>
<p>Now create a new file in components called <strong>Select.tsx</strong>, paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> SelectProps {
    defaultSelectValue: <span class="hljs-built_in">string</span>;
    selectOptions: <span class="hljs-built_in">string</span>[];
    handleSelectChange: <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> Select = <span class="hljs-function">(<span class="hljs-params">{
    defaultSelectValue,
    handleSelectChange,
    selectOptions,
}: SelectProps</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [selectVal, setSelectVal] = useState(defaultSelectValue);
    <span class="hljs-keyword">return</span> (
        &lt;select
            value={selectVal}
            onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                setSelectVal(e.target.value);
                handleSelectChange(e);
            }}
            className=<span class="hljs-string">"bg-inherit border rounded-sm border-input p-2 focus:outline-none focus:ring-1 focus:ring-slate-900 cursor-pointer"</span>
        &gt;
            {selectOptions.map(<span class="hljs-function">(<span class="hljs-params">option</span>) =&gt;</span> (
                &lt;option key={option} value={option}&gt;
                    {option.charAt(<span class="hljs-number">0</span>).toUpperCase() + option.slice(<span class="hljs-number">1</span>)}
                &lt;/option&gt;
            ))}
        &lt;/select&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Select;
</code></pre>
<p>This defines a <code>Select</code> component and its props. The <code>Select</code> component props are a function for handling change, an array of options and the default value it should display. </p>
<p>Now, import the <code>AddTask</code> component in the <strong>Index.tsx</strong> file between the <code>h1</code> tags like so: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> AddTask <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/AddTask"</span>;

<span class="hljs-keyword">const</span> Index = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
            &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
                    AI-enhanced, Voice-enabled, Searchable Task Manager
                &lt;/h1&gt;
                &lt;AddTask /&gt;
            &lt;/section&gt;
        &lt;/main&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Index;
</code></pre>
<p>Your application should now display the form:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-08-at-18.57.25.png" alt="Image" width="600" height="400" loading="lazy">
<em>task app form</em></p>
<h3 id="heading-how-to-set-up-form-to-create-task">How to Set Up Form to Create Task</h3>
<p>To make the form functional, you need to hook it up to a submit function that will call the create function defined in the <strong>db.ts</strong> file. </p>
<p>Additionally, you will need to validate the form to avoid sending bad data and having Appwrite send errors back to the React application.</p>
<p>In the <code>AddTask</code> component file, paste the following code above the <code>return</code> statement and below the <code>setPriority</code> useState:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [priority, setPriority] = useState(priorityArray[<span class="hljs-number">0</span>]);

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> navigate = useNavigate();

<span class="hljs-keyword">const</span> [isSubmitting, setIsSubmitting] = useState(<span class="hljs-literal">false</span>);
<span class="hljs-keyword">const</span> [titleValidationError, setTitleValidationError] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleTitleChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
        setTitleVal(e.target.value);

        <span class="hljs-keyword">if</span> (e.target.value.trim() !== <span class="hljs-string">""</span>) {
            setTitleValidationError(<span class="hljs-string">""</span>);
        }
    };

    <span class="hljs-keyword">const</span> handleSubmitTask = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        setIsSubmitting(<span class="hljs-literal">true</span>);

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">if</span> (!titleVal) {
                setTitleValidationError(<span class="hljs-string">"Please provide at least a title for the task"</span>);
                <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> setTitleValidationError(<span class="hljs-string">""</span>), <span class="hljs-number">2000</span>);
                setIsSubmitting(<span class="hljs-literal">false</span>);
                <span class="hljs-keyword">return</span>;
            }

            <span class="hljs-keyword">if</span> (titleVal.length &gt; <span class="hljs-number">49</span>) {
                setTitleValidationError(
                    <span class="hljs-string">"Title too long. It can only be 49 characters long"</span>
                );
                <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> setTitleValidationError(<span class="hljs-string">""</span>), <span class="hljs-number">2000</span>);
                setIsSubmitting(<span class="hljs-literal">false</span>);
                <span class="hljs-keyword">return</span>;
            }

            <span class="hljs-keyword">const</span> payload: IPayload = {
                title: titleVal,
                description: textAreaVal,
                due_date: dueDate,
                priority: priority,
            };

            <span class="hljs-keyword">await</span> createDocument(payload);

            <span class="hljs-comment">// reset form</span>
            setTitleVal(<span class="hljs-string">""</span>);
            setTextAreaVal(<span class="hljs-string">""</span>);
            setDueDate(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());
            setPriority(priorityArray[<span class="hljs-number">0</span>]);
            setTitleValidationError(<span class="hljs-string">""</span>);
            setIsSubmitting(<span class="hljs-literal">false</span>);
            navigate(<span class="hljs-string">"/tasks"</span>);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error in handleSubmitTask:"</span>, error);
            setIsSubmitting(<span class="hljs-literal">false</span>);
        }
    };

    <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">//rest of the code unchanged below</span>
</code></pre>
<p>Then replace the <code>return</code> statement with the following code:</p>
<pre><code>
<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"form"</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmitTask}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"m-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"title"</span>&gt;</span>Task Title<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">type</span>=<span class="hljs-string">"text"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"title"</span>
            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Title of your task"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{titleVal}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleTitleChange}</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">bg-inherit</span> <span class="hljs-attr">border</span> <span class="hljs-attr">rounded-sm</span> <span class="hljs-attr">p-2</span> <span class="hljs-attr">focus:outline-none</span>                         <span class="hljs-attr">focus:ring-1</span> ${
                    <span class="hljs-attr">titleValidationError</span>
                    ? "<span class="hljs-attr">border-error</span> <span class="hljs-attr">focus:ring-red-500</span> <span class="hljs-attr">invalid:focus:ring-red-</span>                         <span class="hljs-attr">600</span>"
                    <span class="hljs-attr">:</span> "<span class="hljs-attr">border-input</span> <span class="hljs-attr">focus:ring-slate-900</span>"
            }`}
        /&gt;</span>
        {titleValidationError &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-error mt-1"</span>&gt;</span>{titleValidationError}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-1"</span>&gt;</span>
            Task Description
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"description"</span>
            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Describe your task"</span>
            <span class="hljs-attr">maxLength</span>=<span class="hljs-string">{200}</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{textAreaVal}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setTextAreaVal(e.target.value)}
            className={`bg-inherit border rounded-sm p-2 h-32 resize-none                             focus:outline-none focus:ring-1 ${
                        textAreaVal.length &gt; 197
                        ? "border-error focus:ring-red-500 invalid:focus:ring-                             red-600"
                        : "border-input focus:ring-slate-900"
                }`}
        /&gt;
        {textAreaVal.length &gt; 197 &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-error mt-1"</span>&gt;</span>
            Warning description getting too long. Can only be 200 characters
        <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-1"</span>&gt;</span>
            Task Priority
        <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Select</span>
            <span class="hljs-attr">defaultSelectValue</span>=<span class="hljs-string">{priority}</span>
            <span class="hljs-attr">selectOptions</span>=<span class="hljs-string">{priorityArray}</span>
            <span class="hljs-attr">handleSelectChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPriority(e.target.value)}
        /&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-1"</span>&gt;</span>
            Task Due Date
        <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">type</span>=<span class="hljs-string">"date"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"date"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{dueDate!.toISOString().split(</span>"<span class="hljs-attr">T</span>")[<span class="hljs-attr">0</span>]}
            <span class="hljs-attr">min</span>=<span class="hljs-string">{new</span> <span class="hljs-attr">Date</span>()<span class="hljs-attr">.toISOString</span>()<span class="hljs-attr">.split</span>("<span class="hljs-attr">T</span>")[<span class="hljs-attr">0</span>]}
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setDueDate(new Date(e.target.value))}
                className="bg-inherit border rounded-sm border-input p-2                                      focus:outline-none focus:ring-1 focus:ring-slate-                               900 invalid:focus:ring-red-600"
        /&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
        <span class="hljs-attr">disable</span>=<span class="hljs-string">{isSubmitting}</span>
        <span class="hljs-attr">extraBtnClasses</span>=<span class="hljs-string">"bg-primary justify-center text-white font-semibold px-4 py-2 outline-1 hover:bg-primaryHover focus:ring-1 focus:ring-pink-800 w-full"</span>
    &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>
            Add Task
        <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
);
</code></pre><p>Fix import errors and your application should now be validating title and description as well as creating the task then sending you to the "/tasks" route. You can check the Appwrite console to confirm that the task has been created. </p>
<h3 id="heading-how-set-up-read-and-delete-tasks">How Set Up Read and Delete Tasks</h3>
<p>Open the <strong>Task.jsx</strong> file in the routes folder within <strong>src</strong> folder, and add the following code above the <code>return</code> like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [tasks, setTasks] = useState&lt;ITask[]&gt;([]);
<span class="hljs-keyword">const</span> [tasksError, setTasksError] = useState(<span class="hljs-string">""</span>);

useEffect(<span class="hljs-function">() =&gt;</span> {
        getTasks()
        .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
        setTasks(res.reverse());
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(err);
        setTasksError(<span class="hljs-string">"Error fetching tasks, please try again"</span>);
        });
    }, []);

<span class="hljs-keyword">return</span> (
<span class="hljs-comment">//rest of code</span>
</code></pre>
<p>Here, the file is setting some local state using useState to hold the tasks and set any potential task related errors.</p>
<p>Now replace the code in the <code>return</code> with the following code:</p>
<pre><code class="lang-typescript">

&lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
    &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
        &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
        Your Tasks
        &lt;/h1&gt;
        {tasksError ? (
            &lt;span className=<span class="hljs-string">"m-8 text-error"</span>&gt;{tasksError}&lt;/span&gt;
        ) : (
            &lt;div className=<span class="hljs-string">"flex flex-col md:flex-row justify-between"</span>&gt;
                &lt;div className=<span class="hljs-string">"flex-1"</span>&gt;
                    &lt;h3 className=<span class="hljs-string">"text-2xl font-bold m-8"</span>&gt;Pending Tasks&lt;/h3&gt;
                    &lt;div&gt;
                         {tasks
                             .filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> !task.done)
                             .map(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> (
                                &lt;TaskItem key={task.$id} task={task} /&gt;
                         ))}
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div className=<span class="hljs-string">"flex-1"</span>&gt;
                    &lt;h3 className=<span class="hljs-string">"text-2xl font-bold m-8"</span>&gt;Completed Tasks&lt;/h3&gt;
                    &lt;div&gt;
                        {tasks
                            .filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> task.done)
                            .map(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> (
                                &lt;TaskItem key={task.$id} task={task} /&gt;
                        ))}
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        )}
    &lt;/section&gt;
&lt;/main&gt;
</code></pre>
<p>You now need to create the <code>getTasks()</code> function and the <code>TaskItem</code> component. In the components folder, create a file called <strong>TaskItem.tsx</strong> and paste the following code in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">interface</span> TaskItemProps {
    task: ITask;
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{ task }: TaskItemProps</span>) </span>{
    <span class="hljs-keyword">return</span> (
    &lt;&gt;
        &lt;div className=<span class="hljs-string">"m-8 cursor-pointer border border-container rounded-md p-4 hover:shadow-lg transition duration-300 ease-in-out max-h-96"</span>&gt;
            &lt;section
            key={task.$id}
            className=<span class="hljs-string">"flex flex-col justify-between gap-2 my-4 h-full"</span>
            &gt;
            &lt;section className=<span class="hljs-string">"flex gap-4 items-center justify-between flex-wrap"</span>&gt;
                {task.priority &amp;&amp; (
                &lt;span&gt;
                    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Priority: &lt;/span&gt;
                        &lt;span
                            className={<span class="hljs-string">`<span class="hljs-subst">${
                            task.priority === <span class="hljs-string">"low"</span>
                            ? <span class="hljs-string">"bg-lowPriority text-iconColor"</span>
                            : task.priority === <span class="hljs-string">"medium"</span>
                            ? <span class="hljs-string">"bg-mediumPriority text-iconColor"</span>
                            : <span class="hljs-string">"bg-highPriority text-iconColor"</span>
                            }</span> py-1 px-2 rounded-md`</span>}
                        &gt;
                            {task.priority}
                        &lt;/span&gt;
                &lt;/span&gt;
                )}
                &lt;div className=<span class="hljs-string">"flex gap-2 py-1 ml-auto"</span>&gt;
                    &lt;Button
                        handleClick={<span class="hljs-function">() =&gt;</span> handleEdit(task)}
                        extraBtnClasses=<span class="hljs-string">"bg-ok"</span>
                    &gt;
                        &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Edit&lt;/span&gt;
                        &lt;PencilSquareIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden lg:flex"</span> /&gt;
                    &lt;/Button&gt;
                    &lt;Button
                        handleClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> handleDelete(e, task.$id)}
                        extraBtnClasses=<span class="hljs-string">"bg-highPriority"</span>
                    &gt;
                        &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Delete&lt;/span&gt;
                        &lt;TrashIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden lg:flex"</span> /&gt;
                    &lt;/Button&gt;
                &lt;/div&gt;
            &lt;/section&gt;
            &lt;section className=<span class="hljs-string">""</span>&gt;
                &lt;h2 className=<span class="hljs-string">"text-xl font-medium py-2 break-words"</span>&gt;
                    {task.title}
                &lt;/h2&gt;
                &lt;p className=<span class="hljs-string">"py-1 mb-4 min-h-16 break-words"</span>&gt;
                    {task.description.length &gt; <span class="hljs-number">70</span>
                        ? task.description.substring(<span class="hljs-number">0</span>, <span class="hljs-number">70</span>) + <span class="hljs-string">"..."</span>
                        : task.description}
                &lt;/p&gt;
                &lt;span className=<span class="hljs-string">"font-extralight mt-2"</span>&gt;
                    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Due on: &lt;/span&gt;
                        &lt;span className=<span class="hljs-string">"underline"</span>&gt;{<span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
                            task.due_date
                        ).toLocaleDateString()}</span>`</span>}
                    &lt;/span&gt;
                &lt;/span&gt;
                &lt;/section&gt;
                &lt;section className=<span class="hljs-string">"flex justify-between"</span>&gt;
                    {task.done ? (
                        &lt;span className=<span class="hljs-string">"items-center text-ok font-bol ml-auto"</span>&gt;
                            Completed
                        &lt;/span&gt;
                    ) : (
                    &lt;div className=<span class="hljs-string">"flex items-center ml-auto hover:scale-105 transition duration-300 ease-in-out"</span>&gt;
                        &lt;label htmlFor=<span class="hljs-string">"done"</span> className=<span class="hljs-string">"mr-2 font-light"</span>&gt;
                            Mark <span class="hljs-keyword">as</span> complete
                        &lt;/label&gt;
                        &lt;input
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"checkbox"</span>
                            checked={isDone}
                            onClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.stopPropagation()}
                            onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                            setIsDone(e.target.checked);
                            handleCheckbox(task, task.$id, e);
                        }}
                            className=<span class="hljs-string">"size-5 accent-pink-600 rounded-sm"</span>
                    /&gt;
                &lt;/div&gt;
                )}
                &lt;/section&gt;
            &lt;/section&gt;
        &lt;/div&gt;
    &lt;/&gt;
    );
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TaskItem;
</code></pre>
<p>This gives the file some markup to display. It divides the page into two columns, one for the pending tasks and one for the completed tasks, and it handles responsiveness of the page.</p>
<p>In order to get rid of the errors, paste the following code just before the <code>return</code> statement like so: </p>
<pre><code class="lang-typescript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{ task }: TaskItemProps</span>) </span>{

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> [isDone, setIsDone] = useState(<span class="hljs-literal">false</span>);

<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
        currentTaskId: <span class="hljs-built_in">string</span>
    ) =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> deleteDocument(currentTaskId);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(error);
        }
};

<span class="hljs-keyword">const</span> handleCheckbox = <span class="hljs-keyword">async</span> (
        currentTask: IPayload,
        id: <span class="hljs-built_in">string</span>,
        checkedVal: <span class="hljs-built_in">boolean</span>
    ) =&gt; {
        <span class="hljs-keyword">if</span> (!checkedVal) <span class="hljs-keyword">return</span>;

        <span class="hljs-keyword">const</span> payload: IPayload = {
        title: currentTask.title,
        description: currentTask.description,
        due_date: currentTask.due_date,
        priority: currentTask.priority,
        done: checkedVal,
        };

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> updateDocument(payload, id);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(error);
        }
};

<span class="hljs-comment">//rest of code below untouched</span>
<span class="hljs-keyword">return</span> (
......
</code></pre>
<p>This adds the ability to delete a task item and the ability to mark it as complete. </p>
<p>Create a new file in the <strong>utils</strong> folder and call it <strong>shared.ts</strong>. This file will house any function that will be called in more than two places in the application. </p>
<p>The <code>getTasks</code> function is one such repetitive function, so it will be placed in the <strong>shared.ts</strong> file. Paste the following code into it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { readDocuments } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db"</span>;
<span class="hljs-keyword">import</span> { ITask } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getTasks = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> { documents } = <span class="hljs-keyword">await</span> readDocuments();

    <span class="hljs-keyword">return</span> documents <span class="hljs-keyword">as</span> ITask[];
};
</code></pre>
<p>This defines the function and returns an array of <code>ITasks</code>. Go back to the <strong>Task.tsx</strong> file and fix any import errors. </p>
<p>Run the application and you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-00.10.56.png" alt="Image" width="600" height="400" loading="lazy">
<em>your pending and completed tasks displayed in the browser</em></p>
<p>The task can be deleted or marked as complete but you won't see an update on the UI until the page is refreshed. To fix that, go back to the <strong>TaskItem</strong> file and paste the following code below the <code>isDone</code> useState and above the <code>handleDelete</code> function:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [isDone, setIsDone] = useState(<span class="hljs-literal">false</span>);

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> updateTasks = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> allTasks = <span class="hljs-keyword">await</span> getTasks();
        <span class="hljs-keyword">if</span> (setTasks) setTasks(allTasks.reverse());
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};

<span class="hljs-comment">//rest of code below remains as is</span>
<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
</code></pre>
<p>Update <code>TaskItem</code> props interface and the <code>TaskItem</code> function like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">interface</span> TaskItemProps {
    task: ITask;
    setTasks?: <span class="hljs-function">(<span class="hljs-params">tasks: ITask[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{ task, setTasks }: TaskItemProps</span>) </span>{
<span class="hljs-comment">//rest of code below</span>
    <span class="hljs-keyword">const</span> [isDone, setIsDone] = useState(<span class="hljs-literal">false</span>);
</code></pre>
<p>This gives a setter function that resets the tasks array as a prop to the <code>TaskItem</code> component. </p>
<p>Adjust the <code>handleDelete</code> and <code>handleCheckbox</code> functions in the <code>TaskItem</code> component to include the <code>updateTasks</code> function you added above. it should read like this:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
    e: React.MouseEvent&lt;HTMLButtonElement&gt;,
    currentTaskId: <span class="hljs-built_in">string</span>
) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> deleteDocument(currentTaskId);
        updateTasks();
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};

<span class="hljs-keyword">const</span> handleCheckbox = <span class="hljs-keyword">async</span> (
    currentTask: IPayload,
    id: <span class="hljs-built_in">string</span>,
    e: React.ChangeEvent&lt;HTMLInputElement&gt;
    ) =&gt; {

    <span class="hljs-keyword">const</span> payload: IPayload = {
        title: currentTask.title,
        description: currentTask.description,
        due_date: currentTask.due_date,
        priority: currentTask.priority,
        done: e.target.checked,
    };

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> updateDocument(payload, id);
        updateTasks();
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};
</code></pre>
<p>Go back to the <strong>Task.tsx</strong> file and pass <code>setTasks</code> to the <code>TaskItem</code> component like so: <code>&lt;TaskItem key={task.$id} task={task} setTasks={setTasks} /&gt;</code>. Now, the UI updates without needing to manually refresh the page.</p>
<h3 id="heading-how-to-make-the-tasks-editable">How to Make the Tasks Editable</h3>
<p>In order to edit a task, you will need to pass a function to the edit button in the <code>TaskItem</code> component. </p>
<p>Paste the following code in the <strong>TaskItem.tsx</strong> file between the <code>updateTasks</code> and <code>handleDelete</code> functions like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">//paste below updateTasks</span>
<span class="hljs-keyword">const</span> handleEdit = <span class="hljs-keyword">async</span> (
    currentTask: ITask
) =&gt; {
    navigate(<span class="hljs-string">"/"</span>, { state: { task: currentTask } });
};

<span class="hljs-comment">//rest of code untouched below</span>
</code></pre>
<p>Add this line right above the <code>isDone</code> useState: <code>const navigate = useNavigate();</code>. </p>
<p>In the same file, find the edit button and pass the <code>handleEdit</code> function to it. Also, wrap it in a condition that checks if the task is done such that the button is only displayed in the case that the task is not marked as complete. Like so:</p>
<pre><code class="lang-typescript">{!task.done &amp;&amp; (
    &lt;Button
        handleClick={<span class="hljs-function">() =&gt;</span> handleEdit(task)}
        extraBtnClasses=<span class="hljs-string">"bg-ok"</span>
    &gt;
        &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Edit&lt;/span&gt;
        &lt;PencilSquareIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden lg:flex"</span> /&gt;
    &lt;/Button&gt;
)
</code></pre>
<p>The <code>AddTask</code> component has to be adjusted to handle editing a task and the <strong>Index.tsx</strong> file needs to be updated to handle the task to be edited being passed to it via the navigate <code>handleEdit</code> function.</p>
<p>First, go to the <strong>AddTask</strong> file and add some prop definitions directly below the import statements, then pass the new props to the component like so:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span>....

<span class="hljs-comment">// pass a task and an isEdit boolean</span>
<span class="hljs-comment">// if isEdit is true, then the form will be populated with the task's data</span>
<span class="hljs-keyword">interface</span> ITaskFormProps {
    task: ITask | <span class="hljs-literal">null</span>;
    isEdit?: <span class="hljs-built_in">boolean</span>;
    setTasks?: <span class="hljs-function">(<span class="hljs-params">tasks: ITask[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-comment">//pass component props</span>
<span class="hljs-keyword">const</span> AddTask = <span class="hljs-function">(<span class="hljs-params">{ task, isEdit, setTasks }: ITaskFormProps</span>) =&gt;</span> {
<span class="hljs-comment">//code untouched below</span>
</code></pre>
<p>Adjust the due date and priority useStates to read like below:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [dueDate, setDueDate] = useState(
    isEdit &amp;&amp; task?.due_date ? <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(task.due_date) : <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
);

<span class="hljs-keyword">const</span> [priority, setPriority] = useState(
    isEdit &amp;&amp; task?.priority ? task?.priority : priorityArray[<span class="hljs-number">0</span>]
);
</code></pre>
<p>Add a useEffect below the useStates like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [titleValidationError, setTitleValidationError] = useState(<span class="hljs-string">""</span>);

<span class="hljs-comment">//paste below useState statements</span>
useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (isEdit &amp;&amp; task) {
        setTitleVal(task.title);
        setTextAreaVal(task.description);
    } <span class="hljs-keyword">else</span> {
        setTitleVal(<span class="hljs-string">""</span>);
    }
}, [isEdit, task]);
</code></pre>
<p>In the <code>handleSubmit</code> function within the same <strong>AddTask</strong> file, delete this line: <code>await createDocument(payload);</code> and replace with the below:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">if</span> (isEdit &amp;&amp; task) {
    <span class="hljs-keyword">await</span> updateDocument(payload, task!.$id);
    <span class="hljs-keyword">const</span> allTasks = <span class="hljs-keyword">await</span> getTasks();
    <span class="hljs-keyword">if</span> (setTasks) <span class="hljs-keyword">return</span> setTasks(allTasks.reverse());
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">await</span> createDocument(payload);
}
</code></pre>
<p>Now replace the <code>Button</code> component at the bottom of the file just above the form closing tag with this:</p>
<pre><code class="lang-typescript">
&lt;Button
    <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
    disable={isSubmitting}
    extraBtnClasses=<span class="hljs-string">"bg-primary justify-center text-white font-semibold px-4 py-2 outline-1 hover:bg-primaryHover focus:ring-1 focus:ring-pink-800 w-full"</span>
&gt;
    &lt;span&gt;
        {isSubmitting ? <span class="hljs-string">"Submitting..."</span> : task ? <span class="hljs-string">"Edit Task"</span> : <span class="hljs-string">"Add Task"</span>}
    &lt;/span&gt;
&lt;/Button&gt;
<span class="hljs-comment">//unchanged code below</span>
&lt;/form&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AddTask;
</code></pre>
<p>This sets the text on the button depending on whether the form is submitting, creating a new task or updating an exisiting task.</p>
<p>Go to the <strong>Index.tsx</strong> file in the <strong>routes</strong> folder and paste the following above the <code>return</code> statement:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Index = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">//paste here</span>
    <span class="hljs-keyword">const</span> location = useLocation();
    <span class="hljs-keyword">const</span> navigate = useNavigate();

    <span class="hljs-keyword">const</span> taskFromState: ITask = location.state?.task;

    <span class="hljs-keyword">const</span> [taskToEdit] = useState&lt;ITask | <span class="hljs-literal">null</span>&gt;(taskFromState ?? <span class="hljs-literal">null</span>);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (taskFromState) {
            navigate(location.pathname, {});
        }
    }, [taskFromState, location.pathname, navigate]);

    <span class="hljs-comment">//below code remains unchanged</span>
    <span class="hljs-keyword">return</span> (....
</code></pre>
<p>Here, the file gets the task passed to it from the "/tasks" route and sets it to local state. Then the useEffect nullies the passed task so that the form is reset on refresh.</p>
<p>In the <strong>Index.tsx</strong> file replace the <code>AddTask</code> component with this line: <code>&lt;AddTask task={taskToEdit} isEdit={taskToEdit ? true : false} /&gt;</code>.</p>
<p>Run your application and you should be able to click on the edit button, be navigated back to the "/" route, have the form pre-filled with the task details, be able to edit some of the fields and be redirected back to "/tasks" once you click on the "Edit Task" button. </p>
<h3 id="heading-how-to-enable-viewing-of-tasks">How to Enable Viewing of Tasks</h3>
<p>The application now creates, reads, updates and deletes tasks. All that is left on that is the ability to view a particular task. </p>
<p>Go to the <strong>TaskItem</strong> file, add the following to the interface <code>TaskItemProps</code>: <code>isViewTask: boolean;    handleViewTask?: (e: React.MouseEvent&lt;HTMLDivElement&gt;) =&gt; void;</code>.</p>
<p>Add them as props to the <code>TaskItem</code> component and set <code>isViewTask</code> to a default of <code>false</code> like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskItem</span>(<span class="hljs-params">{
    task,
    setTasks,
    isViewTask = <span class="hljs-literal">false</span>,
    handleViewTask,
}: TaskItemProps</span>) </span>{    
    <span class="hljs-comment">//rest of code below unchanged</span>
    <span class="hljs-keyword">const</span> navigate = useNavigate();
</code></pre>
<p>Replace the paragraph tag that displays the task description within the <code>return</code> of the component with this markup:</p>
<pre><code class="lang-typescript">
&lt;p className=<span class="hljs-string">"py-1 mb-4 min-h-16 break-words"</span>&gt;
    {task.description.length &gt; <span class="hljs-number">70</span> &amp;&amp; !isViewTask
        ? task.description.substring(<span class="hljs-number">0</span>, <span class="hljs-number">70</span>) + <span class="hljs-string">"..."</span>
        : task.description
    }
&lt;/p&gt;
</code></pre>
<p>The change introduced will ensure that the full description is visible if <code>isViewTask</code> is set to <code>true</code>.</p>
<p>On the <code>div</code> tag just below the <code>return</code> in the same component, add an <code>onClick</code> handler like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">return</span> (
&lt;&gt;
    &lt;div
        className=<span class="hljs-string">"m-8 cursor-pointer border border-container rounded-md p-4                        hover:shadow-lg transition duration-300 ease-in-out max-h-                       96"</span>
        onClick={handleViewTask}
    &gt;
<span class="hljs-comment">//rest unchanged code</span>
...
</code></pre>
<p>Back in the <strong>Task.tsx</strong> file, paste the following just above the useEffect:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleViewTask = <span class="hljs-function">(<span class="hljs-params">
    e: React.MouseEvent&lt;HTMLDivElement&gt;,
    activeTask: ITask
</span>) =&gt;</span> {
    setIsViewTask(<span class="hljs-literal">true</span>);
    setSelectedTask(activeTask);
};

<span class="hljs-comment">//unchanged code below</span>
useEffect(...
</code></pre>
<p>Add the following useState functions above the <code>handleViewTask</code> function, below the other useStates:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [tasksError, setTasksError] = useState(<span class="hljs-string">""</span>);
<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> [isViewTask, setIsViewTask] = useState(<span class="hljs-literal">false</span>);
<span class="hljs-keyword">const</span> [selectedTask, setSelectedTask] = useState&lt;ITask&gt;();

<span class="hljs-comment">//code below unchanged</span>
<span class="hljs-keyword">const</span> handleViewTask = (...
</code></pre>
<p>Now paste the following code within the <code>return</code> statement in the same file, just above the <code>h1</code> tag displaying "Your Tasks" text:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">return</span> (
&lt;main className=<span class="hljs-string">"container mx-auto"</span>&gt;
    &lt;section className=<span class="hljs-string">"max-w-5xl mx-auto m-12 p-16"</span>&gt;
        <span class="hljs-comment">//paste here</span>
        {isViewTask &amp;&amp; selectedTask &amp;&amp; (
            &lt;Dialog key={selectedTask.$id} setIsViewTask={setIsViewTask}&gt;
                &lt;TaskItem
                    task={selectedTask}
                    handleViewTask={<span class="hljs-function">() =&gt;</span> handleViewTask(selectedTask!)}
                    isViewTask={isViewTask}
                /&gt;
            &lt;/Dialog&gt;
        )}
        &lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
        Your Tasks
        &lt;/h1&gt;
        <span class="hljs-comment">//rest of code below remains unchanged</span>
</code></pre>
<p>You will need to create the Dialog component. Create a new file in the components folder and call it <strong>Dialog.tsx</strong>, then paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { XMarkIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/24/solid"</span>;
<span class="hljs-keyword">import</span> { ReactNode, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { ITask } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;

<span class="hljs-keyword">interface</span> DialogProps {
    setIsViewTask?: <span class="hljs-function">(<span class="hljs-params">isViewTask: <span class="hljs-built_in">boolean</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    children: ReactNode;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dialog</span>(<span class="hljs-params">{ setIsViewTask, children }: DialogProps</span>) </span>{
    <span class="hljs-keyword">const</span> [isOpen, setIsOpen] = useState(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (setIsViewTask) setIsViewTask(<span class="hljs-literal">false</span>);
        setIsOpen(<span class="hljs-literal">false</span>);
    };
    <span class="hljs-keyword">return</span> (
        &lt;dialog
            open={isOpen}
            id=<span class="hljs-string">"modal"</span>
            style={{
            backgroundColor: <span class="hljs-string">"var(--base-bg)"</span>,
            color: <span class="hljs-string">"var(--text-main)"</span>,
            }}
            className={<span class="hljs-string">`<span class="hljs-subst">${
                isOpen ? <span class="hljs-string">"opacity-100"</span> : <span class="hljs-string">"opacity-0 pointer-events-none"</span>
                }</span> transition-opacity duration-300 ease-in-out fixed inset-0                      backdrop-filter backdrop-blur-md backdrop-brightness-50 w-4/6                      border border-container rounded-md max-h-[80vh] overflow-y-auto                 text-main`</span>}
        &gt;
        &lt;Button
            handleClick={closeModal}
            content={{ text: <span class="hljs-string">"Close"</span>, icon: XMarkIcon }}
            extraBtnClasses=<span class="hljs-string">"ml-auto text-main font-medium hover:text-error"</span>
        /&gt;
        &lt;div className=<span class="hljs-string">"max-h-[80vh] overflow-y-auto"</span>&gt;{children}&lt;/div&gt;
        &lt;/dialog&gt;
    );
    }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Dialog;
</code></pre>
<p>Here, the file defined a <code>Dialog</code> component that takes in some props, displays a button and the children it receives from props. </p>
<p>Finally, replace the two <code>TaskItem</code> components in the <code>tasks.filter...</code> function within the return statement of the <strong>Task.tsx</strong> file with the following:</p>
<pre><code class="lang-typescript">
{tasks
    .filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> !task.done)
    .map(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> (
        &lt;TaskItem
            key={task.$id}
            task={task}
            setTasks={setTasks}
            handleViewTask={<span class="hljs-function">() =&gt;</span> handleViewTask(task)}
            isViewTask={isViewTask}
        /&gt;
))}
</code></pre>
<p>You should be able to click on the task items and have the dialog pop up with the details of the task. </p>
<p>However, if you try to delete the item you will notice that it opens the dialog while deleting it. To fix that, adjust the <code>handleDelete</code> function in the <strong>TaskItem.tsx</strong> file to read like this: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleDelete = <span class="hljs-keyword">async</span> (
    e: React.MouseEvent&lt;HTMLButtonElement&gt;,
    currentTaskId: <span class="hljs-built_in">string</span>
) =&gt; {
    e.stopPropagation();
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> deleteDocument(currentTaskId);
        <span class="hljs-keyword">if</span> (isViewTask) {
            navigate(<span class="hljs-number">0</span>);
        } <span class="hljs-keyword">else</span> {
            updateTasks();
        }
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
    }
};
</code></pre>
<p>You've added <code>e.stopPropagation()</code> to stop the event from bubbling up to the parent and potentially interfering with the click to open the dialog. </p>
<p>You've also added a check after deleting the task to see if the task is being viewed, in which case we refresh the page via the <code>navigate(0)</code> to force the UI to update to the proper state. If not, it proceeds to call <code>updateTasks()</code> to refresh the state.</p>
<p>You will notice the same issue when you try to mark the task as complete in which the dialog pops up. To fix this, adjust the checkbox input to include this line: <code>onClick={(e) =&gt; e.stopPropagation()}</code>. </p>
<p>The new line stops the event from bubbling up to the parent <code>div</code>. It is added to the <code>onClick</code> instead of the <code>onChange</code> because the event it is trying to intercept is of type <code>onClick</code>. The input should read like this:</p>
<pre><code class="lang-typescript">
&lt;label htmlFor=<span class="hljs-string">"done"</span> className=<span class="hljs-string">"mr-2 font-light"</span>&gt;
    Mark <span class="hljs-keyword">as</span> complete
&lt;/label&gt;
&lt;input
    <span class="hljs-keyword">type</span>=<span class="hljs-string">"checkbox"</span>
    checked={isDone}
    onClick={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.stopPropagation()}
    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setIsDone(e.target.checked);
    handleCheckbox(task, task.$id, e);
    }}
    className=<span class="hljs-string">"size-5 accent-pink-600 rounded-sm"</span>
/&gt;
</code></pre>
<p>At this point, the React application is responsive, can CRUD the Appwrite database and the user can view individual tasks. </p>
<h3 id="heading-how-to-auto-generate-descriptions-with-vercels-ai-sdk">How to Auto Generate Descriptions with Vercel's AI SDK</h3>
<p>To enhance the application and its' user experience, you can add the ability to auto generate descriptions for the tasks using AI. </p>
<p>To get started, open an integrated terminal and run the following command: <code>npm i ai</code>. This adds the <a target="_blank" href="https://vercel.com/blog/introducing-the-vercel-ai-sdk">Vercel AI SDK</a> to the React application. </p>
<p>Next, run this command in the terminal: <code>npm i @huggingface/inference</code> to add Hugging Face support. The application will use <a target="_blank" href="https://sdk.vercel.ai/docs/guides/providers/huggingface">Hugging Face</a> because you need to pay to get programmatic access to OpenAI.</p>
<p>Create a new file in the utils folder, call it <strong>ai.ts</strong> and paste the following in it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { HfInference } <span class="hljs-keyword">from</span> <span class="hljs-string">"@huggingface/inference"</span>;
<span class="hljs-keyword">import</span> { HuggingFaceStream, StreamingTextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"ai"</span>;

<span class="hljs-comment">// Create a new HuggingFace Inference instance</span>
<span class="hljs-keyword">const</span> Hf = <span class="hljs-keyword">new</span> HfInference(<span class="hljs-keyword">import</span>.meta.env.VITE_HUGGINGFACE_KEY);

<span class="hljs-comment">// IMPORTANT! Set the runtime to edge</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> runtime = <span class="hljs-string">"edge"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> callAI = <span class="hljs-keyword">async</span> (prompt: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> response = Hf.textGenerationStream({
        model: <span class="hljs-string">"OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5"</span>,
        inputs: <span class="hljs-string">`&lt;|prompter|&gt;<span class="hljs-subst">${prompt}</span>&lt;|endoftext|&gt;&lt;|assistant|&gt;`</span>,
        parameters: {
            max_new_tokens: <span class="hljs-number">150</span>,
            <span class="hljs-comment">// @ts-ignore</span>
            typical_p: <span class="hljs-number">0.2</span>,
            repetition_penalty: <span class="hljs-number">1</span>,
            truncate: <span class="hljs-number">1000</span>,
            return_full_text: <span class="hljs-literal">false</span>,
        },
    });

    <span class="hljs-comment">// Convert the response into a friendly text-stream</span>
    <span class="hljs-keyword">const</span> stream = HuggingFaceStream(response);

    <span class="hljs-comment">// Respond with the stream</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StreamingTextResponse(stream);
};
</code></pre>
<p>This boilerplate creates an instance of Hugging Face, creates a function that takes in a prompt, passes that prompt to the <code>textGenerationStream</code> function. Then it converts the response to a stream and to a text stream. </p>
<p>You will need to add a Hugging Face access token. You can genrate one at this <a target="_blank" href="https://huggingface.co/settings/tokens">address</a>. You will need an account before you can access it. </p>
<p>Once you have token, open the env file and add to it the following line</p>
<pre><code class="lang-env">
//replace with your actual token
VITE_HUGGINGFACE_KEY=YOUR-HF-ACCESS-TOKEN
</code></pre>
<p>Open the <strong>AddTask.tsx</strong> file and paste the following button just above the closing div tag of the <code>div</code> containing the textarea input:</p>
<pre><code class="lang-typescript">
{textAreaVal.length &gt; <span class="hljs-number">197</span> &amp;&amp; (
    &lt;span className=<span class="hljs-string">"text-error mt-1"</span>&gt;
        Warning description getting too long. Can only be <span class="hljs-number">200</span> characters
    &lt;/span&gt;
)}
<span class="hljs-comment">//paste here</span>
&lt;Button
    handleClick={generateDesc}
    disable={isGenerating}
    extraBtnClasses=<span class="hljs-string">"bg-light mt-2 w-fit ml-auto"</span>
&gt;
    &lt;span&gt;Generate description&lt;/span&gt;
    &lt;SparklesIcon height={<span class="hljs-number">20</span>} /&gt;
&lt;/Button&gt;
<span class="hljs-comment">//rest of below code unchanged</span>
&lt;/div&gt;
</code></pre>
<p>Define <code>generateDesc</code> function just above the <code>return</code> statement in the <strong>AddTask</strong> file like so:</p>
<pre><code>
<span class="hljs-keyword">const</span> generateDesc = <span class="hljs-keyword">async</span> () =&gt; {
    setTextAreaVal(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">if</span> (!titleVal) {
    alert(<span class="hljs-string">"Please provide a title for the task"</span>);
    <span class="hljs-keyword">return</span>;
    }

    setIsGenerating(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> prompt = <span class="hljs-string">`Provide a description for this task: <span class="hljs-subst">${titleVal}</span>. Keep the description to a maximum of 30 words`</span>;

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> callAI(prompt);
        <span class="hljs-keyword">const</span> responseText = <span class="hljs-keyword">await</span> res.text();

        setIsGenerating(<span class="hljs-literal">false</span>);

        <span class="hljs-comment">//create a typing effect</span>
        responseText.split(<span class="hljs-string">""</span>).forEach(<span class="hljs-function">(<span class="hljs-params">char, index</span>) =&gt;</span> {
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        setTextAreaVal(<span class="hljs-function">(<span class="hljs-params">prevText</span>) =&gt;</span> prevText + char);
        }, index * <span class="hljs-number">32</span>);
        });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"ERROR HUGGING FACE API: "</span> + error);
    }
};
</code></pre><p>The function checks that the title is not empty and uses the title to create a prompt to pass on the Hugging Face helper. The response from the call is saved to local state. A simple typing effect is created as the textarea is populated with the response. </p>
<p>Next, add this useState: <code>const [isGenerating, setIsGenerating] = useState(false);</code> to the other useStates in the <code>AddTask</code> component.</p>
<p>Replace the textarea input in the same component with the following:</p>
<pre><code class="lang-typescript">
&lt;textarea
    id=<span class="hljs-string">"description"</span>
    placeholder=<span class="hljs-string">"Describe your task"</span>
    maxLength={<span class="hljs-number">200</span>}
    value={isGenerating ? <span class="hljs-string">"generating..."</span> : textAreaVal}
    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTextAreaVal(e.target.value)}
    className={<span class="hljs-string">`bg-inherit border rounded-sm p-2 h-32 resize-none          focus:outline-none focus:ring-1 <span class="hljs-subst">${
    textAreaVal.length &gt; <span class="hljs-number">197</span>
    ? <span class="hljs-string">"border-error focus:ring-red-500 invalid:focus:ring-red-600"</span>
    : <span class="hljs-string">"border-input focus:ring-slate-900"</span>
    }</span>`</span>}
/&gt;
</code></pre>
<p>On checking the application, you should see the button and be able to generate a description for the title of a task as below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-04.57.05.png" alt="Image" width="600" height="400" loading="lazy">
<em>ai-enhanced task description</em></p>
<h3 id="heading-voice-enable-the-application-with-the-react-speech-recognition-package">Voice-enable the Application with the React Speech Recognition Package</h3>
<p>First, you need to add the dependency and its Typescript helper by running the following commands in an integrated terminal window: <code>npm i react-speech-recognition</code> and <code>npm i @types/react-speech-recognition</code>. </p>
<p>Additionally, run the following command to get the dependency to work properly: <code>npm i regenerator-runtime</code>.</p>
<p>Create a <strong>hooks</strong> folder within the <strong>src</strong> folder. Create a file within it called <strong>useSpeechToTextHelper.ts</strong> and paste the following in it:</p>
<pre><code class="lang-typeacript">
import "regenerator-runtime/runtime";
import { useState } from "react";
import { useSpeechRecognition } from "react-speech-recognition";

export function useSpeechToTextHelper() {
    const [error, setError] = useState("");

    const {
        transcript,
        listening,
        resetTranscript,
        browserSupportsSpeechRecognition,
    } = useSpeechRecognition();

    if (!browserSupportsSpeechRecognition) {
        setError("Browser doesn't support speech recognition.");
    }

    return {
        error,
        listening,
        transcript,
        resetTranscript,
    };
}
</code></pre>
<p>This hook exposes some built in helper functions from React Speech Recognition, handles the case that the browser does not support the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition">relevant web APIs</a> and returns some of that data.</p>
<p>Create a new file in the components folder called <strong>Speaker.tsx</strong>. Paste the following code into it:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useSpeechToTextHelper } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useSpeechToTextHelper"</span>;
<span class="hljs-keyword">import</span> { MicrophoneIcon, XCircleIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/24/solid"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;
<span class="hljs-keyword">import</span> SpeechRecognition <span class="hljs-keyword">from</span> <span class="hljs-string">"react-speech-recognition"</span>;

<span class="hljs-keyword">interface</span> SpeakerProps {
handleClear: <span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent&lt;HTMLButtonElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker</span>(<span class="hljs-params">{ handleClear }: SpeakerProps</span>) </span>{
    <span class="hljs-keyword">const</span> { listening, error } = useSpeechToTextHelper();

    <span class="hljs-keyword">const</span> handleSpeech = <span class="hljs-function">() =&gt;</span> {
        SpeechRecognition.startListening();
    };

    <span class="hljs-keyword">return</span> (
    &lt;div&gt;
        {error &amp;&amp; &lt;div&gt;{error}&lt;/div&gt;}
        &lt;div className=<span class="hljs-string">"flex gap-2 py-1 items-center text-center justify-center"</span>&gt;
            &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;{listening ? <span class="hljs-string">"Mic on"</span> : <span class="hljs-string">"Mic off"</span>}&lt;/span&gt;
            &lt;Button
                handleClick={handleSpeech}
                extraBtnClasses=<span class="hljs-string">"bg-lightOk"</span>
                title=<span class="hljs-string">"Start"</span>
            &gt;
                &lt;MicrophoneIcon height={<span class="hljs-number">25</span>} /&gt;
            &lt;/Button&gt;
            &lt;Button
                handleClick={handleClear}
                extraBtnClasses=<span class="hljs-string">"bg-light"</span>
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"reset"</span>
                title=<span class="hljs-string">"Reset"</span>
            &gt;
                &lt;XCircleIcon height={<span class="hljs-number">25</span>} /&gt;
            &lt;/Button&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Speaker;
</code></pre>
<p>This components accepts a function to clear the voice input as a prop, uses the helper hook, defines a function to handle the actual speech, handles potential error state and displays a button for handling the speech and another for clearing the voice transcript.</p>
<p>In the <strong>AddTask</strong> file, delete the label for title and replace it with the following markup:</p>
<pre><code class="lang-typescript">
&lt;div className=<span class="hljs-string">"flex flex-row justify-between items-center"</span>&gt;
    &lt;label htmlFor=<span class="hljs-string">"title"</span>&gt;Task Title&lt;/label&gt;
    &lt;Speaker handleClear={clearTranscript} /&gt;
&lt;/div&gt;
</code></pre>
<p>This adds the <code>Speaker</code> component and wraps both the label and the <code>Speaker</code> component in a representational <code>div</code> in order to maintain the form layout. </p>
<p>Add the <code>clearTranscript</code> function just above the <code>handleSubmit</code> function like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleTitleChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    setTitleVal(e.target.value);

    <span class="hljs-keyword">if</span> (e.target.value.trim() !== <span class="hljs-string">""</span>) {
        setTitleValidationError(<span class="hljs-string">""</span>);
    }
};

<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> clearTranscript = <span class="hljs-function">() =&gt;</span> {
    resetTranscript();
};
<span class="hljs-comment">//below code is unchanged</span>
<span class="hljs-keyword">const</span> handleSubmitTask = asyc....
</code></pre>
<p>Next in the same <code>AddTask</code> component, add the following:</p>
<pre><code>
<span class="hljs-keyword">const</span> AddTask = <span class="hljs-function">(<span class="hljs-params">{ task, isEdit, setTasks }: ITaskFormProps</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> navigate = useNavigate();
    <span class="hljs-comment">//paste here</span>
    <span class="hljs-keyword">const</span> { transcript, resetTranscript } = useSpeechToTextHelper();
    <span class="hljs-comment">//rest remains unchanged</span>
</code></pre><p>Replace the useEffect in the file with this new one:</p>
<pre><code class="lang-typescript">
useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (isEdit &amp;&amp; task &amp;&amp; !transcript) {
        setTitleVal(task.title);
        setTextAreaVal(task.description);
    } <span class="hljs-keyword">else</span> {
        setTitleVal(transcript || <span class="hljs-string">""</span>);
    }
}, [isEdit, task, transcript]);
</code></pre>
<p>Your application should now support creating titles for the tasks via voice inputs. And should look something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-05.36.51.png" alt="Image" width="600" height="400" loading="lazy">
<em>voice input functionality added to the form</em></p>
<h3 id="heading-how-to-add-search-functionality-to-the-application">How to Add Search Functionality to the Application</h3>
<p>To increase ease of use, it is useful to have some search functionality in the application. </p>
<p>To start, open up the Appwrite console. Click into your collections, click on Indexes tab then click on the "Create index" button. </p>
<p>Leave the index key as is, select FullText in the index type dropdown. Add title attribute and create the index. Repeat the process for the description attribute.</p>
<p>In your application, open <strong>db.ts</strong> file in <strong>utils</strong> folder and paste the following function just above the <code>export</code> keyword, then add it to the list of exports:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> searchTasks = <span class="hljs-keyword">async</span> (searchTerm: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> resTitle = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
                        Query.search(<span class="hljs-string">"title"</span>, searchTerm),
                    ]);
    <span class="hljs-keyword">const</span> resDesc = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
                        Query.search(<span class="hljs-string">"description"</span>, searchTerm),
                     ]);
    <span class="hljs-keyword">const</span> res = [...resTitle.documents, ...resDesc.documents];

    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">export</span> {
    createDocument,
    readDocuments,
    updateDocument,
    deleteDocument,
    searchTasks,
};
</code></pre>
<p>Create a new file in the components folder, calll it <strong>Search.tsx</strong>. Paste the following into it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FormEvent, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { ITask } <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/interface"</span>;
<span class="hljs-keyword">import</span> Dialog <span class="hljs-keyword">from</span> <span class="hljs-string">"./Dialog"</span>;
<span class="hljs-keyword">import</span> TaskItem <span class="hljs-keyword">from</span> <span class="hljs-string">"./TaskItem"</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"./Button"</span>;
<span class="hljs-keyword">import</span> { searchTasks } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/db"</span>;

<span class="hljs-keyword">const</span> Search = <span class="hljs-function">() =&gt;</span> {
<span class="hljs-keyword">const</span> [searchTerm, setSearchTerm] = useState(<span class="hljs-string">""</span>);
<span class="hljs-keyword">const</span> [isSearching, setIsSearching] = useState(<span class="hljs-literal">false</span>);
<span class="hljs-keyword">const</span> [searchedTasks, setSearchedTasks] = useState&lt;ITask[]&gt;([]);
<span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"flex flex-col w-full md:w-1/2"</span>&gt;
            &lt;form
                className=<span class="hljs-string">"flex flex-col md:flex-row items-start md:items-center gap-2"</span>
                onSubmit={handleSubmit}
            &gt;
                {searchedTasks.length &gt; <span class="hljs-number">0</span> &amp;&amp; (
                    &lt;Dialog setSearchedTasks={setSearchedTasks}&gt;
                        {searchedTasks.map(<span class="hljs-function">(<span class="hljs-params">task: ITask</span>) =&gt;</span> (
                            &lt;TaskItem key={task.$id} task={task} isViewTask={<span class="hljs-literal">true</span>} /&gt;
                        ))}
                    &lt;/Dialog&gt;
                )}
            &lt;input
                aria-roledescription=<span class="hljs-string">"search"</span>
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                id=<span class="hljs-string">"search"</span>
                placeholder=<span class="hljs-string">"search your tasks..."</span>
                value={searchTerm}
                onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setSearchTerm(e.target.value)}
                className={<span class="hljs-string">`bg-inherit w-5/6 border rounded-md p-2 focus:outline-none focus:ring-1 <span class="hljs-subst">${
                error
                ? <span class="hljs-string">"border-error focus:ring-red-500 invalid:focus:ring-red-600"</span>
                : <span class="hljs-string">"border-input focus:ring-slate-900"</span>
                }</span>`</span>}
            /&gt;
            &lt;Button
            <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
            extraBtnClasses=<span class="hljs-string">"bg-primary text-white hover:bg-primaryHover font-medium text-main py-2"</span>
            &gt;
                &lt;span&gt;{isSearching ? <span class="hljs-string">"Searching..."</span> : <span class="hljs-string">"Search"</span>}&lt;/span&gt;
            &lt;/Button&gt;
            &lt;/form&gt;
            &lt;span className=<span class="hljs-string">"text-error font-medium mt-1"</span>&gt;{error}&lt;/span&gt;
        &lt;/div&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Search;
</code></pre>
<p>The new <code>Search</code> component creates some local state and returns a form with an input and search button. It also opens the dialog when it has search results.</p>
<p>Add this <code>handleSubmit</code> function above the <code>return</code> statement like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!searchTerm) {
        setError(<span class="hljs-string">"No search term entered"</span>);
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        setError(<span class="hljs-string">""</span>);
        }, <span class="hljs-number">3000</span>);
        <span class="hljs-keyword">return</span>;
    }

    setIsSearching(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> searchTasks(searchTerm);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"res search: "</span>, res);
    <span class="hljs-keyword">if</span> (res.length === <span class="hljs-number">0</span>) {
        setIsSearching(<span class="hljs-literal">false</span>);
        setError(<span class="hljs-string">"No task found"</span>);
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        setSearchTerm(<span class="hljs-string">""</span>);
        setError(<span class="hljs-string">""</span>);
        }, <span class="hljs-number">3000</span>);
        <span class="hljs-keyword">return</span>;
    }
    setIsSearching(<span class="hljs-literal">false</span>);
    setSearchedTasks(res <span class="hljs-keyword">as</span> ITask[]);
};
</code></pre>
<p>This function sets an error if no search term is received, then it attempts to call the database search function passing it the search term. If successful it sets the tasks to local state and if not, it catches the error.</p>
<p>Click into the dialog component and replace its props with the following, then pass the <code>setSearchedTasks</code> to it like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">interface</span> DialogProps {
    setIsViewTask?: <span class="hljs-function">(<span class="hljs-params">isViewTask: <span class="hljs-built_in">boolean</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    setSearchedTasks?: <span class="hljs-function">(<span class="hljs-params">tasks: ITask[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    children: ReactNode;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dialog</span>(<span class="hljs-params">{ setIsViewTask, setSearchedTasks, children }: DialogProps</span>) </span>{...
</code></pre>
<p>Replace the <code>closeModal</code> function in the dialog component with this snippet:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (setIsViewTask) setIsViewTask(<span class="hljs-literal">false</span>);
    <span class="hljs-comment">//this is the new line</span>
    <span class="hljs-keyword">if</span> (setSearchedTasks) setSearchedTasks([]);
    setIsOpen(<span class="hljs-literal">false</span>);
};
</code></pre>
<p>Go back into the <strong>Task.tsx</strong> file and paste this below the <code>h1</code> tag that displays the "Your Tasks" text:</p>
<pre><code class="lang-typescript">
&lt;h1 className=<span class="hljs-string">"text-4xl md:text-7xl font-bold text-center py-3 mb-16"</span>&gt;
Your Tasks
&lt;/h1&gt;
<span class="hljs-comment">//paste here</span>
&lt;div className=<span class="hljs-string">"m-8 flex flex-col-reverse md:flex-row gap-8 items-start                     md:items-center md:justify-between"</span>&gt;
    &lt;Search /&gt;
    &lt;Button
        handleClick={<span class="hljs-function">() =&gt;</span> navigate(<span class="hljs-string">"/"</span>)}
        extraBtnClasses=<span class="hljs-string">"bg-primary text-white font-medium py-2 hover:bg-    primaryHover ml-auto"</span>
    &gt;
        &lt;span&gt;Add Task&lt;/span&gt;
        &lt;PlusIcon height={<span class="hljs-number">25</span>} className=<span class="hljs-string">"hidden md:flex"</span> /&gt;
    &lt;/Button&gt;
&lt;/div&gt;
<span class="hljs-comment">//rest of code stays unchanged</span>
</code></pre>
<p>This adds the search component and a button that takes you back to the Index page when clicked.</p>
<p>Add the following within the Task component just below the useStates:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> [selectedTask, setSelectedTask] = useState&lt;ITask&gt;();
<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> navigate = useNavigate();
<span class="hljs-comment">//all below remain unchanged</span>
<span class="hljs-keyword">const</span> handleSelectChange = (e: React.ChangeEvent&lt;HTMLSelectElement&gt;)
</code></pre>
<p>You can now test your search functionality. It works but has one bug: if the search term is present in the both the title and the description we get back two search results. </p>
<p>To fix that, modify the <code>searchTasks</code> function in <strong>db.ts</strong> to filter out duplicate tasks by IDs like so: </p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> searchTasks = <span class="hljs-keyword">async</span> (searchTerm: <span class="hljs-built_in">string</span>) =&gt; {
    <span class="hljs-keyword">const</span> resTitle = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
        Query.search(<span class="hljs-string">"title"</span>, searchTerm),
    ]);
    <span class="hljs-keyword">const</span> resDesc = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID, [
        Query.search(<span class="hljs-string">"description"</span>, searchTerm),
    ]);

    <span class="hljs-keyword">const</span> res = [...resTitle.documents, ...resDesc.documents];

    <span class="hljs-comment">// remove duplicate tasks</span>
    <span class="hljs-keyword">const</span> uniqueRes = res.filter(
        <span class="hljs-function">(<span class="hljs-params">task, index, self</span>) =&gt;</span> index === self.findIndex(<span class="hljs-function">(<span class="hljs-params">t</span>) =&gt;</span> t.$id ===                  task.$id)
    );

    <span class="hljs-keyword">return</span> uniqueRes;
};
</code></pre>
<p>Now your search should work as expected and should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-08.00.40.png" alt="Image" width="600" height="400" loading="lazy">
<em>your pending and completed tasks displayed in the browser</em></p>
<h3 id="heading-how-to-add-ability-to-sort-tasks-via-due-date-and-priority">How to Add Ability to Sort Tasks via Due Date and Priority</h3>
<p>The application will only sort the pending tasks as it makes the most sense. It will sort by due date from the earliest date to the latest and vice versa. It will also sort by priority from lowest to highest and vice versa.</p>
<p>To get started, paste the following in the <strong>Tasks.tsx</strong> file right below the <code>h3</code> tag with the "Pending Tasks" text like so:</p>
<pre><code class="lang-typescript">
&lt;h3 className=<span class="hljs-string">"text-2xl font-bold m-8"</span>&gt;Pending Tasks&lt;/h3&gt;
<span class="hljs-comment">//paste here</span>
&lt;div className=<span class="hljs-string">"m-8 flex items-start lg:items-center gap-1 justify-between flex-col lg:flex-row"</span>&gt;
    &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Sort Tasks by: &lt;/span&gt;
    &lt;Select
        defaultSelectValue={selectArray[<span class="hljs-number">0</span>]}
        handleSelectChange={handleSelectChange}
        selectOptions={selectArray}
    /&gt;
&lt;/div&gt;
</code></pre>
<p>Then paste the following array that will contain the options for select component above. Paste it right above <code>handleViewTask</code> function like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> navigate = useNavigate();
<span class="hljs-comment">//paste here</span>
<span class="hljs-keyword">const</span> selectArray = [
    <span class="hljs-string">"priority - (low - high)"</span>,
    <span class="hljs-string">"priority - (high - low)"</span>,
    <span class="hljs-string">"due date - (earliest - latest)"</span>,
    <span class="hljs-string">"due date - (latest - earliest)"</span>,
];
<span class="hljs-comment">//rest remains unchanged</span>
<span class="hljs-keyword">const</span> handleViewTask = (...
</code></pre>
<p>Add the <code>handleSelectChange</code> and the sort functions above the <code>selectArray</code>, like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> sortByPriority = (tasksList: ITask[], isAsc: <span class="hljs-built_in">boolean</span>): ITask[] =&gt; {
    <span class="hljs-keyword">const</span> priorityOrder: { [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">number</span> } = {
    low: <span class="hljs-number">1</span>,
    medium: <span class="hljs-number">2</span>,
    high: <span class="hljs-number">3</span>,
    };

    <span class="hljs-keyword">return</span> [...tasksList].sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> priorityA = priorityOrder[a.priority!.toLowerCase()];
    <span class="hljs-keyword">const</span> priorityB = priorityOrder[b.priority!.toLowerCase()];
    <span class="hljs-keyword">return</span> isAsc ? priorityA - priorityB : priorityB - priorityA;
    });
};

<span class="hljs-keyword">const</span> handleSelectChange = <span class="hljs-keyword">async</span> (
    e: React.ChangeEvent&lt;HTMLSelectElement&gt;
) =&gt; {
    <span class="hljs-keyword">const</span> selectedOption = e.target.value;
    <span class="hljs-keyword">const</span> doneTasks = tasks.filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span> task.done);

    <span class="hljs-keyword">switch</span> (selectedOption) {
        <span class="hljs-keyword">case</span> <span class="hljs-string">"priority - (low - high)"</span>:
        <span class="hljs-keyword">case</span> <span class="hljs-string">"priority - (high - low)"</span>: {
            <span class="hljs-keyword">const</span> isAsc = selectedOption === <span class="hljs-string">"priority - (low - high)"</span>;
            <span class="hljs-keyword">const</span> sortedTasks = sortByPriority(tasks, isAsc);
            setTasks([...doneTasks, ...sortedTasks.filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span>                           !task.done)]);
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">case</span> <span class="hljs-string">"due date - (earliest - latest)"</span>:
        <span class="hljs-keyword">case</span> <span class="hljs-string">"due date - (latest - earliest)"</span>: {
            <span class="hljs-keyword">const</span> isEarliestToLatest =
            selectedOption === <span class="hljs-string">"due date - (earliest - latest)"</span>;
            <span class="hljs-keyword">const</span> dueDateResult = <span class="hljs-keyword">await</span> sortByDueDate(isEarliestToLatest);
            <span class="hljs-keyword">const</span> sortedTasks = dueDateResult.documents <span class="hljs-keyword">as</span> ITask[];
            setTasks([...doneTasks, ...sortedTasks.filter(<span class="hljs-function">(<span class="hljs-params">task</span>) =&gt;</span>                            !task.done)]);
            <span class="hljs-keyword">break</span>;
        }
        <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">break</span>;
        }
};

<span class="hljs-comment">//below remains unchanged</span>
<span class="hljs-keyword">const</span> selectArray = .....
</code></pre>
<p>The sortByPriority function creates an object whose keys map to the priority array and gives them numerical values. This makes it easier to sort as it is hard to tell which string is higher priority without that.</p>
<p>The <code>handleSelectChange</code> function picks out the selected option and filters the tasks to get the completed ones. It does matching logic in the switch statements, calling <code>sortByPriority</code> for the cases where the user is trying to do that and it calls <code>sortByDueDate</code> for the rest of the cases. </p>
<p><code>sortByDueDate</code> is defined in the <strong>db.ts</strong> file. Open it and paste the following at the bottom of the file above the exports. Then add it to the exports list like so:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> sortByDueDate = <span class="hljs-keyword">async</span> (isEarliestToLatest: <span class="hljs-built_in">boolean</span>) =&gt; {
    <span class="hljs-keyword">const</span> orderQuery = isEarliestToLatest
        ? Query.orderAsc(<span class="hljs-string">"due_date"</span>)
        : Query.orderDesc(<span class="hljs-string">"due_date"</span>);
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> databases.listDocuments(dbID, collectionID,              [orderQuery]);
    <span class="hljs-keyword">return</span> res;
};

<span class="hljs-keyword">export</span> {
    createDocument,
    readDocuments,
    updateDocument,
    deleteDocument,
    searchTasks,
    sortByDueDate,
};
</code></pre>
<p>This function leverages Appwrites' Query methods to sort the date string according to the Boolean that is passed to it.</p>
<p>Going back to your application, run it to test the sorting functionality. The application should be sorted and the sorting should only apply to the pending tasks.</p>
<h3 id="heading-bonus-how-to-add-dark-mode-support">Bonus: How to Add Dark Mode Support</h3>
<p>The last thing left is to add Dark Mode support that respects the users' systems setting. </p>
<p>For this, open the <strong>tailwind.config.ts</strong> file and replace its contents with the following:</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    content: [<span class="hljs-string">"./index.html"</span>, <span class="hljs-string">"./src/**/*.{js,ts,jsx,tsx}"</span>],
    darkMode: <span class="hljs-string">"selector"</span>,
    theme: {
        extend: {
            textColor: {
                error: <span class="hljs-string">"var(--text-error)"</span>,
                ok: <span class="hljs-string">"var(--text-ok)"</span>,
                main: <span class="hljs-string">"var(--text-main)"</span>,
                iconColor: <span class="hljs-string">"var(--btn-icon-main)"</span>,
            },
            backgroundColor: {
                base: <span class="hljs-string">"var(--base-bg)"</span>,
                primary: <span class="hljs-string">"var(--btn-bg-primary)"</span>,
                primaryHover: <span class="hljs-string">"var(--btn-bg-primary-hover)"</span>,
                ok: <span class="hljs-string">"var(--btn-bg-ok)"</span>,
                lightOk: <span class="hljs-string">"var(--btn-bg-light-ok)"</span>,
                light: <span class="hljs-string">"var(--btn-bg-light)"</span>,
                lowPriority: <span class="hljs-string">"var(--low-priority)"</span>,
                mediumPriority: <span class="hljs-string">"var(--medium-priority)"</span>,
                highPriority: <span class="hljs-string">"var(--high-priority)"</span>,
            },
            borderColor: {
                container: <span class="hljs-string">"var(--border-container)"</span>,
                input: <span class="hljs-string">"var(--border-input)"</span>,
                error: <span class="hljs-string">"var(--border-error)"</span>,
            },
        },
    },
    plugins: [],
};
</code></pre>
<p>This extends the tailwind preset colors and ties the CSS variables that were set in the <strong>index.css</strong> file to the Tailwind config. </p>
<p>In the <strong>index.css</strong> file, add this dark class below the date class like so:</p>
<pre><code class="lang-css">
<span class="hljs-selector-id">#date</span><span class="hljs-selector-pseudo">::-webkit-calendar-picker-indicator</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--btn-bg-light); 
}
//<span class="hljs-selector-tag">paste</span> <span class="hljs-selector-tag">here</span>
<span class="hljs-selector-class">.dark</span>{
    <span class="hljs-attribute">--base-bg</span>: <span class="hljs-number">#262626</span>;
    <span class="hljs-attribute">--text-main</span>: <span class="hljs-number">#ffffff</span>;
    <span class="hljs-attribute">--text-error</span>: <span class="hljs-number">#fca5a5</span>;
    <span class="hljs-attribute">--text-ok</span>: <span class="hljs-number">#86efac</span>;
    <span class="hljs-attribute">--border-input</span>: <span class="hljs-number">#e2e8f0</span>;
    <span class="hljs-attribute">--border-error</span>: <span class="hljs-number">#fca5a5</span>;
}
</code></pre>
<p>This changes some of the CSS variables values when the dark class is applied.</p>
<p>Now, open the navbar file in the components folder and replace its contents with the following:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> navigate = useNavigate();

    <span class="hljs-keyword">const</span> themeArray = [<span class="hljs-string">"light"</span>, <span class="hljs-string">"dark"</span>, <span class="hljs-string">"system"</span>];
    <span class="hljs-keyword">const</span> [theme, setTheme] = useState(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"theme"</span>) || themeArray[<span class="hljs-number">2</span>];
    });

    <span class="hljs-keyword">const</span> applyTheme = <span class="hljs-function">(<span class="hljs-params">selectedTheme: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> isDarkModePreferred = <span class="hljs-built_in">window</span>.matchMedia(
            <span class="hljs-string">"(prefers-color-scheme: dark)"</span>
            ).matches;

        <span class="hljs-built_in">document</span>.documentElement.classList.remove(<span class="hljs-string">"light"</span>, <span class="hljs-string">"dark"</span>);
        <span class="hljs-built_in">document</span>.documentElement.classList.add(selectedTheme);

        <span class="hljs-keyword">if</span> (selectedTheme === <span class="hljs-string">"system"</span>) {
        <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"dark"</span>, isDarkModePreferred);
        <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"light"</span>,                  !isDarkModePreferred);
        }
    };

    <span class="hljs-keyword">const</span> handleSelectTheme = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> selectedTheme = e.target.value;
        setTheme(selectedTheme);

        <span class="hljs-comment">// Store the selected theme in localStorage</span>
        <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">"theme"</span>, selectedTheme);
    };

    useEffect(<span class="hljs-function">() =&gt;</span> {
        applyTheme(theme);
    }, [theme]);

    <span class="hljs-keyword">return</span> (
        &lt;nav className=<span class="hljs-string">"py-4 border-b-2 border-container shadow-md shadow-gray-400 w-full fixed top-0 bg-base"</span>&gt;
            &lt;ul className=<span class="hljs-string">"flex items-center justify-between  w-11/12 mx-auto"</span>&gt;
                &lt;Link to=<span class="hljs-string">"/"</span>&gt;
                    &lt;Button&gt;
                        &lt;span className=<span class="hljs-string">"font-semibold text-main"</span>&gt;Taskwrite&lt;/span&gt;
                        &lt;PencilIcon height={<span class="hljs-number">20</span>} className=<span class="hljs-string">"text-main"</span> /&gt;
                    &lt;/Button&gt;
                &lt;/Link&gt;
                &lt;div className=<span class="hljs-string">"flex items-center justify-between gap-6"</span>&gt;
                &lt;Link
                    to=<span class="hljs-string">"/tasks"</span>
                    className=<span class="hljs-string">"font-semibold hover:scale-105 transition duration-300 ease-in-out"</span>
                &gt;
                    View Tasks
                &lt;/Link&gt;
                &lt;div className=<span class="hljs-string">"flex gap-2 items-center"</span>&gt;
                    &lt;span className=<span class="hljs-string">"font-semibold"</span>&gt; Theme: &lt;/span&gt;
                    &lt;Select
                        defaultSelectValue={theme}
                        selectOptions={themeArray}
                        handleSelectChange={handleSelectTheme}
                    /&gt;
                &lt;/div&gt;
                &lt;/div&gt;
            &lt;/ul&gt;
        &lt;/nav&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<p>Your application should now have a select in the navigation menu that successfully toggles between dark and light themes while defaulting to the system preferences when set to "System". </p>
<p>It should look like so:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-2024-03-09-at-13.21.38.png" alt="Image" width="600" height="400" loading="lazy">
<em>Taskwrite complete interface and functionalities</em></p>
<p>And Taskwrite is complete! You have successfully built a task manager application that is AI-enhanced, voice-enabled, searchable and sortable using React and Appwrite.</p>
<h2 id="heading-notes">Notes</h2>
<p>Appwrite recently announced some new features that would greatly simplify the search functionality above but, at the time of writing, these changes were not rolled out to their cloud offering. </p>
<p>The application could be further simplified by using state management solutions and this will be added to it in subsequent articles.</p>
<p>The application is live <a target="_blank" href="https://taskwrite.netlify.app/">here</a>.</p>
<h2 id="heading-limitations">Limitations</h2>
<p>The following are some known limitations and issues with this application:</p>
<ul>
<li>The navigation menu is not responsive</li>
<li>The application has no tests written</li>
<li>The permissions set for Appwrite are permissive and not recommended for production environments</li>
<li>The application could leverage <a target="_blank" href="https://appwrite.io/docs/apis/realtime">Appwrites' Realtime</a> capabilites for a smoother experience</li>
<li>The application could do with push notifications to remind the user when the task due date is coming up</li>
</ul>
<p>That said, the application is going to continue being improved and worked on. You can follow along with that on <a target="_blank" href="https://github.com/FatumaA/taskwrite">GitHub</a>. All contributions and improvements on the codebase are welcome. Please star the repository while you are at it.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up Authentication in Your Apps with Supabase Auth ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you'll learn the basic key concepts that'll help you grasp how authentication and authorization work.  You'll start by learning what authentication and authorization are, and then learn how to implement authentication in your applica... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/set-up-authentication-in-apps-with-supabase/</link>
                <guid isPermaLink="false">66b999acd9d170feecefbbc7</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Mon, 29 Jan 2024 10:53:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Group-3--9-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you'll learn the basic key concepts that'll help you grasp how authentication and authorization work. </p>
<p>You'll start by learning what authentication and authorization are, and then learn how to implement authentication in your applications using Supabase auth.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
    <li>
        <a href="#prerequisites">
            Prerequisites
        </a>
    </li>
    <li>
        <a href="#what-is-authentication-and-authorization">
            What Is Authentication and Authorization?
        </a>
    </li>
    <li>
        <a href="#how-does-authentication-work">
            How Does Authentication Work?
        </a>
    </li>
    <li>
        <a href="#session-management-with-tokens-secrets-and-cookies">
            Session Management with Tokens, Secrets, and Cookies
        </a>
    </li>
    <li>
        <a href="#types-of-authentication-factors">
            Types of Authentication Factors
        </a>
    </li>
    <li>
        <a href="#common-authentication-strategies">
            Common Authentication Strategies
        </a>
        <ul>
            <li>
                <a href="#password-based-authentication">
                     Password-based Authentication
                </a>
            </li>
            <li>
                <a href="#password-less-authentication">
                     Password-less Authentication
                </a>
            </li>
            <li>
                <a href="#two-factor-authentication-2fa-">
                     Two-Factor Authentication (2FA)
                </a>
            </li>
            <li>
                <a href="#multi-factor-authentication-mfa-">
                     Multi-Factor Authentication (MFA)
                </a>
            </li>
              <li>
                <a href="#oauth-2-0-and-social-authentication">
                    OAuth 2.0 and Social Authentication
                </a>
            </li>
              <li>
                <a href="#sso-and-saml">
                    SSO and SAML
                </a>
            </li>
        </ul>
    </li>
    <li>
        <a href="#authentication-and-security">
            Authentication and Security
        </a>
    </li>
    <li>
        <a href="#supabase-and-supabase-authentication-service">
            Supabase and Supabase Authentication Service
        </a>
    </li>
    <li>
        <a href="#how-to-use-supabase-auth">
            How to use Supabase Auth
        </a>
        <ul>
            <li>
                <a href="#the-api">
                    The API
                </a>
            </li>
            <li>
                <a href="#sdks">
                    SDKs
                </a>
            </li>
            <li>
                <a href="#auth-ui-helpers">
                    Auth UI Helpers
                </a>
            </li>
        </ul>

<p>    </p></li>
     <li>
         <a href="#summary">
             Summary
         </a>
    </li>
    <li>
        <a href="#resources">
            Resources
        </a>
    </li>
</ul><p></p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You'll need the following to make the most out of this article:</p>
<ul>
<li>Basic programming knowledge.</li>
<li><a target="_blank" href="https://supabase.com/">A Supabase project</a> to follow along.</li>
<li>And a text editor to try out the example code snippets.</li>
</ul>
<h2 id="heading-what-is-authentication-and-authorization">What Is Authentication and Authorization?</h2>
<p>In simple terms, authentication is the process of a user identifying themselves to a system and the system confirming that the user is who they claim to be.   </p>
<p>On the other hand, authorization is the process of the system determining which parts of the application the user is allowed to view or interact with, and which parts the user is not allowed to access. </p>
<h2 id="heading-how-does-authentication-work">How Does Authentication Work?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-135.png" alt="Image" width="600" height="400" loading="lazy">
<em>A flowchart depicting the user authentication process</em></p>
<p>The first time a user interacts with a system, they will be requested to register. Typically, the user will provide a piece of information and a secret that is meant to be known only by them and the system. This is the registration part of the authentication process.</p>
<p>The next time the user interacts with the same system, they will be required to provide the identifying information along with the previously defined secret in order to verify their identity. </p>
<p>The device the user initiates the interaction from is the client and the system is the server. Once the system verifies the user, it sends over some information to the client about the user. </p>
<p>Because this process takes time and requires some action from the user, the client will store this information and send it back to the system whenever the user needs to access the system. This reduces friction by not requiring the user to actively re-authenticate every single time. This creates a user session.</p>
<h2 id="heading-session-management-with-tokens-secrets-and-cookies">Session Management with Tokens, Secrets, and Cookies</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-136.png" alt="Image" width="600" height="400" loading="lazy">
<em>A sequence diagram showing session management in a client-server architecture</em></p>
<p>The server can pass the user's information to the client in two ways – through tokens or session ids.</p>
<p>In the case of tokens, the server generates a signed token and passes it to the client. This token is typically a JWT and may contain information regarding the user. The client will store this token and send it back to the server every time the user makes a request. </p>
<p>The server is able to verify the integrity of the token because it signed it. This is referred to as stateless authentication because the token is self contained and the server does not need to store session data in a database or cache. </p>
<p>In the case of cookies, the server creates a record of the user session in a database or a cache that will include a session ID. The server send this session ID to the client.</p>
<p>The client stores this session ID in a cookie and sends it back to the server whenever the user makes a request. The session ID is a random string that acts as pointer to the actual user record in the database. </p>
<p>When the server receives this cookie, it matches the session ID it contains to its session records, and then matches that record to the user data in the database. This is referred to as stateful authentication because a database look-up is needed.</p>
<h2 id="heading-types-of-authentication-factors">Types of Authentication Factors</h2>
<p>An authentication factor refers to a type of credential that can be used to verify a user's identity. There are 3 factors typically used in the authentication process, and they are:</p>
<ol>
<li>Something you know: an examples is a password.</li>
<li>Something you have: an example is a token sent to your phone.</li>
<li>Something you are: an example is your fingerprint.</li>
</ol>
<h2 id="heading-common-authentication-strategies">Common Authentication Strategies</h2>
<p>Authentication strategies refer to the processes used to verify a user. Different types of authentication strategies include:</p>
<h3 id="heading-password-based-authentication">Password-based Authentication</h3>
<p>This refers to the traditional way of users identifying themselves by providing a text-based secret that is user defined. Typically, the system handles the entire process on its servers and is responsible for security and reliability.</p>
<h3 id="heading-password-less-authentication">Password-less Authentication</h3>
<p>In this approach, the system verifies the user’s identity without requiring user defined passwords. The system will, instead, generate a one time password (OTP) and send to the user. This OTP is then used in place of a password to gain access to the system. Examples include magic links, where the system sends a code to the user’s email.</p>
<h3 id="heading-two-factor-authentication-2fa">Two-Factor Authentication (2FA)</h3>
<p>The system attempts to verify the user is who they claim to be by requiring an extra piece of information after the primary authentication has checked out. </p>
<p>This can be an OTP sent to the user via email or SMS, or it can be by requiring the users' biometric information before the system grants access.</p>
<h3 id="heading-multi-factor-authentication-mfa">Multi-Factor Authentication (MFA)</h3>
<p>This is similar to 2FA, except that the system will use more than one extra method to verify the user’s identity. The extra methods or factors used in both MFA and 2FA are usually external to the system, such as an SMS requiring a phone.</p>
<h3 id="heading-oauth-20-and-social-authentication">OAuth 2.0 and Social Authentication</h3>
<p>OAuth is an authorization framework that allows clients to access information from an external server on the user's behalf. The external server prompts the user for permission to share the requested resources with the client. </p>
<p>After user permits the action, the external server issues an access token to the client. </p>
<p>The client then gives this access token to the original server, which verifies the token's validity and manages access to the requested resources. OAuth 2.0 is the latest version of OAuth and is the more widely used framework. </p>
<p>OAuth 2.0 extends support for non-browser based systems. Social Authentication is based on OAuth 2.0 but in this case, the external server that the client redirects the user to is typically a social media platform. This is the type of authentication process carried out whenever you see a "Continue with Twitter/X" button on an authentication page. </p>
<h3 id="heading-sso-and-saml">SSO and SAML</h3>
<p>SAML stands for Security Assertion Markup Language. It is a standard for passing authentication and authorization information between systems. One system acts as the requesting system or the service provider (SP) and the other system holds the requested information or acts as the Identity Provider (IdP). </p>
<p>On receiving this request, the identity provider will generate some statements in SAML form that contains some user information. The service provider then uses this information to decide how to handle the user in relation with its protected resources. </p>
<p>SSO refers to Single Sign On. This is an authentication strategy that lets users sign in through one system/application that then lets them access multiple applications within the same network. </p>
<p>This improves the user experience by not requiring the user to log in to different related applications. An example of this is Google workspace. You don't need to log into Docs separately if you are already logged into your Gmail account. SSO is facilitated by SAML as SAML provides a standard authentication mechanism and allows different systems to trust each other. </p>
<h2 id="heading-authentication-and-security">Authentication and Security</h2>
<p>Authentication involves handling, moving, and storing sensitive user information in relation to protected server resources. This makes security and best practices an important aspect of an authentication system.</p>
<p>There are some basic steps you can take to greatly increase the security of your authentication systems. These include:</p>
<ul>
<li>Enforcing stronger passwords.</li>
<li>Requiring the user to register an extra factor to enable 2FA.</li>
<li>Encrypting sensitive data as it is being transferred via HTTPS.</li>
<li>Storing passwords in an encrypted manner.</li>
<li>Using standard authentication frameworks like OAuth 2.0.</li>
</ul>
<p>There are certain compliances that your system should consider when handling sensitive user data beyond specific authentication information. This is even more so if operating in certain countries or handling enterprise applications. These compliances include:</p>
<ul>
<li><strong>GDPR</strong>: This compliance enforces standards around data handling including confidentiality and integrity.</li>
<li><strong>HIPAA</strong>: This compliance applies to medical data. It expects high levels of integrity.</li>
<li><strong>SOC</strong>: This is a framework more generally required of cloud technologies. It is based on the American Institute of CPAs and covers aspects of privacy, security, availability, integrity and confidentiality.</li>
</ul>
<p>Keeping all this in mind, you will find that it is often easier to use dedicated authentication services for your applications instead of rolling out your own auth.</p>
<p>There are lots of options for this, including dedicated authentication services such as Clerk and Auth0, and Backend-as-a-Service such as Supabase and Firebase. In this case, let's take a look at the Supabase authentication offering.</p>
<h2 id="heading-supabase-and-supabase-authentication-service">Supabase and Supabase Authentication Service</h2>
<p>Supabase is an open source Backend as a Service (BaaS) platform that makes developing a backend for your applications very easy and fast. It is based on open source technologies and actively supports the open source ecosystem. </p>
<p>Supabase offers common services that most backend applications will require. These services are:</p>
<ul>
<li>Database: This is a fully featured Postgres database.</li>
<li>Authentication: This is an enterprise ready authentication service that is based on a fork of the goTrue server.</li>
<li>Realtime: This is an API that adds the ability to use real-time capabilities in your applications.</li>
<li>Storage: This is a storage service which is an s3 wrapper.</li>
<li>Edge Functions: These are serverless functions that run on the edge. Powered by the Deno runtime.</li>
<li>Vector: This is a vector database that makes it easier to work with embeddings in your AI applications.</li>
</ul>
<p>Supabase is SOC2, HIPAA and GDPR compliant, self-host-able and open source. Furthermore, their authentication service exposes many strategies, giving you full control over your data and can be used independently of their other offerings. This and their auto documenting API makes it a very good choice for your applications. </p>
<h3 id="heading-how-to-use-supabase-auth">How to use Supabase Auth</h3>
<p>The first step is to set up your <a target="_blank" href="https://app.supabase.com/">Supabase project</a>'s auth settings. You can enable the exact authentication methods you want to use via the settings. There are three ways you can start using Supabase auth in your project:</p>
<h4 id="heading-the-api">The API</h4>
<p>You can directly use the authentication service in your applications by calling the auth endpoint and passing the user information to it. You can also get, update and delete your users.   </p>
<p>The API is automatically available when you create a project via the Supabase console and can be called like so: </p>
<pre><code class="lang-javascript"><span class="hljs-comment">//This will return an object containing an access token, the newly created user data and other metadata</span>
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://&lt;your-project-ref&gt;/auth/v1/signup"</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
  <span class="hljs-attr">headers</span>: {
    <span class="hljs-attr">authorization</span>: <span class="hljs-string">"Bearer YOUR_SUPABASE_KEY"</span>,
    <span class="hljs-string">"content-type"</span>: <span class="hljs-string">"application/json"</span>,
  },
  <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
    <span class="hljs-attr">email</span>: <span class="hljs-string">"user-email"</span>,
    <span class="hljs-attr">password</span>: <span class="hljs-string">"user-password"</span>,
  }),
});
</code></pre>
<h4 id="heading-sdks">SDKs</h4>
<p>Supabase offers a few SDKs (software development kits) in different programming languages meant to make interacting with your Supabase project straightforward. Languages officially supported include Dart and JavaScript, with Python and others having strong community support. </p>
<p>The procedure for getting started involves adding the SDK as a dependency, then connecting your application to your Supabase project. </p>
<p>In the case of the JavaScript SDK, this would look something like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Install via npm:</span>
npm install @supabase/supabase-js

<span class="hljs-comment">// or add cdn links: </span>
&lt;script src=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"</span>&gt;&lt;/script&gt;
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">//Then initialize </span>
Supabaseimport { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>

<span class="hljs-keyword">const</span> supabaseUrl = <span class="hljs-string">'https://&lt;your-project-ref&gt;.supabase.co'</span>
<span class="hljs-keyword">const</span> supabaseKey = process.env.SUPABASE_ANON_KEY
<span class="hljs-keyword">const</span> supabase = createClient(supabaseUrl, supabaseKey)
</code></pre>
<p>Then you can access the authentication methods under the auth namespace like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> supabase.auth.signUp({
  <span class="hljs-attr">email</span>: <span class="hljs-string">'user email'</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">'user password'</span>,
})
</code></pre>
<h4 id="heading-auth-ui-helpers">Auth UI Helpers</h4>
<p>Supabase provides helper libraries to make authentication using their service even easier. These libraries provide customizable login screens that support magic links, password based and social login strategies. </p>
<p>Currently, the libraries are available for JavaScript and Flutter. Supabase also provides a separate SSR (Server Side Rendering) package for applications that use server side frameworks and require a Supabase access token to be available to them.</p>
<p>To start using React Auth UI, as an example, first you need to install the dependencies as shown below:</p>
<pre><code class="lang-bash">npm install @supabase/supabase-js @supabase/auth-ui-react 
@supabase/auth-ui-shared
</code></pre>
<p>Then you can start using the library after initializing Supabase as in the SDK example above. Here is some sample code that shows how to use the auth UI library in a React application:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> { Auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"@supabase/auth-ui-react"</span>;
<span class="hljs-keyword">import</span> { ThemeSupa } <span class="hljs-keyword">from</span> <span class="hljs-string">"@supabase/auth-ui-shared"</span>;
<span class="hljs-keyword">import</span> { supa } <span class="hljs-keyword">from</span> <span class="hljs-string">"../constants"</span>;

<span class="hljs-keyword">const</span> AuthUi = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> navigate = useNavigate();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> {
      <span class="hljs-attr">data</span>: { subscription },
    } = supa.auth.onAuthStateChange(<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (event === <span class="hljs-string">"SIGNED_IN"</span>) {
        navigate(<span class="hljs-string">"/authenticated"</span>);
      }
    });

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> subscription.unsubscribe();
  }, [navigate]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Auth</span>
      <span class="hljs-attr">supabaseClient</span>=<span class="hljs-string">{supa}</span>
      <span class="hljs-attr">providers</span>=<span class="hljs-string">{[</span>"<span class="hljs-attr">google</span>", "<span class="hljs-attr">github</span>", "<span class="hljs-attr">apple</span>", "<span class="hljs-attr">discord</span>"]}
      // <span class="hljs-attr">controls</span> <span class="hljs-attr">whether</span> <span class="hljs-attr">to</span> <span class="hljs-attr">display</span> <span class="hljs-attr">only</span> <span class="hljs-attr">social</span> <span class="hljs-attr">providers</span>
      // <span class="hljs-attr">onlyThirdPartyProviders</span>
      <span class="hljs-attr">redirectTo</span>=<span class="hljs-string">"http://localhost:3000/authenticated"</span>
      // <span class="hljs-attr">comes</span> <span class="hljs-attr">with</span> <span class="hljs-attr">preconfigured</span> <span class="hljs-attr">themes</span>, <span class="hljs-attr">can</span> <span class="hljs-attr">add</span> <span class="hljs-attr">custom</span> <span class="hljs-attr">themes</span>
      <span class="hljs-attr">appearance</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">theme:</span> <span class="hljs-attr">ThemeSupa</span> }}
      // <span class="hljs-attr">controls</span> <span class="hljs-attr">how</span> <span class="hljs-attr">to</span> <span class="hljs-attr">display</span> <span class="hljs-attr">the</span> <span class="hljs-attr">social</span> <span class="hljs-attr">provider</span> <span class="hljs-attr">icons</span>
      <span class="hljs-attr">socialLayout</span>=<span class="hljs-string">"horizontal"</span>
    /&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AuthUi;
</code></pre>
<p>This would display the following form on screen: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-26-at-18.43.39.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-summary">Summary</h2>
<p>Authentication is the process of the user identifying themselves and the server verifying this identity while authorization is the system determining what access the user should have to the resources and limiting the user accordingly.</p>
<p>After the server authenticates the user, it will pass the user information in the form of either a token or session ID within a cookie. </p>
<p>The information will be passed back and forth between the client and server whenever the user needs certain access until they expire or the user terminates the cycle by logging out or deleting their account.</p>
<p>This process of user verification occurs by employing certain factors of authentication. For example, one system may only require a password while another requires a password and a code sent to the users phone number.</p>
<p>Your authentication system can allow multiple strategies of authentication using any of the three auth factors.</p>
<p>Supabase is an excellent option if you opt not to handle your own auth.</p>
<p>Supabase auth can be accessed via the API, the SDKs and the Auth libraries. Supabase maintains an SSR package for server side frameworks.</p>
<h2 id="heading-resources">Resources</h2>
<p>The following resources are helpful further reading. They offer more explanations on authentication and authorization, as well as Supabase specific documentation.</p>
<ul>
<li><a target="_blank" href="https://www.upguard.com/blog/oauth#:~:text=OAuth%201.0%20has%20a%20consumer,resource%20server%2C%20and%20resource%20owner.">An indepth explanation of OAuth</a></li>
<li><a target="_blank" href="https://supabase.com/security">Supabase security</a></li>
<li><a target="_blank" href="https://supabase.com/docs/guides/auth">Supabase docs on authentication</a></li>
<li><a target="_blank" href="https://supabase.com/docs/guides/auth/auth-helpers/auth-ui">Auth UI docs page</a></li>
<li><a target="_blank" href="https://supabase.com/docs/guides/auth/auth-helpers">Supabase on auth helpers and SSR</a></li>
<li><a target="_blank" href="https://supabase.com/docs/guides/auth/sso/auth-sso-saml">On SSO, SAML and enterprise auth</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
