<?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: Michael</title>
    <description>The latest articles on DEV Community by Michael (@floratobydev).</description>
    <link>https://dev.to/floratobydev</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%2F941330%2F88c9897d-f5c0-4f78-94f8-94bfe4582f38.png</url>
      <title>DEV Community: Michael</title>
      <link>https://dev.to/floratobydev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/floratobydev"/>
    <language>en</language>
    <item>
      <title>Restaurant Website Builder</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Mon, 30 Jun 2025 06:54:21 +0000</pubDate>
      <link>https://dev.to/floratobydev/restaurant-website-builder-5702</link>
      <guid>https://dev.to/floratobydev/restaurant-website-builder-5702</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/storyblok"&gt;Storyblok Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;The goal of this project is to &lt;strong&gt;build a restaurant website builder using Storyblok&lt;/strong&gt;. I wanted to enable restaurants to launch beautiful, fully functional websites quickly, without relying heavily on developers, while maintaining a clean, premium design aesthetic.&lt;/p&gt;

&lt;p&gt;To ensure the builder was practical, I conducted &lt;strong&gt;extensive research on what content restaurants typically need to manage&lt;/strong&gt;, identifying key data structures such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Menus&lt;/li&gt;
&lt;li&gt;Location and embedded map&lt;/li&gt;
&lt;li&gt;Hours of operation&lt;/li&gt;
&lt;li&gt;About Us pages&lt;/li&gt;
&lt;li&gt;Online ordering links&lt;/li&gt;
&lt;li&gt;Galleries for dishes and ambiance&lt;/li&gt;
&lt;li&gt;Reviews and testimonials&lt;/li&gt;
&lt;li&gt;Contact forms for reservations or inquiries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using these insights, I implemented all necessary components to create a &lt;strong&gt;scalable restaurant website builder on Storyblok&lt;/strong&gt;.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Storyblok Space:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;(Available upon request while final polish is in progress)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Website&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://incredible-manatee-d2b954.netlify.app/" rel="noopener noreferrer"&gt;https://incredible-manatee-d2b954.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Repository:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/FloratobyDev/storyblok-restaurant-website-builder.git" rel="noopener noreferrer"&gt;Code Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/FloratobyDev/storyblok-plugins.git" rel="noopener noreferrer"&gt;Plugins Repository&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Demo Video or Screenshots&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvia.placeholder.com%2F1200x700.png%3Ftext%3DRestaurant%2BWebsite%2BBuilder%2BDemo" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvia.placeholder.com%2F1200x700.png%3Ftext%3DRestaurant%2BWebsite%2BBuilder%2BDemo" alt="Demo Screenshot" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React + TypeScript + Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMS:&lt;/strong&gt; Storyblok&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI:&lt;/strong&gt; OpenAI (menu PDF extraction)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Vercel (planned)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  How I Used Storyblok
&lt;/h3&gt;

&lt;p&gt;I leveraged Storyblok’s:&lt;br&gt;
✅ Visual Editor for live content editing.&lt;br&gt;&lt;br&gt;
✅ Component-based system to build reusable blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hero section&lt;/li&gt;
&lt;li&gt;Navbar with active highlighting&lt;/li&gt;
&lt;li&gt;Footer with branding, tagline, and navigation&lt;/li&gt;
&lt;li&gt;Signature food menus with price, description, and images&lt;/li&gt;
&lt;li&gt;Gallery using an image group block&lt;/li&gt;
&lt;li&gt;About Us “Story Clip” sections with optional reversed layouts for storytelling&lt;/li&gt;
&lt;li&gt;Contact sections with forms, embedded Google Maps, and operating hours&lt;/li&gt;
&lt;li&gt;Reviews and testimonials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ Map plugin for simple Google Maps integration via address entry.&lt;br&gt;&lt;br&gt;
✅ Structure to enable restaurant owners and teams to &lt;strong&gt;manage content independently while maintaining design consistency&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  AI Integration
&lt;/h3&gt;

