<?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: Peyton McGinnis</title>
    <description>The latest articles on DEV Community by Peyton McGinnis (@sergix).</description>
    <link>https://dev.to/sergix</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%2F183203%2F8abb2776-dd3d-438c-b32b-8e43176fb226.png</url>
      <title>DEV Community: Peyton McGinnis</title>
      <link>https://dev.to/sergix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sergix"/>
    <language>en</language>
    <item>
      <title>Conquering the Cloud Resume Challenge</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Fri, 22 Mar 2024 03:39:45 +0000</pubDate>
      <link>https://dev.to/sergix/the-cloud-resume-challenge-2mjo</link>
      <guid>https://dev.to/sergix/the-cloud-resume-challenge-2mjo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/Sergix/cloud-resume-challenge"&gt;GitHub Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://cloudresume.sergix.dev/"&gt;Completed Project&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🤔 What is the Cloud Resume Challenge?
&lt;/h2&gt;

&lt;p&gt;In developing skills for my future career, a friend recommended the  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://cloudresumechallenge.dev/docs/the-challenge/aws/"&gt;Cloud Resume Challenge (AWS Edition)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;as a practical way to develop cloud development and problem-solving skills. The challenge offers high-level instructions for building a basic serverless application with a static frontend using AWS, Google Cloud, or Azure. The AWS route took me about 3 days to complete (besides the recommended AWS certification).&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚔️ Take the challenge!
&lt;/h2&gt;

&lt;p&gt;I highly recommend anyone else trying to improve their cloud development skills to complete this project. It teaches you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to &lt;em&gt;think&lt;/em&gt; cloud -- frontend, backend, API endpoints, ...&lt;/li&gt;
&lt;li&gt;How to use a cloud platform like AWS&lt;/li&gt;
&lt;li&gt;How IaC simplifies provisioning and deploying your application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And, it improves your problem-solving skills and analysis of edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Backend

&lt;ul&gt;
&lt;li&gt;AWS SAM -- Serverless Application Model (deploys as AWS CloudFormation)

&lt;ul&gt;
&lt;li&gt;AWS API Gateway

&lt;ul&gt;
&lt;li&gt;AWS Lambda&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;AWS DynamoDB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Frontend

&lt;ul&gt;
&lt;li&gt;AWS CloudFront

&lt;ul&gt;
&lt;li&gt;AWS S3 static site&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Netlify + Netlify DNS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CI/CD: GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  (ง'̀-'́)ง The Challenge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;I created a basic HTML/CSS frontend with some placeholder content to start off with. I then initialized the git repo and pushed to GitHub using the GitHub CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  🪣 S3
&lt;/h3&gt;

&lt;p&gt;AWS S3 buckets are an inexpensive and convenient way to host static sites (plain HTML/JS/CSS). Normally I've used Netlify, but since this frontend doesn't have a build step, S3 is much easier.&lt;/p&gt;

&lt;p&gt;I wanted to use a subdomain of &lt;code&gt;sergix.dev&lt;/code&gt; to point to my static site, &lt;code&gt;cloudresume.sergix.dev&lt;/code&gt;. I found that the S3 bucket first has to explicitly be named &lt;code&gt;cloudresume.sergix.dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One of the biggest difficulties with learning AWS is &lt;em&gt;policy configuration&lt;/em&gt;. Everything in AWS has a designated &lt;em&gt;ARN&lt;/em&gt; (Amazon Resource Name) that designates each individual created resource. For example, my S3 bucket's ARN is &lt;code&gt;arn:aws:s3:::cloudresume.sergix.dev&lt;/code&gt;. Access policies for resources can then be configured for AWS IAM roles, users, or directly attached to the resource. To make the S3 bucket publicly accessible, a policy must be directly attached to the bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cloud-Resume-Challenge-S3-Policy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowStaticWebsiteAccess"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::cloudresume.sergix.dev/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows any client to &lt;code&gt;s3:GetObject&lt;/code&gt; and retrieve any object (&lt;code&gt;/*&lt;/code&gt;) in the bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  ↪️⤵️ DNS Routing
&lt;/h3&gt;

&lt;p&gt;My website &lt;code&gt;sergix.dev&lt;/code&gt; is hosted on Netlify, my favorite web development and deployment platform. My website's domain also routes its DNS through Netlify's DNS, and so I can configure any DNS records through Netlify.&lt;/p&gt;

&lt;p&gt;I created a CloudFront distribution to be in front of the S3 bucket and for DNS integration. The CloudFront distribution configures an &lt;em&gt;Origin&lt;/em&gt;, in this case an S3 static site bucket, to redirect traffic to. I then added a CNAME record in Netlify DNS that points to the CloudFront public distribution domain name assigned by AWS. This gives HTTP-only routing, but this will be changed to HTTPS in the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔒 HTTPS
&lt;/h3&gt;

&lt;p&gt;First, I created an SSL certificate in ACM (Amazon Certificate Manager) and then added a CNAME DNS record to &lt;code&gt;sergix.dev&lt;/code&gt; with the required validation information. AWS uses this key/value pair in the DNS record to verify that I own the domain and to appropriately provision the SSL certificate for my domain.&lt;/p&gt;

&lt;p&gt;I then added the new ACM certificate to my CloudFront distribution by just selecting the certificate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now, HTTPS is ready to go!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🛢 DynamoDB
&lt;/h3&gt;

&lt;p&gt;AWS DynamoDB is a &lt;strong&gt;document-based database&lt;/strong&gt; that flexibly stores items based on a primary key and each individual item can have its own defined columns. Creating a DynamoDB instance is easy to setup and only requires a primary key and primary key type. I chose &lt;code&gt;WebPropertyName: String&lt;/code&gt; as my primary key since I didn't know at first how many different pieces of data the frontend would want to store and need to access.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ Lambda API
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is a serverless function platform that runs Python, Node.js, or other server-side code whenever a request is received. I chose to write my function and tests in Python.&lt;/p&gt;

&lt;p&gt;This challenge requires a visitor count function that is displayed on the frontend. I decided to add an &lt;code&gt;action&lt;/code&gt; query parameter to the endpoint that performs one of two actions when the &lt;code&gt;GET&lt;/code&gt; API is requested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get&lt;/code&gt;: return the current visitor count&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;increment&lt;/code&gt;: increment then return the visitor count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The query parameters are available via the &lt;code&gt;event&lt;/code&gt; object's &lt;code&gt;queryStringParameters&lt;/code&gt; in the &lt;code&gt;lambda_handler&lt;/code&gt; function, which is the entry point for the Lambda function.&lt;/p&gt;

&lt;p&gt;The function uses the Python &lt;code&gt;boto3&lt;/code&gt; library developed by AWS to interact directly with the DynamoDB instance in the &lt;code&gt;visitorcount&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aws-sam/visitor_count/app.py
&lt;/span&gt;
&lt;span class="n"&gt;dynamodb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;visitorcount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;WebPropertyName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;VisitorCount&lt;/span&gt;&lt;span class="sh"&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;I also created an AWS API Gateway public endpoint that restricts any access to the function to the appropriate HTTP methods. I only have &lt;code&gt;GET&lt;/code&gt; enabled for this function, and so the gateway only allows those requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &amp;lt;/&amp;gt; Frontend JavaScript
&lt;/h3&gt;

&lt;p&gt;I added a classic XMLHttpRequest to the frontend (we're going pre-&lt;code&gt;Promise&lt;/code&gt; here...) that just calls the endpoint once the document loads and waits for a response:&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;// www/index.js&lt;/span&gt;

&lt;span class="c1"&gt;// when the document is loaded&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;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&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;var&lt;/span&gt; &lt;span class="nx"&gt;xhr&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;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

   &lt;span class="c1"&gt;// when the request finishes...&lt;/span&gt;
    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onreadystatechange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="c1"&gt;// if it was successful,&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// update HTML&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

   &lt;span class="c1"&gt;// prepare the request object&lt;/span&gt;
    &lt;span class="nx"&gt;xhr&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseURL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/visitor-count?action=increment&lt;/span&gt;&lt;span class="dl"&gt;"&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="c1"&gt;// send the request!&lt;/span&gt;
   &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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;However, I immediately ran into the dreaded &lt;code&gt;CORS&lt;/code&gt; errors when requesting the endpoint. After searching on StackOverflow and countless other resources for the configuration I was missing, I ended up with the following solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable authorization in the AWS API Gateway (since this endpoint is public anyway)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackoverflow.com/a/43029002"&gt;Manually add the CORS header to all responses in Lambda&lt;/a&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;'headers':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;'Access-Control-Allow-Origin':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'*'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⚙️ AWS SAM
&lt;/h3&gt;

&lt;p&gt;This challenge then asks you to reconstruct the three critical application resources into AWS's Serverless Application Model: DynamoDB, Lambda, and API Gateway.&lt;/p&gt;

&lt;p&gt;SAM is a simplified frontend for AWS CloudFormation designed specifically for serverless applications that typically utilize these resources. SAM is an &lt;em&gt;infrastructure as code&lt;/em&gt; (IaC) platform that takes a configuration file and any associated resources (code, static resources, ...) and automatically provisions and deploys those resources on AWS.&lt;/p&gt;

&lt;p&gt;This mindset shift from manually deploying resources and servers to automatic provision was difficult for me -- one of my biggest barriers was that IaC is not a procedural, batch-script-deployment way of thinking. With batch-script or Docker-style deployment, you step-by-step (1) authenticate, (2) provision, (3) upload/update, and (4) run. However, IaC brings this all together into a &lt;em&gt;declarative&lt;/em&gt;, rather than procedural, environment. The declarative (YAML) configuration is then automatically provisioned and executed (deployed) by the SAM platform. The advantage of IaC, and the problem that it solves, is that all of my resources are then plainly defined with specific versioning and resource requirements, and this configuration can be easily replicated.&lt;/p&gt;

&lt;h4&gt;
  
  
  SAM Templates
&lt;/h4&gt;

&lt;p&gt;SAM templates are similar to other YAML configuration formats. You can run all SAM-configuration locally using the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html"&gt;&lt;code&gt;sam&lt;/code&gt; CLI&lt;/a&gt;, which is packaged separately from the &lt;code&gt;aws&lt;/code&gt; CLI.&lt;/p&gt;

&lt;p&gt;After initializing the SAM directory using &lt;code&gt;sam init&lt;/code&gt;, I investigated the default YAML configuration. Each service is defined under the &lt;code&gt;Resources&lt;/code&gt; section and all AWS console configuration can be defined there. It does, however, take a while to find the approprite configuration object definition in the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification.html"&gt;SAM specification&lt;/a&gt;. (SAM objects can also use normal CloudFormation objects as well.)&lt;/p&gt;

&lt;p&gt;After tinkering with the Lambda resource configuration, I ended up with the following resource object that deploys my Lambda function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aws-sam/template.yaml&lt;/span&gt;

&lt;span class="na"&gt;VisitorCountFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
   &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;visitor_count/&lt;/span&gt;
         &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.lambda_handler&lt;/span&gt;
         &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.10&lt;/span&gt;
         &lt;span class="na"&gt;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;x86_64&lt;/span&gt;
         &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;VisitorCountHttpApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Api&lt;/span&gt;
            &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
               &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/visitor-count&lt;/span&gt;
               &lt;span class="na"&gt;Method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;VisitorCountHttpApi&lt;/code&gt; is an inline &lt;code&gt;EventSource&lt;/code&gt; object that &lt;em&gt;defines an API Gateway&lt;/em&gt;, instead of creating a separate resource definition.&lt;/p&gt;

&lt;p&gt;The DynamoDB resource is configured in just a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aws-sam/template.yaml&lt;/span&gt;

&lt;span class="na"&gt;VisitorCountTable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::SimpleTable&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;visitorcount&lt;/span&gt;
      &lt;span class="na"&gt;PrimaryKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WebPropertyName&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The Lambda function ensures that the appropriate table item exists.)&lt;/p&gt;

