DEV Community

Robert
Robert

Posted on

Build a Medium UI clone with Svelte, Materialize and Web Monetization API

In this post, I'll be taking you through how I designed a simple Medium-like UI that utilizes our svelte-monetization component.

Demo: https://mediocre.now.sh

Setting up the application

I built the app using Svelte. I made use of materializecss for the responsive design and svelte-monetization for showing/hiding ads and remove image watermarks.

Open up your terminal and run the following to generate a new svelte app.

npx degit sveltejs/template medium-clone
Enter fullscreen mode Exit fullscreen mode

Open App.svelte and add the <svelte:head> element together with your payment pointer.

<script>
...
</script>

<svelte:head>
  <meta
    name="monetization"
    content="$coil.xrptipbot.com/701298d5-481d-40ff-9945-336671ab2c42" />
</svelte:head>
Enter fullscreen mode Exit fullscreen mode

Next, install the following dependencies.

npm install materialize-css rollup-plugin-css-only svelte-monetization watermarkjs
Enter fullscreen mode Exit fullscreen mode

Setup materialize and rollup

In rollup.config.js, configure the plugin by importing it import css from 'rollup-plugin-css-only' and add the css({output: "public/build/base.css"}) to the list of plugins.

Now we can import .css files in src/main.js:

import '../node_modules/materialize-css/dist/css/materialize.css'
Enter fullscreen mode Exit fullscreen mode

Don't forget to update public/index.html to include the generated base.css instead of global.css:

- <link rel='stylesheet' href='/global.css'>
+ <link rel='stylesheet' href='/build/base.css'>
Enter fullscreen mode Exit fullscreen mode

Components

Inside the src folder, add a components folder and create 3 files - Nav.svelte, Footer.svelte, and Advertisement.svelte.

Our Nav.svelte component is a simple Navbar with a title.

<nav class="white" role="navigation">
  <div class="nav-wrapper container">
    <a href="/" class="brand-logo black-text">Mediocre</a>
  </div>
</nav>
Enter fullscreen mode Exit fullscreen mode

Next, the Footer.svelte component that contains dummy text.

<footer class="page-footer grey darken-4">
  <div class="container">
    <div class="row">
      <div class="col l4 s12">
        <h5 class="white-text">Discover Mediocre</h5>
        <p class="grey-text text-lighten-4">
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Eius
          architecto dolores voluptatum ipsa aliquid fuga asperiores vitae
          veniam laudantium non!
        </p>
      </div>
      <div class="col l4 s12">
        <h5 class="white-text">Make Mediocre yours</h5>
        <p class="grey-text text-lighten-4">
          Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ad velit
          molestiae excepturi error est aperiam?
        </p>
      </div>
      <div class="col l4 s12">
        <h5 class="white-text">Become a member</h5>
        <p class="grey-text text-lighten-4">
          Lorem ipsum dolor sit amet consectetur, adipisicing elit. Reiciendis
          officia consequatur repellendus dolores voluptate commodi.
        </p>
      </div>
    </div>
  </div>
  <div class="footer-copyright">
    <div class="container">Mediocre</div>
  </div>
</footer>
Enter fullscreen mode Exit fullscreen mode

And lastly, the Advertisement component which should appear if the user is not web monetized.

<div class="card">
  <div class="card-content">
    <span class="card-title">Get unlimited access</span>
    <p>Become a member to keep reading.</p><br />
    <a href="https://coil.com/" class="waves-effect waves-light btn">
      VISIT COIL
    </a><br /><br />
    <p>Here's what you get when you upgrade to membership:</p><br />
    <p>
      <span style="font-weight: bold;">Unlimited access.</span>
      Explore Mediocre's library filled with everything you're curious about.
    </p><br />
    <p>
      <span style="font-weight: bold;">No ads.</span>
      There are zero ads on Mediocre and we don't sell your data.
    </p><br />
    <p>
      <span style="font-weight: bold;">Reward quality writing.</span>
      When you spend time reading a story, you make the author happy.
    </p><br />
    <p>
      "I love Mediocre's membership — it gives me access to the stories I love
      by the writers I love, and it allows me to help support those writers
      financially."
    </p>
    <p style="font-weight: bold;">—John Doe, Mediocre member</p>
  </div>
</div>

Enter fullscreen mode Exit fullscreen mode

Using svelte-monetization

Inside our App.svelte, let's import our components and add dummy content.

<script>
  import Nav from "./components/Nav.svelte";
  import Footer from "./components/Footer.svelte";
  import Advertisement from "./components/Advertisement.svelte";
  let imageUrl = "https://i.imgur.com/tMxofag.png";
</script>

<!-- Nav -->
<Nav />
<!-- Main -->
<main>
  <div class="container">
    <div class="section">
      <h4>Lorem ipsum dolor sit amet, consectetur adipisicing</h4>
      <p>Oct 1, 2020 · 11 min read</p>
      <div style="text-align: center;">
        <img src={imageUrl} alt="banner" class="responsive-img" />
      </div>
     <p>
        Lorem ipsum dolor sit amet...
    </p>
    <p>
        Lorem ipsum dolor sit amet...
    </p>
    <Advertisement />
    </div>
  </div>
