<?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>DEV Community: Daniel Zotti</title>
    <description>The latest articles on DEV Community by Daniel Zotti (@danielzotti).</description>
    <link>https://dev.to/danielzotti</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%2F253174%2Fedd767df-832f-4c38-b1d5-e1f27d05f370.jpg</url>
      <title>DEV Community: Daniel Zotti</title>
      <link>https://dev.to/danielzotti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielzotti"/>
    <language>en</language>
    <item>
      <title>#LearnedToday: Object.groupBy()</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Mon, 26 Aug 2024 12:00:56 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-objectgroupby-4kl9</link>
      <guid>https://dev.to/danielzotti/learnedtoday-objectgroupby-4kl9</guid>
      <description>&lt;p&gt;🥳 It is finally out! No more need to write ugly code to group an array of objects by a specific value of a field!&lt;/p&gt;

&lt;p&gt;Since late 2023, there is an official static method for &lt;code&gt;Object&lt;/code&gt; called &lt;code&gt;groupBy()&lt;/code&gt; that does it for us!&lt;br&gt;
It accepts an &lt;code&gt;Iterable&lt;/code&gt;, such as an &lt;code&gt;Array&lt;/code&gt;, and a function, which is executed for each element and must return the "category" of that specific element.&lt;br&gt;
The method returns a new &lt;code&gt;Object&lt;/code&gt; where each key is a different category that contains an array of objects belonging to that specific category.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: The elements in the returned object and the original iterable are the same (not deep copies!). Changing the internal structure of the elements will be reflected in both the original iterable and the returned object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Let's give a practical example to see how easy it is to &lt;strong&gt;group all the Ninja Turtles characters by age&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The initial Array
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ninjaTurtlesCharacters&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="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Michelangelo&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Raffaello&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Donatello&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Leonardo&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Splinter&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Casey Jones&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;April O&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;Neil&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The good OLD way (with &lt;code&gt;reduce&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ninjaTurtlesCharactersByAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ninjaTurtlesCharacters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;groupedPeople&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;groupedPeople&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;groupedPeople&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&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="nx"&gt;groupedPeople&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;item&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="nx"&gt;item&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;h3&gt;
  
  
  The MODERN way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ninjaTurtlesCharactersByAgeNew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;ninjaTurtlesCharacters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Tip: use &lt;code&gt;Map.groupBy()&lt;/code&gt; if you want to return a &lt;code&gt;Map&lt;/code&gt; instead of an &lt;code&gt;Object&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;16&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="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Michelangelo&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Raffaello&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Donatello&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Leonardo&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;25&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="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Casey Jones&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;April O&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;Neil&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;91&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="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Splinter&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🧑🏻‍💻 As usual, I created a simple &lt;a href="https://stackblitz.com/edit/javascript-group-by-example?file=index.js" rel="noopener noreferrer"&gt;Stackblitz prject&lt;/a&gt; to play with the code.&lt;/p&gt;

&lt;p&gt;🗄 Docs: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ℹ Browser support: &lt;a href="https://caniuse.com/mdn-javascript_builtins_object_groupby" rel="noopener noreferrer"&gt;https://caniuse.com/mdn-javascript_builtins_object_groupby&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>learnedtoday</category>
      <category>array</category>
    </item>
    <item>
      <title>Voxxed Days Trieste (May 30, 2024)</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Sat, 15 Jun 2024 13:54:54 +0000</pubDate>
      <link>https://dev.to/danielzotti/voxxed-days-trieste-may-30-2024-13id</link>
      <guid>https://dev.to/danielzotti/voxxed-days-trieste-may-30-2024-13id</guid>
      <description>&lt;h2&gt;
  
  
  Is 2 weeks enough time to recover from an event?
&lt;/h2&gt;

&lt;p&gt;If it's the &lt;a href="https://voxxeddays.com/trieste/" rel="noopener noreferrer"&gt;Voxxed Days Trieste&lt;/a&gt;, certainly not, but it's definitely time for a post now!&lt;/p&gt;

&lt;p&gt;Having an &lt;strong&gt;international event&lt;/strong&gt; for devs in &lt;em&gt;Trieste (Italy)&lt;/em&gt; is not everyday stuff, and I am really proud to have been a part of it!&lt;/p&gt;

&lt;p&gt;Although historically Voxxed has always been associated with Java and the backend, this year there was also a frontend track (React) with a very high level of talks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maya Shavin&lt;/strong&gt; (Microsoft) opened the FE track with her talk “&lt;a href="https://voxxeddays.com/trieste/schedule/talk/?id=3609" rel="noopener noreferrer"&gt;Are we React-ing wrongly&lt;/a&gt;” explaining how to use React for REAL. A talk that I wanted so much because of its wisdom and also because Maya is an explosive mix of sweetness and expertise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Katarzyna Dusza&lt;/strong&gt; (Spotify) brought a talk called “&lt;a href="https://voxxeddays.com/trieste/schedule/talk/?id=4303" rel="noopener noreferrer"&gt;Modern &amp;amp; secure adaptive streaming on the Web&lt;/a&gt;”. Her really accurate, clear and in depth presentation will surely simplify the study of these technologies for those who want to use them! As we all know, docs are never that comprehensive and one often has to study from different sources to understand how things really work; Katarzyna did that for us and even added some live coding, so there is no excuse not to give it a try!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Omar Diop&lt;/strong&gt; (Learnn) brought “&lt;a href="https://voxxeddays.com/trieste/schedule/talk/?id=3613" rel="noopener noreferrer"&gt;A 4-Year Retrospective : Lessons Learned from Building a Video Player from Scratch with ReactNative&lt;/a&gt;”. Unfortunately, I wasn't able to attend his talk because I was busy with “staff stuff”, but I got to talk to him during the speakers' dinner the day before, and he is a bubbly, energetic guy! We got to talk about our side projects (which I LOVE doing) and his are all really interesting!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edoardo Dusi&lt;/strong&gt; (SparkFabrik) with the talk “&lt;a href="https://voxxeddays.com/trieste/schedule/talk/?id=5501" rel="noopener noreferrer"&gt;ServerComponents are React's superweapon in the Stack Wars: Can PHP strike back?&lt;/a&gt;” brought one of the trendiest topics of 2024, telling it like only he can. It was a “historical” talk, from the first version of php to NextJs, through the various FE frameworks we have loved and hated... So many good old memories!!!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Borut Jogan&lt;/strong&gt; (Honeywell): “&lt;a href="https://voxxeddays.com/trieste/schedule/talk/?id=2505" rel="noopener noreferrer"&gt;Fail your tasks successfully&lt;/a&gt;”, a talk that every conference (of any kind) should give space to. He brought attention to the issues of mental health and how to get out of bad situations that any of us might face at work. A talk of a simplicity but disarming power. Really a good time of sharing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main track talks were also interesting, but my favorite was the “&lt;a href="https://voxxeddays.com/trieste/schedule/talk/?id=3268" rel="noopener noreferrer"&gt;Cracking the Code: Decoding Anti-Bot Systems!&lt;/a&gt;” by &lt;strong&gt;Fabien Vauchelles&lt;/strong&gt; (Scrapoxy) who explained how you can do scraping without getting caught. I love these kinds of talks because they encapsulate the REAL experience of several years in one talk of a few minutes 😍 &lt;/p&gt;

&lt;p&gt;Let me add just a couple of words about the location: how cool is it to do a conference in one (actually two) &lt;strong&gt;cinema&lt;/strong&gt;?!? WOW. &lt;/p&gt;

&lt;p&gt;And last but not least, I can't thank &lt;strong&gt;Alessandra Laderchi&lt;/strong&gt; enough for getting me involved in the program committee and asking me to be part of the staff. &lt;br&gt;
I got the chance to work with a great team and meet fantastic people: &lt;strong&gt;Borut Jogan&lt;/strong&gt;, &lt;strong&gt;Enrico Giacomazzi&lt;/strong&gt;, &lt;strong&gt;Federico Morsut&lt;/strong&gt;, &lt;strong&gt;Gabriele Savio&lt;/strong&gt;, &lt;strong&gt;Mariela Nasi&lt;/strong&gt;, &lt;strong&gt;Ornela Kita&lt;/strong&gt;, &lt;strong&gt;Mario Fusco&lt;/strong&gt; and &lt;strong&gt;Daniele Zonca&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxhd4bbquaxtlus5szf3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxhd4bbquaxtlus5szf3x.png" alt="Image description" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>conference</category>
      <category>community</category>
      <category>react</category>
      <category>java</category>
    </item>
    <item>
      <title>#LearnedToday: Slider in pure CSS</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Tue, 16 Jan 2024 08:23:13 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-slider-in-pure-css-3cji</link>
      <guid>https://dev.to/danielzotti/learnedtoday-slider-in-pure-css-3cji</guid>
      <description>&lt;p&gt;🏞️ Do you remember when you had to use heavy JS libraries to create image slideshows?&lt;/p&gt;

