<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Riccardo Perra</title>
    <description>The latest articles on Forem by Riccardo Perra (@riccardoperra).</description>
    <link>https://forem.com/riccardoperra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F821834%2Fb8e9ae83-ba18-4e47-95d2-3284e936a1a1.jpg</url>
      <title>Forem: Riccardo Perra</title>
      <link>https://forem.com/riccardoperra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/riccardoperra"/>
    <language>en</language>
    <item>
      <title>SpecFlow - Integrating Hanko and Supabase in a Solid.js client-side application</title>
      <dc:creator>Riccardo Perra</dc:creator>
      <pubDate>Sat, 28 Oct 2023 19:22:37 +0000</pubDate>
      <link>https://forem.com/riccardoperra/specflow-integrating-hanko-and-supabase-in-a-solidjs-client-side-application-550m</link>
      <guid>https://forem.com/riccardoperra/specflow-integrating-hanko-and-supabase-in-a-solidjs-client-side-application-550m</guid>
      <description>&lt;p&gt;Lately at work I've had the need to write technical documentation and have to generate different types of diagrams and share them. &lt;/p&gt;

&lt;p&gt;I've always used the mermaid live editor but found it rather inconvenient, so I took advantage of &lt;a href="https://www.hanko.io/hackathon" rel="noopener noreferrer"&gt;hanko's hackathon&lt;/a&gt; to build SpecFlow: a tool which allows you to centralize all your project specs and documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Hanko?
&lt;/h3&gt;

&lt;p&gt;From their &lt;a href="https://github.com/teamhanko/hanko" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hanko is an open-source authentication and user management solution with a focus on moving the login beyond passwords while being 100% deployable today.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built for passkeys as introduced by Apple, Google, and Microsoft&lt;/li&gt;
&lt;li&gt;Fast integration with Hanko Elements web components (login box and user profile)&lt;/li&gt;
&lt;li&gt;API-first, small footprint, cloud-native&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a really interesting solution and is probably now the one that allows you to integrate passkeys in the easiest/fastest way possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  About SpecFlow
&lt;/h3&gt;

&lt;p&gt;SpecFlow is an open-source tool available on GitHub and hosted on Netlify which uses Hanko for authenticate their users. Its'currently an MVP.&lt;/p&gt;

