<?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: Chris Dermody</title>
    <description>The latest articles on DEV Community by Chris Dermody (@chipd).</description>
    <link>https://dev.to/chipd</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%2F105061%2F92657891-6689-42f3-8a40-1f57281ad027.jpg</url>
      <title>DEV Community: Chris Dermody</title>
      <link>https://dev.to/chipd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chipd"/>
    <language>en</language>
    <item>
      <title>Updating relative time in UI with Nuxt3</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Fri, 24 Feb 2023 15:52:53 +0000</pubDate>
      <link>https://dev.to/chipd/updating-relative-time-in-ui-with-nuxt3-2ikm</link>
      <guid>https://dev.to/chipd/updating-relative-time-in-ui-with-nuxt3-2ikm</guid>
      <description>&lt;p&gt;I bumped into this problem when working on a side project - &lt;a href="https://aihairstyles.com?r=devto"&gt;aihairstyles.com&lt;/a&gt;. When users generate a new set of styles, I want to show them when that set was generated, relative to now. I also want that time to update dynamically as time goes on, without the need for them to refresh the page. Finally, I want to use this logic throughout my app in lots of places.&lt;/p&gt;

&lt;p&gt;I solved it with a combination of global state, date-fns library and a small plugin I wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a state variable to hold a "ticker"
&lt;/h2&gt;

&lt;p&gt;In my &lt;code&gt;useState.ts&lt;/code&gt; file, I'm adding a variable that we'll update later via a plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const useTicker = () =&amp;gt; useState&amp;lt;any&amp;gt;('ticker', () =&amp;gt; new Date().getTime())
2. Create a plugin to update this ticker at a given interval

// globalTicker.client.ts

export default defineNuxtPlugin(nuxtApp =&amp;gt; {

  // updating a global ticker in useState that can be used to auto-update times in the UI

  const ticker = useTicker()

  const TICKER_INTERVAL = 30000; // milliseconds

  setInterval(() =&amp;gt; {
    let newVal = new Date().getTime();
    ticker.value = newVal
  }, TICKER_INTERVAL);

})
With this plugin, every 30 seconds the ticker will update (30000 milliseconds).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Install date-fns library (or a lib of your choosing)
&lt;/h2&gt;

&lt;p&gt;I used to use moment.js, but date-fns is my go-to now as it's smaller and can be tree-shaken. But use whatever makes you happy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install date-fns --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Create a composable that exports a relative time function
&lt;/h2&gt;

&lt;p&gt;As I mentioned, I wanted this logic throughout my app in lots of places, so creating a composable was the way to go.&lt;/p&gt;

&lt;p&gt;To do this, I created a file called &lt;code&gt;useDateHelpers.ts&lt;/code&gt; in my &lt;code&gt;/composables&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /composables/useDateHelpers.ts

import formatDistance from 'date-fns/formatDistance'

export const relativeDate = (date:string) =&amp;gt; {
  console.log("date:", date)
  const ticker = useTicker()
  return formatDistance(new Date(date), ticker.value)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we're importing our global ticker and using it, making our function's return value reactive in Vue's eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Finally, use the function in our components
&lt;/h2&gt;

&lt;p&gt;Then, in the html, we use this function and pass in the created_date and the global ticker variable. In the code below we have a for-loop that loops over all image sets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- rest of preview page ) --&amp;gt;

&amp;lt;h2 class="gradient-text-1 text-2xl"&amp;gt;
    {{preview.data.style}}
&amp;lt;/h2&amp;gt;
&amp;lt;p class="text-sm muted mb-3"&amp;gt;
    {{ relativeDate(preview.data.result_data?.created_at)}} ago
&amp;lt;/p&amp;gt;

&amp;lt;!-- rest of preview page --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result? We have relative time updating in our UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--930bsb75--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50adte89ui4znkx365ti.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--930bsb75--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50adte89ui4znkx365ti.gif" alt="Image description" width="880" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think it works quite well. &lt;/p&gt;

&lt;p&gt;Any suggestions for improvement? Let me know :)&lt;/p&gt;

&lt;p&gt;Also, I'm actively building &lt;a href="https://aihairstyles.com?r=devto"&gt;aihairstyles.com&lt;/a&gt;, would love feedback on that too 🙏🏼.&lt;/p&gt;