&lt;p&gt;💪 Those days are gone! You can do it in pure CSS now!  (&lt;strong&gt;experimental&lt;/strong&gt; in Chrome, Edge and Opera)&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/9os0AZvxvqM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Main concepts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-timeline"&gt;Scroll timeline&lt;/a&gt;&lt;/strong&gt;: define a &lt;em&gt;name&lt;/em&gt; and a &lt;em&gt;direction&lt;/em&gt; for the scrollable element on which the shorthand CSS &lt;code&gt;scroll-timeline&lt;/code&gt; property is applied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline"&gt;Animation Timeline&lt;/a&gt;&lt;/strong&gt;: The &lt;code&gt;animation-timeline&lt;/code&gt; CSS property specifies the timeline (defined by the &lt;code&gt;scroll-timeline&lt;/code&gt; property) that is used to control the progress of a CSS animation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope"&gt;Timeline scope&lt;/a&gt;&lt;/strong&gt;: The &lt;code&gt;timeline-scope&lt;/code&gt; CSS property modifies the scope of a named animation timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation-range"&gt;Animation range&lt;/a&gt;&lt;/strong&gt;: The &lt;code&gt;animation-range&lt;/code&gt; CSS shorthand property is used to set the start and end of an animation's attachment range along its timeline, i.e. where along the timeline an animation will start and end.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Associate an horizontal (x axis) scroll timeline to the element that contains the slides (&lt;code&gt;.slider__entries&lt;/code&gt;) using the &lt;code&gt;scroll-timeline&lt;/code&gt; property (it's a sort of &lt;em&gt;event listener&lt;/em&gt; of the scroll event for CSS)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.slider__entries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;scroll-timeline-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--carousel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;scroll-timeline-axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;horizontal&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Raise the scope of the timeline from the child (&lt;code&gt;.slider__entries&lt;/code&gt;) to the parent (&lt;code&gt;.slider&lt;/code&gt;) using the &lt;code&gt;timeline-scope&lt;/code&gt; property
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;timeline-scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--carousel&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;ul&gt;
&lt;li&gt;Associate the scroll animation to the single "dot" in order to highlight the correct "dot"
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;color-bullet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&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;--dot-color--active&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="nc"&gt;.dot&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;color-bullet&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;animation-timeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--carousel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;animation-range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&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;--i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calc&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;--i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100.01%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="py"&gt;NB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;because&lt;/span&gt; &lt;span class="n"&gt;we&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Adding a "snap" effect during the scroll and hide the scrollbar
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.slider__entries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;scroll-snap-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;mandatory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;horizontal&lt;/span&gt;
  &lt;span class="py"&gt;scrollbar-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;hide&lt;/span&gt; &lt;span class="err"&gt;scrollbar&lt;/span&gt;
  &lt;span class="py"&gt;scroll-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;scroll&lt;/span&gt; &lt;span class="err"&gt;smoothly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Adding the slide switch when clicking on the button
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- DOT: --&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#slide_01"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- SLIDE: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"slide_01"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I created as usual a working &lt;a href="https://stackblitz.com/edit/js-jjkrlz?file=style.scss"&gt;demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>learnedtoday</category>
      <category>slider</category>
    </item>
    <item>
      <title>How we built an app in less than 5 days with Qwik, Supabase and Vercel</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Mon, 20 Nov 2023 13:47:04 +0000</pubDate>
      <link>https://dev.to/danielzotti/how-we-built-an-app-in-less-than-5-days-with-qwik-supabase-and-vercel-1b3n</link>
      <guid>https://dev.to/danielzotti/how-we-built-an-app-in-less-than-5-days-with-qwik-supabase-and-vercel-1b3n</guid>
      <description>&lt;h2&gt;
  
  
  Fortitude Convention Companion App
&lt;/h2&gt;

&lt;p&gt;September 29 was the day of the &lt;strong&gt;Fortitude Group's convention&lt;/strong&gt;. It was a truly memorable day and I can't even describe the joy I felt finally spending time together with all the colleagues who work scattered around Italy (and whom I usually see only through a monitor).&lt;/p&gt;

&lt;p&gt;But let's go back a bit, to two weeks earlier to be precise, when the email from HR with the official agenda for the convention arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thursday 14th September
&lt;/h2&gt;

&lt;p&gt;The desire to see each other and do something together is always huge, and after looking at the schedule, we realized that there was a &lt;strong&gt;3-hour "free time" slot&lt;/strong&gt; with an asterisk next to it that said &lt;em&gt;"Possibility of using the pools and the volleyball, soccer and table tennis facilities"&lt;/em&gt;. Within minutes after receiving the email I got a message from &lt;em&gt;Luigi&lt;/em&gt; proposing to arrange teams and matches for the respective sports.&lt;/p&gt;

&lt;p&gt;The next day, things have already gotten out of hand and together with &lt;em&gt;Gabri&lt;/em&gt; we decide to organize something more complex:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;the Fortigames&lt;/strong&gt;! A two-hour challenge between two teams that will compete simultaneously playing soccer, volleyball and table tennis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make the event more interesting, we decided to name both teams. After brainstorming, we opted for the symbol of &lt;strong&gt;Yin and Yang&lt;/strong&gt; because of the classic &lt;em&gt;black and white T-shirts&lt;/em&gt;. With a little more creativity, we decided on &lt;em&gt;&lt;strong&gt;Tigers (Yin)&lt;/strong&gt;&lt;/em&gt; versus &lt;em&gt;&lt;strong&gt;Dragons (Yang)&lt;/strong&gt;&lt;/em&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;More info about &lt;a href="https://new.artsmia.org/programs/teachers-and-students/teaching-the-arts/artwork-in-focus/japanese-tiger-and-dragon"&gt;Yin and Yang / Tigers and Dragons&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tuesday 19th September
&lt;/h2&gt;

&lt;p&gt;We create a form on &lt;strong&gt;Google Forms&lt;/strong&gt; to collect participants data and at the same time, ask the HR team for the full list of convention attendees with their company info and emails.&lt;/p&gt;

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

&lt;p&gt;That being said, you may ask, &lt;em&gt;"isn't Daniel's blog usually filled with technical posts?"&lt;/em&gt; You are right! In fact, after this preamble, I'll take you to a week before the event, specifically Friday, September 22.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friday 22nd September
&lt;/h2&gt;

&lt;p&gt;Out of the blue, &lt;em&gt;Gabri&lt;/em&gt; comes up with the idea of developing a &lt;strong&gt;convention "companion" app&lt;/strong&gt;, mostly focusing on the &lt;em&gt;Fortigames&lt;/em&gt; and convenient for managing the results and helping attendees figure out where to go, what to do, etc.&lt;/p&gt;

&lt;p&gt;Initially we think it's a joke; &lt;strong&gt;&lt;em&gt;making an app from scratch in less than a week, especially working in our spare time, is unthinkable&lt;/em&gt;&lt;/strong&gt;. But then &lt;em&gt;Stefano&lt;/em&gt; joins in as well, and he proposes to use some technology never tried before, and I couldn't resist.&lt;/p&gt;

&lt;p&gt;So we decide to develop a PWA using the following technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Balsamiq&lt;/strong&gt;: for &lt;em&gt;wireframe&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Figma&lt;/strong&gt;: for the &lt;em&gt;UI&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwik&lt;/strong&gt;: as the main &lt;em&gt;framework&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Font Awesome&lt;/strong&gt; for &lt;em&gt;icons&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SCSS&lt;/strong&gt; structure copy-pasted from &lt;a href="https://www.danielzotti.it"&gt;my website&lt;/a&gt; with &lt;strong&gt;CSS modules&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt;: &lt;em&gt;realtime database&lt;/em&gt; with &lt;em&gt;Google authentication&lt;/em&gt; (since every Fortitude Group employee has their own Google corporate account)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: to save the &lt;em&gt;open source code&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt;: for the &lt;em&gt;deployment&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project creation
&lt;/h3&gt;

&lt;p&gt;On Friday night, I already started creating the project on the &lt;strong&gt;&lt;a href="https://supabase.com"&gt;Supabase&lt;/a&gt;&lt;/strong&gt; website and installing &lt;strong&gt;&lt;a href="https://qwik.builder.io/"&gt;Qwik&lt;/a&gt;&lt;/strong&gt;, with its plugin for &lt;em&gt;Supabase&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create qwik@latest

npm &lt;span class="nb"&gt;install&lt;/span&gt; @supabase/supabase-js supabase-auth-helpers-qwik
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://qwik.builder.io/docs/integrations/supabase"&gt;Supabase integrations for &lt;em&gt;Qwik&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Google Authentication setup
&lt;/h3&gt;

&lt;p&gt;Activating authentication via Google from the &lt;em&gt;Fortigames&lt;/em&gt; project on the &lt;a href="https://supabase.com/dashboard"&gt;Supabase dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll1b6hmja67u8ge6bn3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll1b6hmja67u8ge6bn3i.png" alt="Supabase - Google OAuth Provider" width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;set the return url for the provider&lt;/p&gt;

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

&lt;p&gt;and create the web app on &lt;a href="https://console.cloud.google.com/apis/dashboard"&gt;Google Cloud&lt;/a&gt; (with the correct return URL)&lt;/p&gt;

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