</main>
<!-- Footer -->
<Footer />
Enter fullscreen mode Exit fullscreen mode

Our app then will look like this.

Styling our page

Now the next thing that we want is to hide the Advertisement component and show full content of the article when the user is web monetized. We can now import svelte-monetization.

We will use Svelte's each block to loop over our dummy text.

<script>
  import Nav from "./components/Nav.svelte";
  import Footer from "./components/Footer.svelte";
  import Advertisement from "./components/Advertisement.svelte";
  import SvelteMonetization from "svelte-monetization";
  let imageUrl = "https://i.imgur.com/tMxofag.png";
</script>

<!-- Nav -->
<Nav />
<!-- Main -->
<main>
  <div class="container">
    <div class="section">
      <h4>Lorem ipsum dolor sit amet, consectetur adipisicing</h4>
      <p>Oct 1, 2020 · 11 min read</p>
      <div style="text-align: center;">
        <img src={imageUrl} alt="banner" class="responsive-img" />
      </div>
      <SvelteMonetization let:isLoading let:isMonetized>
        {#if isLoading}
          <div>Loading...</div>
        {:else if isMonetized}
           {#each [...Array(10).keys()] as item}
           <p>Lorem ipsum dolor sit amet...</p>
           {/each}
        {:else}
           {#each [...Array(2).keys()] as item}
           <p>Lorem ipsum dolor sit amet...</p>
           {/each}
           <Advertisement />
        {/if}
      </SvelteMonetization>
    </div>
  </div>
</main>
<!-- Footer -->
<Footer />
Enter fullscreen mode Exit fullscreen mode

Output:

Image 2

To test this, make sure you have the Coil extension installed and the Simulate Monetization bookmarklet added to your bookmarks. Steps can be seen here.

What if we want to add watermarks to our image? And remove it when user is web monetized? Good thing there's a watermarking library named watermarkjs and we can apply it to our app.

Let's import it inside App.svelte and do the code below.

<script>
  import { onMount } from "svelte";
  import Nav from "./components/Nav.svelte";
  import Footer from "./components/Footer.svelte";
  import Advertisement from "./components/Advertisement.svelte";
  import SvelteMonetization from "svelte-monetization";
  import watermark from "watermarkjs";
  const imageUrl = "https://i.imgur.com/tMxofag.png";
  let src = imageUrl;

  const addWatermark = async () => {
    const text = watermark.text.center(
      "I'm a watermark",
      "38px serif",
      "#fff",
      0.5
    );
    const options = {
      init(img) {
        img.crossOrigin = "anonymous";
      }
    };
    const img = await watermark([imageUrl], options).image(text);
    src = img.src;
  };

  onMount(() => {
    addWatermark();
  });
</script>

<!-- Nav -->
<Nav />
<!-- Main -->
<main>
  <div class="container">
    <div class="section">
      <h4>Lorem ipsum dolor sit amet, consectetur adipisicing</h4>
      <p>Oct 1, 2020 · 11 min read</p>
      <div style="text-align: center;">
        <img {src} alt="banner" class="responsive-img" />
      </div>
      <!-- Use the start event to track monetizationstart event -->
      <SvelteMonetization 
        let:isLoading 
        let:isMonetized 
        on:start={() => (src = imageUrl)}>
        <!-- Rest of our code -->
      </SvelteMonetization>
    </div>
  </div>
</main>
<!-- Footer -->
<Footer />
Enter fullscreen mode Exit fullscreen mode

We created a function called addWatermark to add a watermark to our image on load. When the start event is dispatched, meaning the user is web monetized, the watermark is removed. Our app will finally look like this.

Image 3

Full code: https://github.com/sorxrob/svelte-monetization/tree/master/example

Conclusion

There you have it, if you have followed this tutorial to this point, you should have your own Medium-like UI with Web Monetization working. The Web Monetization API will be very helpful to content creators.

If you found this post useful, please add a ❤️ & 🦄 both here and in my entry post.

Top comments (5)

Collapse
 
faraazahmad profile image
Syed Faraaz Ahmad

Could you also build an annoying pop ups feature? It's not really a Medium clone without that

Collapse
 
wobsoriano profile image
Robert • Edited

I have not experienced any of this pop ups. Could you point me to a sample?

Collapse
 
faraazahmad profile image
Syed Faraaz Ahmad
Thread Thread
 
wobsoriano profile image
Robert

Thanks! To implement something like that you can use Materializecss Modals.

Something like this:

<script>
import { onMount } from "svelte";
import SvelteMonetization from 'svelte-monetization';
let modal;
onMount(() => {
    const modalElem = document.querySelector('.modal');
    modal = M.Modal.init(modalElem);

    // Show modal on load
    modal.open();
});

function handleStart() {
    modal.close();
}
</script>


<SvelteMonetization on:start={handleStart} />

<div class="modal">
<!-- Modal content -->
</div>
Collapse
 
xenogew profile image
Natta Wang

The code just display components, did you implement editor?