&lt;p&gt;For this project, I &lt;strong&gt;incorporated AI using OpenAI to extract structured data from menu PDFs&lt;/strong&gt;. This allows restaurant owners to upload their existing PDF menus and have them converted into structured JSON, which can then be easily imported into Storyblok, drastically reducing onboarding time and manual data entry for restaurant teams.&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;AI-powered extraction pipeline&lt;/strong&gt; ensures even restaurants with only a physical menu can digitize their content and launch quickly on Storyblok.&lt;/p&gt;




&lt;h2&gt;
  
  
  Learnings and Takeaways
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;What I’m proud of:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building a structured, scalable system that empowers non-technical users to manage restaurant content with ease.&lt;/li&gt;
&lt;li&gt;Leveraging Storyblok’s flexibility to maintain a clean, premium design system while allowing dynamic content.&lt;/li&gt;
&lt;li&gt;Successfully integrating AI to transform PDF menu data into structured content for rapid deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Challenges:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time constraints prevented a full visual UI polish pass across all components.&lt;/li&gt;
&lt;li&gt;I would have liked to expand the AI integration to include an article generation plugin to help restaurants create seasonal updates and blog content, but I could not complete it within the challenge window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project serves as a &lt;strong&gt;strong foundation for a restaurant website builder on Storyblok&lt;/strong&gt;, enabling restaurants to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Showcase hero sections, galleries, and signature dishes&lt;/li&gt;
&lt;li&gt;Display menus, location, and operating hours&lt;/li&gt;
&lt;li&gt;Share customer reviews and receive contact inquiries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while maintaining &lt;strong&gt;control, consistency, and scalability&lt;/strong&gt;, with the added power of &lt;strong&gt;AI-assisted data onboarding&lt;/strong&gt;.&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>storyblokchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Map Location Picker Plugin</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Sun, 22 Jun 2025 14:29:34 +0000</pubDate>
      <link>https://dev.to/floratobydev/map-location-picker-plugin-4ng7</link>
      <guid>https://dev.to/floratobydev/map-location-picker-plugin-4ng7</guid>
      <description>&lt;h2&gt;
  
  
  Map Location Picker Plugin for Storyblok
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/storyblok"&gt;Storyblok Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Code Repository:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/FloratobyDev/storyblok-get-location-plugin.git" rel="noopener noreferrer"&gt;GitHub – storyblok-get-location-plugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo Video or Screenshots&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://youtu.be/C1nkED5VtAk" rel="noopener noreferrer"&gt;Watch the demo on YouTube&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;I created a &lt;strong&gt;Storyblok Field Plugin&lt;/strong&gt; that makes adding real-world locations as easy as typing an address. Powered by the &lt;strong&gt;Google Maps Geocoding API&lt;/strong&gt;, editors can search for a place, see it instantly pinned on a map, and automatically store clean location data — &lt;strong&gt;latitude&lt;/strong&gt;, &lt;strong&gt;longitude&lt;/strong&gt;, and &lt;strong&gt;formatted address&lt;/strong&gt; — into their Storyblok content.&lt;/p&gt;

&lt;p&gt;No complex setup. No copying coordinates from Google. Just search, pin, and go.&lt;/p&gt;

&lt;p&gt;It’s a lightweight plugin for a heavyweight need. Whether it’s &lt;strong&gt;contact pages&lt;/strong&gt;, &lt;strong&gt;store locators&lt;/strong&gt;, &lt;strong&gt;event listings&lt;/strong&gt;, or any content that benefits from geographic context, this solves a common problem with minimal friction.&lt;/p&gt;

&lt;p&gt;The goal wasn’t to reinvent maps. It was to take something almost every site needs and make it feel like a native part of the Storyblok editing experience. I wanted location data to be just another field — not a separate workflow.&lt;/p&gt;

