DEV Community

Cover image for Netlify Dynamic Site Challenge: Motizen, a treasure hunt game
Sergo
Sergo

Posted on

Netlify Dynamic Site Challenge: Motizen, a treasure hunt game

This is a submission for the Netlify Dynamic Site Challenge: Build with Blobs.

First of all, I want to apologize. Both to myself and to the community. Unfortunately, I couldn't and didn't have time to implement everything I wanted. I didn't estimate my strength and time. And this is a very big mistake. But I always remember that many smart people said that it's better late than never. Or it's better to participate than not to participate.

What I Built

I wanted to surprise you with a game where each user would uncover pieces of a picture and find various treasures. Every day, piece by piece. But since I don't have enough experience, and I didn't work closely with Netlify, it turned out the way it did.

First of all, when you go to the site, don't be scared. Yes, there is a registration. 😅 I implemented it with Netlify Integrity. The game was supposed to show players who found treasures. But I didn't have time to implement this.

Demo

Mista

You can try the game here.
Attention! Please do not click too quickly and too much! I didn't implement serverless functions very well.😭😥

Platform Primitives

I implemented two functions, loadGameState, saveGameState, with which you can save the game state using Netlify Blobs as storage. And I did it through Netlify Function Serverless.

async function loadGameState(gameStateBlobId) {
    return fetch(`/.netlify/functions/load-game-state?blobID=${gameStateBlobId}`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
        }
    })
    .then(response => {
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
    })
    .then(result => {
      const gameState = JSON.parse(result.gameState);
      console.log('Game state loaded successfully: ', gameState);
      return gameState;
    })
    .catch(error => {
      console.log('Error in loading game state: ', error);
      throw error;
    });
}

async function saveGameState(gameState) {
    const json = JSON.stringify(gameState);

    return fetch('/.netlify/functions/save-game-state', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: json
    })
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
    })
    .then(result => {
      console.log('Game state saved successfully: ', result)
      return result;
    })
    .then(result => {
        localStorage.setItem('gameStateBlobId', result.blobID);
    })
    .catch(error => {
      console.log('Error in saving game state: ', error);
      throw error;
    });
}
Enter fullscreen mode Exit fullscreen mode

This is a serverless function for load-game-state.mjs:

import { getStore } from '@netlify/blobs';

exports.handler = async function(event, context) {
    const siteID = process.env.NETLIFY_SITE_ID;
    const token = process.env.NETLIFY_TOKEN;
    const store = getStore({ siteID, token, name: 'my-store' });

    if (event.httpMethod !== 'GET') {
      return { statusCode: 405, body: 'Method Not Allowed' };
    }

    const blobID = event.queryStringParameters.blobID;
    if (!blobID) {
      return { statusCode: 400, body: JSON.stringify({ message: "BlobID is required" }) };
    }

    let buffer;
    try {
      buffer = await store.get(blobID);
    } catch (error) {
      console.error("Error fetching blob:", error);
      return { statusCode: 500, body: JSON.stringify({ message: "Error fetching game state" }) };
    }

    if (!buffer) {
      return { statusCode: 404, body: JSON.stringify({ message: "Game state not found" }) };
    }

    const gameState = buffer.toString();

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ gameState })
    };
};
Enter fullscreen mode Exit fullscreen mode

And this is how I implemented Netlify Integrity (maybe, someone it will helpful):

'use client'

import netlifyIdentity from 'netlify-identity-widget';

export default function Home() {

...

    useEffect(() => {
      netlifyIdentity.init();

      const handleLogin = (currentUser) => {
        netlifyIdentity.open();
        setIsLoggedIn(true);
        setPlayerName(currentUser.user_metadata.full_name || currentUser.email);
      };

      const handleLogout = () => {
        setIsLoggedIn(false);
        setPlayerName('');
        netlifyIdentity.logout(); // выход из системы
      };

      netlifyIdentity.on('login', handleLogin);
      netlifyIdentity.on('logout', handleLogout);

      if (netlifyIdentity.currentUser()){
        handleLogin(netlifyIdentity.currentUser());
      }

      return () => {
        netlifyIdentity.off('login', handleLogin);
        netlifyIdentity.off('logout', handleLogout);
      };
    }, []); 

    return (
      <main className={styles.main}>
        <ImageUploader onFileSelect={(selectedFile) => setFile(selectedFile)} setName={(name) => {setUserName(name); setPlayerName(name);}} />
        <CanvasComponent processImage={processImage} />
        <div ref={nameRef}>{playerName}</div>{/* Div для отображения имен */}
        {isLoggedIn ? (
          <div className={styles.userIdentity}>
            {playerName}
            <button onClick={() => netlifyIdentity.logout()}>Logout</button>
          </div>
        ) : (
          <button onClick={() => netlifyIdentity.open()}>Login / Sign Up</button>
        )}
      </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

This is not the End

In this project I used Next.js, Netlify Blobs Store, Netlify Integrity, Canvas API, Netlify Serverless and deployed on Netlify.
It was hard, but very interesting. Thank you for this challenge!

Also, I will plan to continue work on this project and someday finish it. I want to connect it with another my game about motivation. If you want to follow, I created a new channel in Telegram. Thank you for your patience and attention!

Griffith

Top comments (0)