&lt;p&gt;In this current configuration, the DynamoDB table is deleted whenever new changes are deployed. To persist, a &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html"&gt;CloudFormation change set&lt;/a&gt; can be used, but I have not configured it yet.&lt;/p&gt;

&lt;h4&gt;
  
  
  👤 IAM Policies
&lt;/h4&gt;

&lt;p&gt;The most tedious part about setting up SAM was applying the proper IAM roles. In more secure setups, you would configure an IAM identity, but for this project I just created a normal IAM user and authenticated SAM using the IAM access key. The IAM role attaches all necessary access policies for DynamoDB, S3, Lambda, etc. However, SAM would continuously fail to deploy because of a missing access policy, so I had to manually update the IAM policies until all necessary services were included.&lt;/p&gt;

&lt;p&gt;In addition, an inline policy had to be attached directly to the Lambda resource in the SAM configuration to enable DynamoDB access, which is delegated from the IAM user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aws-sam/template.yaml&lt;/span&gt;

&lt;span class="na"&gt;VisitorCountFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
   &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DynamoReadWriteVisitorCountPolicy&lt;/span&gt;
            &lt;span class="s"&gt;Effect&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="s"&gt;Action&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:GetItem&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:Scan&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:Query&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:DescribeTable&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:PutItem&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dynamodb:UpdateItem&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 GitHub Actions
&lt;/h3&gt;

&lt;p&gt;This challenge requires GitHub actions pipelines for CI/CD. I thought that this would be one of the simpler parts of this project; however, it was unfortunately a painful process of trying to figure out how to setup SAM to work inside a workflow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Backend
&lt;/h4&gt;

&lt;p&gt;For SAM, you first have to authenticate on every workflow job using &lt;code&gt;aws-actions/configure-aws-credentials@v4&lt;/code&gt;, then grabbing the environment variables defined for those secrets in the repository. I was stuck for a while on this since I forgot that GitHub repos can have &lt;a href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment"&gt;sandboxed environments&lt;/a&gt; set up that contain separate credentials, and so I had to manually add that detail to the workflow job by adding &lt;code&gt;environment: aws-sam&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After this, though, it was a straightforward process of using &lt;code&gt;sam build&lt;/code&gt; and &lt;code&gt;sam deploy&lt;/code&gt; to deploy the repository &lt;code&gt;on: [push]&lt;/code&gt;. (Albeit, with even more IAM policy wrangling.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Tests
&lt;/h4&gt;

&lt;p&gt;Python tests were automatically generated by &lt;code&gt;sam init&lt;/code&gt;, so I tweaked them to run the AWS SAM Lambda function and API Gateway. The template worked very well by just running &lt;code&gt;pytest&lt;/code&gt; in the repository, and it tests both the &lt;code&gt;increment&lt;/code&gt; and &lt;code&gt;get&lt;/code&gt; actions by ensuring the function returns just an integer in the response body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aws-sam/tests/unit/test_handler.py
&lt;/span&gt;
&lt;span class="c1"&gt;# apigw_get_event: pytest fixture that returns a JSON request object
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_lambda_handler_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apigw_get_event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apigw_get_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&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;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, this test was added to the GitHub action by scaffolding &lt;code&gt;pytest&lt;/code&gt; and running the test. If the test succeeds, the SAM deployment runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/sam-pipeline.yaml&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pytest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-sam&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;pip install pytest&lt;/span&gt;
        &lt;span class="s"&gt;pytest&lt;/span&gt;
  &lt;span class="na"&gt;sam-build-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pytest&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Frontend
&lt;/h4&gt;

&lt;p&gt;Although the challenge asks you to split the frontend and backend into two repos, I decided to just split the resources in the same repo into two different subdirectories. Each workflow runs whenever a change is pushed to its respective directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/sam-pipeline.yaml&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws-sam/**'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thankfully, the frontend CI/CD configuration was much simpler than the backend configuration. Although AWS authentication is still needed, S3 has a &lt;code&gt;sync&lt;/code&gt; command that syncs the given directory with the designated bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .github/workflows/www.yaml&lt;/span&gt;

aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; ./ s3://cloudresume.sergix.dev &lt;span class="nt"&gt;--delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, if this command succeeds, it invalidates the CloudFront cache so that the edge cache contains the latest copy of the S3 bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .github/workflows/www.yaml&lt;/span&gt;

aws cloudfront create-invalidation &lt;span class="nt"&gt;--distribution-id&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.AWS_CLOUDFRONT_ID &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--paths&lt;/span&gt; &lt;span class="s1"&gt;'/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://cloudresume.sergix.dev/"&gt;Click here to see the final result!&lt;/a&gt;
&lt;/h2&gt;

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

&lt;p&gt;Although the project is complete, I would still like to accomplish the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Persist DynamoDB table across deploys using changesets&lt;/li&gt;
&lt;li&gt;Use a static endpoint for the API Gateway under my subdomain, i.e. &lt;code&gt;https://cloudresume.sergix.dev/visitor-count&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Complete an AWS certification&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Thank you for reading!
&lt;/h2&gt;

&lt;p&gt;You can find me and my blog at &lt;a href="https://sergix.dev"&gt;sergix.dev&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/Sergix/cloud-resume-challenge"&gt;GitHub Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://cloudresume.sergix.dev/"&gt;Completed Project&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
      <category>python</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Add CSS to your GitHub READMEs</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Wed, 09 Dec 2020 18:25:17 +0000</pubDate>
      <link>https://dev.to/sergix/add-css-to-your-github-readmes-gf8</link>
      <guid>https://dev.to/sergix/add-css-to-your-github-readmes-gf8</guid>
      <description>&lt;p&gt;Today on Hacker News, &lt;a href="https://news.ycombinator.com/item?id=25349068" rel="noopener noreferrer"&gt;a link to a repository popped up&lt;/a&gt; with the intriguing title,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"CSS in GitHub Readmes"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sindresorhus/css-in-readme-like-wat" rel="noopener noreferrer"&gt;The repository&lt;/a&gt; contains some interesting &lt;a href="https://github.com/sindresorhus/css-in-readme-like-wat/blob/master/header.svg?short_path=8ee6fdb" rel="noopener noreferrer"&gt;code&lt;/a&gt; and &lt;a href="https://github.com/sindresorhus/css-in-readme-like-wat/blob/master/explanation.md" rel="noopener noreferrer"&gt;explanation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it up
&lt;/h2&gt;

&lt;p&gt;In your repository, add a new SVG file with whatever name you like, such as &lt;code&gt;header.svg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, embed the image in your README using some plain ol' HTML:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;img src="header.svg" width="800" height="400"&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding your CSS
&lt;/h2&gt;

&lt;p&gt;The following template is all you need to get started. Add this code to your SVG file:&lt;/p&gt;


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

&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 800 400"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"&lt;a href="http://www.w3.org/2000/svg" rel="noopener noreferrer"&gt;http://www.w3.org/2000/svg&lt;/a&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;foreignObject&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"&lt;a href="http://www.w3.org/1999/xhtml" rel="noopener noreferrer"&gt;http://www.w3.org/1999/xhtml&lt;/a&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;span class="nt"&amp;gt;&amp;amp;lt;style&amp;amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;span class="c"&amp;gt;/* your CSS */&amp;lt;/span&amp;gt;
  &amp;lt;span class="nt"&amp;gt;&amp;amp;lt;/style&amp;amp;gt;&amp;lt;/span&amp;gt;

  &amp;lt;span class="c"&amp;gt;&amp;amp;lt;!-- your HTML --&amp;amp;gt;&amp;lt;/span&amp;gt;

&amp;lt;span class="nt"&amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;/foreignObject&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;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;
  How does this work?&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;According to the repo,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can put HTML (actually XHTML) and CSS inside a &amp;lt;foreignObject&amp;gt; tag inside a SVG file inside an &amp;lt;img&amp;gt; tag inside your readme. 🤯&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;foreignObject&amp;gt;&lt;/code&gt; tag is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject" rel="noopener noreferrer"&gt;a valid SVG element&lt;/a&gt; that can contain any form of XML data. The XML data type (&lt;em&gt;namespace&lt;/em&gt;) is specified using the inner tag's &lt;code&gt;xmlns&lt;/code&gt; attribute. In this case, we'll use the XHTML namespace:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;div xmlns="http://www.w3.org/1999/xhtml"&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now all of the code inside this &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; will be rendered as XHTML!&lt;/p&gt;

&lt;p&gt;Take extra care when writing XHTML markup. The browser forces much more strict rules than regular HTML, and you might notice some issues that the browser would typically ignore for non-XHTML markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about JavaScript?
&lt;/h2&gt;

&lt;p&gt;GitHub has very specific content loading policies on their servers, and when loading any type of asset the server first &lt;em&gt;sanitizes&lt;/em&gt; the data to ensure no foreign JavaScript executes in the browser. Trying to embed &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags or using inline &lt;code&gt;onclick&lt;/code&gt; attributes results in content policy errors in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can't I interact with the SVG's contents?
&lt;/h2&gt;

&lt;p&gt;When rendering the SVG, GitHub loads it inside an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag that removes the interactivity from the SVG. However, any animations are still rendered in the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Hope you enjoyed this fun experiment!&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Check out what I did with it &lt;a href="https://github.com/Sergix/sergix/blob/main/header.svg" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.sergix.dev" rel="noopener noreferrer"&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%2Fzumybxsrwwcl9hj8ap68.png" alt="sergix"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Let's develop a website that fits you. Don't settle for a template.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.sergix.dev" rel="noopener noreferrer"&gt;Visit my portfolio&lt;/a&gt; to see what I'm about.

</description>
      <category>github</category>
      <category>css</category>
      <category>html</category>
    </item>
    <item>
      <title>Effective application action design</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Sat, 05 Dec 2020 19:10:19 +0000</pubDate>
      <link>https://dev.to/sergix/ux-illuminating-intention-198k</link>
      <guid>https://dev.to/sergix/ux-illuminating-intention-198k</guid>
      <description>&lt;p&gt;&lt;a href="https://www.sergix.dev/"&gt;Read this on my blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Some of these recommendations are my own opinions on solving the core issue presented. As with many problems in UX, a panacea doesn't exist. A good designer will test their applications thoroughly to determine their best approach.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Importance of Intention&lt;/li&gt;