&lt;p&gt;It’s simple by design — and that’s the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Clone the repository
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-username/storyblok-map-location-plugin.git
&lt;span class="nb"&gt;cd &lt;/span&gt;storyblok-map-location-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Deploy the plugin to Storyblok
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run deploy &lt;span class="nt"&gt;--workspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;get-location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Follow the Prompts
&lt;/h4&gt;

&lt;p&gt;It will ask for your Storyblok Personal Access Token.&lt;br&gt;
You can create one at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storyblok → My Account → Personal Access Tokens&lt;/li&gt;
&lt;li&gt;Make sure the token has permission to manage your space and plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Add the plugin to a Block
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Storyblok Space&lt;/li&gt;
&lt;li&gt;Open or create a component&lt;/li&gt;
&lt;li&gt;Add a new field:

&lt;ul&gt;
&lt;li&gt;Type: custom&lt;/li&gt;
&lt;li&gt;Plugin: Select your deployed plugin (e.g., get-location)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save the schema&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can use the following code to implement the visual UI of the data:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.address-map-iframe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;280px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e5e7eb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.map-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.address-map-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.95rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#374151&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.address-map-fallback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.875rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#9ca3af&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="m"&gt;0&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;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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AddressMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;blok&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;blok&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;address_view&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;embedUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;blok&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;address_view&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;formatted_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address-map-fallback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;map-wrapper&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address-map-iframe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lazy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;blok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address_view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;embedUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Google Maps Location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;referrerPolicy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-referrer-when-downgrade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;allowFullScreen&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/iframe&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Plugin:&lt;/strong&gt; React + Vite
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework/Platform:&lt;/strong&gt; Storyblok Field Plugin SDK (iframe-based integration)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design System:&lt;/strong&gt; Custom CSS mimicking &lt;a href="https://blok.ink" rel="noopener noreferrer"&gt;Blok.ink (Storyblok Design System)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input &amp;amp; Button Components:&lt;/strong&gt; Styled to match &lt;code&gt;SbTextField&lt;/code&gt; and &lt;code&gt;SbButton&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Function:&lt;/strong&gt; Firebase Functions for address-to-coordinate resolution
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maps API:&lt;/strong&gt; Google Maps Geocoding API (called via Firebase Function)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UX Behavior:&lt;/strong&gt; Debounced search, cooldown logic, and loading indicator
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview Feature:&lt;/strong&gt; Google Maps iframe showing selected location
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Management:&lt;/strong&gt; React &lt;code&gt;useState&lt;/code&gt; for managing form and result states
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I Used Storyblok
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Registered the plugin as a &lt;code&gt;custom&lt;/code&gt; field type using the Field Plugin UI&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;useFieldPlugin()&lt;/code&gt; to sync data between the Visual Editor and plugin iframe&lt;/li&gt;
&lt;li&gt;Saved structured data such as:
&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;lat:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;lng:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;formatted_address:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;embedUrl:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;string&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;



</description>
      <category>devchallenge</category>
      <category>storyblokchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>PikPok – Share Your Creativity, Your Way.</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Mon, 14 Oct 2024 06:48:28 +0000</pubDate>
      <link>https://dev.to/floratobydev/pikpok-share-your-creativity-your-way-4m6l</link>
      <guid>https://dev.to/floratobydev/pikpok-share-your-creativity-your-way-4m6l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pinata"&gt;The Pinata Challenge &lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://pikpok-8e666.web.app" rel="noopener noreferrer"&gt;Live App&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/FloratobyDev/pikpok" rel="noopener noreferrer"&gt;Github Code&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;PikPok&lt;/strong&gt; is a fun and interactive platform where you can share and explore different types of content, from videos and images to audio and even apps. It’s like TikTok, but with more ways to get creative! Plus, you can earn points by posting or interacting with others (TBD), making it a playful experience that rewards your engagement. Whether you’re sharing videos, music, or cool apps, PikPok is the place to connect, create, and have fun.&lt;/p&gt;