&lt;p&gt;Chris&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>vue</category>
    </item>
    <item>
      <title>I built Pushpop - a PushBullet alternative for iOS using Nuxt3 with CapacitorJS</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Mon, 26 Sep 2022 10:17:39 +0000</pubDate>
      <link>https://dev.to/chipd/i-built-pushpop-a-pushbullet-alternative-for-ios-using-nuxt3-with-capacitorjs-2bf6</link>
      <guid>https://dev.to/chipd/i-built-pushpop-a-pushbullet-alternative-for-ios-using-nuxt3-with-capacitorjs-2bf6</guid>
      <description>&lt;p&gt;&lt;em&gt;If you'd just like to try it out, visit &lt;a href="https://getpushpop.com"&gt;getpushpop.com&lt;/a&gt;. To know the story, read on&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Late last year I moved to iOS. 'Twas a learning curve of course, but the hardest part? No PushBullet :(&lt;/p&gt;

&lt;p&gt;I would use PushBullet daily on Android to move files, text and links from my phone to my laptop and vice-versa. I also used it as a kind-of "to-do" app for links or Reddit posts I find on mobile that I want to look into more. Yes I could bookmark it or add it to a todo list, but with Pushbullet I could just push it from my phone, and next time I open my laptop, it'll open up in my browser so I can read it, download it, or save for later in some form. It's great.&lt;/p&gt;

&lt;p&gt;I wanted that on iOS, but it looks like it's just not economically viable for them to do it, as I found in &lt;a href="https://www.reddit.com/r/PushBullet/comments/eirc1m/not_available_on_ios/"&gt;this Reddit post:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For those not aware, fully updating our iOS app for the latest versions of Swift, iOS, etc and then adding Sign in with Apple is a huge amount of work. Sadly iOS is our least popular / used platform. It's also the platform we can to the least interesting things. As a result, we've chosen to unpublish it for the time being to focus on the platforms where we can do more interesting things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, damn. What's a reasonably semi-capable developer to do, ay?&lt;/p&gt;

&lt;h2&gt;
  
  
  Oh look, Nuxt3 just went into release candidate stage...
&lt;/h2&gt;

&lt;p&gt;I love Vue, and have been using Nuxt for the "marketing" portion of a few of my side projects like &lt;a href="https://reservadesk.com/"&gt;Reservadesk&lt;/a&gt;, &lt;a href="https://referextra.com/"&gt;Referextra&lt;/a&gt;, and &lt;a href="https://livedata.ninja/"&gt;Livedata.ninja&lt;/a&gt; to much success. I love how it builds to static HTML and gets those sweet sweet Lighthouse scores.&lt;/p&gt;

&lt;p&gt;So of course, over a few nights and weekends I go and build the web app using Nuxt3. And it works pretty well (although I'll talk about some problems I had later). Before too long, I was able to get it to a minimum viable product, so to speak.&lt;/p&gt;

&lt;p&gt;Then, we needed to get the app running on mobile devices. For this, I turned to &lt;a href="https://capacitorjs.com/"&gt;Capacitorjs&lt;/a&gt;. I was a bit worried at first, I know building cross-platform mobile applications can be a bit of a minefield, but I really like Capacitorjs. The documentation is great, the ecosystem of plugins is healthy, and I was able to achieve everything I wanted with it (although I'm still, as of time of writing, trying to get Android to work).&lt;/p&gt;

&lt;p&gt;After some back and forth and some wrangling, I was able to publish the app on the app store, check it out here - &lt;a href="https://apps.apple.com/ie/app/pushpop/id1633893054"&gt;Pushpop&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  And it works!
&lt;/h2&gt;

&lt;p&gt;Using my plucky little app, I'm now able to share anything on my phone to pushpop, and have it appear as a notification on my laptop, and vice versa. It makes sending links, screenshots, photos - anything - from one device to another easy.&lt;/p&gt;

&lt;p&gt;It does not do some of the more fancier things that PushBullet can do on Android like SMS management etc, but I don't need that. And I'm the primary target market for this app 😂.&lt;/p&gt;


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/551c858e56284ec5b38cfc31c6cef154"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Problems I found with Nuxt3
&lt;/h2&gt;

&lt;p&gt;Sorry but I still cannot figure out how to use plugins like I did in the older version. Any plugins that have to render a html component I just couldn't get to work, like one of my favourites &lt;a href="https://www.npmjs.com/package/vue-json-pretty"&gt;vue-json-pretty&lt;/a&gt;. If someone knows, please enlighten me.&lt;/p&gt;

&lt;p&gt;Another thing I was trying to do was build middleware which would automatically redirect the user to https if the request was http. I still haven't figured it out and am relying on the frontend to detect and redirect 🤮. I think I need to build a Nitro plugin to handle it but that's brand new as a technology it seems and is quite light on documentation and guides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else did I learn?
&lt;/h2&gt;

&lt;p&gt;Building for iOS is a certifiable nightmare. Why do Apple make things so unbelievably difficult?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terrible documentation like &lt;a href="https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundledocumenttypes/cfbundletypename"&gt;this page&lt;/a&gt; which is supposed to tell you what CFBundleTypeName is in your info.plist file. But no. It just tells you it's required, is a string, and its name is Document Type Name. Trying to cobble together what this value actually means is next to impossible.&lt;/li&gt;
&lt;li&gt;Bugs in Xcode where the Apple developer forum just recommends downgrading to an older version.&lt;/li&gt;
&lt;li&gt;The inability to test push notifications properly in the simulator&lt;/li&gt;
&lt;li&gt;App Store Connect which is an absolute nightmare to try and navigate and figure out where you are and what you're doing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nuxt3&lt;/li&gt;
&lt;li&gt;TailwindCSS&lt;/li&gt;
&lt;li&gt;CapacitorJS&lt;/li&gt;
&lt;li&gt;Node&lt;/li&gt;
&lt;li&gt;Heroku&lt;/li&gt;
&lt;li&gt;Firebase&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools tried
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tauri. I saw this on fireship's Youtube channel and wanted to give it a try. Turns out it's just a little too new so I turned to Electron&lt;/li&gt;
&lt;li&gt;Electron. Turns out your iOS app can run on mac. Who knew? :D&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;There are a few things I'd like to do next with the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get it running on Android and put it in the Play store&lt;/li&gt;
&lt;li&gt;Some UI/UX improvements to make it easier to navigate and quicker to share things from within the app itself&lt;/li&gt;
&lt;li&gt;Fix a bug where it's re-counting devices for some reason (I think on app update)&lt;/li&gt;
&lt;li&gt;Add a configuration so that when it's running on a desktop device, it can auto-open links and files in the browser when they're received.&lt;/li&gt;
&lt;li&gt;(Maybe) see if I can get it running in Electron again and build it for Windows, but when I looked at that last time the documentation seemed lacking and confusing.&lt;/li&gt;
&lt;li&gt;Find out who this app might be great for. Explore use cases and see if there's a market for this that could make me some beer money. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've made it this far, I'd love your feedback. Comment below or check out &lt;a href="https://getpushpop.com"&gt;getpushpop.com/support&lt;/a&gt; (yes it's a support form but just put in whatever you think is good/bad 🙂)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>vue</category>
      <category>firebase</category>
    </item>
    <item>
      <title>My 9-5 just became a Unicorn. These are the top 5 features that helped us get there which every app should have</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Mon, 21 Feb 2022 12:06:58 +0000</pubDate>
      <link>https://dev.to/chipd/my-9-5-just-became-a-unicorn-these-are-the-top-5-features-that-helped-us-get-there-which-every-app-should-have-5dhb</link>
      <guid>https://dev.to/chipd/my-9-5-just-became-a-unicorn-these-are-the-top-5-features-that-helped-us-get-there-which-every-app-should-have-5dhb</guid>
      <description>&lt;p&gt;Three years ago I joined &lt;a href="https://flipdish.com"&gt;Flipdish&lt;/a&gt; as their first Product Manager. Back then it was a startup that had found product market fit and just needed to execute quickly and grow. I had product experience, but I think the kicker that got me the job was that I built my own app to track things like tips and delivery addresses when I worked as a fast food delivery driver, so I could hold my own technically too.&lt;/p&gt;

&lt;p&gt;I don’t code in Flipdish (officially) but I can’t keep my hands out of VSCode, so over Christmas I decided to build a &lt;a href="https://reservadesk.com/?utm_source=dev.to&amp;amp;utm_medium=forum&amp;amp;utm_campaign=flipdish"&gt;co-working app&lt;/a&gt; with my fiance. She did most of the backend (node/Express/Firestore) and I worked on the marketing site and web app (Nuxt/Vue3).&lt;/p&gt;

&lt;p&gt;Without realising it, I found myself building all the features that I saw make a huge difference to Flipdish’s success. I’ve never seen anyone write about these in a clear, easy list, so here we go. I wanted this list to be tactical, that anyone can take and build out a solid product with, so below are 5 things that I’ll be building from the start in all my future projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Audit logs
&lt;/h2&gt;

&lt;p&gt;Being able to see an “activity log” of actions taken in a given account is immensely powerful. In Flipdish it’s used daily by clients and support staff to debug any queries that may arise, or just to understand general activity on a given account. For &lt;a href="https://reservadesk.com/?utm_source=dev.to&amp;amp;utm_medium=forum&amp;amp;utm_campaign=flipdish"&gt;Reservadesk&lt;/a&gt;, it’s proven hugely helpful early on to not only understand account activity, but simply for quick development. I can see when I make changes and the before/after of what they were. I’ve written a separate post about best &lt;a href="https://chrisdermody.com/best-practices-for-audit-logging-in-a-saas-business-app/?utm_source=dev.to&amp;amp;utm_medium=forum&amp;amp;utm_campaign=flipdish"&gt;practices for audit logging&lt;/a&gt; if you’d like to learn more about what makes a useful audit log.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Granular user permissions
&lt;/h2&gt;

&lt;p&gt;When trying to figure out what way to configure your user permissions, it’s easy to do something simple, like giving yourself and your staff an “admin” permission (god mode, basically), and your users something general like “guest” or “teammate”. This will bite you later on, and is a tricky thing to unwind.&lt;/p&gt;

&lt;p&gt;From now on, all of my projects will have granular user permissions from the outset. It has minimal up-front cost, and gives you flexibility down the road. You can always “bundle” your granular permissions together so that it appears in UI as a general thing, but you’ll have that flexibility down the road should you need to sell to enterprise or grow into different markets/regions.&lt;/p&gt;

&lt;p&gt;In Flipdish, since we’ve exploded across the UK, Europe and North America, we have country managers, support staff, success staff, contractors etc, and they all need specific permissions to different things. Had we not had granular user permissions from the outset, this would have inevitably slowed us down and hindered growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Image manipulation API
&lt;/h2&gt;

&lt;p&gt;It’s pretty rare that the projects you build won’t have images in some form. Before joining Flipdish, I think I was simply serving huge images in my sites/apps with minimal optimisation. Rookie error. I’ve since discovered a whole new world of intelligent image optimisation via services Imgix or imagekit.io. They both offer what is effectively an API layer on top of your images, meaning you can build your UI and simply pass in query parameters to “transform” the image based on what the UI needs at that particular time.&lt;/p&gt;

&lt;p&gt;For instance, let’s say your user uploads an huge 1000px x 1000px image, which will only be used as a small icon. You don’t want to load that massive image on a mobile device.&lt;/p&gt;

&lt;p&gt;With the image manipulation API you can request that image, but at a smaller size, like 40px x 40px. The service will transform it on the fly for you. It’s very impressive. Once you start using this, you’ll never go back in my opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Localisation
&lt;/h2&gt;

&lt;p&gt;I can see you rolling your eyes at this one, but hear me out. You don’t need to localise your app into multiple languages from the outset, but building in i18n from the start is something I’ll do religiously from now on, for two reasons.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The cost is low. I still dev in English, but then quickly create the strings in localise.biz (or similar service) before finally building a release for production. If sometime down the line I see that the app is popular in a given region that has other languages, it’s fairly trivial to get your app translated and you’re away.&lt;/li&gt;
&lt;li&gt;You can let your customer define their own locale customisations. This gives your app a layer of customisation that’s relatively simple to achieve, but can be a real differentiator. Users can sometimes just want a certain word to say something else. In &lt;a href="https://reservadesk.com/?utm_source=dev.to&amp;amp;utm_medium=forum&amp;amp;utm_campaign=flipdish"&gt;reservadesk&lt;/a&gt;, we built a system where our users can define certain words themselves, like the main call to action to reserve a desk can say “book now” or “reserve” - whatever the customer wants. In Flipdish, we have customisation requests every single day from clients and our ability to accommodate their requests has been central to keeping them as clients.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. Referral scheme
&lt;/h2&gt;

&lt;p&gt;This one doesn’t necessarily need much dev work initially, but it’s something I’ll be keeping in mind for all future projects. Letting your customers who love your app be rewarded for sending others your way is an absolute win-win. In Flipdish we’ve had partners and affiliates as a key strategic element of our growth plans across the globe. Having a robust system for rewarding your best referrers in a systematic way will absolutely help you down the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus - your API is an asset
&lt;/h2&gt;

&lt;p&gt;One huge thing I’ve learned in Flipdish is that your API isn’t just something you need for your UI to work. If you go about it right, building an open, public, well-documented API can make magic happen. You’ll find that companies start building things on top of your APIs for clients who need it, making them extremely sticky to your platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extra bonus - we’re hiring - remotely
&lt;/h2&gt;

&lt;p&gt;My Fiance and I are looking for a house, which I'm sure you know is not cheap so we need some sweet sweet referral bonuses. If you like the sound of working somewhere like Flipdish, &lt;a href="https://grnh.se/f6341a92teu"&gt;we're hiring&lt;/a&gt;. Our stack is primarily C# on backend, React/React Native frontend, as well as native iOS and Android development in Swift &amp;amp; Kotlin.&lt;/p&gt;

&lt;p&gt;I’m Chris Dermody on &lt;a href="https://www.linkedin.com/in/chrisdermody/"&gt;Linkedin&lt;/a&gt;, or &lt;a href="https://twitter.com/cderm"&gt;cderm&lt;/a&gt; on Twitter. I'm more than happy to answer any questions about any roles, or get you in touch with the person who can answer them if I can’t 🙂&lt;/p&gt;

</description>
      <category>product</category>
      <category>webdev</category>
      <category>career</category>
      <category>startup</category>
    </item>
    <item>
      <title>Making audit logs sexy - best practices for audit logging with examples</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Sat, 19 Feb 2022 11:31:45 +0000</pubDate>
      <link>https://dev.to/chipd/making-audit-logs-sexy-best-practices-for-audit-logging-with-examples-1pab</link>
      <guid>https://dev.to/chipd/making-audit-logs-sexy-best-practices-for-audit-logging-with-examples-1pab</guid>
      <description>&lt;p&gt;It might be a sign of my ever-growing age, but I've fallen deeply in love with a robust, detailed audit logging system. For all of my future projects, it'll be one of the first features I build. Knowing who did what, when they did it, and what was changed, is hugely powerful in not only debugging business logic, but getting a quick glance at a user's account activity and understanding how they're using your software. &lt;/p&gt;

&lt;p&gt;When searching for "best practices for audit logging", I didn't find anything, so I thought I'd try fill that void with my experience in how massively helpful a good audit log is, and how to go about building it. &lt;/p&gt;

&lt;h2&gt;
  
  
  What are audit logs?
&lt;/h2&gt;

&lt;p&gt;Audit logs are effectively an activity log of things that happened in a given entity (usually an "account").&lt;/p&gt;

&lt;p&gt;For instance, for one of my side projects - &lt;a href="https://chrisdermody.com/best-practices-for-audit-logging-in-a-saas-business-app/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=audit-logs-best-practices" rel="noopener noreferrer"&gt;reservadesk.com&lt;/a&gt;, we log all activity within an "organisation" so that admin members (and us internally) who manage the organisation can see who changed what, and when. It makes it very easy to debug customer problems, as well as simply being able to track active accounts and see what people are doing as they poke about at your app.&lt;/p&gt;

&lt;p&gt;What's more, a lot of large clients will expect this kind of thing, and are willing and able to pay for it.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What audit logs are NOT
&lt;/h2&gt;

&lt;p&gt;Audit logs are not like console logs in the console, or backend logging in your API like you might have in Datadog, Rapid7 or some other log provider. Those type of logs have equal, if not more, importance. But they serve a different purpose. Those types of logs are for your developers or technical support to interrogate and check for problems. Audit logging is for your customers themselves (so they can see changes to things over time) and also your support/success staff for issue resolution and, to a lesser degree, usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning from Flipdish
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.flipdish.com" rel="noopener noreferrer"&gt;Flipdish&lt;/a&gt; is my day job. I work in Product and at the time of writing, we just reached Unicorn status with our latest round of investment. Since joining three years ago, it's been a whirlwind, to say the least, so right now feels like a good opportunity to pause and reflect on some of the things I've learned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chrisdermody.com/flipdish-just-reached-unicorn-status-ive-learned-a-lot-here-are-5-features-that-ill-be-building-in-my-future-products-from-the-start/?utm_source=dev.to&amp;amp;utm_medium=forum&amp;amp;utm_campaign=5-things-flipdish"&gt;Here's that list&lt;/a&gt;. One of the bigger things on that list audit logs. When a customer calls in with a query, being able to track down who changed what, and when, is a powerful tool to have in your arsenal to get any issues resolved as quickly and painlessly as possible.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://grnh.se/f6341a92teu" rel="noopener noreferrer"&gt;BTW - I'm hiring for product managers, and we're hiring for engineers too - get in touch if you're curious about any of the roles.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What should be logged?
&lt;/h2&gt;

&lt;p&gt;The first logical question when tackling audit logs, is what actually should be logged, and what shouldn't. A good rule of thumb is if something changes in your database, it should be logged. Typically, this means all POST, UPDATE, PATCH and DELETE requests. GET requests typically would not be logged, but that doesn't mean they can't be if you've got a compelling reason to know when someone viewed something, for instance.&lt;/p&gt;

&lt;p&gt;Ask yourself the question "As the customer managing the configuration or monitoring of this app, would I like to know when this activity has happened, by who, and when?". If the answer is a yes, then you need an audit log.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should be in an audit log?
&lt;/h2&gt;

&lt;p&gt;For me, there are a few key elements of an audit log that need to be there in order for it to be effective and useful to your team.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The entity that was changed.&lt;/strong&gt; What was the entity that was created/updated/deleted etc. In the example below, the entity was a "resource".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The type of event.&lt;/strong&gt; Was it an update, a deletion, or something else? Being able to glance at an audit log and know the action that was taken is essential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the event occurred.&lt;/strong&gt; You need to know when the event happened, and of course if you're showing a list of events, they should be in chronological order. Bonus points here if you show both the time, and relative time (eg "10 minutes ago")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who made the change.&lt;/strong&gt; Knowing who made the change is essential. Was it a support team member, or the customer themselves? Bonus points here for including the user ID, to make chasing down the culprit that bit quicker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What was changed?&lt;/strong&gt; Understanding the properties that were changed, the previous value, and the new value, is essential. Without this information, your audit logs are a lot less effective.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  How long should I keep audit logs?
&lt;/h2&gt;

&lt;p&gt;This is up to your business, but in my experience, typically any more than 3 months is overkill. If there is anything to debug, it'll usually crop up in that time frame. The only caveat to this would be large customers who want to retain logs for longer. This is of course an opportunity to upsell those clients onto a longer log retention period 😉.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apps that do audit logging well
&lt;/h2&gt;

&lt;p&gt;Of course I'm going to say &lt;a href="https://flipdish.com" rel="noopener noreferrer"&gt;Flipdish&lt;/a&gt; first, with &lt;a href="https://chrisdermody.com/best-practices-for-audit-logging-in-a-saas-business-app/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=audit-logs-best-practices" rel="noopener noreferrer"&gt;reservadesk.com&lt;/a&gt; as a close second!&lt;/p&gt;

&lt;p&gt;Other apps I've noticed that do it well are &lt;a href="https://stripe.com" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://split.io" rel="noopener noreferrer"&gt;split.io&lt;/a&gt; and &lt;a href="https://localise.biz" rel="noopener noreferrer"&gt;localize.biz&lt;/a&gt; - although in localize's case it's more of a light activity log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anything else I should know, Chris?
&lt;/h2&gt;

&lt;p&gt;I'm glad you asked. Once you have your audit log system set up, and it has appropriate filters, you should link to the audit logs from every entity that has them. In our examples above, this means that if you're logged in as an organisation teammate, and you view a resource like Desk 1, you should have a link that goes directly to your audit logs list page that has the filter pre-filled for that resource.&lt;/p&gt;

&lt;p&gt;Or even, better, display the logs for that item in the UI itself, no need to redirect the user.&lt;/p&gt;

&lt;p&gt;Did you like this post? &lt;a href="https://chrisdermody.com/subscribe" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; for more tidbits in product development, things I've learned from Flipdish, and more. I'm &lt;a href="https://twitter.com/cderm" rel="noopener noreferrer"&gt;@cderm&lt;/a&gt; on twitter and &lt;a href="https://www.linkedin.com/in/chrisdermody/" rel="noopener noreferrer"&gt;Chris Dermody on Linkedin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>product</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Roses are red, violets are bigger, I built a website to make your avatar a sticker</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Sun, 14 Feb 2021 15:13:18 +0000</pubDate>
      <link>https://dev.to/chipd/roses-are-red-violets-are-bigger-i-built-a-website-to-make-your-avatar-a-sticker-4ek6</link>
      <guid>https://dev.to/chipd/roses-are-red-violets-are-bigger-i-built-a-website-to-make-your-avatar-a-sticker-4ek6</guid>
      <description>&lt;p&gt;Check it out: &lt;a href="https://snoostickers.com?ref=devto"&gt;snoostickers.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few weeks back, my girlfriend (Hey &lt;a href="https://dev.to/k7y"&gt;Catia&lt;/a&gt; 😘) showed me the Snoovatar she built. I laughed because it was exactly like her in real life, down to the shoe colour. Immediately, I wanted it as a sticker for my laptop. &lt;/p&gt;

&lt;p&gt;Snoovatars are Reddit's version of Avatars that you can customise the look of. It was announced a few months back. They're pretty fun, and I thought if I wanted my girlfriend's snoovatar as a sticker, maybe others would, too. &lt;/p&gt;

&lt;p&gt;So, like any obsessed product-builder, I started Googling for APIs that I could use that would automate the actual sticker printing, and leave me to worry about the website. Luckily, I found &lt;a href="https://zazzle.com"&gt;Zazzle.com&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Zazzle's API is quite powerful, it has the ability to render images on top of items as if they were already printed on them. Check out the cup below. That's not a photo of a cup I ordered with her snoovatar printed on it - that's an API-generated image. That cup doesn't exist yet!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---tKbct_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/esitpy8xobqhqiwjthgm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---tKbct_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/esitpy8xobqhqiwjthgm.jpeg" alt="Zazzle api-generated image of cup with an avatar projected onto it" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I had a criticism of their API, it's that it's documented in PDF form. Really Zazzle, it's 2021, if you want developers to use your API, this needs to be better. &lt;br&gt;
That said, I was able to cobble together what I needed. A user can now add a link to their snoovatar and see what it'd look like on a range of products (currently just stickers, badges, cups and phone holders, but more to come). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--40-pTEpg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b40e2r1jsfb8gp0zhv0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--40-pTEpg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b40e2r1jsfb8gp0zhv0b.png" alt="Your avatar printed on stickers, badges, cups, phone holders and more" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Affiliate
&lt;/h2&gt;

&lt;p&gt;Zazzle also has an affiliate program, so in theory if someone clicks-through and orders something, I get a small commission. Maybe this side project will pay for its own servers! :D &lt;/p&gt;

&lt;h2&gt;
  
  
  The Future
&lt;/h2&gt;

&lt;p&gt;Well, when I finally got this built, I realised it's a bit silly to limit it to reddit snoovatars. It should also be able to show Gravatars, gaming avatars, and more. That's something I'll keep in the back pocket, for now. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech
&lt;/h2&gt;

&lt;p&gt;I wanted this as a very simple static page, so I used Nuxt - [everything I make is Vue based now].(&lt;a href="https://chrisdermody.com/products/)"&gt;https://chrisdermody.com/products/)&lt;/a&gt;). I'm hosting it on my own Digitalocean droplet. &lt;a href="https://chrisdermody.com/deploying-a-static-website-to-a-digitalocean-droplet/"&gt;Here's a post I made while making this project for my future self so that I remember how to deploy &lt;em&gt;another&lt;/em&gt; static site to the same server&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>watercooler</category>
      <category>vue</category>
    </item>
    <item>
      <title>Tailwind CSS static navbar with shadow on scroll for Vue applications</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Sun, 02 Feb 2020 13:42:27 +0000</pubDate>
      <link>https://dev.to/chipd/tailwind-css-static-navbar-with-shadow-on-scroll-for-vue-applications-46o5</link>
      <guid>https://dev.to/chipd/tailwind-css-static-navbar-with-shadow-on-scroll-for-vue-applications-46o5</guid>
      <description>&lt;p&gt;I first wrote about this &lt;a href="https://chrisdermody.com/tailwind-css-static-navbar-with-shadow-on-scroll/?r=devto" rel="noopener noreferrer"&gt;over on my blog&lt;/a&gt; - really only for my own personal reference since I'll need this code again in my next project. But thought I'd share here too. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frlq57ltibd28kyy1otyr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frlq57ltibd28kyy1otyr.gif" alt="demo of scrolling effect for shadow on navbar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're not using Tailwind for your CSS needs - I highly recommend it. It's now a staple of my product building toolkit, and it just fits so well with Vue.js and Nuxt workflows that I can't imagine moving to something else.&lt;/p&gt;

&lt;p&gt;One thing about Tailwind is it leaves the Javascript to you. This is so it's library-agnostic.&lt;/p&gt;

&lt;p&gt;For most of my projects I want that smooth shadow under the navbar - here's the code I use to achieve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTML
&lt;/h2&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;nav&lt;/span&gt; 
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"{ 'scrolled': !view.atTopOfPage }"&lt;/span&gt; 
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fixed flex w-full bg-white border-b items-center justify-between flex-wrap p-5 m-auto top-0 animated"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, we're adding the &lt;code&gt;.scrolled&lt;/code&gt; class when the value in &lt;code&gt;view.atTopOfPage&lt;/code&gt; is false.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CSS
&lt;/h2&gt;

&lt;p&gt;I have a navbar component that I use throughout the app, so this code would go there. PS: Yes this is technically SCSS...&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;

&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="nc"&gt;.scrolled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@apply&lt;/span&gt; &lt;span class="nt"&gt;shadow-2xl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;Apply the shadow to the navbar when it has the class &lt;code&gt;scrolled&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Javascript
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;// in data, I like to store a view object with all &lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// the values I need for a component to manage &lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// it's 'view' state - ie loading, &lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// or in this case, if the user is at the top of the page or not&lt;/span&gt;&lt;br&gt;
&lt;span class="nf"&gt;data &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
            &lt;span class="na"&gt;atTopOfPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;br&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="c1"&gt;// a beforeMount call to add a listener to the window&lt;/span&gt;&lt;br&gt;
&lt;span class="nf"&gt;beforeMount &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="nb"&gt;window&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;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleScroll&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="c1"&gt;// the function to call when the user scrolls, added as a method&lt;/span&gt;&lt;br&gt;
    &lt;span class="nf"&gt;handleScroll&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;&lt;br&gt;
        &lt;span class="c1"&gt;// when the user scrolls, check the pageYOffset&lt;/span&gt;&lt;br&gt;
        &lt;span class="k"&gt;if&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;pageYOffset&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;br&gt;
            &lt;span class="c1"&gt;// user is scrolled&lt;/span&gt;&lt;br&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atTopOfPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atTopOfPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;br&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;br&gt;
            &lt;span class="c1"&gt;// user is at top of page&lt;/span&gt;&lt;br&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atTopOfPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atTopOfPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;br&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The Result&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Buttery smooth shadows on your navbar. Check it out in action on my product &lt;a href="https://referextra.com?r=devto" rel="noopener noreferrer"&gt;Referextra.com&lt;/a&gt;&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/khy10yI1eYI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>css</category>
    </item>
    <item>
      <title>Myself and a friend built a chrome extension manager in order to learn git. Best feature? Groups! Latest feature? Tabbing! 🙌💜</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Tue, 20 Aug 2019 12:36:25 +0000</pubDate>
      <link>https://dev.to/chipd/myself-and-a-friend-built-a-chrome-extension-manager-in-order-to-learn-git-best-feature-groups-latest-feature-tabbing-4mcf</link>
      <guid>https://dev.to/chipd/myself-and-a-friend-built-a-chrome-extension-manager-in-order-to-learn-git-best-feature-groups-latest-feature-tabbing-4mcf</guid>
      <description>&lt;p&gt;Built this chrome extension manager a few years ago with my good friend &lt;a href="https://twitter.com/ciaranmag"&gt;Ciaran Maguire&lt;/a&gt; when he was in South East Asia for 6 months and we both wanted to learn Git.&lt;/p&gt;

&lt;p&gt;Here's a &lt;em&gt;&lt;a href="https://chrome.google.com/webstore/detail/custom-chrome-extension-m/balnpimdnhfiodmodckhkgneejophhhm?hl=en"&gt;link to the extension for those interested&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We're both fiends for chrome extensions, and we wanted a way to quickly toggle them on or off so that they weren't eating resources when they didn't have to be. &lt;/p&gt;

&lt;p&gt;Also, we wanted to &lt;em&gt;group&lt;/em&gt; them so that you could just hit one button and that &lt;em&gt;group&lt;/em&gt; of extensions all turn on (or off). &lt;/p&gt;

&lt;p&gt;So, we built it. We hit 10,000 users recently and almost hitting 11,000, which is amazing. &lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Group extensions so you can quickly toggle them on or off in one go (handy for webdev, or adblocking etc)&lt;/li&gt;
&lt;li&gt;Can show chrome apps currently installed (via settings)&lt;/li&gt;
&lt;li&gt;Quickly uninstall extensions&lt;/li&gt;
&lt;li&gt;Quickly get to their homepage (if they have one)&lt;/li&gt;
&lt;li&gt;Compact or expanded view&lt;/li&gt;
&lt;li&gt;Material theme (built with materialise.css)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Latest Features
&lt;/h2&gt;

&lt;p&gt;We just added some stuff. I have a short video here: &lt;a href="https://chrisdermody.com/extension-manager-custom-chrome-update-1-0-3/"&gt;custom chrome extension manager latest update&lt;/a&gt; if you'd prefer video. ;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Tabbing
&lt;/h3&gt;

&lt;p&gt;Version 1.0.3 adds proper tabbing! This was really annoying me as when I opened the extension it auto-focuses the search, which is great, but tabbing from there didn't focus the first extension's toggle, and now it does! &lt;/p&gt;

&lt;p&gt;This means that, combined with a shortcut to open the chrome extension (configured at &lt;a href="https://dev.tochrome://extensions/shortcuts"&gt;chrome://extensions/shortcuts&lt;/a&gt;), you can completely manage your extensions with &lt;em&gt;just&lt;/em&gt; your keyboard. &lt;/p&gt;

&lt;h3&gt;
  
  
  Quickly search the chrome web store right from the extension
&lt;/h3&gt;

&lt;p&gt;Lets say you're not sure you have an extension installed, so you open custom chrome, and search for it. It's not there, but you want it.&lt;/p&gt;

&lt;p&gt;Well now, we give you a shortcut to the web store with that search term, so you can quickly get an extension you need. &lt;/p&gt;




&lt;p&gt;Just wanted to share here, never mentioned it before, and also to ask for feedback. We'd love your thoughts. &lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Which dev.to loading GIF do you prefer?</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Thu, 01 Aug 2019 10:16:57 +0000</pubDate>
      <link>https://dev.to/chipd/i-made-some-dev-to-loading-gifs-which-is-best-i1m</link>
      <guid>https://dev.to/chipd/i-made-some-dev-to-loading-gifs-which-is-best-i1m</guid>
      <description>&lt;p&gt;So I've gone ahead and made some dev.to loading gifs based on the logo. Which one's your favourite? &lt;br&gt;
&lt;em&gt;This large gif is a bit choppy - see the actual gifs further below ;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fppe8z0ilpi0pv0qtfviw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fppe8z0ilpi0pv0qtfviw.gif" alt="Which one is best?"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whenever I start a &lt;a href="https://chrisdermody.com/products/" rel="noopener noreferrer"&gt;side product&lt;/a&gt;, one of the first things that pops into my mind is the loading animation. Lately, I've found a nice tool that I'd like to share called &lt;a href="https://www.animaapp.com/" rel="noopener noreferrer"&gt;Anima&lt;/a&gt;. It's a sketch plugin that lets you animate things like this. It's quite powerful and I'm still discovering more about the tool every week. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If anyone's interested, I'll make a quick tutorial showing how to make the GIF everyone likes, and maybe then I'll jump in and try contribute to the project on Github - that'd be cool :)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first thoughts were what if the "V" moved to the left, replicating a console, and then "loading" just scrolled across...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqb0wtz2xaqbgub8slcic.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fqb0wtz2xaqbgub8slcic.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I quite like this, but it wasn't quite right. After chatting with my lady friend (who's also a dev, hey Catia 😘), she suggested doing something like "npm run DEV" - which I really like the idea of. So, we came up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fczf56wunsqu2jkzx9yqi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fczf56wunsqu2jkzx9yqi.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And a slight variation on that&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F90l6yftawx9kbiqyejlu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F90l6yftawx9kbiqyejlu.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Some other options
&lt;/h3&gt;

&lt;p&gt;It's always good to have options! While building the above, I thought what if I just faded the letters in and out sequentially. So:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0vbfzhjcsjs0u716x88o.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0vbfzhjcsjs0u716x88o.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And another version of the above&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F664ckj1det1xhop9mwni.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F664ckj1det1xhop9mwni.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which do you prefer?
&lt;/h2&gt;

&lt;p&gt;I know which is my favourite, but let me know what you think is best, and obviously if you've any ideas for how to improve it, lemme know. &lt;/p&gt;

&lt;p&gt;PS: you can check out some of my other animations over &lt;a href="https://chrisdermody.com/tag/animation/?r=devto" rel="noopener noreferrer"&gt;here&lt;/a&gt;, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chrisdermody.com/css-only-coding-loading-animation-step-by-step-tutorial/?r=devto" rel="noopener noreferrer"&gt;This "coding" animation I made with CSS and SVG&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chrisdermody.com/mkbhd-logo-animation-step-by-step-tutorial-in-svg-and-css/?r=devto" rel="noopener noreferrer"&gt;This one I made using MKBHD's logo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chrisdermody.com/if-tesla-had-a-loading-icon/?r=devto" rel="noopener noreferrer"&gt;Or this one I made using the Tesla logo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discuss</category>
      <category>showdev</category>
      <category>help</category>
    </item>
    <item>
      <title>Spotify's lowkey tech playlist is brain fuel for my coding</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Sun, 23 Jun 2019 09:49:57 +0000</pubDate>
      <link>https://dev.to/chipd/spotify-s-lowkey-tech-playlist-is-brain-fuel-for-my-coding-1lbc</link>
      <guid>https://dev.to/chipd/spotify-s-lowkey-tech-playlist-is-brain-fuel-for-my-coding-1lbc</guid>
      <description>&lt;p&gt;I'm always on the lookout for music to listen to while I code. Finding the right album or playlist can help me sink into deep work and I get lost in code for hours (in a good way, with deep focus I mean). &lt;/p&gt;

&lt;p&gt;Here's one from Spotify that I'm loving right now. Really gets me in the zone. Would love to hear suggestions for other good ones though. &lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://open.spotify.com/embed/spotify/37i9dQZF1DX0r3x8OtiwEM" width="100%" height="px"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Playlist link: &lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX0r3x8OtiwEM?si=f8MXDYSxQ_6-xE4-OPF67w"&gt;https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX0r3x8OtiwEM?si=f8MXDYSxQ_6-xE4-OPF67w&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eager to hear other's suggestions too - I find discovering music difficult nowadays that I don't listen to radio at all.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>healthydebate</category>
    </item>
    <item>
      <title>dev.to seemed to like my app tripcoster.com. Yesterday it made its first € 🙌🙌🙌</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Mon, 10 Jun 2019 12:17:54 +0000</pubDate>
      <link>https://dev.to/chipd/dev-to-seemed-to-like-my-app-tripcoster-com-yesterday-it-made-its-first-36c0</link>
      <guid>https://dev.to/chipd/dev-to-seemed-to-like-my-app-tripcoster-com-yesterday-it-made-its-first-36c0</guid>
      <description>&lt;p&gt;Just sharing a (very) small win. Two weeks ago I &lt;a href="https://dev.to/chipd/my-girlfriend-likes-to-travel-i-like-to-know-how-much-it-s-gonna-cost-vue-node-and-some-apis-later-i-give-you-tripcoster-com-1cja"&gt;shared with you guys&lt;/a&gt; an app I built in the last couple of months - &lt;a href="https://tripcoster.com?r=devto"&gt;tripcoster.com&lt;/a&gt;. It gives you the entire cost of a trip from anywhere, to anywhere, using live data for flights and accommodation. &lt;/p&gt;

&lt;p&gt;Well, just last night it got it's first little bit of referral revenue. Someone had clicked a link in my app and gone on to make a booking. &lt;/p&gt;

&lt;p&gt;The percentages mean that this sale only makes me €13, but to say I'm excited is an understatement. Really really eager to see where I can take this little app in the coming months. &lt;/p&gt;

&lt;p&gt;That's all, just wanted to share! :) &lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>vue</category>
    </item>
    <item>
      <title>My girlfriend likes to travel, I like to know how much it's gonna cost. Vue, node and some APIs later, I give you tripcoster.com</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Sat, 25 May 2019 13:37:59 +0000</pubDate>
      <link>https://dev.to/chipd/my-girlfriend-likes-to-travel-i-like-to-know-how-much-it-s-gonna-cost-vue-node-and-some-apis-later-i-give-you-tripcoster-com-1cja</link>
      <guid>https://dev.to/chipd/my-girlfriend-likes-to-travel-i-like-to-know-how-much-it-s-gonna-cost-vue-node-and-some-apis-later-i-give-you-tripcoster-com-1cja</guid>
      <description>&lt;p&gt;After a few weeks work, I'm happy to share &lt;a href="https://tripcoster.com?r=devto"&gt;tripcoster.com&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;For me, figuring out where is good value to travel to for a break away has always been a hassle. I don't know the &lt;em&gt;specific&lt;/em&gt; dates I might want to go on, I just sort-of want to browse. I want to ask an app "How much would it be for a weekend in Amsterdam, travelling from Dublin". So, I built one. :) &lt;/p&gt;

&lt;p&gt;Of course, you can also search specific dates too. &lt;/p&gt;

&lt;h2&gt;
  
  
  APIs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Kiwi/Tequila for flights. I found this one a lot easier to work with than Skyscanner - although it can be quite slow for the vague queries&lt;/li&gt;
&lt;li&gt;Hostelworld for accommodation. So far this has been reasonably solid, apart from the odd bit of downtime/errors. &lt;/li&gt;
&lt;li&gt;Unsplash for the photos - I actually need to add in the image attribution...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm pretty happy with the app so far. But, plenty more todo&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Other accommodation sources. Airbnb/Booking.com for some hotels/apartments etc&lt;/li&gt;
&lt;li&gt;Integrate Numbeo - an API for telling you how much stuff costs (like beers, for example) - so you can compare the cost of actually &lt;em&gt;being&lt;/em&gt; somewhere, not just getting there and staying there. &lt;/li&gt;
&lt;li&gt;Dynamic URL routing. At the moment, you can't share a search result link. That kinda sucks. For now though, it works. &lt;/li&gt;
&lt;li&gt;MAYBE user accounts and saving trips that you want to go on. Not sure about this one just yet. I quite like not needing a database for this app...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything else? Would love feedback on design, functionality etc. &lt;/p&gt;

&lt;p&gt;PS: I know the results can be slow to load, I'm working on backend caching and also I've some ideas to keep the user occupied while the app waits on the API response :) &lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>vue</category>
    </item>
    <item>
      <title>Last night my side project made one person happy, and someone else pissed off</title>
      <dc:creator>Chris Dermody</dc:creator>
      <pubDate>Mon, 18 Feb 2019 14:35:40 +0000</pubDate>
      <link>https://dev.to/chipd/last-night-my-side-project-made-one-person-happy-and-someone-else-pissed-off-8pn</link>
      <guid>https://dev.to/chipd/last-night-my-side-project-made-one-person-happy-and-someone-else-pissed-off-8pn</guid>
      <description>&lt;p&gt;I've been working on a side project, &lt;a href="https://mydevportfol.io?r=devto"&gt;mydevportfol.io&lt;/a&gt; for about 2 years now (on and off). It's finally at a point where it's good enough that people are willing to pay for it, which is an incredible feeling. &lt;/p&gt;

&lt;p&gt;I woke this morning a a Stripe notification that I'd gotten a sale overnight. Amazing! Definitely a great start to the day. &lt;/p&gt;

&lt;p&gt;But then I opened my emails and saw I had a message from a disgruntled user, that I had wasted their time because they thought it was free...&lt;/p&gt;

&lt;p&gt;These were two different people of course, and maybe it's just a natural thing to happen as more people find your app, and I think maybe as devs we're just used to cool stuff on the internet being free. &lt;/p&gt;

&lt;p&gt;But as devs I also think we need to make sure that our work is valued, and we should know that software isn't free. Sure, sometimes we'll build cool stuff and put it out there just for the fun of it - eg a &lt;a href="https://chrome.google.com/webstore/detail/custom-chrome-extension-m/balnpimdnhfiodmodckhkgneejophhhm?hl=en"&gt;chrome extension for managing other chrome extensions&lt;/a&gt; I built with a friend a while back, but that doesn't mean EVERYTHING we build has to be free. &lt;/p&gt;

&lt;p&gt;Just wanted to share - I'm a big proponent of letting people pay you for your work via donation buttons or putting a price tag on your side project - but happy to hear other thoughts. &lt;/p&gt;

</description>
      <category>showdev</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