&lt;p&gt;To log the user in, simply create a button that, when clicked, calls Supabase's &lt;code&gt;createClient&lt;/code&gt; function by setting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Google&lt;/code&gt; as the provider&lt;/li&gt;
&lt;li&gt;entering as parameters&lt;/li&gt;
&lt;li&gt;the project URL&lt;/li&gt;
&lt;li&gt;the key we find on the &lt;em&gt;Supabase&lt;/em&gt; site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Supabase&lt;/em&gt; does the rest (it basically takes care of reading the token data from the URL and saving it to the localStorage)&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="c1"&gt;// /src/components/auth/login/login.tsx&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;Login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="nf"&gt;$&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;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocation&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;handleGoogleLogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;&amp;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;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;PUBLIC_SUPABASE_URL&lt;/span&gt; &lt;span class="o"&gt;||&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;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;PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signInWithOAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&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;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick$&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleGoogleLogin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Login&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;Google&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Saturday 23rd September
&lt;/h2&gt;

&lt;p&gt;We agree to meet Saturday morning at 9 a.m. to start jotting down ideas and talk about graphics and features.&lt;/p&gt;

&lt;h3&gt;
  
  
  The team
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stefano&lt;/strong&gt;: Head of UX/UI Engineering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gabriella&lt;/strong&gt;: Team Leader UX/UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daniel&lt;/strong&gt;: Team Leader Frontend&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  App Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard page&lt;/strong&gt;: with realtime results and schedule, countdown start/end games and location map (+ cup with winner at the end)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams page&lt;/strong&gt;: list of team members + filters by team and sport type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Games page&lt;/strong&gt;: results of the 3 sports + section only for referees, facilitators and admin to score and start/stop games&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boardgames page&lt;/strong&gt; (yes, those who don't want to play sports can have fun playing with boardgames!) with list of available games and some additional info&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Info page&lt;/strong&gt;: rules, agenda, location, etc..&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile page&lt;/strong&gt;: with custom icon based on team membership and links to dedicated chats on Slack for team, facilitators, boardgamers, etc...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin section&lt;/strong&gt;: to manage convention registrants and Fortigames participants.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wireframe
&lt;/h3&gt;

&lt;p&gt;The very first app wireframe:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4c7cubdawvsc1jgh1kk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4c7cubdawvsc1jgh1kk.jpg" alt="First wireframe" width="800" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;p&gt;After reasoning about the features, we begin from the creation of the DB directly on the &lt;em&gt;Supabase&lt;/em&gt; website. Having little time, we try to simplify the structure a bit so as to speed up the work.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Users
&lt;/h4&gt;

&lt;p&gt;List of users who can access the app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal information (first name, last name, company)&lt;/li&gt;
&lt;li&gt;Info about roles (admin, facilitator, referee)&lt;/li&gt;
&lt;li&gt;Info about games (team affiliation, game participation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email&lt;/strong&gt;: we use this field to associate the person's info with the user authenticated through Google&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Agenda
&lt;/h4&gt;

&lt;p&gt;The day's agenda information. Each row corresponds to a task and has a beginning and an ending time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Games Results
&lt;/h4&gt;

&lt;p&gt;The information about the results of the various games. Each row corresponds to the result of a specific sport (&lt;code&gt;soccer&lt;/code&gt;, &lt;code&gt;volleyball&lt;/code&gt;, and &lt;code&gt;table_tennis&lt;/code&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  Config
&lt;/h4&gt;

&lt;p&gt;The information about the status of the games: &lt;em&gt;planned start/end time&lt;/em&gt;, &lt;em&gt;actual start/end time&lt;/em&gt;, a flag to manage the &lt;em&gt;paused game&lt;/em&gt; (e.g., bad weather or not plannable problems) and, of course, the name of the &lt;em&gt;winner&lt;/em&gt;!&lt;/p&gt;

&lt;h4&gt;
  
  
  Layout
&lt;/h4&gt;

&lt;p&gt;We didn't think about a desktop version (or at least totally responsive) so as to speed up the work. From the usual &lt;em&gt;"mobile first"&lt;/em&gt; approach, you could say that we switched to a more simple approach that I would call &lt;em&gt;"mobile only"&lt;/em&gt; 😀 (basically we put a &lt;code&gt;max-width&lt;/code&gt; and handled responsive only for smartphones and tablets, without revising the page structure).&lt;/p&gt;

&lt;p&gt;This is how the very first working version of the app looked like.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  SCSS file structure
&lt;/h4&gt;

&lt;p&gt;To speed up the work, we used the SCSS files from the danielzotti.it website.&lt;/p&gt;

&lt;p&gt;The reasons are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No UI frameworks&lt;/strong&gt; are used&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;modular&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It already has &lt;strong&gt;reset&lt;/strong&gt; styles included&lt;/li&gt;
&lt;li&gt;It works with &lt;strong&gt;CSS variables&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It supports &lt;strong&gt;dark/light theme&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It already contains some &lt;strong&gt;basic styles&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structure is as follows and each file has its own purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;base&lt;/strong&gt;: styles of basic html tags such as &lt;code&gt;h1&lt;/code&gt;,..., &lt;code&gt;h6&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;ul&lt;/code&gt;, &lt;code&gt;code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;common&lt;/strong&gt;: common styles used in different contexts but which are not base tags. (e.g. &lt;code&gt;.table-container&lt;/code&gt; to handle overflow of tables)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fonts&lt;/strong&gt;: &lt;code&gt;@import&lt;/code&gt; of the various fonts used&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;layout&lt;/strong&gt;: responsive styles similar to &lt;em&gt;Bootstrap&lt;/em&gt;: container, breakpoint, etc...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reset&lt;/strong&gt;: reset of all basic styles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;theme&lt;/strong&gt;: the variables for dark/light themes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;variables-css&lt;/strong&gt;: the general and theme-independent variables: &lt;code&gt;spacing&lt;/code&gt;, &lt;code&gt;html-max-width&lt;/code&gt;, ...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;variables-scss&lt;/strong&gt;: the SCSS variables (unfortunately &lt;code&gt;@media()&lt;/code&gt; does not support css variables yet, so we had to keep the SCSS variables to use them in that context)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;components&lt;/strong&gt; (folder): styles of reusable components such as buttons, icons, ...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mixins&lt;/strong&gt; (folder): reusable mixins within the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/danielzotti/new.danielzotti.it/tree/github/src/scss"&gt;Daniel's website SCSS file structure&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Around 3pm we get to the point where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the layout structure is ready and supports the dark/light theme&lt;/li&gt;
&lt;li&gt;we are able to authenticate through Google&lt;/li&gt;
&lt;li&gt;we have imported the convention participant data via CSV (directly on the &lt;em&gt;Supabase&lt;/em&gt; website)&lt;/li&gt;
&lt;li&gt;we are able to read the data from the users table&lt;/li&gt;
&lt;li&gt;we have the ability to use icons conveniently thanks to FontAwesome&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Monday 25th September
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Supabase CLI
&lt;/h3&gt;

&lt;p&gt;The developer experience of &lt;em&gt;Supabase&lt;/em&gt; is really nice. In addition to the well-written documentation and useful SDK, there is also a &lt;em&gt;cli&lt;/em&gt; to make life easier for developers.&lt;/p&gt;

&lt;p&gt;To use it, just install it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i supabase &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;generate a personal access token.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://supabase.com/dashboard/account/tokens"&gt;Personal Access Token&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and login&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;supabase login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://supabase.com/docs/reference/cli/supabase-login"&gt;Supabase login&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Supabase and TypeScript
&lt;/h3&gt;

&lt;p&gt;And for those who, like me, love working with TypeScript, the &lt;em&gt;cli&lt;/em&gt; provides a feature to &lt;em&gt;automatically generate DB types&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;supabase gen types typescript &lt;span class="nt"&gt;--project-id&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;project_id&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; src/types/database.types.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://supabase.com/docs/reference/cli/supabase-gen-keys"&gt;Generate &lt;em&gt;Supabase&lt;/em&gt; types&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  User filter
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Supabase&lt;/em&gt; has the ability to &lt;em&gt;manage policies within the DB&lt;/em&gt;, but time is short and there is no ready method for filtering authenticated users by domain, so we decide to go the easy way and &lt;em&gt;filter users client-side only&lt;/em&gt;, simply checking to see if they are in the &lt;code&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication session
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Supabase&lt;/em&gt; handles the authentication through Google easily, but &lt;em&gt;maintaining the session is the responsibility of the app developer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In fact, &lt;strong&gt;reading the user's data from the &lt;code&gt;localStorage&lt;/code&gt; every time causing bad performances&lt;/strong&gt; and, in addition, there is no way (or we didn't find it) to figure out when the JWT token is actually saved in the localStorage and can finally be read and saved in memory. Initially, we used the good old &lt;code&gt;setTimeout&lt;/code&gt; of &lt;code&gt;500ms&lt;/code&gt; but of course it became even &lt;em&gt;less performant&lt;/em&gt; because we had to wait for the timeout &lt;em&gt;each time we needed to read it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As a result, we decided to manually handle saving the data in the &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The flow is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user clicks on the &lt;code&gt;Login&lt;/code&gt; button.&lt;/li&gt;
&lt;li&gt;He/She is sent to the classic Google Login page&lt;/li&gt;
&lt;li&gt;Once logged in, he is redirected to the &lt;code&gt;/auth&lt;/code&gt; page that will take care of reading the token from the URL parameters&lt;/li&gt;
&lt;li&gt;with a call to the DB, data is read from the users table (filtered for the email with which the user has authenticated)&lt;/li&gt;
&lt;li&gt;if the user does not exist in the users table, an error page is shown because the user is not in the list of users authorized to use the app&lt;/li&gt;
&lt;li&gt;if the user exists, we enrich the information taken from the users table to the session data&lt;/li&gt;
&lt;li&gt;save the session data in the &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;save the session data in the context (using the &lt;code&gt;useAuth&lt;/code&gt; hook) so that we can directly use the context and not the &lt;code&gt;localStorage&lt;/code&gt; as the data source (single source of truth)&lt;/li&gt;
&lt;li&gt;the user is eventually redirected to the home page (dashboard)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/danielzotti/fortigames/blob/master/src/routes/auth/index.tsx"&gt;&lt;code&gt;/auth&lt;/code&gt; page&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What happens if we reload the page (after successfully logging in)?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We added the &lt;code&gt;useCheckSession()&lt;/code&gt; hook on the layout that encapsulates all protected pages and returns the session&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useCheckSession&lt;/code&gt; internally checks if the context exists&lt;/li&gt;
&lt;li&gt;If the context exists, it returns it immediately&lt;/li&gt;
&lt;li&gt;If the context does not exist:&lt;/li&gt;
&lt;li&gt;it goes to check if a token exists in the &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if the token does not exist, the user is redirected to the login&lt;/li&gt;
&lt;li&gt;if the token exists, it copies the information into the context and returns the newly updated context
&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="c1"&gt;// /src/hooks/useCheckSession.ts&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;useCheckSession&lt;/span&gt;&lt;span class="p"&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;navigate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNavigate&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;auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&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;isTokenExpired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;expires_at&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AuthSession&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;expires_at&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="kc"&gt;false&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&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;getSessionFromLocalStorage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&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;tokenString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jwtTokenLocalStorageName&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenString&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthSession&lt;/span&gt; &lt;span class="o"&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenString&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;isTokenExpired&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="k"&gt;return&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="k"&gt;return&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="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="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;useVisibleTask&lt;/span&gt;&lt;span class="nf"&gt;$&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="o"&gt;=&amp;gt;&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;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;;&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="nf"&gt;getSessionFromLocalStorage&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="o"&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="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&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;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;auth&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;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/danielzotti/fortigames/blob/master/src/hooks/useAuth.ts"&gt;&lt;code&gt;useAuth&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/danielzotti/fortigames/blob/master/src/hooks/useCheckSession.ts"&gt;&lt;code&gt;useCheckSession&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;NB:&lt;/strong&gt; One thing that is not immediate to understand about &lt;em&gt;Qwik&lt;/em&gt; at first is that &lt;strong&gt;&lt;em&gt;code can be executed either client or server side&lt;/em&gt;&lt;/strong&gt; (based on different logics).. In our specific case, we needed to execute the client-side check (where the JWT token session persists) and thus we used &lt;code&gt;useVisibleTask$&lt;/code&gt; to make sure that the client-side code was executed, after the first rendering of the component.&lt;/p&gt;

&lt;p&gt;In fact, the &lt;code&gt;useVisibleTask$()&lt;/code&gt; is similar to &lt;code&gt;useTask$()&lt;/code&gt; but it only runs on the browser and after initial rendering.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://qwik.builder.io/docs/components/tasks/#usevisibletask"&gt;&lt;code&gt;useVisibleTask&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tuesday 26th September
&lt;/h2&gt;

&lt;p&gt;There are still many features to be developed, and&lt;br&gt;
&lt;em&gt;Erik&lt;/em&gt; is added to the developer team to help us out with a couple of components.&lt;/p&gt;
&lt;h3&gt;
  
  
  Time manager
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;countdown&lt;/strong&gt; that follows this logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before the games start, the countdown points to the scheduled start date of the games&lt;/li&gt;
&lt;li&gt;When the games have started, the countdown points to the expected end date of the games&lt;/li&gt;
&lt;li&gt;When the games are finished, the countdown points to the actual end date of the games&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, the current event on the agenda and upcoming events are shown.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/danielzotti/fortigames/blob/master/src/shared/components/games-time-manager/games-time-manager.tsx"&gt;&lt;code&gt;GamesTimeManager&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Minimal UI kit
&lt;/h3&gt;

&lt;p&gt;In order to speed up the development, we created a couple of reusable components: button, back to top button, company logo, back button, etc.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/danielzotti/fortigames/blob/master/src/shared/components/ui/button/button.tsx"&gt;&lt;code&gt;Button&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  First deploy on Vercel
&lt;/h3&gt;

&lt;p&gt;Deploying to &lt;strong&gt;&lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt;&lt;/strong&gt; is really straightforward with &lt;em&gt;Qwik&lt;/em&gt;, in fact there is an adapter that you simply install with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run qwik add vercel-edge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://qwik.builder.io/docs/deployments/vercel-edge/"&gt;Vercel edge&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and then just go to the &lt;em&gt;Vercel&lt;/em&gt; website and connect the Git repo&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc95l8grr4tazj1fq23f8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc95l8grr4tazj1fq23f8.png" alt="Vercel - Import git" width="686" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, you can configure the project to automatically &lt;strong&gt;deploy on "push"&lt;/strong&gt; on a specific branch (&lt;code&gt;Settings → Git&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv89t0ipf1xfxgxzvdp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv89t0ipf1xfxgxzvdp4.png" alt="Vercel - Set production branch" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After pushing to the &lt;code&gt;deploy-vercel&lt;/code&gt; branch just go to &lt;a href="https://fortigames.vercel.app"&gt;https://fortigames.vercel.app&lt;/a&gt; and see your app working.&lt;/p&gt;

&lt;p&gt;The pattern for the URL is &lt;code&gt;https://{project_name}.vercel.app&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wednesday 27th September
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Time for Real Time!
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Supabase&lt;/em&gt; has the &lt;strong&gt;Realtime&lt;/strong&gt; function and it is really easy to activate: just go to the table details, click on the button and you're done!&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;"Fun" fact: &lt;em&gt;Stefano&lt;/em&gt; was in charge of studying the realtime part and, initially, he had activated that feature only for the users table. When I set out to develop the code for the realtime results, I lost 2 hours to realize that realtime had not been enabled on that table as well. In fact, &lt;em&gt;Supabase&lt;/em&gt; does not return an error, but simply empties data so, with no error, it took me a while to figure it out (they could definitely improve this!!).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is little data in the DB, and from a realtime perspective &lt;strong&gt;it makes sense to load the whole list of users&lt;/strong&gt; (and all the other data) without thinking about pagination and just stay listening for the few data changes and update the data in memory accordingly. For convenience of use, we decided to wrap the logic in &lt;strong&gt;&lt;em&gt;hooks&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  hooks for realtime
&lt;/h3&gt;

&lt;p&gt;NB: We will take &lt;code&gt;useParticipants()&lt;/code&gt; hook as an example, but all the hooks are developed pretty much the same way (following the same pattern).&lt;/p&gt;

&lt;p&gt;The idea is to &lt;em&gt;initialize the hook's store data in the protected pages layout&lt;/em&gt; (since the application works only after authentication), so &lt;em&gt;data is taken only once and, in any case, after authentication&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The single source of truth is our &lt;strong&gt;store&lt;/strong&gt;, and from there we filter the data we are interested in, such as the list of users who participate in games (those who are associated with a team), or the list of people who play board games. We listen for realtime changes in the DB and update the store accordingly, and all other &lt;code&gt;useComputed$&lt;/code&gt; properties will update automatically.&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="c1"&gt;// /src/hooks/useParticipants.ts&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;useParticipants&lt;/span&gt; &lt;span class="o"&gt;=&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;// Single source of truth&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ParticipantsContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// List of all people in Fortitue Group who participate to the convention&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useComputed$&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Participant&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Participants in a team&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;participantsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useComputed$&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Participant&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// People who play boardgames&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boardgamersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useComputed$&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Participant&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_playing_boardgames&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Get participant by a specifica email (ID)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;participantByEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;email&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// It is used in the layout of the protected route&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initializeContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&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="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="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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabaseClient&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;users&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;span class="c1"&gt;// Save data in key:value structure (email is the key)&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;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&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="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&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;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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="c1"&gt;// Listen to data changes&lt;/span&gt;
    &lt;span class="nx"&gt;supabaseClient&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;custom-update-channel&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;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres_changes&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&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;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RealtimePostgresUpdatePayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;store&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="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&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;span class="nf"&gt;subscribe&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;{&lt;/span&gt;
    &lt;span class="nx"&gt;initializeContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;participantByEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;participantsList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;usersList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;boardgamersList&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;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/danielzotti/fortigames/blob/master/src/hooks/useParticipants.ts"&gt;&lt;code&gt;useParticipants&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://qwik.builder.io/docs/components/state/#usecomputed"&gt;&lt;code&gt;useComputed$&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://qwik.builder.io/docs/components/state/#usesignal"&gt;&lt;code&gt;useSignal&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Manage score
&lt;/h3&gt;

&lt;p&gt;Once the realtime feature is ready, we can work on updating the score of the matches. This is a fairly straightforward operation: just do "+1" or "-1" on the data when you click on the appropriate button (kind of like the classic “counter” example). One thing to keep in mind is to immediately disable the button after the user clicked, so as to avoid multiple updates.&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="c1"&gt;// /src/routes/(protected)/games/[id]/index.tsx&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&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;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;team&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dragons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tigers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&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;value&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;;&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;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&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;row&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_update&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;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;
      &lt;span class="p"&gt;};&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;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;There was an 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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;
  &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;selected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;onClick$&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;updateScore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dragons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&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;value&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dragons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;+&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Almost done
&lt;/h3&gt;

&lt;p&gt;It's 1:40am. The app seems usable and (almost) complete. We can go to sleep now, but first let's take a picture to celebrate!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxld0th2z0v6wc2quhgjb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxld0th2z0v6wc2quhgjb.jpg" alt="Fortigames Devs" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thursday 28th September
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Start/Stop Fortigames
&lt;/h3&gt;

&lt;p&gt;In order to have something up and running on the day of the convention, we left the components for managing the games for last, since this could be handled by editing the DB directly anyway (from the &lt;em&gt;Supabase&lt;/em&gt; website).&lt;/p&gt;

&lt;p&gt;Fortunately, there was still a day to go, and by taking just a couple of hours off in the afternoon I was able to manage the buttons to &lt;strong&gt;start games&lt;/strong&gt;, &lt;strong&gt;pause&lt;/strong&gt; and &lt;strong&gt;restart&lt;/strong&gt; them.&lt;/p&gt;

&lt;p&gt;Each button has the same logic: &lt;em&gt;it reads the status of the games in realtime and, when clicked, handles the specific Start/Stop/Reset operation of the games&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Winner page
&lt;/h3&gt;

&lt;p&gt;Graphics aside (an SVG of the cup drawn by &lt;em&gt;Gabri&lt;/em&gt;), the component keeps listening to the &lt;code&gt;winner&lt;/code&gt; field of the &lt;code&gt;config&lt;/code&gt; table and, at the time it is set with the value &lt;em&gt;"dragons"&lt;/em&gt; or &lt;em&gt;"tigers"&lt;/em&gt;, the component displays the trophy with the name of the &lt;em&gt;winner&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Last moments
&lt;/h2&gt;

&lt;p&gt;It's 8pm of the day before the convention. The next day's departure is at 6am (and a 3-hour drive!) and the sleep backlog has to be made up somehow (going to bed every day at 2am was probably not a good idea 😅).&lt;/p&gt;

&lt;p&gt;Just time for a few last graphical fixes, &lt;strong&gt;ZERO testing&lt;/strong&gt;, and the last deployment is done hoping for good luck!&lt;br&gt;
Time to pack up and try to rest up for the two-day event!&lt;/p&gt;

&lt;p&gt;One last look at the &lt;strong&gt;&lt;em&gt;evolution of the UI&lt;/em&gt;&lt;/strong&gt; before concluding our journey:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgnmnrvm0wq05nw6yv6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgnmnrvm0wq05nw6yv6k.png" alt="From Balsamiq, to Figma and the real app" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Okay, the time was really short but we made it develop this app! Sure, adding ideas and features along the way was not a good idea, but in the end we did it and &lt;strong&gt;&lt;em&gt;it was worth it&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I will never stop saying it, if you have fun and put passion into it you can do anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's take stock of the choices made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knowing that the app is used by trusted people and only internally, we did not take large security controls into account&lt;/li&gt;
&lt;li&gt;The app was created just for fun, so we didn’t care about performances and chose instead to add more features.&lt;/li&gt;
&lt;li&gt;We were so focused on functionality that we did not have time to focus on shared development patterns (ES: DB access mode, folder and file structure, naming conventions, ...)&lt;/li&gt;
&lt;li&gt;We did not study &lt;em&gt;Qwik&lt;/em&gt; or &lt;em&gt;Supabase&lt;/em&gt; in a structured way. We went ahead by trial and error and reading only the part of the documentation that could be useful to us (usually this is not the best way to study)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Qwik&lt;/em&gt; is nice but it is definitely different from known frameworks, although JSX, hooks, routes are also used in other frameworks. At first you feel something "strange" and you bang your head on it but then, once you understand its logic, I must say it is a pleasure to develop with it.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Supabase&lt;/em&gt; has a nice dev experience and a really well written doc. On the other hand, realtime feature seemed a little slow compared to Firebase.&lt;/li&gt;
&lt;li&gt;Overall we are happy with how it went, partly because we used both &lt;em&gt;Qwik&lt;/em&gt; and &lt;em&gt;Supabase&lt;/em&gt; for the first time and, in a few days, we were able to develop a working app with very little effort. These are two tools that we will keep in mind also for more complex projects, taking advantage of doing a deep dive to study them properly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Next steps / Improvements
&lt;/h3&gt;

&lt;p&gt;Of course there are certainly improvements that can be made. I don't know if we will do them but I’d like to list them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improving &lt;strong&gt;DB security&lt;/strong&gt; configuring &lt;em&gt;Supabase&lt;/em&gt; Policies.&lt;/li&gt;
&lt;li&gt;Adding a &lt;strong&gt;toggle theme button&lt;/strong&gt; to select the preferred theme (currently, the default OS theme is used)&lt;/li&gt;
&lt;li&gt;Improving the UI of the &lt;strong&gt;admin section&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Managing the &lt;strong&gt;&lt;code&gt;config&lt;/code&gt; table directly from UI&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code cleanup&lt;/strong&gt;: revise folder structure a bit, clean up comments and unused code, use shared patterns in various pages/components&lt;/li&gt;
&lt;li&gt;…and more that I can't think of now!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;I leave the link to the &lt;a href="https://github.com/danielzotti/fortigames"&gt;GitHub repo&lt;/a&gt; so you can browse through the code. If you want to try using the app you just need to use the repo code and follow the steps to create your own DB on&lt;br&gt;
&lt;em&gt;Supabase&lt;/em&gt; and your own app on Google Cloud console.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Demo
&lt;/h2&gt;

&lt;p&gt;You'll have to settle for a video, I'm sorry...&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wRukdA-ZBAs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;This was a very long post... I would say it took more time to write the article than the actual development work! 😅&lt;br&gt;
I hope the tutorial is clear and useful for someone. And, as usual, if you have questions or you need more information&lt;br&gt;
about a specific part, don't hesitate to leave a comment! 🙃&lt;/p&gt;

&lt;p&gt;❤️ Thanks for reading it! ❤️&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>qwik</category>
      <category>supabase</category>
      <category>vercel</category>
    </item>
    <item>
      <title>#LearnedToday: ":empty" pseudo class</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Fri, 13 Oct 2023 09:52:57 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-empty-pseudo-class-40e3</link>
      <guid>https://dev.to/danielzotti/learnedtoday-empty-pseudo-class-40e3</guid>
      <description>&lt;p&gt;I just bumped into this &lt;a href="https://www.youtube.com/shorts/wk79huqm1h4"&gt;Kevin Powell's video&lt;/a&gt; and it blew my mind! 😱&lt;/p&gt;

&lt;p&gt;😠 How many times did I have to use JavaScript to hide an HTML element just because it was empty?&lt;/p&gt;

&lt;p&gt;😎 With the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:empty"&gt;&lt;code&gt;:empty&lt;/code&gt;&lt;/a&gt; pseudo class it became as simple as adding &lt;code&gt;:empty&lt;/code&gt; to a selector!&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="nf"&gt;#task-list&lt;/span&gt;&lt;span class="nd"&gt;:empty&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;none&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;blockquote&gt;
&lt;p&gt;This code means that the element with ID &lt;code&gt;task-list&lt;/code&gt; will be hidden if it has no children. Children can be either element nodes or text (including whitespace!)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🧑🏻‍💻 As usual, I created a simple &lt;a href="https://stackblitz.com/edit/js-cbbotr?file=index.html,index.js,style.css"&gt;stackblitz project&lt;/a&gt; (a todo list app) to test this feature!&lt;/p&gt;

&lt;p&gt;This is the basic structure:&lt;/p&gt;

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

&lt;p&gt;and thanks to CSS &lt;code&gt;:empty&lt;/code&gt; pseudo class, in case there's no tasks, I could easily move from this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm30atu61wcmyllxx5cw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm30atu61wcmyllxx5cw.png" alt="example without :empty pseudo class" width="459" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...to this!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkz5d3bcunae98a36xex0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkz5d3bcunae98a36xex0.png" alt="example with :empty pseudo class" width="453" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;How cool is that?!&lt;/em&gt;&lt;/strong&gt; 😍&lt;br&gt;
CSS is getting easier to use every day! In my opinion, studying it and keeping up to date with its new features can definitely make our work easier!&lt;/p&gt;

&lt;p&gt;P.S. the funny thing is that &lt;code&gt;:empty&lt;/code&gt; pseudo class is &lt;a href="https://caniuse.com/mdn-css_selectors_empty"&gt;supported&lt;/a&gt; since Chrome 4 and IE9 😅&lt;/p&gt;

</description>
      <category>css</category>
      <category>learnedtoday</category>
      <category>webdev</category>
    </item>
    <item>
      <title>#LearnedToday: Web Workers</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Thu, 21 Sep 2023 10:22:37 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-web-workers-4kkb</link>
      <guid>https://dev.to/danielzotti/learnedtoday-web-workers-4kkb</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API"&gt;Web Workers&lt;/a&gt;&lt;/strong&gt; are a powerful feature in modern web development, enabling JavaScript to run concurrently in the &lt;em&gt;background without blocking the main user interface thread&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;traditional web development&lt;/strong&gt; model, JavaScript code executes on the &lt;em&gt;same thread as the user interface&lt;/em&gt;. This means that computationally intensive tasks, such as complex calculations, data processing, or network requests, can significantly slow down the user experience. Users may encounter &lt;strong&gt;unresponsive web pages or even complete freezes when a script monopolizes the CPU&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web Workers&lt;/strong&gt; address this problem by introducing a &lt;strong&gt;multi-threaded approach to JavaScript&lt;/strong&gt; execution &lt;strong&gt;&lt;em&gt;within the browser&lt;/em&gt;&lt;/strong&gt;. With them, you can create separate threads that run concurrently alongside the main application thread. &lt;/p&gt;

&lt;h2&gt;
  
  
  Key points to understand Web Workers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File&lt;/strong&gt;: Web Workers are just JavaScript files loaded by the main thread.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;: Web Workers provide true concurrency by executing JavaScript code in parallel. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: Each Web Worker runs in isolation from the main thread and other workers. They have their own global scope, which means they cannot directly access variables or functions defined in the main thread or other workers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Communication&lt;/strong&gt;: Communication between workers and the main thread is achieved through message passing (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"&gt;&lt;code&gt;postMessage&lt;/code&gt;&lt;/a&gt;), ensuring data integrity and safety.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Background Processing&lt;/strong&gt;: Web Workers are ideal for offloading resource-intensive operations like data parsing, image processing, or lengthy computations to prevent them from affecting the user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No UI Access&lt;/strong&gt;: Web Workers are unable to access the Document Object Model (DOM) directly, as they run outside the main thread. This limitation ensures that UI operations remain exclusive to the main thread, preventing potential conflicts and race conditions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Browser Support&lt;/strong&gt;: Web Workers are &lt;a href="https://caniuse.com/webworkers"&gt;widely supported in modern browsers&lt;/a&gt;, making them a viable technology for a broad range of web applications. However, it's essential to check browser compatibility when implementing a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker"&gt;Worker&lt;/a&gt; in your projects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok, too much chat... Let's dive into the code as usual!&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Worker support
&lt;/h2&gt;

&lt;p&gt;First of all. We need to make sure that the browser supports Web Workers. We do this by checking if the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker"&gt;&lt;code&gt;Worker&lt;/code&gt;&lt;/a&gt; instance is present in the window object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Your browser doesn't support Web Workers!`&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;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Web Worker creation
&lt;/h2&gt;

&lt;p&gt;To use a Web Worker, simply create a new instance of a &lt;code&gt;Worker&lt;/code&gt;, passing the &lt;strong&gt;URL&lt;/strong&gt; of the associated &lt;em&gt;file&lt;/em&gt; as an argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&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;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./worker.js&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;
  
  
  Prepare the communication with the Web Worker
&lt;/h2&gt;

&lt;p&gt;To "connect" main thread with the Web Worker we need to listen to the messages the Web Worker might send using its exposed methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&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;// when an error occurs in the worker&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// everytime a worker use `postMessage` to send a message&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessageerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// e.g. the message from the worker can't be serialized&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Communication
&lt;/h2&gt;

&lt;p&gt;In order to communicate with the web worker &lt;em&gt;from the main thread&lt;/em&gt;, we just push a message using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"&gt;&lt;code&gt;postMessage&lt;/code&gt;&lt;/a&gt; method of the &lt;code&gt;Worker&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Do something&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;p&gt;...and inside the worker we listen to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent"&gt;&lt;code&gt;message&lt;/code&gt;&lt;/a&gt; and read the &lt;code&gt;data&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// worker.js&lt;/span&gt;
&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="c1"&gt;// { message: 'Do something'}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can do the other way round by calling the &lt;code&gt;postMessage&lt;/code&gt; inside the worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// worker.js&lt;/span&gt;
&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A message from the worker!&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;p&gt;...and react to the message event in the main thread using the &lt;code&gt;onmessage&lt;/code&gt; method of the worker instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="c1"&gt;// { message: 'A message from the worker!'}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The whole code
&lt;/h2&gt;

&lt;p&gt;Let's recap the basic code for the two files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main thread
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Your browser doesn't support Web Workers!`&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;;&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;worker&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;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessageerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Do something&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;h3&gt;
  
  
  Web Worker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// worker.js&lt;/span&gt;
&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Do something&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;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Ok, I'll do!`&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`I won't do anything...`&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;blockquote&gt;
&lt;p&gt;NB: &lt;code&gt;postMessage&lt;/code&gt; is the only way the Web Worker can comunicate with the main thread and viceversa!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;And as always, can't miss the &lt;a href="https://web-workers-playground.danielzotti.it/"&gt;demo&lt;/a&gt; and open source &lt;a href="https://github.com/danielzotti/web-workers-playground"&gt;project&lt;/a&gt; on GitHub!&lt;/p&gt;

&lt;p&gt;The project guides the user through a series of steps to follow, and the idea is to show the &lt;em&gt;difference&lt;/em&gt; between a &lt;strong&gt;long-running task&lt;/strong&gt; performed by the &lt;em&gt;main thread&lt;/em&gt; (which blocks the UI) and one performed by a &lt;em&gt;web worker&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NB: &lt;strong&gt;&lt;em&gt;Performances are the same but with the Web Worker the UI is still usable.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fg9ktq4skbmtq0w0ppp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fg9ktq4skbmtq0w0ppp.png" alt="Web Workers Playground preview" width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To &lt;em&gt;simulate&lt;/em&gt; a long-running task I used this simple code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;longTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&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;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;sum&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;



</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>webworkers</category>
      <category>learnedtoday</category>
    </item>
    <item>
      <title>#LearnedToday: Notifications in Browsers</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Fri, 15 Sep 2023 10:34:09 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-notifications-in-browsers-4ji0</link>
      <guid>https://dev.to/danielzotti/learnedtoday-notifications-in-browsers-4ji0</guid>
      <description>&lt;p&gt;How nice would it be to have notifications in browsers like a regular mobile application?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Well, we already have it!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are just a couple of concepts we need to master before start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser support&lt;/li&gt;
&lt;li&gt;Notifications permissions&lt;/li&gt;
&lt;li&gt;Service Workers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Does the browser support it?
&lt;/h2&gt;

&lt;p&gt;First of all, we need to check if the browser supports the &lt;code&gt;Notification&lt;/code&gt; and the &lt;code&gt;serviceworker&lt;/code&gt; features. Without these services we cannot manage notifications!&lt;/p&gt;

&lt;p&gt;It is as easy as typing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serviceWorker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No Service Worker support&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="k"&gt;if &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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No Notification support&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NB: This applies to any browser feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notifications permissions
&lt;/h2&gt;

&lt;p&gt;In order to activate notifications on a specific device, the user must be asked if they want to receive notifications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestPermission&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An alert will pop up in the browser asking the user to respond...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr9xzh3lkr2aue0u0vq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr9xzh3lkr2aue0u0vq6.png" alt="Notification Permissions Request" width="620" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...and our application must handle the user's choice in this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestPermission&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;permission&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;permission&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;granted&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;// The user clicked on "Allow" button&lt;/span&gt;
    &lt;span class="c1"&gt;// Browser CAN send notifications&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&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;permission&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;denied&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;// The user clicked on "Block" button&lt;/span&gt;
    &lt;span class="c1"&gt;// Browser CANNOT send notifications&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&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;permission&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&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;// The user closed the popup&lt;/span&gt;
    &lt;span class="c1"&gt;// we can ask for permissions again if we want!&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;Okay, now we may be ready to send our first notification to the user, but first let's talk about ServiceWorker!&lt;/p&gt;

&lt;h2&gt;
  
  
  ServiceWorker
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"&gt;MDN&lt;/a&gt; &lt;strong&gt;Service Workers&lt;/strong&gt; essentially act as &lt;strong&gt;&lt;em&gt;proxy servers&lt;/em&gt;&lt;/strong&gt; that sit between web applications, the browser, and the network (when available). &lt;/p&gt;

&lt;p&gt;Practically speaking, a &lt;code&gt;ServiceWorker&lt;/code&gt; is a &lt;strong&gt;JavaScript file&lt;/strong&gt; that runs in the &lt;em&gt;background&lt;/em&gt; in a &lt;em&gt;separate thread&lt;/em&gt;. It must be registered (installed) first, and then it can talk with the application (NB: &lt;code&gt;https&lt;/code&gt; is mandatory!)&lt;/p&gt;

&lt;p&gt;Why do we need a ServiceWorker to send notification? Because it runs in the background even if our application page is not open in the browser! And, above all, because the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration"&gt;&lt;code&gt;SerciveWorkerRegistration&lt;/code&gt;&lt;/a&gt; has a method called &lt;code&gt;showNotification&lt;/code&gt; which is used to displays the notification on the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send a notification
&lt;/h2&gt;

&lt;p&gt;We just have to wait until the service worker is ready and call the method by passing the notification title as the first parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&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;swRegistration&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;swRegistration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My First notification!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These few lines of code will show the notification:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdm1aldwfvjdv3ldv7g5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdm1aldwfvjdv3ldv7g5.png" alt="Notification popup on a Mac" width="390" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notification properties
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification"&gt;&lt;code&gt;showNotification&lt;/code&gt;&lt;/a&gt; method has a second argument that accepts an object of options which allows us to customize the notification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;icon&lt;/code&gt;: an URL representing an image that will be displayed next to the notification title&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;image&lt;/code&gt;: an URL representing an image that will be displayed as a content of the notification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;actions&lt;/code&gt;: an array of actions that will result in a list of clickable buttons below the notification content

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;action&lt;/code&gt;: a unique string representing the ID of the action&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt;: a human readable string to be displayed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;icon&lt;/code&gt;: an URL representing the image next to the title&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt;: A string representing an extra content to display within the notification.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;badge&lt;/code&gt;: a string containing the URL of an image to represent the notification when there is not enough space to display the notification itself such as for example, the Android Notification Bar.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt;: Arbitrary data that you want to be associated with the notification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tag&lt;/code&gt;: An ID for a given notification that allows you to find, replace, or remove the notification using a script if necessary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;silent&lt;/code&gt;: if &lt;code&gt;true&lt;/code&gt;, no vibration or alert sound&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vibrate&lt;/code&gt;: an array of numbers representing the vibration patter&lt;/li&gt;
&lt;li&gt;...and many more!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  "Come back" notification
&lt;/h2&gt;

&lt;p&gt;Let's try creating together a "come back" notification that shows up when the application lose focus and hides automatically when it is on focus again. It also takes the user directly back to the application if the notification is clicked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visibilitychange&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="o"&gt;=&amp;gt;&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibilityState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&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="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Come baaaaack!&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Click here and come back to the website!`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;come-back&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// required if silent is set to "true"&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="nx"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&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;registration&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;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNotifications&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;come-back&lt;/span&gt;&lt;span class="dl"&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;notifications&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;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;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;h2&gt;
  
  
  Notifications without ServiceWorker
&lt;/h2&gt;

&lt;p&gt;"&lt;em&gt;What?! You just said that ServiceWorker are required to send notification!!&lt;/em&gt;"&lt;br&gt;
I know... There is an alternative method which doesn't involve &lt;code&gt;ServiceWorker&lt;/code&gt; actually, but it relies on a deprecated feature! The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification"&gt;&lt;code&gt;window.Notification&lt;/code&gt;&lt;/a&gt; constructor is not marked as deprecated, however, it’s marked as deprecated in Chrome on Android, thus it won't work on mobile devices! This is why I preferred to use the ServiceWorker method directly!&lt;/p&gt;

&lt;p&gt;I'm going to write an example because it might be interesting for those who don't deal with mobile browsers!&lt;/p&gt;
&lt;h2&gt;
  
  
  "OLD" way: Notification constructor
&lt;/h2&gt;

&lt;p&gt;Let's re-create the "come back" notification using the &lt;code&gt;Notification&lt;/code&gt; constructor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;comeBackNotification&lt;/span&gt;&lt;span class="p"&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;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visibilitychange&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="o"&gt;=&amp;gt;&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibilityState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&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="nx"&gt;comeBackNotification&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;Notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Come baaaaack!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;comeBackNotification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&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="nx"&gt;e&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Notification CLOSED!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;comeBackNotification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="nx"&gt;e&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Notification CLICKED!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&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;comeBackNotification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;h2&gt;
  
  
  Good to know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Starting in Chrome 49, notifications don't work in incognito mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;notification.vibrate&lt;/code&gt; doesn't work in Android &amp;gt; 8.0&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sw.js&lt;/code&gt;, if we don't need it, it could be just an empty file!&lt;/li&gt;
&lt;li&gt;Chrome for Android requires the call to be made with a service worker registration&lt;/li&gt;
&lt;li&gt;iOS requires website to first be added to the Home Screen&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wait Until
&lt;/h3&gt;

&lt;p&gt;One of the things to understand about service workers is that you have little control over when the service worker code is going to run. The browser decides when to wake it up and when to terminate it. The only way you can tell the browser to wait, is to pass a promise into the &lt;code&gt;event.waitUntil()&lt;/code&gt; method. With this, the browser will keep the service worker running until the promise you passed in has settled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: `sw,js`&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&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;promiseChain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Push Notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promiseChain&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;h2&gt;
  
  
  Push Notifications (from remote server)
&lt;/h2&gt;

&lt;p&gt;The essential difference between &lt;em&gt;local notifications&lt;/em&gt; and &lt;em&gt;push notifications&lt;/em&gt; is simple: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local notifications&lt;/strong&gt; are scheduled by an app locally and are &lt;em&gt;delivered by the same device&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push notifications&lt;/strong&gt; are sent by a &lt;em&gt;remote server&lt;/em&gt; which sends these notifications to devices on which the app is installed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won't talk about the server side part, thus I'm going to explain how to simulate a Push Notification using Chrome DevTools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simulate a Push Notification with Chrome DevTools:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open Chrome DevTools&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;Application&lt;/code&gt; tab&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Service Workers&lt;/code&gt; on the left&lt;/li&gt;
&lt;li&gt;Write a custom message on the right anche click &lt;code&gt;Push&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3n70dfybf2qfe7io6zr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3n70dfybf2qfe7io6zr.png" alt="Test Push Notification message on Chrome DevTools" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;As usual, I created a &lt;a href="https://push-notifications-playground.danielzotti.it"&gt;demo&lt;/a&gt; and a &lt;a href="https://github.com/danielzotti/push-notifications-local"&gt;GitHub project&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While running the demo, check the &lt;code&gt;console&lt;/code&gt; for more information.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>learnedtoday</category>
      <category>notification</category>
    </item>
    <item>
      <title>#LearnedToday: Drag&amp;Drop API</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Fri, 08 Sep 2023 17:58:04 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-dragdrop-api-1hkm</link>
      <guid>https://dev.to/danielzotti/learnedtoday-dragdrop-api-1hkm</guid>
      <description>&lt;p&gt;👀 I have never used &lt;strong&gt;drag&amp;amp;drop&lt;/strong&gt; functionality with vanilla JavaScript. &lt;/p&gt;

&lt;p&gt;In the enterprise applications I work on every day, I usually rely on frameworks such as &lt;em&gt;Angular&lt;/em&gt;, &lt;em&gt;React&lt;/em&gt;, or &lt;em&gt;Vue&lt;/em&gt; that provide easier ways to handle it and, in addition, solve the classic problems that I would have to handle by hand.&lt;/p&gt;

&lt;p&gt;💡 That's why I wanted to give it a try, developing a very basic project in HTML and JS.&lt;/p&gt;

&lt;p&gt;Here, briefly, is what I figured out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In order to activate the dragging functionality on a element, it must be set the &lt;code&gt;draggable&lt;/code&gt; attribute on it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt; &lt;span class="na"&gt;draggable=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Drag me&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If we need to add some actions (e.g. save element's data for future purpose) once the element is "grabbed", a listener to the &lt;code&gt;dragStart&lt;/code&gt; event must be added to it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"my-item"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;
  &lt;span class="na"&gt;draggable=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; 
  &lt;span class="na"&gt;ondragstart=&lt;/span&gt;&lt;span class="s"&gt;handleDragStart(event)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Drag me&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;handleDragStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are dragging &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If we want to drop the selected element somewhere, we need to create one or more drop zones in the HTML. 
In order to create them, we need to make the target element listen to &lt;code&gt;dragOver&lt;/code&gt; and &lt;code&gt;drop&lt;/code&gt; events.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dropzone"&lt;/span&gt;
  &lt;span class="na"&gt;ondrop=&lt;/span&gt;&lt;span class="s"&gt;"handleDrop(event)"&lt;/span&gt;
  &lt;span class="na"&gt;ondragover=&lt;/span&gt;&lt;span class="s"&gt;"handleDragOver(event)"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Drop the dragging element here!
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;handleDrop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prevent default action (e.g. open as link for some elements)&lt;/span&gt;
  &lt;span class="c1"&gt;// CODE HERE (e.g. append element to the drop zone)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;handleDragOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Required to allow Drop event&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's pretty much it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;As usual, I created a &lt;a href="https://stackblitz.com/edit/dz-drag-drop?file=index.js"&gt;stackblitz project&lt;/a&gt; where you can use drag&amp;amp;drop to switch placements in a podium and choose which is the best 🏆 framework/library among Angular🥇, Vue🥈, and React🥉! (Try to guess what my ranking is 😁)&lt;/p&gt;

&lt;p&gt;And here is the link to the &lt;a href="https://dz-drag-drop.stackblitz.io"&gt;demo&lt;/a&gt; if you just want to play with it!&lt;/p&gt;

&lt;p&gt;P.S. I did not use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/dataTransfer"&gt;dataTransfer&lt;/a&gt; property, but I will create a more "data-driven" demo to explain this function as well in the future.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>learnedtoday</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>#LearnedToday: FileSystemAccess API</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Tue, 29 Aug 2023 08:52:00 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-filesystemaccess-api-2361</link>
      <guid>https://dev.to/danielzotti/learnedtoday-filesystemaccess-api-2361</guid>
      <description>&lt;p&gt;🤔  What if you could manage the device's files with a web app frontend?&lt;/p&gt;

&lt;p&gt;😱  Well, YOU CAN ALREADY DO IT!!!&lt;/p&gt;

&lt;p&gt;📚 The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_API"&gt;FileSystemAPI&lt;/a&gt; allows you to read, write and manage the device files and folders.&lt;/p&gt;

&lt;p&gt;😍 This is what we needed to finally get rid of desktop apps once and for all! Combined with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps"&gt;PWA&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API"&gt;Push Notification&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"&gt;ServiceWorker&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API"&gt;WebWorker&lt;/a&gt; functionalities, we are now able to create an "installable desktop web app".&lt;/p&gt;

&lt;p&gt;😢 Unfortunately, not all that glitters is gold... It’s currently supported only by &lt;strong&gt;Chromium&lt;/strong&gt; browsers, but I'm sure it will be supported by the other browsers in a short time 💪&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo project
&lt;/h2&gt;

&lt;p&gt;👨‍💻  I created a &lt;strong&gt;vanilla JavaScript&lt;/strong&gt; &lt;a href="https://github.com/danielzotti/file-explorer-web"&gt;opensource project&lt;/a&gt; to test &lt;code&gt;FileSystemAPI&lt;/code&gt; features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading file properties&lt;/li&gt;
&lt;li&gt;reading a directory with its children recursively (with a little help of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator"&gt;AsyncGenerators&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController"&gt;AbortController&lt;/a&gt; for huge directories)&lt;/li&gt;
&lt;li&gt;media preview (with the help of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static"&gt;&lt;code&gt;createObjectURL&lt;/code&gt;&lt;/a&gt; to create the URL for &lt;code&gt;src&lt;/code&gt; attribute of &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, from the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer"&gt;&lt;code&gt;ArrayBuffer&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;create/edit/delete a text file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 &lt;a href="https://danielzotti.github.io/file-explorer-web"&gt;Demo&lt;/a&gt; (remember to use &lt;strong&gt;Chrome&lt;/strong&gt;!)&lt;/p&gt;

&lt;p&gt;❓ Did you know about &lt;strong&gt;FileSystemAPI&lt;/strong&gt;? In my opinion it is &lt;em&gt;one of the most revolutionary APIs&lt;/em&gt; of recent years!&lt;/p&gt;

</description>
      <category>learnedtoday</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>file</category>
    </item>
    <item>
      <title>#LearnedToday: EyeDropper Browser API</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Mon, 21 Aug 2023 12:02:32 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-eyedropper-browser-api-5h06</link>
      <guid>https://dev.to/danielzotti/learnedtoday-eyedropper-browser-api-5h06</guid>
      <description>&lt;h2&gt;
  
  
  How to open a color picker &lt;em&gt;natively&lt;/em&gt; in the Browser
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eyeDropper&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;EyeDropper&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&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;colorSelectionResult&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;eyeDropper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// It will wait until the user selects a color&lt;/span&gt;

  &lt;span class="c1"&gt;// This part is executed once the user has selected the color&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colorSelectionResult&lt;/span&gt;&lt;span class="p"&gt;)&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;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// User has canceled the selection&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is an object and this will be the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; { sRGBHex: "#433633" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docs
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to explore other features of &lt;em&gt;EyeDropper API&lt;/em&gt; you can browse&lt;br&gt;
the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper_API"&gt;MDN Doc&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Browser support
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this API is marked as "experimental" as of 20th August 2023 and it seems to work on last versions of Chrome&lt;br&gt;
and Edge only.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to check whether your browser supports this feature, you can&lt;br&gt;
browse &lt;a href="https://caniuse.com/mdn-api_eyedropper"&gt;caniuse.com&lt;/a&gt; to have more details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Theory is useless without practice, thus I created&lt;br&gt;
a &lt;a href="https://stackblitz.com/edit/js-fzhgfp?file=index.js"&gt;stackblitz project&lt;/a&gt; to show how it works!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>browserapi</category>
      <category>webdev</category>
      <category>learnedtoday</category>
    </item>
    <item>
      <title>#LearnedToday: Pushable 3D button</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Fri, 18 Aug 2023 10:31:22 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-pushable-3d-button-203m</link>
      <guid>https://dev.to/danielzotti/learnedtoday-pushable-3d-button-203m</guid>
      <description>&lt;p&gt;🏖  I've been on vacation for almost 2 weeks and I think I only turned on my PC to write last week's post.... And so I will do this week!&lt;/p&gt;

&lt;p&gt;😎 I focused on something "lighter" like &lt;strong&gt;CSS&lt;/strong&gt; and I created a configurable &amp;amp; responsive 3D &lt;strong&gt;button&lt;/strong&gt; using a mixture of &lt;code&gt;box-shadows&lt;/code&gt;, &lt;code&gt;isolation: isolate&lt;/code&gt;, &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt; pseudo elements and a bit of &lt;strong&gt;math&lt;/strong&gt;, without using additional elements but a button tag!&lt;/p&gt;

&lt;p&gt;💪  Actually, I used &lt;strong&gt;SCSS&lt;/strong&gt; harnessing the power of mixins to make it reusable and configurable. At the moment, you can decide the min/max/viewport based size and you can change the aspect ratio, depth, color and font size.&lt;/p&gt;

&lt;p&gt;⚠️ This is a very first version and I'm sure the calculations can be simplified, but I'd say it's working decently 😎 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F426gt2nm2x8kmdaqglu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F426gt2nm2x8kmdaqglu6.png" alt="Doomsday app preview" width="697" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⏱  You can see it in action in the &lt;a href="https://doomsday.danielzotti.it"&gt;Doomsday project demo&lt;/a&gt; which is a sort of countdown to the doomsday.&lt;/p&gt;

&lt;p&gt;📰  I'll add the link to a &lt;a href="https://codepen.io/danielzotti/pen/LYMYxor"&gt;Codepen project&lt;/a&gt; where you can find the SCSS code to play with it.&lt;/p&gt;

</description>
      <category>css</category>
      <category>scss</category>
      <category>learnedtoday</category>
      <category>webdev</category>
    </item>
    <item>
      <title>#LearnedToday: Ambigrams</title>
      <dc:creator>Daniel Zotti</dc:creator>
      <pubDate>Thu, 10 Aug 2023 14:08:28 +0000</pubDate>
      <link>https://dev.to/danielzotti/learnedtoday-ambigrams-40n0</link>
      <guid>https://dev.to/danielzotti/learnedtoday-ambigrams-40n0</guid>
      <description>&lt;p&gt;Vacation mode: on!&lt;/p&gt;

&lt;p&gt;As of this Monday, I am on &lt;strong&gt;vacation&lt;/strong&gt; 🏖  and will return to work as soon as early September.... I don't think I've ever taken a full month off in my entire life, not even for my honeymoon!!&lt;/p&gt;

&lt;p&gt;Anyway, I'm trying to get away from the 💻 PC for a while and these first few days of vacation I went to the beach in Trieste on skates ⛸ (yes, just like in the 90's! 🕺), started reading a book 📖  that has nothing to do with programming, and played a game of Pickleball 🏓 with a friend of mine (if you don't know what Pickleball is don't worry, I didn't know what it was until yesterday either!).&lt;/p&gt;

&lt;p&gt;Anyway, while immersed in my vacation, I would like to share a &lt;strong&gt;graphic design project&lt;/strong&gt; of mine and talk about something that may not be so well known to many: &lt;strong&gt;AMBIGRAMS&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;To make it short and easy, they are &lt;strong&gt;graphically symmetrical words&lt;/strong&gt;; in this case, I created an &lt;em&gt;ambigram of my name&lt;/em&gt;! 🤓 &lt;/p&gt;

&lt;p&gt;You can see a &lt;a href="https://www.danielzotti.it/projects/ambigram"&gt;demo&lt;/a&gt; and find additional information about ambigrams on my website. 🙃 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx21r3dz0mhu840kko14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx21r3dz0mhu840kko14.png" alt="Daniel's ambigram" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you want to know how I developed the animation on scroll 👨‍💻, I'll share a link to the &lt;a href="https://github.com/danielzotti/new.danielzotti.it/tree/github/src/app/projects/ambigram/components"&gt;open source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And you? &lt;em&gt;Did you know about ambigrams&lt;/em&gt;? Have you created or simply seen one with your name?&lt;/p&gt;

</description>
      <category>graphics</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