&lt;li&gt;Types of actions&lt;/li&gt;
&lt;li&gt;Order&lt;/li&gt;
&lt;li&gt;Reduce choices&lt;/li&gt;
&lt;li&gt;Separation&lt;/li&gt;
&lt;li&gt;Visual weight&lt;/li&gt;
&lt;li&gt;Microcopy&lt;/li&gt;
&lt;li&gt;Destructive primary actions&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;Research&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Importance of Intention &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;User Experience (UX) designers determine how we live our technological lives. UX engineers specifically design each interaction we experience with our devices by psychologically influencing how we interpret information through the organization and visual style of an interface. By composing accessible, user-friendly interfaces, the user has a positive response to the application, and will likely continue using it. A major part of interface design is providing the user with &lt;em&gt;actions&lt;/em&gt;. An interface can have a variety of user actions, each with a different intention and goal, that guide the user through the application and allow the user to accomplish tasks. However, this variety can overwhelm and confuse the user if not designed properly since each action visible on the interface adds &lt;em&gt;cognitive overhead&lt;/em&gt;. Each choice increases the complexity of the user's decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you, as a designer or developer, effectively guide the user to the action they should take?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of actions &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There are 3 main types of actions: &lt;strong&gt;primary&lt;/strong&gt;, &lt;strong&gt;secondary&lt;/strong&gt;, and &lt;strong&gt;tertiary&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Primary action —&lt;/strong&gt; the primary goal and intended action of an interface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secondary action —&lt;/strong&gt; an alternative to the primary action that is typically less utilized.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tertiary action —&lt;/strong&gt; a miscellaneous action the user could take.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NAdqcJgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yr6da43df33kujtlt9le.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NAdqcJgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yr6da43df33kujtlt9le.png" alt="Types"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each action type serves a different purpose for the user. Normally, the primary action has a &lt;em&gt;positive&lt;/em&gt; result, such as a dialog confirmation or form submission, while the secondary action is its negation, such as canceling a dialog's action or returning to the previous step in a form. Sometimes, though, these roles are flipped — the Destructive primary actions section later in this article describes these situations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Order &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--1aX7axvy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1323910121513226240/4bgfrt6T_normal.jpg" alt="Mario Martin profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Mario Martin
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @hydrosound
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      'Ok-key’ and ‘cancel-key’, which one should be set up on the left? It’s an eternal war of button sort between UI designers preferring ‘ok/cancel’ type and the adherents of ‘cancel/ok’ type&lt;br&gt;&lt;a href="https://twitter.com/hashtag/UI"&gt;#UI&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/UX"&gt;#UX&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/UX"&gt;#UX&lt;/a&gt;Design &lt;a href="https://twitter.com/hashtag/Webdesign"&gt;#Webdesign&lt;/a&gt;&lt;br&gt;&lt;a href="https://t.co/TUYPaFqAex"&gt;medium.muz.li/ok-key-and-can…&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      12:51 PM - 19 Apr 2018
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=986950410022936576" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=986950410022936576" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      3
      &lt;a href="https://twitter.com/intent/like?tweet_id=986950410022936576" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      10
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;The presented order of actions in an interface is a hotly debated topic among UX designers (see &lt;a href="https://ux.stackexchange.com/questions/1072/ok-cancel-on-left-right"&gt;this UX StackExchange question&lt;/a&gt; and the above tweet). The two sides of the argument are (as I have defined):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linguistic&lt;/strong&gt; order (&lt;em&gt;Primary action&lt;/em&gt; | &lt;em&gt;Secondary action&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Since most Western cultures and the English language read left-to-right, give the primary action first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2McQabEi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/k39qpy60dqzja621kyxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2McQabEi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/k39qpy60dqzja621kyxa.png" alt="Linguistic order"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Progressive&lt;/strong&gt; order (&lt;em&gt;Secondary action&lt;/em&gt; | &lt;em&gt;Primary action&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Most primary actions involve concluding a step or progressing to the next step in an application, so order the actions as if they were labeled &lt;em&gt;Previous&lt;/em&gt; and &lt;em&gt;Next&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rUsnlPtP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/z40tjnzs6irlnj06i70j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rUsnlPtP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/z40tjnzs6irlnj06i70j.png" alt="Progressive order"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both arguments have a good point. So what do we choose?&lt;/p&gt;

&lt;p&gt;In short, &lt;strong&gt;it doesn't really matter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://measuringuserexperience.com/SubmitCancel/"&gt;Surveys show&lt;/a&gt; that the order itself has marginal significance. What actually matters is consistency. If you choose to order your actions a certain way in one part of your application, maintain that order in your entire application. &lt;strong&gt;A user's first interaction will set their expectation&lt;/strong&gt;, and you must meet that expectation throughout their entire experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which order is most common?
&lt;/h3&gt;

&lt;p&gt;Some designers argue that since Windows is the most popular operating system and it uses the linguistic order, all web applications should use the same order. However, many style guides (including &lt;a href="https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/dialogs/"&gt;Apple's Human Interface Guidelines&lt;/a&gt;) implement progressive order. If you're building a web application, you can perform tests to find out which order your users are most familiar with, but ultimately it doesn't make much of a difference. &lt;/p&gt;

&lt;p&gt;Here's a few examples I found with what order they specify.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;UX Guide&lt;/th&gt;
&lt;th&gt;Linguistic Order&lt;/th&gt;
&lt;th&gt;Progressive Order&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple Human Interface Guidelines&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GNOME&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Material (+ Android)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows User Experience Guidelines&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New York State User Experience Toolkit&lt;/td&gt;
&lt;td&gt;✔️&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To clarify, I did not search for any specific order. The fact that the choice is split 50/50 is a coincidence, but it generously emphasizes the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if you have more than two options?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dsVvDWyH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cz9v6oiwcu7cxpw90mwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dsVvDWyH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cz9v6oiwcu7cxpw90mwn.png" alt="Three options"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The primary action and its secondary, negative action should be the first and last actions, in either order. This follows the &lt;a href="https://cxl.com/blog/serial-position-effect/"&gt;&lt;em&gt;Serial Position Effect&lt;/em&gt;&lt;/a&gt;, a composition of the &lt;a href="http://changingminds.org/explanations/theories/primacy_effect.htm"&gt;&lt;em&gt;Primacy Effect&lt;/em&gt;&lt;/a&gt; and &lt;a href="http://changingminds.org/explanations/theories/recency_effect.htm"&gt;&lt;em&gt;Recency Effect&lt;/em&gt;&lt;/a&gt;, which states that the first and last items in a list are psychologically perceived to have greater significance than those in the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduce choices &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lawsofux.com/hicks-law"&gt;Hick's Law&lt;/a&gt;, in essence, states that "the time it takes to make a decision increases with the number and complexity of choices."&lt;/p&gt;

&lt;p&gt;Users have to make countless choices when interacting with your application. To help the user efficiently decide on their intended action, a simple (but difficult) solution is to &lt;strong&gt;reduce the number of visible choices of actions the user can make&lt;/strong&gt;. This reduces the &lt;em&gt;cognitive overhead&lt;/em&gt; the application forces on the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An interface should only provide one primary action.&lt;/strong&gt; Each section of your application should have &lt;em&gt;one goal&lt;/em&gt;. However, multiple secondary or tertiary actions are acceptable since there could be various alternatives to the primary action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separation &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The visual separation of actions greatly influences how they are interpreted. Typically, actions that relate to the same objective (whether it be confirming or denying that objective) should be grouped together visually with similar visual style so that the user can easily discover related controls quickly.&lt;/p&gt;

&lt;p&gt;Visually separating controls results in interesting user behavior. Typically, when actions are placed on opposite ends of an interface horizontally, they are initially interpreted in &lt;em&gt;progressive order&lt;/em&gt;, even if they are not actually ordered that way.&lt;/p&gt;

&lt;p&gt;Incorrect &lt;em&gt;alignment&lt;/em&gt; of actions confuses the user. In modal dialogs, actions should be aligned to the right of the interface. In virtually every other type of interface, the actions should be left-aligned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--42-sGG6n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ap01dbw7zjuf0qz9wlaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--42-sGG6n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ap01dbw7zjuf0qz9wlaw.png" alt="Alignment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In general, &lt;strong&gt;keep primary and secondary actions in the same visual group&lt;/strong&gt; and aligned left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual Weight &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Make the primary action of an interface stand out to draw the user's attention. Various stylistic attributes can be applied to create visual prominence, such as color, border, and iconography. A combination of these can also be used to clarify importance and intention. Secondary actions should have a less prominent style that heavily contrasts with the primary action. Sufficient contrast also alleviates issues colorblind users might have when using your application. &lt;strong&gt;If you removed the action labels, could you identify the primary action?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Color
&lt;/h3&gt;

&lt;p&gt;Color is an invaluable tool in UX design since humans naturally associate colors with different emotions. Green evokes positive emotions and is commonly related to progression, such as the green signal on a traffic light, while red is normally associated with danger and urgency, such as a stop sign. Positive primary actions should have a positive color such as green or blue, and negative primary actions have a negative color such as red or orange. Secondary actions should be a neutral color such as gray.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lufXEyFE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cfgwg6pjaxr428q8g3a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lufXEyFE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cfgwg6pjaxr428q8g3a3.png" alt="Color"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice, however, the specific examples I used to illustrate the associations of green and red: traffic signals. Cultural differences should be a slight consideration in your designs since some cultures have atypical color associations. Analyze your target audience and decide whether you need to comply with these differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Icons
&lt;/h3&gt;

&lt;p&gt;Used strategically, icons clarify an action's purpose and effectively add visual weight. Buttons in particular benefit from the proper use of an icon preceding its label. Use icons only on primary actions since adding icons to secondary actions quickly confuses the user with unnecessary visual weight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VlopNQzb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/khwcn64z9pnb72qnat1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VlopNQzb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/khwcn64z9pnb72qnat1i.png" alt="Icon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Icons should almost always be paired with a textual label.&lt;/strong&gt; Many icons, even common ones, create confusion if not explicitly explained in their contexts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microcopy &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Every action needs to have a corresponding textual label, whether it is explicit (visual), implicit (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA"&gt;ARIA attributes&lt;/a&gt;), or both. Your users obviously need to know what an action does, and well-written microcopy helps your user determine the primary action of an interface. &lt;strong&gt;An action's label should be clear and concise.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Action verbs
&lt;/h3&gt;

&lt;p&gt;You should &lt;em&gt;rarely&lt;/em&gt; use just the words &lt;em&gt;OK&lt;/em&gt; or &lt;em&gt;Yes&lt;/em&gt; as an action's label. These terms provide no contextual information relating to the primary action and require the user to repeatedly verify the interface's intention. Associate the &lt;em&gt;action&lt;/em&gt; with an &lt;em&gt;action verb&lt;/em&gt;. &lt;strong&gt;Clarify the action the user is about to perform by replacing passive nouns and interjections with imperatives.&lt;/strong&gt; A well-chosen &lt;em&gt;verb + noun&lt;/em&gt; combination typically meets the requirements of an unambiguous action label.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finality
&lt;/h3&gt;

&lt;p&gt;While the &lt;em&gt;Cancel&lt;/em&gt; label functions well in most circumstances, consider the &lt;em&gt;finality&lt;/em&gt; of your chosen verbs. If the action does not modify any user information and is reversible (non-final), choose a less destructive verb such as &lt;em&gt;Go Back&lt;/em&gt; or &lt;em&gt;Return&lt;/em&gt;. &lt;strong&gt;A verb with a safe connotation reduces the time the user spends considering their choice of action.&lt;/strong&gt; However, if the action does modify information and perform a potentially irreversible procedure, use a strong verb.&lt;/p&gt;

&lt;p&gt;In addition, an &lt;em&gt;undo&lt;/em&gt; tertiary action present in the interface affirms the user that any potentially destructive actions can be reversed, reducing the finality of the operations and enabling the user to quickly take action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Destructive primary actions &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Many rules come with exceptions, and the ones I have listed so far are no exception. Occasionally, a user needs to intentionally perform a truly &lt;em&gt;destructive&lt;/em&gt; (irrecoverable) action. These cases need to be specifically and separately addressed to prevent unwanted consequences.&lt;/p&gt;

&lt;p&gt;As mentioned previously, adding an &lt;em&gt;undo&lt;/em&gt; tertiary action relieves some of the user's stress in performing the action, but because the action is intended to be destructive, you should assume the worst-case scenario.&lt;/p&gt;

&lt;p&gt;I provide the following advice when dealing with destructive actions in an interface:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When the primary action is negative (destructive), the secondary action needs to be positive.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_FYcCncf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gs90qlnl9v6t0kfrhemf.png" alt="Destructive"&gt;
&lt;/li&gt;
&lt;li&gt;Secondary destructive actions should be separated into a separate visual group and can be aligned opposite the primary action.
&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T4L_IE5b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/DgJRnVgUwAIcmml.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--uvpA-qUo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1323875849230114816/Zl8kKCSM_normal.jpg" alt="Doug Collins profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Doug Collins
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @dougcollinsux
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Provide physical separation between vital &lt;a href="https://twitter.com/hashtag/controls"&gt;#controls&lt;/a&gt; to avoid catastrophic consequences - like shifting into park at 60 MPH when you're trying to turn the volume down. &lt;br&gt;&lt;br&gt;&lt;a href="https://twitter.com/hashtag/UX"&gt;#UX&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/userexperience"&gt;#userexperience&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/uxdesign"&gt;#uxdesign&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/automotivedesign"&gt;&lt;/a&gt;&lt;a href="https://twitter.com/hashtag/automotivedesign"&gt;#automotivedesign&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/ui"&gt;#ui&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/userinterface"&gt;#userinterface&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/ui"&gt;#ui&lt;/a&gt;design &lt;a href="https://twitter.com/hashtag/design"&gt;#design&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/automotivedesign"&gt;&lt;/a&gt;&lt;a href="https://twitter.com/hashtag/automotivedesign"&gt;#automotivedesign&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/cars"&gt;#cars&lt;/a&gt; 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      15:38 PM - 20 Jun 2018
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1009460435509268482" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1009460435509268482" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      13
      &lt;a href="https://twitter.com/intent/like?tweet_id=1009460435509268482" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      30
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;Destructive actions should initially be disabled, requiring a "safety" primary action to be performed first to verify the user's intention.&lt;/li&gt;
&lt;li&gt;In some cases, primary destructive actions should be visually de-emphasized. Swap the visual weights and style of the primary and secondary actions. The secondary positive action may technically be the user's intended action since interfaces with destructive consequences can be opened mistakenly.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---xe8DULD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7mhi7rlxvkchic63q9i0.png" alt="Failsafe"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These guidelines ensure the user does not unintentionally perform an unwanted action in your interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Despite these guidelines, the most important factor to consider in designing actions in your application is &lt;strong&gt;consistency&lt;/strong&gt;. Consistency reassures your user that each interface will behave according to their expectations, and a consistent application efficiently guides the user throughout their journey. Without proper consideration of each action's visual presentation, your application irresponsibly neglects your users' expectations and needs. Responsible applications need responsible designers willing to consider these seemingly "insignificant" details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/Primary-Action-e446a7b8ba554362b8cad8baf2902261"&gt;Research notes&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.figma.com/file/ZhDevFK5c0deRxpoLdBzRg/Untitled?node-id=0%3A1"&gt;Figma prototype&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.lukew.com/ff/entry.asp?571%5D(https://www.lukew.com/ff/entry.asp?571)"&gt;https://www.lukew.com/ff/entry.asp?571](https://www.lukew.com/ff/entry.asp?571)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://apps.labor.ny.gov/ux/doc/v1/design-primary-actions.html"&gt;https://apps.labor.ny.gov/ux/doc/v1/design-primary-actions.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://uxplanet.org/primary-secondary-action-buttons-c16df9b36150"&gt;https://uxplanet.org/primary-secondary-action-buttons-c16df9b36150&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://uxdesign.cc/ui-cheat-sheets-buttons-7329ed9d6112"&gt;https://uxdesign.cc/ui-cheat-sheets-buttons-7329ed9d6112&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ux.stackexchange.com/a/49996"&gt;https://ux.stackexchange.com/a/49996&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://balsamiq.com/learn/articles/button-design-best-practices/"&gt;https://balsamiq.com/learn/articles/button-design-best-practices/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://uxmovement.com/buttons/visual-weight-of-primary-and-secondary-action-buttons/"&gt;https://uxmovement.com/buttons/visual-weight-of-primary-and-secondary-action-buttons/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.nngroup.com/articles/ok-cancel-or-cancel-ok/"&gt;https://www.nngroup.com/articles/ok-cancel-or-cancel-ok/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://designsystem.digital.gov/components/button/"&gt;https://designsystem.digital.gov/components/button/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://material.io/components/dialogs"&gt;https://material.io/components/dialogs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://measuringuserexperience.com/SubmitCancel/"&gt;http://measuringuserexperience.com/SubmitCancel/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.invisionapp.com/inside-design/comprehensive-guide-designing-ux-buttons/"&gt;https://www.invisionapp.com/inside-design/comprehensive-guide-designing-ux-butt&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://lawsofux.com/hicks-law"&gt;https://lawsofux.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/@jonyablonski/designing-with-occams-razor-3692df2f3c7f"&gt;https://medium.com/@jonyablonski/designing-with-occams-razor-3692df2f3c7f&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cxl.com/blog/serial-position-effect/"&gt;https://cxl.com/blog/serial-position-effect/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://www.sergix.dev"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9ivenYc6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xo1s9bqxmg4sc2270ifk.png" alt="sergix"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Let's develop a website that fits you. Don't settle for a template.&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://www.sergix.dev"&gt;Visit my portfolio&lt;/a&gt; to see what I'm about.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>design</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Web Monetization Plugin for Gridsome</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Tue, 02 Jun 2020 20:13:31 +0000</pubDate>
      <link>https://dev.to/sergix/web-monetization-plugin-for-gridsome-1h2l</link>
      <guid>https://dev.to/sergix/web-monetization-plugin-for-gridsome-1h2l</guid>
      <description>&lt;h4&gt;
  
  
  &lt;strong&gt;UPDATE: This plugin is now featured on the &lt;a href="https://webmonetization.org/" rel="noopener noreferrer"&gt;homepage of the WICG's Web Monetization website&lt;/a&gt; under "Web Monetization Tools". So excited! 😄&lt;/strong&gt;
&lt;/h4&gt;




&lt;p&gt;Here's my entry for the &lt;a href="https://dev.to/devteam/announcing-the-grant-for-the-web-hackathon-on-dev-3kd1"&gt;Grant For The Web x DEV Community hackathon&lt;/a&gt;. I hope you find it useful!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/Sergix/gridsome-plugin-monetization" rel="noopener noreferrer"&gt;Link to repository&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://www.npmjs.com/package/gridsome-plugin-monetization" rel="noopener noreferrer"&gt;NPM&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;



&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A web monetization plugin for the &lt;a href="https://www.gridsome.org/" rel="noopener noreferrer"&gt;Gridsome&lt;/a&gt; static site generator.&lt;/p&gt;
&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Foundational Technology&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Plugins and integrations initiate acceptance for new web technologies. This plugin helps developers using the Vue-based Gridsome static site generator to avoid interacting directly with the DOM and easily attach events, modify monetization tags, and get monetization state with just a few lines.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo (CodeSandbox)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Open the demo in a separate tab. &lt;a href="https://webmonetization.org/docs/api#iframes" rel="noopener noreferrer"&gt;Monetization is disabled in &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; by default.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/gridsome-plugin-monetization-3nf3w"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Sergix" rel="noopener noreferrer"&gt;
        Sergix
      &lt;/a&gt; / &lt;a href="https://github.com/Sergix/gridsome-plugin-monetization" rel="noopener noreferrer"&gt;
        gridsome-plugin-monetization
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Web monetization for Gridsome.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;gridsome-plugin-monetization&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://npmjs.com/package/gridsome-plugin-monetization" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4926eb9bb4caf3fcf30ec825a559ca7d68d4b257091f8eb0befe7262ee01011d/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f67726964736f6d652d706c7567696e2d6d6f6e6574697a6174696f6e2f6c61746573742e737667" alt="npm version"&gt;&lt;/a&gt;
&lt;a href="https://npmjs.com/package/gridsome-plugin-monetization" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4eecbbe835abfb1d152cd359c004dc683b1d5d29241b046af18b98b2524724b0/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f67726964736f6d652d706c7567696e2d6d6f6e6574697a6174696f6e2e737667" alt="npm downloads"&gt;&lt;/a&gt;
&lt;a href="https://github.com//actions?query=workflow%3Aci" rel="noopener noreferrer"&gt;&lt;img src="https://github.com//workflows/ci/badge.svg" alt="Github Actions CI"&gt;&lt;/a&gt;
&lt;a href="https://codecov.io/gh/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/39dbeb9a6a99f2f7a5d8a0e5116c5e64e138d2d75d37b5db978cd326a2e0084c/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f2e737667" alt="Codecov"&gt;&lt;/a&gt;
&lt;a href="https://npmjs.com/package/gridsome-plugin-monetization" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a0b4c95cc8073114c91c08708bb11787b35982056ff5a4cb4ff91298d684d6f9/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f67726964736f6d652d706c7567696e2d6d6f6e6574697a6174696f6e2e737667" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Sergix/gridsome-plugin-monetizationassets/gridsome_webmo_logo.svg"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FSergix%2Fgridsome-plugin-monetizationassets%2Fgridsome_webmo_logo.svg" alt="Gridsome + Web Monetization"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web monetization plugin for Gridsome.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/Sergix/gridsome-plugin-monetization./CHANGELOG.md" rel="noopener noreferrer"&gt;📖 &lt;strong&gt;Release Notes&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;gridsome-plugin-monetization&lt;/code&gt; dependency to your project&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn add gridsome-plugin-monetization &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or npm install gridsome-plugin-monetization&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Add &lt;code&gt;gridsome-plugin-monetization&lt;/code&gt; to the &lt;code&gt;plugins&lt;/code&gt; section of &lt;code&gt;gridsome.config.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;plugins&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;use&lt;/span&gt;: &lt;span class="pl-s"&gt;"gridsome-plugin-monetization"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;options&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-c1"&gt;paymentPointer&lt;/span&gt;: &lt;span class="pl-s"&gt;"$wallet.example.com/alice"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c"&gt;// your payment pointer&lt;/span&gt;
        &lt;span class="pl-c1"&gt;global&lt;/span&gt;: &lt;span class="pl-c1"&gt;true&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c"&gt;// add monetization to every page; default: true&lt;/span&gt;
      &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;For &lt;code&gt;gridsome-plugin-monetization&lt;/code&gt; to work properly, you must &lt;a href="https://paymentpointers.org/" rel="nofollow noopener noreferrer"&gt;specify your payment pointer&lt;/a&gt; (&lt;code&gt;paymentPointer&lt;/code&gt;) in the gridsome plugin options as shown above.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; As of now, any of the following methods are unable to be accessed at build time through component &lt;code&gt;data&lt;/code&gt; or &lt;code&gt;template&lt;/code&gt;s due to SSR constraints.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Methods&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;&lt;code&gt;$monetization.enable()&lt;/code&gt;&lt;/h4&gt;

&lt;/div&gt;
&lt;p&gt;Enables web monetization for the current page if not already enabled.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Returns: HTMLElement | false&lt;/em&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;&lt;code&gt;$monetization.disable()&lt;/code&gt;&lt;/h4&gt;

&lt;/div&gt;
&lt;p&gt;Disables web monetization for the current page if not already disabled.&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Sergix/gridsome-plugin-monetization" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;Gridsome plugins are fairly simple to write. For client plugins (such as this), all you have to do is make a &lt;code&gt;gridsome.client.js&lt;/code&gt;, and you export a function such as the following:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&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;Vue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&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;$monetization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="c1"&gt;// server code&lt;/span&gt;
  &lt;span class="c1"&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="c1"&gt;// client-only code&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$monetization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$monetization&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you link your plugin to a Gridsome project via &lt;code&gt;npm link gridsome-plugin-monetization&lt;/code&gt; for plugin development and testing.&lt;/p&gt;

&lt;p&gt;I used the &lt;code&gt;context&lt;/code&gt; to interact with Gridsome's build step when the &lt;code&gt;global&lt;/code&gt; option in the plugin configuration is enabled.&lt;/p&gt;

&lt;p&gt;I attempted to keep the code as composable and clean as possible, but when interacting with the DOM it can be a challenge. 😆&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/Sergix/gridsome-plugin-monetization/blob/master/README.md" rel="noopener noreferrer"&gt;Full setup instructions and documentation are in the repository's README&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Install via &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; —&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add gridsome-plugin-monetization &lt;span class="c"&gt;# or npm install gridsome-plugin-monetization&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the plugin to Gridsome, and set your &lt;code&gt;paymentPointer&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;// gridsome.config.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gridsome-plugin-monetization&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;paymentPointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$wallet.example.com/alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// enabled on every page (default: true)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use in your components, pages, and layouts by accessing &lt;code&gt;Vue.prototype.$monetization&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;// component.vue&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;enableExtraContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&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;mounted&lt;/span&gt;&lt;span class="p"&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;$monetization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&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;$monetization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onStart&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableExtraContent&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;
  
  
  Additional Resources/Info
&lt;/h2&gt;

&lt;p&gt;Due to the nature of server-side rendering and static sites, and the state of the monetization API (as well as Gridsome's plugin API), the plugin's methods are only accessible via the client and cannot be used in a component's &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;computed&lt;/code&gt;, and &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For now, the best way to use the API is to declare defaults within your component's &lt;code&gt;data&lt;/code&gt; and add event hooks within your component's &lt;code&gt;mounted()&lt;/code&gt; hook. This way, the DOM has time to load before attempting to access the monetization API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you for reading! God bless!
&lt;/h2&gt;




&lt;p&gt;You can find me at &lt;a href="https://www.sergix.dev/" rel="noopener noreferrer"&gt;sergix.dev&lt;/a&gt;.&lt;br&gt;
Reach out at &lt;a href="https://sergix.dev" rel="noopener noreferrer"&gt;hello@sergix.dev&lt;/a&gt; for anything that's on your mind!&lt;/p&gt;

</description>
      <category>gftwhackathon</category>
      <category>vue</category>
      <category>webmonetization</category>
    </item>
    <item>
      <title>My New Personal Website!</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Sun, 31 May 2020 01:23:58 +0000</pubDate>
      <link>https://dev.to/sergix/my-new-personal-website-12e3</link>
      <guid>https://dev.to/sergix/my-new-personal-website-12e3</guid>
      <description>&lt;p&gt;&lt;em&gt;Code hosted on &lt;a href="https://github.com/Sergix/sergix.net" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;



&lt;h2&gt;
  
  
  It's finally done!
&lt;/h2&gt;

&lt;p&gt;After about a week-and-a-half of design and development, &lt;a href="https://www.sergix.dev/" rel="noopener noreferrer"&gt;my personal portfolio site has been completed.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently, it only shows off a few designs and a couple websites I've worked on, but I really enjoyed building it and maybe starting a new personal blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;The site was built with -&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridsome.org/" rel="noopener noreferrer"&gt;Gridsome&lt;/a&gt; static site generator&lt;br&gt;
&lt;a href="https://www.tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; for styling&lt;br&gt;
&lt;a href="https://netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; for hosting&lt;br&gt;
&lt;a href="https://figma.com/" rel="noopener noreferrer"&gt;Figma&lt;/a&gt; for UI prototyping&lt;/p&gt;

&lt;p&gt;For those of you who don't know, Gridsome is an awesome static site generator for Vue. If you're interested in making a personal site and love Vue, it's the way to go!&lt;/p&gt;

&lt;p&gt;All the blog posts are written in plain markdown and pulled in via the &lt;a href="https://gridsome.org/plugins/@gridsome/source-filesystem" rel="noopener noreferrer"&gt;Gridsome filesystem plugin&lt;/a&gt; and the &lt;a href="https://gridsome.org/docs/data-layer/" rel="noopener noreferrer"&gt;GraphQL layer Gridsome provides&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxnk6kw1949r2bcgql62n.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%2Fi%2Fxnk6kw1949r2bcgql62n.png" alt="Landing page screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Future features
&lt;/h2&gt;

&lt;p&gt;I'm looking to add -&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webmonetization.org/" rel="noopener noreferrer"&gt;Web monetization&lt;/a&gt;&lt;br&gt;
&lt;a href="https://usefathom.com/" rel="noopener noreferrer"&gt;Fathom analytics&lt;/a&gt;&lt;br&gt;
Comment system (might roll my own via &lt;a href="https://www.netlify.com/products/functions/" rel="noopener noreferrer"&gt;Netlify Functions and AWS&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;...and hopefully some other interesting features in the future!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading! God bless!
&lt;/h2&gt;




&lt;p&gt;&lt;em&gt;Contact me at &lt;a href="//mailto:hello@sergix.dev"&gt;hello@sergix.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vue</category>
      <category>ux</category>
    </item>
    <item>
      <title>DEV UI Update</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Tue, 12 May 2020 15:09:06 +0000</pubDate>
      <link>https://dev.to/sergix/ui-update-17kj</link>
      <guid>https://dev.to/sergix/ui-update-17kj</guid>
      <description>&lt;p&gt;It seems like the DEV.to team's been hard at work as always with a big UI overhaul and a &lt;a href="https://github.com/thepracticaldev/dev.to/commit/2abe8303770de0b0c181afea76767874c0abc2c1"&gt;monster commit&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Quick question to the DEV team: how does it determine when to show comments for a post on the homepage? It seems like some posts show one or two comments but most don't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yI3iq0_w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/81hzpakjsys1udj7z42b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yI3iq0_w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/81hzpakjsys1udj7z42b.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>meta</category>
    </item>
    <item>
      <title>Discussion Subscribe Button</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Wed, 06 May 2020 02:40:47 +0000</pubDate>
      <link>https://dev.to/sergix/discussion-subscribe-button-3lo7</link>
      <guid>https://dev.to/sergix/discussion-subscribe-button-3lo7</guid>
      <description>&lt;p&gt;A meta discussion about discussions. Can't get any more meta.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Qu5RlxI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/juo3chprfirw4pybtnc2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Qu5RlxI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/juo3chprfirw4pybtnc2.jpeg" alt="Button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Is this a new feature? or am I just blind?&lt;/p&gt;

</description>
      <category>meta</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Workdrop — #Twiliohackathon Entry</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Thu, 30 Apr 2020 03:52:58 +0000</pubDate>
      <link>https://dev.to/sergix/workdrop-twiliohackathon-entry-3aik</link>
      <guid>https://dev.to/sergix/workdrop-twiliohackathon-entry-3aik</guid>
      <description>&lt;p&gt;&lt;em&gt;Here's my entry for #twiliohackathon. I hope you enjoy it!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Sergix/workdrop"&gt;GitHub Repository&lt;/a&gt; (MIT Licensed)&lt;br&gt;
&lt;a href="https://stackshare.io/Sergix/workdrop"&gt;Stackshare&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.workdrop.app/"&gt;Live Demo&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm a senior in high school.&lt;/p&gt;

&lt;p&gt;When South Carolina, my state of residency, announced the closing of schools statewide, teachers scrambled to quickly find working solutions for eLearning.&lt;/p&gt;

&lt;p&gt;Some issues, such as videos (YouTube) and classroom sessions (Zoom), were solved quickly. Other problems took some more research, and one seemed particularly difficult: assignment collection.&lt;/p&gt;

&lt;p&gt;Our school had a student system set up prior to COVID-19 that had an integrated homework collection system, but it was archaic and scarcely used, and ended up &lt;em&gt;creating&lt;/em&gt; more problems than it solved.&lt;/p&gt;

&lt;p&gt;My english teacher in particular became particularly frustrated as the school resorted to collecting files by email, which quickly becomes unmanageable, especially considering all the other email teachers receive. It would be much simpler to even just receive one email with all the assignments.&lt;/p&gt;

&lt;p&gt;I took notice of this. When the #Twiliohackathon was announced, I remembered that Twilio owns &lt;a href="https://www.sendgrid.com"&gt;SendGrid&lt;/a&gt;, and it seemed impossible to not take this opportunity to create a solution to this evident problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3IAhrlUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/t5kl1kj6qus8k9m43jcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3IAhrlUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/t5kl1kj6qus8k9m43jcb.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is workdrop?
&lt;/h2&gt;

&lt;p&gt;Workdrop is a simplicity-conscious web app for teachers and educators needing to request files from their students.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who made it?
&lt;/h2&gt;

&lt;p&gt;This project was completed in around 15 days by &lt;a href="https://github.com/sergix"&gt;me, Peyton McGinnis (sergix)&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes it different?
&lt;/h2&gt;

&lt;p&gt;Most people refer to Dropbox as already having solved this problem.&lt;/p&gt;

&lt;p&gt;However, when trying to figure out how I could explain how to use Dropbox's request feature, and coupled with Dropbox's pricing, I knew it would be difficult to recommend to one teacher and end up needing to have the school finance it.&lt;/p&gt;

&lt;p&gt;But, in particular, Workdrop focuses on one thing, assignment collection, and is good &lt;em&gt;at that one feature&lt;/em&gt;. Some may think this is a disadvantage, but that was my &lt;em&gt;intention&lt;/em&gt; with building the app.&lt;/p&gt;

&lt;p&gt;Teachers are not often tech savvy, and piling feature upon feature increases mental overhead and frustration for someone trying to accomplish a task as simple as requesting an assignment. In most schools, the teacher simply writes their assignment on the whiteboard/blackboard and every student then knows it's due. It should be just as simple online.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work? 🤔
&lt;/h2&gt;

&lt;p&gt;My series here on DEV.to has detailed the set up process and design, but the concept is simple.&lt;/p&gt;

&lt;p&gt;A teacher visits the website. They click on "Request an Assignment". They enter the name of the assignment, their students' emails, an attached message, and their email. Then, they submit this information.&lt;/p&gt;

&lt;p&gt;An email is sent to the teacher with a link to access their student's submissions. An email is also sent to each student with a link to submit their assignment.&lt;/p&gt;

&lt;p&gt;When a file is submitted by a student, the teacher can access the submissions page and download the assignment.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://stackshare.io/Sergix/workdrop"&gt;The Stack&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Frontend
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Vue.js&lt;/li&gt;
&lt;li&gt;Nuxt.js&lt;/li&gt;
&lt;li&gt;TailwindCSS&lt;/li&gt;
&lt;li&gt;Sentry&lt;/li&gt;
&lt;li&gt;Netlify&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Backend
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;MongoDB Atlas&lt;/li&gt;
&lt;li&gt;MongoDB Stitch&lt;/li&gt;
&lt;li&gt;AWS S3&lt;/li&gt;
&lt;li&gt;Twilio SendGrid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z3T5k092--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j274lompov086qr56c26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z3T5k092--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j274lompov086qr56c26.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it scalable?
&lt;/h2&gt;

&lt;p&gt;I built workdrop to scale up and down very easily. The frontend is hosted as a static site on Netlify, and the backend runs a MongoDB Atlas cluster and an AWS S3 bucket. Each of these services tend to scale very well, and since workdrop's core logic is very simple it wouldn't be hard to integrate more services, such as a Redis edge cache.&lt;/p&gt;

&lt;p&gt;At the moment, every one of these services is being hosted for free. The only thing I've paid for so far is the domain name, &lt;a href="https://www.workdrop.app"&gt;workdrop.app&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live demo!
&lt;/h2&gt;

&lt;p&gt;The app can be accessed online at &lt;a href="https://www.workdrop.app"&gt;workdrop.app&lt;/a&gt; and is a fully-functioning web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code ⌨️
&lt;/h2&gt;

&lt;p&gt;All code is hosted in &lt;a href="https://www.github.com/sergix/workdrop"&gt;this GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twilio API integration ✉️
&lt;/h2&gt;

&lt;p&gt;This project uses &lt;strong&gt;Twilio SendGrid&lt;/strong&gt; as the core communication API to send out assignment emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category entry 👍
&lt;/h2&gt;

&lt;p&gt;For my project, I would like to enter the &lt;strong&gt;Covid-19 Communications&lt;/strong&gt; category. While I suppose it could fit into a couple different categories, &lt;em&gt;remote education&lt;/em&gt; was specifically listed for this category so I thought it would be best to enter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jggf5Meq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5hspvwbcejjo7nf3082d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jggf5Meq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5hspvwbcejjo7nf3082d.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.workdrop.app/about"&gt;about page on the workdrop website&lt;/a&gt; goes into more detail, and is nicely illustrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application security 🔒
&lt;/h2&gt;

&lt;p&gt;As I detailed in my previous post, I have properly set up (to my knowledge) a secure S3 instance that is only accessible via my MongoDB Stitch instance.&lt;/p&gt;

&lt;p&gt;All these services are secured with multi-factor authentication, and my domain is SSL-enforced.&lt;/p&gt;

&lt;p&gt;Every API key has only been created when absolutely needed and is not stored anywhere in plaintext (except the Sentry DSN, but that's intended to be plaintext.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Future features 🆕
&lt;/h2&gt;

&lt;p&gt;Just because this hackathon has ended &lt;em&gt;does not&lt;/em&gt; mean I'm done with workdrop. There's still a pile of improvements and small features needed before I really put it into full-production mode, but the application is definitely at a usable state.&lt;/p&gt;

&lt;p&gt;In addition, I'd like to move away from an email-less solution entirely, but this would only (in my understanding) be viable through a mobile app. I've looked into and heavily considered using NativeScript-Vue.&lt;/p&gt;

&lt;p&gt;Lastly, I would like to add SMS support, but I would need to use a link minimizer and I haven't found a great solution quite yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on the Terms of Use and Privacy Policy 📝
&lt;/h2&gt;

&lt;p&gt;Since Workdrop is currently being hosted and readily available to users, I thought it would be wise to craft a Terms of Use and Privacy Policy agreement.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.workdrop.app/terms"&gt;Terms of Use&lt;/a&gt; states that the project is MIT licensed, and mostly is intended to protect against unlawful types of file submissions.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.workdrop.app/privacy"&gt;Privacy Policy&lt;/a&gt; states some simple notes about data collection and usage. Workdrop does not store any analytics or usage data, and only stores data that the user provides that is critical for the application's function.&lt;/p&gt;

&lt;p&gt;As of right now, &lt;a href="https://dev.to/louy2/comment/o8l9"&gt;as another user pointed out&lt;/a&gt;, I cannot under United States law allow children under 13 to access the app &lt;a href="https://www.termsfeed.com/blog/coppa-privacy-policy/"&gt;without becoming COPPA compliant&lt;/a&gt;. I'm taking steps to ensure that this becomes available, but since the target audience for Workdrop is currently middle and high school students it doesn't affect much.&lt;/p&gt;

&lt;h2&gt;
  
  
  A final note
&lt;/h2&gt;

&lt;p&gt;Thank you Twilio and DEV for hosting this hackathon! It was a great experience to learn some new serverless stuff, and it's always a joy to learn a new API. Thank you so much for reading! God bless!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LO1fOJ3v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/62zitvpwfsxiltw3phdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LO1fOJ3v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/62zitvpwfsxiltw3phdt.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>twiliohackathon</category>
    </item>
    <item>
      <title>Workdrop — The Frontend</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Thu, 30 Apr 2020 03:43:59 +0000</pubDate>
      <link>https://dev.to/sergix/workdrop-the-frontend-10bg</link>
      <guid>https://dev.to/sergix/workdrop-the-frontend-10bg</guid>
      <description>&lt;p&gt;&lt;em&gt;This continues my entry for #twiliohackathon!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Sergix/workdrop"&gt;GitHub Repository&lt;/a&gt; (MIT Licensed)&lt;br&gt;
&lt;a href="https://stackshare.io/Sergix/workdrop"&gt;Stackshare&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In a previous post, &lt;a href="https://dev.to/sergix/workdrop-ui-design-and-prototyping-2g9i"&gt;Workdrop — UI Design and Prototyping&lt;/a&gt;, I went over my project's design system and some basic UI elements. This post will summarize the actual frontend, built with &lt;a href="https://www.nuxtjs.org/"&gt;Nuxt&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Nuxt setup
&lt;/h2&gt;

&lt;p&gt;Usually, I use &lt;a href="https://www.gridsome.org"&gt;Gridsome&lt;/a&gt; for my Vue SPA's, but it seems like Nuxt is much more oriented towards dynamic applications that are constantly interacting with a backend.&lt;/p&gt;

&lt;p&gt;When creating my project, I did not use SSR mode because I wanted to host my site on Netlify. I suppose using SSR would reduce client bundle size, but for now it'll stay an SPA.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;nuxt.config.js&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Nuxt provides a really nice config file for configuring &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; contents, plugins, middleware, routing, and other build settings.&lt;/p&gt;

&lt;p&gt;I inserted some custom &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags for &lt;a href="https://ogp.me/"&gt;OpenGraph tag support&lt;/a&gt; and some other service integration for PWAs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;npm_package_name&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="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&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;apple-mobile-web-app-status-bar-style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;black-translucent&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;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;twitter:card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workdrop&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;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;twitter:url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.workdrop.app/&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;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;twitter:title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workdrop&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;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;twitter:description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An assignment requesting app for teachers and educators.&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workdrop&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.workdrop.app/&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;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;og:image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.workdrop.app/ogimage.png&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;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Plugins
&lt;/h3&gt;

&lt;p&gt;For error tracking, I'm using &lt;a href="https://www.sentry.io/"&gt;Sentry&lt;/a&gt;. All you have to do to add Sentry integration to your Nuxt project is install &lt;code&gt;@nuxtjs/sentry&lt;/code&gt; and add it to your modules and set your Sentry DSN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.js&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nl"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nuxtjs/sentry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="nx"&gt;sentry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;DSN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[Your DSN]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Tailwind and PurgeCSS
&lt;/h2&gt;

&lt;p&gt;When creating a new Nuxt project, you can choose to automatically setup &lt;a href="https://tailwindcss.com/"&gt;TailwindCSS&lt;/a&gt; and &lt;a href="https://purgecss.com/"&gt;PurgeCSS&lt;/a&gt;, which go together like bread and butter.&lt;/p&gt;

&lt;p&gt;However, global styling rules can be slightly frustrating to configure since PurgeCSS will automatically remove CSS that it doesn't think is being used.&lt;/p&gt;

&lt;p&gt;To circumvent this, I added a &lt;code&gt;donotpurge.css&lt;/code&gt; (appropriately named) stylesheet that is loaded along with the ignored assets loaded with Tailwind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* tailwind.css */&lt;/span&gt;

&lt;span class="c"&gt;/* purgecss start ignore */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'tailwindcss/base'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'~/assets/css/donotpurge.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'tailwindcss/components'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;/* purgecss end ignore */&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'tailwindcss/utilities'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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



&lt;h2&gt;
  
  
  The Design
&lt;/h2&gt;

&lt;p&gt;&lt;a href=""&gt;In my earlier post&lt;/a&gt; I discussed the basics of the design system, but did not disclose the full UI. Well, here it is!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bk4BLiKf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pjn4bdzpawp6o07uxwpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bk4BLiKf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pjn4bdzpawp6o07uxwpz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, onto the actual implementation .&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;p&gt;For desktop navigation, it's a pretty simple Navbar with a little stylish border animation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9yijiLXM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rr6s3zmisog3okd08n1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9yijiLXM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rr6s3zmisog3okd08n1v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For mobile, I normally like to implement a fullscreen navigation menu to make the links larger and easier to tap. I right-justified the text since most people are right handed to make it easier to reach. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sHZ17RAM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n4qwvnmxt22epcrqp3xn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sHZ17RAM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n4qwvnmxt22epcrqp3xn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, I really considered some of the details in the simplicity of my app, considering the target audience. In this project, I tried to move away from non-labeled buttons for the most part, so rather than using a hamburger icon to open the menu it simply says "MENU", which obviates its function. I actually am considering doing this with all my projects from now on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j3p_igVJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hjqdnddegw404u2yo05z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j3p_igVJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hjqdnddegw404u2yo05z.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The footer is very basic as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2eIsWU1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7nkonjca4zxqeaa5yexz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2eIsWU1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7nkonjca4zxqeaa5yexz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Landing Page
&lt;/h3&gt;

&lt;p&gt;For the landing page, I am using an illustration from &lt;a href="https://isometric.online/"&gt;isometric.online&lt;/a&gt; as mentioned in my previous post. I customized the colors to fit the design system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UbNfn3Fm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vzwu85z5av1yggpqgd44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UbNfn3Fm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vzwu85z5av1yggpqgd44.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted to get my users up and running ASAP, so the "Request an Assignment" button takes you to the request form without needing to sign in.&lt;/p&gt;

&lt;h3&gt;
  
  
  About page
&lt;/h3&gt;

&lt;p&gt;I really enjoyed laying out this page's content. Since it doesn't require a lot of interaction, I had a lot more creative freedom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y_HOE0HA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mgnebzrjvyy9ac6ziwzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y_HOE0HA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mgnebzrjvyy9ac6ziwzi.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Request page
&lt;/h3&gt;

&lt;p&gt;This page was very interesting to design and program.&lt;/p&gt;

&lt;p&gt;The form is split into four parts, and each part requires one specific piece of information. This way, it's clear each step of the way what is needed and reduces mental overhead.&lt;/p&gt;

&lt;p&gt;In the code, it's a bit &lt;em&gt;hacky&lt;/em&gt;, but I used a dynamic Vue component. To transition between each part of the form, each form emits a &lt;code&gt;continue&lt;/code&gt; or &lt;code&gt;back&lt;/code&gt; event. This calls a method that increments a counter and changes the dynamic component to the step of the form that the counter is on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt;
  &lt;span class="na"&gt;:is=&lt;/span&gt;&lt;span class="s"&gt;"currentFormSection"&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;continue=&lt;/span&gt;&lt;span class="s"&gt;"nextStep"&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;back=&lt;/span&gt;&lt;span class="s"&gt;"previousStep"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/component&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="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;FORM_STEPS&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;RequestFormAssignmentName&lt;/span&gt;&lt;span class="dl"&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;RequestFormStudents&lt;/span&gt;&lt;span class="dl"&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;RequestFormMessage&lt;/span&gt;&lt;span class="dl"&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;RequestFormEmail&lt;/span&gt;&lt;span class="dl"&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;RequestFormReview&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;currentFormSection&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;FORM_STEPS&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;currentStep&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;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I really want to refactor this to use a state machine library such as XState, but for the time being it works well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Form errors
&lt;/h4&gt;

&lt;p&gt;Whenever a field is empty of invalid, such as emails, it opens my custom toast notification through a reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;toast&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"errorToast"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Uh oh!"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  We couldn't create the assignment. Refresh and try again.
&lt;span class="nt"&gt;&amp;lt;/toast&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorToast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Form data
&lt;/h4&gt;

&lt;p&gt;Since the form switches between components, it was obvious that Vuex would be needed as a centralized store. The Vuex module is very straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// store/request.js&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;state&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="na"&gt;assignmentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;students&lt;/span&gt;&lt;span class="p"&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="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="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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mutations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;addStudent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;students&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="p"&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="nx"&gt;editStudent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newEmail&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;students&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newEmail&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;setStudentValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;students&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;removeStudent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;students&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&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="nx"&gt;setAssignmentName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;assignmentName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignmentName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;assignmentName&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignmentName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;students&lt;/span&gt; &lt;span class="o"&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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="p"&gt;}&lt;/span&gt;

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



&lt;h4&gt;
  
  
  Email Validation Microinteraction
&lt;/h4&gt;

&lt;p&gt;A few weeks ago, &lt;a href="https://dribbble.com/shots/4779275-Email-validation-animation"&gt;I found a very nice email validation microinteraction from dribbble&lt;/a&gt; that had been &lt;a href="https://codepen.io/aaroniker/pen/VdRRpM"&gt;converted into an actual CSS keyframe transition&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I took the code and converted it into a Vue component, and thought this would be a great opportunity to use it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BMM79eJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.dribbble.com/users/414694/screenshots/4779275/shot.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BMM79eJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.dribbble.com/users/414694/screenshots/4779275/shot.gif" alt="Email validation"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://dribbble.com/ai"&gt;&lt;em&gt;From Aaron Iker on dribbble&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Submit page
&lt;/h3&gt;

&lt;p&gt;The submit page has two possible states: accessing and submitting. The state depends on the provided queries in the URL. Currently, the solution is pretty ugly, but it works.&lt;/p&gt;

&lt;p&gt;When accessing submissions, the assigner has the capability to individually download each submission or download them all simultaneously. I plan to integrate &lt;code&gt;zip.js&lt;/code&gt; or a similar library to compress the downloads when downloading them all. &lt;/p&gt;

&lt;p&gt;When submitting, I used &lt;a href="https://pqina.nl/filepond/"&gt;FilePond&lt;/a&gt; to easily integrate a nice file uploading component in my page. When a file is submitted, it gets the &lt;code&gt;AwsService&lt;/code&gt; from MongoDB Stitch and calls &lt;code&gt;PutObject&lt;/code&gt; on the file object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g_HGj4E0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cvg8u22bsa5nl5fyytne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g_HGj4E0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cvg8u22bsa5nl5fyytne.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;However&lt;/em&gt; (and this had me stuck for a couple days), when using Stitch you have to convert the file to a specific binary type using MongoDB's &lt;code&gt;BSON&lt;/code&gt; type by first converting an &lt;code&gt;ArrayBuffer&lt;/code&gt; from the file's contents to a &lt;code&gt;UInt8Array&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/submit.vue&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileBson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// upload to S3&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  The Logic
&lt;/h3&gt;

&lt;p&gt;So now that I've detailed the design, here's a high-level layout of the entire application's flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IgOO-tSs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/du6d1ypp37hq4ahlrgw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IgOO-tSs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/du6d1ypp37hq4ahlrgw5.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you for reading! The next post will be the official submission. God bless!
&lt;/h2&gt;

</description>
      <category>twiliohackathon</category>
      <category>vue</category>
      <category>javascript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Workdrop — Setting Up Serverless</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Mon, 27 Apr 2020 16:46:42 +0000</pubDate>
      <link>https://dev.to/sergix/workdrop-setting-up-serverless-53hc</link>
      <guid>https://dev.to/sergix/workdrop-setting-up-serverless-53hc</guid>
      <description>&lt;p&gt;&lt;em&gt;This continues my entry for #twiliohackathon!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Sergix/workdrop"&gt;GitHub Repository&lt;/a&gt; (MIT Licensed)&lt;br&gt;
&lt;a href="https://stackshare.io/Sergix/workdrop"&gt;Stackshare&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Before we get going with the frontend, I'll describe a summary of some of the serverless setup with &lt;strong&gt;MongoDB Stitch&lt;/strong&gt;, &lt;strong&gt;AWS&lt;/strong&gt;, and &lt;strong&gt;Twilio SendGrid&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  MongoDB
&lt;/h2&gt;

&lt;p&gt;I already discussed setting up a MongoDB cluster in the second entry in this series, but now it's time to get cooking with &lt;em&gt;serverless&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Functions
&lt;/h3&gt;

&lt;p&gt;MongoDB Stitch, of course, has serverless functions built-in, and makes Atlas database interactions a breeze.&lt;/p&gt;
&lt;h4&gt;
  
  
  Requests
&lt;/h4&gt;

&lt;p&gt;The architecture is pretty simple: users enter their assignment name, student emails, a personalized message, and their email. Then, it sends all that data to a MongoDB function.&lt;/p&gt;

&lt;p&gt;There are two collections in the database: &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;submissions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before the user's data is put in the &lt;code&gt;requests&lt;/code&gt; collection, two UUID tokens are generated by a separate function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// generateToken()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workdrop.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DNS&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;accessor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workdrop.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DNS&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessor&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first token will be sent out to the requester and each student. The second &lt;code&gt;accessor&lt;/code&gt; token will only be sent out to the requester, and will allow them to access a page where they can download the submissions.&lt;/p&gt;

&lt;p&gt;Those tokens are inserted along with the user data in a new document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// createRequest&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generateToken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestsCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-cluster&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workdrop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newRequest&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;accessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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="nx"&gt;request_emails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;requestsCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Successfully inserted request with _id: &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;insertedId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&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="s2"&gt;`Failed to insert request: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After this document is inserted, a trigger calls another function which uses the &lt;strong&gt;Twilio SendGrid API&lt;/strong&gt; to send out the emails.&lt;/p&gt;

&lt;h4&gt;
  
  
  Submissions
&lt;/h4&gt;

&lt;p&gt;For submissions, the client first connects to MongoDB and then, through the MongoDB API, connects to the AWS S3 bucket &lt;em&gt;(details below)&lt;/em&gt;. The submitted file is uploaded to AWS, and the returned URL is sent to MongoDB through a function virtually identical to the request function.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS
&lt;/h3&gt;

&lt;p&gt;Setting up AWS can be pretty complicated sometimes, especially involving authentication. This was my first time setting up an S3 bucket and permissions.&lt;/p&gt;

&lt;p&gt;First, you need to &lt;a href="https://portal.aws.amazon.com/gp/aws/developer/registration"&gt;create your account&lt;/a&gt;. This will create a &lt;em&gt;root user&lt;/em&gt;, which has access to &lt;em&gt;everything&lt;/em&gt; in AWS. You should quickly enable multi-factor authentication and have a strong and secure password for your root user.&lt;/p&gt;

&lt;p&gt;Your serverless app &lt;em&gt;should never&lt;/em&gt; (and I'm not even sure if it can) authenticate with AWS via the root user. Instead, you create a special &lt;em&gt;IAM user&lt;/em&gt; where you specify your exact permissions. API keys and secrets are then generated specifically for this user.&lt;/p&gt;

&lt;p&gt;For myself, I simply created a new policy called &lt;code&gt;workdrop-stitch&lt;/code&gt; and added the S3 &lt;code&gt;GetObject&lt;/code&gt; and &lt;code&gt;PutObject&lt;/code&gt; permissions.&lt;/p&gt;

&lt;p&gt;After setting up AWS via the management console, I went back over to MongoDB and setup a new &lt;strong&gt;Third-Party Service&lt;/strong&gt;, selecting &lt;strong&gt;AWS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jTwJZiKA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eef5fpere7w15a7l83ix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jTwJZiKA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eef5fpere7w15a7l83ix.png" alt="MongoDB Stitch Service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you specify your API key and you're off to go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Twilio SendGrid
&lt;/h2&gt;

&lt;p&gt;Here's the somewhat critical part of my entry: &lt;strong&gt;Twilio API integration&lt;/strong&gt;. For my project, the Twilio service I'm using is &lt;a href="https://www.sendgrid.com"&gt;&lt;strong&gt;Twilio SendGrid&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ObhbmvHl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/sendgrid/sendgrid-python/raw/master/twilio_sendgrid_logo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ObhbmvHl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/sendgrid/sendgrid-python/raw/master/twilio_sendgrid_logo.png" alt="Twilio SendGrid"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even if using a Twilio API wasn't a part of the hackathon requirements, I'm certain I would be still using SendGrid because of how fantastic its API is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x1cGVVp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hkbqzz5tq8017s0ac11r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x1cGVVp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hkbqzz5tq8017s0ac11r.png" alt="Twilio SendGrid Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sender authentication
&lt;/h3&gt;

&lt;p&gt;In order to properly ensure that emails sent out with SendGrid are verified with all email clients and services, you must set up some DNS records to validate your domain with SendGrid. Then, you can have the emails sent out from your app look like &lt;code&gt;xxxxxx@workdrop.app&lt;/code&gt; instead of using SendGrid's default &lt;code&gt;xxxxxx@sendgrid.net&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Setup is straightforward and simple, especially if you're using &lt;a href="https://www.cloudflare.com/dns/"&gt;CloudFlare DNS&lt;/a&gt; as a proxy or &lt;a href="https://docs.netlify.com/domains-https/netlify-dns"&gt;Netlify's DNS&lt;/a&gt;. All that's required is creating a few specific CNAME records that point to &lt;code&gt;sendgrid.net&lt;/code&gt;. Instructions specific to your user are in your SendGrid dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on SendGrid with SSL
&lt;/h3&gt;

&lt;p&gt;If you &lt;em&gt;have&lt;/em&gt; to have SSL security on all traffic through your domain, keep in mind that if you're using &lt;a href="https://sendgrid.com/docs/ui/account-and-settings/tracking/"&gt;Click Tracking with SendGrid&lt;/a&gt; (which I do have disabled for my app at the moment), &lt;a href="https://sendgrid.com/docs/ui/analytics-and-reporting/click-tracking-ssl/"&gt;you'll have to direct your traffic through a seperate proxy secured with a certificate for your domain&lt;/a&gt;. This is because the CNAME record used to point the traffic from your domain to SendGrid's tracking servers is not HTTPS secured, so most browsers will simply refuse to connect to the link if HTTPS is enforced on your domain. And keep in mind, &lt;code&gt;.app&lt;/code&gt; and &lt;code&gt;.dev&lt;/code&gt; domains are SSL-enforced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the API
&lt;/h3&gt;

&lt;p&gt;For JavaScript, you can either use the &lt;a href="https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail"&gt;SendGrid Node.JS API&lt;/a&gt; or &lt;a href="https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html"&gt;manually call the API via REST&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's an example of my app's REST API call using JavaScript and MongoDB Stitch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sendRequestEmails()&lt;/span&gt;

&lt;span class="c1"&gt;// context.http.post is provided by MongoDB Stitch functions&lt;/span&gt;

&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&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;https://api.sendgrid.com/v3/mail/send&lt;/span&gt;&lt;span class="dl"&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;headers&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;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`Bearer [API_KEY]`&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;body&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;personalizations&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;to&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;email&lt;/span&gt;&lt;span class="dl"&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;example@example.com&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;from&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;email&lt;/span&gt;&lt;span class="dl"&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;no-reply@workdrop.app&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;encodeBodyAsJSON&lt;/span&gt;&lt;span class="dl"&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;true&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`EMAIL SUCCESS [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`EMAIL ERROR [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  AWS S3 Policy Setup
&lt;/h2&gt;

&lt;p&gt;Here's the part of the project that was the most frustrating for me.&lt;/p&gt;

&lt;p&gt;AWS policies can be very difficult to set up, especially for someone like me who never has done it before. And any information on specific policies can be difficult to find on forums and other sites.&lt;/p&gt;

&lt;h3&gt;
  
  
  User management
&lt;/h3&gt;

&lt;p&gt;First, &lt;a href="https://portal.aws.amazon.com/billing/signup#/start"&gt;after you create your AWS root account&lt;/a&gt;, you must create an IAM user that your app will use to connect to your services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7TENxEOC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kn5gvj46dikplphlbgqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7TENxEOC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kn5gvj46dikplphlbgqc.png" alt="AWS IAM User"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When creating your IAM user, you must enable &lt;em&gt;programmatic access&lt;/em&gt; since this will only be accessed through an API and not the AWS console.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 Policy
&lt;/h3&gt;

&lt;p&gt;You can attach an arbitrary number of policies to any IAM user to enable it to perform specific actions across AWS services.&lt;/p&gt;

&lt;p&gt;For S3, there are two primary policies that we need to enable: &lt;code&gt;GetObject&lt;/code&gt; and &lt;code&gt;PutObject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cT17-OEU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/93pzyvohu6o08c1cczj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cT17-OEU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/93pzyvohu6o08c1cczj7.png" alt="AWS S3 Policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, I found that I had to enable &lt;code&gt;GetObjectAcl&lt;/code&gt; and &lt;code&gt;PutObjectAcl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After enabling these policies, you simply select the bucket resource from your account in the &lt;em&gt;Resources&lt;/em&gt; field pictured above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bucket setup
&lt;/h3&gt;

&lt;p&gt;After creating these policies, you have to manage a couple permissions for your S3 bucket.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Permissions&lt;/strong&gt; tab of your bucket, you need to enable all &lt;em&gt;block public accecss&lt;/em&gt; settings &lt;em&gt;except&lt;/em&gt; &lt;code&gt;Block public access to buckets and objects granted through new access control lists (ACLs)&lt;/code&gt;. This involves the &lt;code&gt;GetObjectAcl&lt;/code&gt; and &lt;code&gt;PutObjectAcl&lt;/code&gt; permissions we had to set up earlier so authenticated APIs can access our bucket properly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bkeNUU9K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9q2yg7iopwpxxhrcm0v6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bkeNUU9K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9q2yg7iopwpxxhrcm0v6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, in the &lt;strong&gt;Bucket Policy&lt;/strong&gt; tab, you need to insert an ARN policy to control access from users. Anything within brackets &lt;code&gt;[]&lt;/code&gt; below will need to be replaced with your information. &lt;a href="https://awspolicygen.s3.amazonaws.com/policygen.html"&gt;AWS has a policy generator that can help.&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[policy ID]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Sid]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Your AWS user ARN]"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObjectAcl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObjectAcl"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Your S3 bucket resource ID]"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Lastly, in the &lt;strong&gt;CORS configuration&lt;/strong&gt; tab, you need to put the following XML. This allows any cross-origin request to be authenticated with the S3 API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;CORSConfiguration&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://s3.amazonaws.com/doc/2006-03-01/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;CORSRule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowedOrigin&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/AllowedOrigin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;HEAD&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;GET&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;PUT&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowedMethod&amp;gt;&lt;/span&gt;POST&lt;span class="nt"&gt;&amp;lt;/AllowedMethod&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AllowedHeader&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/AllowedHeader&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CORSRule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CORSConfiguration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;







&lt;h2&gt;
  
  
  Now onto the frontend. Thank you for reading! God bless!
&lt;/h2&gt;

</description>
      <category>twiliohackathon</category>
      <category>serverless</category>
      <category>aws</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>I need some help with a software engineering research paper!</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Tue, 21 Apr 2020 04:19:30 +0000</pubDate>
      <link>https://dev.to/sergix/i-need-some-help-with-a-software-engineering-research-paper-3m1a</link>
      <guid>https://dev.to/sergix/i-need-some-help-with-a-software-engineering-research-paper-3m1a</guid>
      <description>&lt;h2&gt;
  
  
  &lt;em&gt;Hey everyone!&lt;/em&gt; 👋
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt;&lt;br&gt;
Thank you to everyone who has sent me their contributions! The requirement for my paper has been fulfilled and everyone has been a great help to me.&lt;/p&gt;




&lt;p&gt;As a senior in high school, one of my requirements for graduating this semester is completing a &lt;em&gt;vocational research paper&lt;/em&gt;. Essentially, I research and write about a field I would like to enter, and obviously I decided to write about software engineering!&lt;/p&gt;

&lt;p&gt;Part of the paper is having someone (or multiple people) currently in the field of software engineering/development complete a &lt;em&gt;questionnaire&lt;/em&gt; inquiring about their job.&lt;/p&gt;

&lt;p&gt;As great as this is for receiving experience and answers to common questions about entering the field of software, I do not personally know anyone who works in developing software. So I've decided to reach out here and see if anyone would like to contribute!&lt;/p&gt;

&lt;h2&gt;
  
  
  The details
&lt;/h2&gt;

&lt;p&gt;The questionnaire is simply a short (8 questions) list of questions that you would type out the answers to and send back to me via email.&lt;/p&gt;

&lt;p&gt;Your answers will not be used in any manner outside of submitting the paper to my school. Your name will be the only disclosed information.&lt;/p&gt;

&lt;p&gt;If you are interested in helping out a budding software developer, &lt;strong&gt;please reply below!&lt;/strong&gt; I'll send out the questionnaire to the email that is public on your profile.&lt;/p&gt;

&lt;p&gt;And of course, your time and answers are extremely valuable to me. I am passionate about software development and engineering, and I would love &lt;em&gt;any&lt;/em&gt; advice you can give.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thank you! 😄
&lt;/h3&gt;

</description>
      <category>help</category>
    </item>
    <item>
      <title>Workdrop — UI Design and Prototyping</title>
      <dc:creator>Peyton McGinnis</dc:creator>
      <pubDate>Mon, 13 Apr 2020 00:23:55 +0000</pubDate>
      <link>https://dev.to/sergix/workdrop-ui-design-and-prototyping-2g9i</link>
      <guid>https://dev.to/sergix/workdrop-ui-design-and-prototyping-2g9i</guid>
      <description>&lt;p&gt;&lt;em&gt;This continues my series on my #twiliohackathon submission! Thanks for reading!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Sergix/workdrop" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; (MIT Licensed)&lt;br&gt;
&lt;a href="https://stackshare.io/Sergix/workdrop" rel="noopener noreferrer"&gt;Stackshare&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Normally, I do all my UI/UX design and prototyping with &lt;a href="https://www.adobe.com/products/xd.html" rel="noopener noreferrer"&gt;Adobe XD&lt;/a&gt;, but given all the great things I've been reading about &lt;a href="https://www.figma.com/" rel="noopener noreferrer"&gt;Figma&lt;/a&gt;, I thought I'd give it a whirl.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Theme
&lt;/h2&gt;

&lt;p&gt;Starting out, I created a new project in Figma and created an iPhone 8 size layer. &lt;em&gt;(mobile first!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the post cover image, I already have a logo designed, but I haven't fully decided on a theme yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Colors
&lt;/h3&gt;

&lt;p&gt;I've decided to go with a mostly monochromatic color scheme so that the actions really pop out with bright neons, such as mint and red for success and error, respectively.&lt;/p&gt;

&lt;p&gt;I'm not that great with colors, but here's a base pallette I hope will work well (I used &lt;a href="https://colordesigner.io" rel="noopener noreferrer"&gt;ColorDesigner&lt;/a&gt; to put this together):&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%2Fyt1uilmll5jlnhbmlidu.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%2Fi%2Fyt1uilmll5jlnhbmlidu.png" alt="Colors"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, I can change the colors as needed, but I'll stick with it for now.&lt;/p&gt;

&lt;p&gt;The full color pallette:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background — #FAFAFA&lt;/li&gt;
&lt;li&gt;Foreground — #121212&lt;/li&gt;
&lt;li&gt;Error — #F23034&lt;/li&gt;
&lt;li&gt;Note/Info — #732DD9&lt;/li&gt;
&lt;li&gt;Success — #4FED77&lt;/li&gt;
&lt;li&gt;Warning — #FFD10F&lt;/li&gt;
&lt;li&gt;Interaction — #1C3FFD&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Type choice
&lt;/h3&gt;

&lt;p&gt;I absolutely &lt;em&gt;love&lt;/em&gt; type design, and my go-to typeset for web projects is &lt;a href="https://typetype.org/fonts/tt-hoves/" rel="noopener noreferrer"&gt;TT Hoves&lt;/a&gt;, which sadly isn't free but it's well worth the cost.&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%2Fbn32dogk0t68k0u4kr4b.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%2Fi%2Fbn32dogk0t68k0u4kr4b.png" alt="Type spec"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;&lt;a href="https://typetype.org/files/typetype/fonts/hoves/tt_hoves_specimen.pdf" rel="noopener noreferrer"&gt;TT Hoves type specimen by TypeType&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Completed design system
&lt;/h3&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%2Feu2r30psufghp5idw8il.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%2Fi%2Feu2r30psufghp5idw8il.png" alt="Design system"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After finishing the main theme, I created local styles in Figma for both the colors and the type and then exported it as a library for use in my other Figma files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Components
&lt;/h2&gt;

&lt;p&gt;Now that we have a basic theme down, it's time to start on a couple of the components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifications
&lt;/h3&gt;

&lt;p&gt;I played around a bit with some icons and colors, and got to a place where I liked the notification design. I'm trying to keep the design mostly flat, but I'll end up adding some box shadows later on. I also adjusted some of the text colors to be accessible with a minimum contrast of 7.0.&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%2Fhe3209lrri8dce5cudol.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%2Fi%2Fhe3209lrri8dce5cudol.png" alt="Notifications"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Buttons
&lt;/h3&gt;

&lt;p&gt;The top button is the &lt;em&gt;primary action button&lt;/em&gt;. Each page will only have one primary action, as to make clear the suggested action the user should take. The bottom button is the &lt;em&gt;secondary action button&lt;/em&gt;, which is an alternative but likely action the user could perform.&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%2Fkxkptd0whd9rnb0vc9rx.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%2Fi%2Fkxkptd0whd9rnb0vc9rx.png" alt="Buttons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Illustrations
&lt;/h3&gt;

&lt;p&gt;To make the interface pop, I added some illustrations from &lt;a href="https://isometric.online/" rel="noopener noreferrer"&gt;isometric.online&lt;/a&gt;, which provides free, open-sourced (MIT) isometric illustrations. I customized the colors in them to conform to the design system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full UI
&lt;/h2&gt;

&lt;p&gt;As of now, I have completed the entirety of the UI, but I will not be showing it off until the hackathon is completed. Check back later to see it in action!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you for reading! God bless!
&lt;/h2&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>twiliohackathon</category>
    </item>
  </channel>
</rss>