&lt;p&gt;Repository &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/riccardoperra" rel="noopener noreferrer"&gt;
        riccardoperra
      &lt;/a&gt; / &lt;a href="https://github.com/riccardoperra/specflow" rel="noopener noreferrer"&gt;
        specflow
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Write markdown documentation and generate mermaid diagrams with ease - Made with Hanko, SolidJS and Supabase.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SpecFlow&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://app.netlify.com/sites/specflow/deploys" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b3bf0772e2facfb69367c3a2bede601669f5616cd3ca697411a2c35f92f752b9/68747470733a2f2f6170692e6e65746c6966792e636f6d2f6170692f76312f6261646765732f33303138363937662d313739362d346139652d393537382d3832623561663536613139332f6465706c6f792d737461747573" alt="Netlify Status"&gt;&lt;/a&gt;
&lt;a href="https://www.hanko.io/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9fb85f8bc4c950b2de99989192334b3ea552fbb8a1e9095e7cdac5c4cd13b3f2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c74253230776974682d48616e6b6f2d726564" alt="Made with Hanko"&gt;&lt;/a&gt;
&lt;a href="https://github.com/solidjs/solid" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ad527917c9cd8849b5d27e1d1049ab3f37c6222192bf1485a3d16486a7f78a1a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c74253230776974682d536f6c69644a532d626c7565" alt="Made with SolidJS"&gt;&lt;/a&gt;
&lt;a href="https://github.com/seek-oss/vanilla-extract" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f29bfd55447e35357b2b4bf605bf3da031b01e1351e4723aa929b1c72b3e6c6e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c74253230776974682d56616e696c6c61253230457874726163742d666636396234" alt="Made with Vanilla Extract"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;a href="https://supabase.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/be1cb0378521d97f12a1c838abda01da548af5dc2e5b19a279317364db23fdd7/68747470733a2f2f73757061626173652e636f6d2f62616467652d6d6164652d776974682d73757061626173652d6461726b2e737667" alt="Made with Supabase"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="markdown-alert markdown-alert-note"&gt;
&lt;p class="markdown-alert-title"&gt;Note&lt;/p&gt;
&lt;p&gt;SpecFlow is an open-source tool (MIT License) made for the &lt;a href="https://www.hanko.io/hackathon" rel="nofollow noopener noreferrer"&gt;Hanko hackathon&lt;/a&gt;
It's an MVP made in less than two weeks far away to be a complete product, born with the aim of testing integrations
and interactions between new tech/libraries, and to better understand the authentication flow by also integrating
passkeys.&lt;/p&gt;
&lt;p&gt;More in detail, in this project I experiment with Hanko's authentication by integrating it with a third party system
like supabase, the latter used trying to take advantage of the generated types, RLS policies, realtime and edge
functions.&lt;/p&gt;
&lt;p&gt;Furthermore, I made a small use of OpenAI API via edge functions to generate code directly from a user-defined
prompt.&lt;/p&gt;
&lt;p&gt;This project it's also a way to improve my &lt;a href="https://github.com/riccardoperra/codeui" rel="noopener noreferrer"&gt;UI Kit library&lt;/a&gt; based
on &lt;a href="https://github.com/kobaltedev/kobalte" rel="noopener noreferrer"&gt;Kobalte&lt;/a&gt; and &lt;a href="https://vanilla-extract.style/" rel="nofollow noopener noreferrer"&gt;Vanilla Extract&lt;/a&gt; that I'm
working on, initially born to be the CodeImage design system.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/riccardoperra/specflow./docs/1.png"&gt;&lt;img alt="Homepage of SpecFlow" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Friccardoperra%2Fspecflow.%2Fdocs%2F1.png" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Read the integration post:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/riccardoperra/specflow-integrating-hanko-and-supabase-in-a-solidjs-client-side-application-550m" rel="nofollow"&gt;https://dev.to/riccardoperra/specflow-integrating-hanko-and-supabase-in-a-solidjs-client-side-application-550m&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;💡 Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✅…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/riccardoperra/specflow" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;App&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://specflow.netlify.app" rel="noopener noreferrer"&gt;
      specflow.netlify.app
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;It hasn't obviously created to replace products currently existing on the market, but it's a way for me to try new technologies and see how they perform together. &lt;/p&gt;

&lt;p&gt;SpecFlow is mainly designed to to succeed in these tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Secure your data so that it is only accessible if authenticated&lt;/li&gt;
&lt;li&gt;✅ Write project notes, requirement and specifications using a Markdown-like interface.&lt;/li&gt;
&lt;li&gt;✅ Write and export diagrams such as sequence diagrams, ER, Mind maps etc. complaint to mermaid syntax.&lt;/li&gt;
&lt;li&gt;✅ Make a small use of AI to see how to integrate OpenAI into an application&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💻 Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Login page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthxk25gix6oiqvsxmk5f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthxk25gix6oiqvsxmk5f.gif" alt="Login Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Passcode challenge&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwonqka24idc8qbqresla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwonqka24idc8qbqresla.png" alt="Passcode challenge login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dashboard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkpjht042luqkbk4h511.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkpjht042luqkbk4h511.png" alt="Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Project page - Markdown editor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2til35hhn9d296slf5k5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2til35hhn9d296slf5k5.png" alt="Project page - Markdown editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Project page - Diagrams editor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm2yjix8sxmvz8l4tdzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm2yjix8sxmvz8l4tdzj.png" alt="Project page - Diagrams editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Project page - Exports a diagram&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy2lgatwpcujpqketzp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy2lgatwpcujpqketzp9.png" alt="Project page - Exports a diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Profile page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fil0zld6389izzk52r2d8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fil0zld6389izzk52r2d8.png" alt="Profile page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 Tech stack
&lt;/h2&gt;