&lt;p&gt;Here are some of the features I built:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sophisticated Upload Form&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Profile Gallery&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Custom Video Player and Content Snap Scrolling&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Custom Image Viewer&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Application Viewer with the ability to Download&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Custom Audio Player&lt;/strong&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  More Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Storage Versatility
&lt;/h3&gt;

&lt;p&gt;I can store any file types I want -- image/&lt;em&gt;, audio/&lt;/em&gt;, application/*, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzaescj0xzowdt8h1mcc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzaescj0xzowdt8h1mcc.png" alt="Image description" width="532" height="201"&gt;&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%2Fuploads%2Farticles%2Fd2428gnvz8yc60mnnddc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2428gnvz8yc60mnnddc.png" alt="Image description" width="526" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-side Upload using JWT with custom use limit and scoped rules
&lt;/h3&gt;

&lt;p&gt;I created a Firebase Cloud Function that generates the JWT and uses it to upload a file in Firestore.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Generate a signed URL for limited-time access
&lt;/h3&gt;

&lt;p&gt;I have both public and paid content. The public option immediately generates a signed URL on upload and the paid option generates one after users pay with points. Both can be accessed blazingly fast, given their CDN support. &lt;/p&gt;

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

&lt;p&gt;Overall, PikPok combines creativity, interactivity, and rewards to deliver an engaging experience for users, all powered by Pinata’s reliable and flexible storage solutions, making content sharing fun and accessible.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pinatachallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>National Identity-focused Social Platform</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Mon, 27 May 2024 04:35:53 +0000</pubDate>
      <link>https://dev.to/floratobydev/national-identity-focused-social-platform-150o</link>
      <guid>https://dev.to/floratobydev/national-identity-focused-social-platform-150o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/awschallenge"&gt;The AWS Amplify Fullstack TypeScript Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;h4&gt;
  
  
  General Overview
&lt;/h4&gt;

&lt;p&gt;A social platform that connects people worldwide by celebrating different national identities and cultures. It's perfect for expatriates, global enthusiasts, and anyone interested in exploring cultural diversity. Users can share their traditions, discuss important issues, and learn about various countries in a straightforward and engaging way. This platform aims to build understanding and cooperation among its global community.&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Overview
&lt;/h4&gt;

&lt;p&gt;This application is built on top of the starter pack environment that AWS Amplify Gen 2 docs provided. It includes all &lt;strong&gt;FOUR INTEGRATIONS&lt;/strong&gt; but the UI and everything frontend is done from scratch. &lt;/p&gt;

&lt;h2&gt;
  
  
  Demo and Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Design
&lt;/h3&gt;

&lt;h2&gt;
  
  
  Home
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Profile and Settings
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Login/Signup Page
&lt;/h2&gt;

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

&lt;p&gt;Link to my Github Repository: &lt;a href="https://github.com/FloratobyDev/culture-application" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Link to my app: &lt;a href="https://deploy-branch.d3vdeei8om1244.amplifyapp.com/" rel="noopener noreferrer"&gt;Culture Application&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: If you're trying to sign up, you will have to reload the site once you're in and log in. Once you see your email at the top right, that indicates that you are logged in. Sorry for the inconvenience. I tried.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrations
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Amazon Cognito
&lt;/h4&gt;

&lt;p&gt;Used mainly for user authentication and preliminary data storage.&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Lambda
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Post confirmation trigger that creates a new user to the User table in DynamoDB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  DynamoDB
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Main database for storing new data for connections, messaging, notifications, categories, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  S3
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Profile Picture storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  AppSync
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;GraphQL and PubSub APIs to connect my application to my data and events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  IAM and IAM Identity Center
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Roles and policies for services and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Feature Full&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>awschallenge</category>
      <category>amplify</category>
      <category>fullstack</category>
    </item>
  </channel>
</rss>