&lt;p&gt;SpecFlow is a client-side SPA built using &lt;a href="https://github.com/solidjs/solid" rel="noopener noreferrer"&gt;SolidJS&lt;/a&gt; as a front-end framework, &lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; for database, realtime, edge functions...and &lt;a href="https://hanko.io" rel="noopener noreferrer"&gt;Hanko&lt;/a&gt; to handle the authentication.&lt;/p&gt;

&lt;p&gt;All styles have been made with Vanilla-Extract and Tailwind integrating a design system I used for &lt;a href="https://github.com/riccardoperra/codeimage" rel="noopener noreferrer"&gt;CodeImage&lt;/a&gt; which is a wrapper of &lt;a href="https://kobalte.dev/docs/core/overview/introduction" rel="noopener noreferrer"&gt;Kobalte&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To manage the editor I used &lt;a href="https://codemirror.net/" rel="noopener noreferrer"&gt;CodeMirror6&lt;/a&gt; and &lt;a href="https://tiptap.dev" rel="noopener noreferrer"&gt;Tiptap&lt;/a&gt;, the latter used to preview the markdown having the chance one day to also integrate a wysiwyg editor.&lt;/p&gt;

&lt;p&gt;For a better development experience, I also integrated &lt;a href="https://mswjs.io/" rel="noopener noreferrer"&gt;MockServiceWorker&lt;/a&gt; in order to be able to mock the entire auth flow and develop by integrating Hanko components without disturbing the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 Integrating Hanko in a SolidJS application
&lt;/h2&gt;

&lt;p&gt;Hanko employes did a really great job regarding the documentation: everything is extremely clear and ready to use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.hanko.io/introduction" rel="noopener noreferrer"&gt;https://docs.hanko.io/introduction&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently there isn't a dedicated guide for SolidJS, then I've followed a bit the documentation of react/angular docs, but the steps for Solid are almost identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating Hanko in a SPA
&lt;/h3&gt;

&lt;p&gt;First of all, we need to install the &lt;code&gt;@teamhanko/hanko-elements&lt;/code&gt; package, which will contains the Auth and Profile web components which we will use later to manage authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @teamhanko/hanko-elements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, we should retrieve the Hanko API URL from their cloud console and place it in our .env file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VITE_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can integrate the auth web component by importing the &lt;code&gt;register&lt;/code&gt; function in order to register &lt;code&gt;&amp;lt;hanko-auth&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;hanko-profile&amp;gt;&lt;/code&gt; with the browser CustomElementRegistry.&lt;/p&gt;

&lt;p&gt;Note that unlike react, we need to overwrite the SolidJS namespace to add hanko's custom elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solid-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@teamhanko/hanko-elements&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hankoApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_HANKO_API_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HankoAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// handle error&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;hanko&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;JsxIntrinsicElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IntrinsicElements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solid-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IntrinsicElements&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hanko-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsxIntrinsicElements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hanko-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once done, we are able to use our component in order to authenticate. The same steps could be done for the Profile page.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 How's Hanko can be leveraged with Supabase
&lt;/h2&gt;

&lt;p&gt;We have seen that in a few lines of code you can integrate Hanko into your application, but now comes the challenging part: the integration with supabase.&lt;/p&gt;

&lt;p&gt;Supabase Database comes with a useful &lt;a href="https://supabase.com/docs/guides/auth/row-level-security" rel="noopener noreferrer"&gt;RLS policy&lt;/a&gt; which&lt;br&gt;
allows to restrict the data access using custom rules (postgres policies).&lt;/p&gt;

&lt;p&gt;In a traditional supabase application we've could used it's authentication system and make use of the &lt;code&gt;Auth&lt;/code&gt; client of their library.&lt;/p&gt;

&lt;p&gt;In SpecFlow, Hanko &lt;strong&gt;is replacing&lt;/strong&gt; supabase auth, so we need to somehow make supabase understand who is making the requests in order to apply all related policies. For example we need that &lt;strong&gt;each user can view or update only the entities he own&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To authenticate these requests, we can sign our own JWT that contains the necessary info for supabase in order to apply the policies.&lt;/p&gt;

&lt;p&gt;In the steps following we will cover the creation of a JWT that contains hanko's user_id signing it with the supabase private key, and then integration with supabase database and the UI.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Creating a postgres function for supabase to retrieve a &lt;code&gt;user_id&lt;/code&gt; from a given jwt.
&lt;/h3&gt;

&lt;p&gt;In The first step we'll create a postgres function which will extract the hanko &lt;code&gt;user_id&lt;/code&gt; from a JWT in order to let supabase knows which user is authenticated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hanko_user_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;nullif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'request.jwt.claims'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'userId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;stable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Creating a postgres policy to restrict our data
&lt;/h3&gt;

&lt;p&gt;We can now define a new policy in order to restrict our data. The following example uses a &lt;code&gt;todos&lt;/code&gt; table as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Allows all operations"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;PERMISSIVE&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;
&lt;span class="c1"&gt;-- ✅ Use our user_id function to get hanko user_id from jwt&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hanko_user_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hanko_user_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Writing the business logic to sign our custom JWT
&lt;/h3&gt;

&lt;p&gt;The signing of the JWT &lt;strong&gt;must always be done server-side&lt;/strong&gt; in order to keep our private keys safe.&lt;/p&gt;

&lt;p&gt;Since we are using supabase we can take advantage of their &lt;code&gt;edge-functions&lt;/code&gt; (&lt;a href="https://supabase.com/docs/guides/functions" rel="noopener noreferrer"&gt;https://supabase.com/docs/guides/functions&lt;/a&gt;) that runs on Deno in order to build the JWT. &lt;/p&gt;

&lt;p&gt;Another solution would have been for example to have a custom backend or to use a full stack front-end framework like &lt;a href="https://start.solidjs.com/getting-started/what-is-solidstart" rel="noopener noreferrer"&gt;Solid Start&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can read more info about jwt &lt;a href="https://jwt.io/introduction" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We've to carry out 3 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieves Hanko jwks configurations. JWKS is a set of keys containing the public keys used to verify any JSON Web Token&lt;/li&gt;
&lt;li&gt;Thanks to the JWKS we can verify the Hanko JWT sent from the UI request in order to be sure it's a valid token.&lt;/li&gt;
&lt;li&gt;Signing a new JWT valid for supabase including in the payload the hanko &lt;code&gt;user_id&lt;/code&gt; claim.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jose&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/x/jose@v4.9.0/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hankoApiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HANKO_API_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. ✅ Retrieves Hanko JWKS configuration&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JWKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRemoteJWKSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hankoApiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.well-known/jwks.json`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. ✅ Verify Hanko token&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;jose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jwtVerify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JWKS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. ✅ Sign new token for supabase using it's private key&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PRIVATE_KEY_SUPABASE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supabaseToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;jose&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SignJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExpirationTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProtectedHeader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HS256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the edge function is deployed, we intercept the event once we are authenticated.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Calling our API from the front-end
&lt;/h3&gt;

&lt;p&gt;The Hanko package is exporting an &lt;code&gt;Hanko&lt;/code&gt; class which is the client that allows us to retrieve the session info and listen for some specific events. &lt;/p&gt;

&lt;p&gt;In our case we can take advantage of the &lt;code&gt;onAuthFlowCompleted()&lt;/code&gt; event, which will be triggered once we complete the login flow.&lt;/p&gt;

&lt;p&gt;Inside that event we must invoke our edge function passing the hanko JWT, then we should patch supabase headers in order to put the right Authorization Bearer token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Hanko&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@teamhanko/hanko-elements&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supabase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_CLIENT_SUPABASE_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_CLIENT_SUPABASE_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hankoApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_HANKO_API_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supabaseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supabaseKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hanko&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;hanko&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAuthFlowCompleted&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hanko&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;our-function-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;patchSupabaseRestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;patchSupabaseRestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ✅ Set functions auth in order to put the jwt token &lt;/span&gt;
  &lt;span class="c1"&gt;// for edge functions which need authentication&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;supabaseKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ✅ Patching rest headers that will be used &lt;/span&gt;
    &lt;span class="c1"&gt;// for querying the database through rest.&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;supabaseKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's done! Now all the calls that will use the supabase rest client to query the db will contain our token containing the user_id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="err"&gt;✅&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt; &lt;span class="nx"&gt;our&lt;/span&gt; &lt;span class="nx"&gt;custom&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;
&lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🖌️ Styling Hanko Elements
&lt;/h2&gt;

&lt;p&gt;Hanko Elements exports two useful web components that allows to handle the auth flow and the user profile.&lt;/p&gt;

&lt;p&gt;There are several ways to customize&lt;br&gt;
them (&lt;a href="https://github.com/teamhanko/hanko/blob/main/frontend/elements/README.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For SpecFlow I followed two approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vanilla-extract.style/" rel="noopener noreferrer"&gt;Vanilla-Extract&lt;/a&gt; to verride css variables and define all styles via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part" rel="noopener noreferrer"&gt;::part attribute&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Plain CSS to override some internal elements inside the shadow dom. This is the case for the accordion component in the profile page. Note that I didn't disable the
shadow dom since the hanko authors don't recommend it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried to follow my UI Kit tokens in order to make something that fits good inside this application.&lt;/p&gt;

&lt;p&gt;First, I made a solid component for each web component in order to decouple it's logic, and to extend some behaviors and&lt;br&gt;
the JSX interface, since solid has its own.&lt;/p&gt;

&lt;p&gt;Each web component will have attached the custom classes generated by vanilla-extract, and a custom &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag inside&lt;br&gt;
the shadow dom which I add once the component is mounted to the dom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/auth/HankoAuth.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HankoAuth.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solid-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;overrides&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./hanko-auth-overrides.css?raw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Get the css text from the file.&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HankoAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hankoAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styleElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;styleElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;hankoAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;styleElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hanko&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;auth&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hankoAuth&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;JsxIntrinsicElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IntrinsicElements&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solid-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IntrinsicElements&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hanko-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsxIntrinsicElements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hanko-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated class from vanilla-extract will contain all custom vars and base styles defined through a base class, and&lt;br&gt;
the custom ones needed only for the auth/profile component. Basically, I made the base styles for the "Layout"&lt;br&gt;
components like buttons, forms, headlines etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vanilla-extract/css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;themeTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;themeVars&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@codeui/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hankoTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hankoVars&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTheme&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;brandColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;themeVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dangerColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#500f1c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;buttonCriticalColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;themeVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;containerPadding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;themeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;themeVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreground&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Other vars...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;hankoTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Overrides hanko vars&lt;/span&gt;
      &lt;span class="c1"&gt;// https://github.com/teamhanko/hanko/tree/main/frontend/elements#css-variables&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hankoVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--brand-color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hankoVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brandColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Other vars...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Base layout styles&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;::part(error)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hankoVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dangerColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hankoVars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;themeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;themeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// Button styles&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;::part(button)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transitions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// Input styles&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;::part(input text-input), &amp;amp;::part(input passcode-input)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;themeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// Other styles...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hankoAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// custom styles for hanko auth wc...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hankoProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// custom styles for hanko profile wc...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The override file will contain only some particular styles that cannot be accessed outside the shadow dom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/components/Auth/hanko-profile-overrides.css */&lt;/span&gt;

&lt;span class="nc"&gt;.hanko_paragraph&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="nc"&gt;.hanko_headline&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--paragraph-inner-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthxk25gix6oiqvsxmk5f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthxk25gix6oiqvsxmk5f.gif" alt="Login Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Conclusion
&lt;/h2&gt;

&lt;p&gt;In the end, Hanko was a great discovery. It's a new system that rightly doesn't have all the features yet, but it's really promising. &lt;/p&gt;

&lt;p&gt;Integrating it and managing the entire server-side part was not complex, the documentation, I repeat, is very well done therefore it is a system accessible to anyone.&lt;/p&gt;

&lt;p&gt;Anyway, partecipating in this hackathon reminded me of the times I worked at CodeImage, trying many new technologies and learning many new things.&lt;/p&gt;

&lt;p&gt;If you made it this far, thank you 😊. Feedback and/or a star is always appreciated&lt;/p&gt;

&lt;p&gt;All related code of SpecFlow it's available on GitHub.&lt;br&gt;
&lt;a href="https://github.com/riccardoperra/specflow" rel="noopener noreferrer"&gt;https://github.com/riccardoperra/specflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My others projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/riccardoperra/codeimage" rel="noopener noreferrer"&gt;CodeImage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/riccardoperra/solid-codemirror" rel="noopener noreferrer"&gt;Solid CodeMirror&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/riccardoperra/statebuilder" rel="noopener noreferrer"&gt;StateBuilder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hanko</category>
      <category>solidjs</category>
      <category>supabase</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>IssueManager - A realtime kanban board made with Appwrite and Angular14</title>
      <dc:creator>Riccardo Perra</dc:creator>
      <pubDate>Thu, 12 May 2022 23:53:38 +0000</pubDate>
      <link>https://forem.com/riccardoperra/issuemanager-manage-a-real-time-kanban-board-with-appwrite-and-angular14-12ai</link>
      <guid>https://forem.com/riccardoperra/issuemanager-manage-a-real-time-kanban-board-with-appwrite-and-angular14-12ai</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;IssueManager&lt;/em&gt; is an application built with Angular 14 and AppWrite. It's a small kanban management tool inspired by GitHub Projects, which make use of some Appwrite features like Account, Database, Realtime, Functions, Storage and Teams.&lt;/p&gt;

&lt;p&gt;I took the opportunity of this hackathon to create a project different from the usual clones, with the purpose of learning how Appwrite works and to try the newest Angular features in a real world application 😊.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KuCIsHeM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/upumulrt5r38kmicb2dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KuCIsHeM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/upumulrt5r38kmicb2dz.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Web2 Wizards&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/riccardoperra"&gt;
        riccardoperra
      &lt;/a&gt; / &lt;a href="https://github.com/riccardoperra/issue-manager"&gt;
        issue-manager
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A realtime kanban management tool built with Appwrite and Angular 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1 id="user-content-issuemanager"&gt;&lt;a class="heading-link" href="https://github.com/riccardoperra/issue-manager#issuemanager"&gt;IssueManager&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;A kanban management tool made with Angular and Appwrite&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="user-content-️-read-this"&gt;&lt;a class="heading-link" href="https://github.com/riccardoperra/issue-manager#%EF%B8%8F-read-this"&gt;⚠️ &lt;strong&gt;READ THIS&lt;/strong&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This project is a demo, born mainly to test Appwrite integration with Angular experimental features, and &lt;strong&gt;it's not ready for production!&lt;/strong&gt;
The site currently online is a test and will be discontinued soon. Once discontinued, you will need to self-host appwrite to try the application&lt;/p&gt;
&lt;h2 id="user-content--about"&gt;&lt;a class="heading-link" href="https://github.com/riccardoperra/issue-manager#-about"&gt;🎯 About&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;IssueManager&lt;/em&gt; is an application built with Angular 14 and AppWrite
for the &lt;a href="https://dev.to/devteam/announcing-the-appwrite-hackathon-on-dev-1oc0" rel="nofollow"&gt;AppWrite Hackathon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's a small kanabn management tool inspired by GitHub Projects
leveraging of some AppWrite features such as &lt;a href="https://appwrite.io/docs/account" rel="nofollow"&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://appwrite.io/docs/database" rel="nofollow"&gt;&lt;code&gt;Database&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://appwrite.io/docs/realtime" rel="nofollow"&gt;&lt;code&gt;Realtime&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/riccardoperra/issue-manager"&gt;&lt;code&gt;Functions&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://appwrite.io/docs/storage" rel="nofollow"&gt;&lt;code&gt;Storage&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://appwrite.io/docs/client/teams" rel="nofollow"&gt;&lt;code&gt;Teams&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="user-content--technologies"&gt;&lt;a class="heading-link" href="https://github.com/riccardoperra/issue-manager#-technologies"&gt;🚀 Technologies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The frontend is built with Angular 14 using entirely the new &lt;strong&gt;Standalone Component&lt;/strong&gt; feature. &lt;a class="issue-link js-issue-link" href="https://github.com/angular/angular/discussions/43784"&gt;angular/angular#43784&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It also includes &lt;a href="https://github.com/rx-angular/rx-angular"&gt;RxAngular&lt;/a&gt;, a toolset focused on runtime performance and template rendering &lt;a href="https://github.com/Tinkoff/taiga-ui"&gt;taiga-ui&lt;/a&gt;, an UI component library.&lt;/p&gt;
&lt;p&gt;WYSIWYG editor is built with &lt;a href="https://github.com/facebook/lexical"&gt;Lexical&lt;/a&gt; and a small…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/riccardoperra/issue-manager"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;The frontend is built with Angular 14 using &lt;strong&gt;Standalone Components&lt;/strong&gt;. &lt;a href="https://github.com/angular/angular/discussions/43784"&gt;https://github.com/angular/angular/discussions/43784&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Angular users, this mean that this application is built entirely without modules, although I had to come up with some workarounds to get all the libraries that don't fully support it work properly.&lt;/p&gt;

&lt;p&gt;It also includes &lt;a href="https://github.com/rx-angular/rx-angular"&gt;RxAngular&lt;/a&gt;, a toolset focused on runtime performance and template rendering, and &lt;a href="https://github.com/Tinkoff/taiga-ui"&gt;taiga-ui&lt;/a&gt;, an UI component library.&lt;/p&gt;

&lt;p&gt;WYSIWYG editor for task editing is built with &lt;a href="https://github.com/facebook/lexical"&gt;Lexical&lt;/a&gt; and a small angular wrapper made by my-self (the repo is currently private but available to &lt;a href="https://npmjs.com/lexical-angular"&gt;NPM&lt;/a&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ Features
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Authentication
&lt;/h5&gt;

&lt;p&gt;IssueManager authentication system is entirely based on Appwrite, and currently supports login and registration via Email/Password, Google and Github.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xhahyE3X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://user-images.githubusercontent.com/37072694/168169039-74bb5278-e7ff-426b-9d05-a3bfd28a3003.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xhahyE3X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://user-images.githubusercontent.com/37072694/168169039-74bb5278-e7ff-426b-9d05-a3bfd28a3003.png" alt="issue-manager-demo vercel app_login (2)" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Creating a new project
&lt;/h5&gt;

&lt;p&gt;Each authenticated user has the possibility to create a new project to IssueManager. A project is a kanban board where you can track your tasks.&lt;/p&gt;

&lt;p&gt;Creating a project involves a series of processes to create the user's workspace, in fact thanks to an &lt;a href="//./functions/create-project"&gt;Appwrite function&lt;/a&gt; that will first craete a new &lt;a href="https://appwrite.io/docs/client/teams"&gt;Appwrite Team&lt;/a&gt; where the user which has created the project is the owner, next it will create a new &lt;a href="https://appwrite.io/docs/client/storage"&gt;Appwrite Storage Bucket&lt;/a&gt; where users can upload their files, then it will finally creates the &lt;code&gt;Project&lt;/code&gt; entity to the database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--laGsDKit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t22norxks27tz68c1cz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--laGsDKit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t22norxks27tz68c1cz0.png" alt="Image description" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZmfKssnd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ui15570o0g4tkbyhwr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZmfKssnd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ui15570o0g4tkbyhwr1.png" alt="Image description" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4zHNqyOd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kl3u0gvosaw0ny4g3prg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4zHNqyOd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kl3u0gvosaw0ny4g3prg.png" alt="Image description" width="800" height="421"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;A project can be either public or private. When creating a public project, every user is able to view it even if it cannot update it. Private projects, on the other hand, can only be seen and updated by team members.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JgCviPid--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzd6q8sv46iuf3dc2n1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JgCviPid--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzd6q8sv46iuf3dc2n1f.png" alt="Example of public project" width="601" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Realtime feature is used to update automatically the list of available projects in the case that the project is deleted or if his visibility changes, in order to notify all users&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X1wO07iW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fskcl1ttpd7fogvn2zb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X1wO07iW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fskcl1ttpd7fogvn2zb1.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pgi8dpza--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3r8o1edpga39vpo9ed1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pgi8dpza--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3r8o1edpga39vpo9ed1.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Managing the board
&lt;/h5&gt;

&lt;p&gt;After creating a new project, users can access the board and can start to manage their tasks. Thanks to Appwrite realtime, each user which is visiting the board at that time will always have the updated issues on their screen.&lt;/p&gt;

&lt;p&gt;Users have the possibility to create, archive or re-order a new Category, then to create and move the Tasks inside just like a Kanban board. The ordering is made with &lt;a href="https://medium.com/whisperarts/lexorank-what-are-they-and-how-to-use-them-for-efficient-list-sorting-a48fc4e7849f"&gt;JIRA LexoRanks&lt;/a&gt; in order to optimize the performances and database requests. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xl1ckNB3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iz9xlz1ufz0otjanedir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xl1ckNB3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iz9xlz1ufz0otjanedir.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n1nRo9Yd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpnb4oj02guzhbw1dtia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n1nRo9Yd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpnb4oj02guzhbw1dtia.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Adding new members to the board
&lt;/h5&gt;

&lt;p&gt;Users which have the authorities to update the board can also invite new members and allow them to view the board. Once the user's e-mail in question has been entered, it will be sent to his mailbox and must be accepted to enable the user for the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mWjaNonp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://user-images.githubusercontent.com/37072694/168176124-bd5faf8c-c067-496a-95c8-b6c401102749.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mWjaNonp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://user-images.githubusercontent.com/37072694/168176124-bd5faf8c-c067-496a-95c8-b6c401102749.png" alt="image" width="623" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Viewing and updating tasks
&lt;/h5&gt;

&lt;p&gt;Each created task of the kanban board can be readed and updated. Users can manage their task updating the expiration date, priority and also the description. The WYSIWYG is made with &lt;code&gt;Lexical&lt;/code&gt; and &lt;code&gt;lexical-angular&lt;/code&gt; (Angular binding for lexical, which is currently a private repo in development, but already available to &lt;a href="https://npmjs.com/lexical-angular"&gt;NPM&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t_tlOULy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ryy781b370vxfw2koek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t_tlOULy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ryy781b370vxfw2koek.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Adding attachments to tasks
&lt;/h5&gt;

&lt;p&gt;Thanks to Appwrite Storage, the user has the possibility to manage the attachments of a task. Attachments consists of uploaded files that can be downloaded, deleted or viewed (if the preview is &lt;br&gt;
available).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ARYWatrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ta2cmj2js0ltevkd6837.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ARYWatrf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ta2cmj2js0ltevkd6837.png" alt="Image description" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Demo
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://issue-manager-demo.vercel.app/"&gt;live deployment&lt;/a&gt; of the app is available to try it out. You must login to access the application pages but you are able to delete your account if you need to.&lt;/p&gt;




&lt;p&gt;Some useful links:&lt;br&gt;
&lt;a href="https://issue-manager-demo.vercel.app"&gt;issue-manager - Demo application&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/riccardoperra/codeimage"&gt;codeimage.dev - My latest project&lt;/a&gt;&lt;/p&gt;

</description>
      <category>appwritehack</category>
      <category>webdev</category>
      <category>angular</category>
    </item>
  </channel>
</rss>
