# Personalize profiles with 2bttns and Next.js

We're going to implement 2bttns in the sign up flow of our app to generate rich user profiles with just two buttons.&#x20;

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2FlEcjpeG3D996s32TDWYM%2FScreenshot%202024-02-22%20at%207.37.55%E2%80%AFPM.png?alt=media&#x26;token=0f500f93-f75c-45ef-b794-df64b74863da" alt=""><figcaption><p>What we're building today</p></figcaption></figure>

## Our example app <a href="#our-example-app" id="our-example-app"></a>

In this example, we have a React.js (React 16) client and a basic Express.js server. Here's what our app looks like right now:

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2FFLaW3ZyXJUaz9PQGY3VF%2FScreenshot%202024-02-21%20at%207.09.09%E2%80%AFPM.png?alt=media&#x26;token=ef37fab1-7e6d-4961-9960-766289911c94" alt=""><figcaption><p>Basic sign up form</p></figcaption></figure>

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2F6tNCkGmF4M4zsWA1sM2a%2FScreenshot%202024-02-21%20at%207.10.44%E2%80%AFPM.png?alt=media&#x26;token=ee90b0bf-61d6-4114-be60-1dbb1759438a" alt=""><figcaption><p>Blank profile page after sign in</p></figcaption></figure>

We have a simple sign in form that asks for a username and password. Once signed up, users can view their profile. We're going to use 2bttns to enrich the profile with details about the user's hobbies and interests.

## Why use 2bttns? <a href="#why-use-2bttns" id="why-use-2bttns"></a>

In this example, we want to collect some information about their favorite hobbies and activities. Rather than building my own data collection component (and designing a good one), we can use 2bttns.&#x20;

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2FEBplbVJAKBqRorZnW4MQ%2FScreenshot%202024-02-22%20at%206.32.20%E2%80%AFPM.png?alt=media&#x26;token=605ca565-4c0b-462f-9808-cd35460f7740" alt=""><figcaption><p>The profile page using data generated by playing 2bttns</p></figcaption></figure>

Once everything is set up, I can have a whole personalization system configured and adjust various aspects from the results to the game's user experience through a no-code dashboard.&#x20;

**💭 Ideas galore:** With these score we can make our profiles rich, personalized, and ready for content recommendations and personalized user experiences.

***

## Set up Console <a href="#set-up-console" id="set-up-console"></a>

We'll need to set up the 2bttns Console. We're going to use the 2bttns CLI to spin up our Console.&#x20;

{% hint style="info" %}
&#x20; If you'd like to play and test Games online, be sure to deploy your Console to a cloud environment.
{% endhint %}

#### **Install the `2bttns-cli`**

To get started, you need to install the 2bttns command-line interface (CLI) in your development environment. Open your terminal and run:

```bash
npm install @2bttns/2bttns-cli
```

#### **Using the CLI** <a href="#using-the-cli" id="using-the-cli"></a>

With the CLI installed, you can now create a new console. Follow the steps to configure your Console with your `DATABASE_URL`. In your terminal, execute:

{% hint style="warning" %}
&#x20; Make sure Docker is running!🐳
{% endhint %}

```bash
2bttns-cli new 
```

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2Fu1UEKYXvD2SBIXFyc9NC%2FScreenshot%202024-02-23%20at%2011.48.14%E2%80%AFPM.png?alt=media&#x26;token=bd4a4116-46c5-4a12-920a-205b8280a086" alt="" width="375"><figcaption><p>You've successfully set up and launched your Console</p></figcaption></figure>

Behind the scenes, this will:

* create a `docker-compose.yml` file in the current directory.&#x20;
* launch your Console,&#x20;
* apply migrations to your specified database,&#x20;
* seed the database with examples (optional)

You can place the Docker Compose YAML file anywhere. This Docker Compose file includes a PostgreSQL database container. Considering our React application lacks a configured database, we'll keep it.

{% hint style="info" %}
&#x20; **💡 Helpful Tip:** To avoid occupying your terminal window while running the container, include the `--detach` (or `-d`) flag like this:&#x20;

```bash
docker-compose up -d
```

Your Console server logs will occupy your terminal when running `docker-compose up` without the `--detach` flag

We can also close our Console by running:

```bash
docker-compose down
```

{% endhint %}

**4. Open your console** should be running on localhost:3262&#x20;

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2FsG8OPGoHXyNnp9JIbiWl%2FScreenshot%202024-02-21%20at%208.12.11%E2%80%AFPM.png?alt=media&#x26;token=78b45125-e13b-497a-a1f5-7966f4ed4736" alt=""><figcaption><p>Open your Console by going to localhost:3262/</p></figcaption></figure>

**5. Now we'll create an admin account** in our database using the 2bttns CLI inside our twobttns container so we can login to our Console.

```bash
docker-compose exec twobttns 2bttns-cli admin create
```

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2F6WX9XOcvanaRjtjD1UVS%2FScreenshot%202024-02-21%20at%208.27.48%E2%80%AFPM.png?alt=media&#x26;token=c307441f-8066-49df-bcc7-5dc82a380cfc" alt="" width="375"><figcaption><p>Initial prompt when running <code>2bttns-cli admin create</code></p></figcaption></figure>

{% hint style="info" %}
&#x20; **💡 Helpful Tip:** 2bttns advises opting for the Username/Password authentication for its speed. We're going to create an account with a username "admin" and password "admin" to get going.&#x20;
{% endhint %}

With the credentials you just created, log into the console. You can manage these credentials anytime from the Settings page in the Console.&#x20;

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2FtqVap9lDrAfhQ8YiTuW8%2FScreenshot%202024-02-21%20at%208.32.34%E2%80%AFPM.png?alt=media&#x26;token=f88458a3-ec80-476b-83a0-dd7f6a41283c" alt="" width="375"><figcaption><p>After logging in, here's what you'll see:2bttns Console home page</p></figcaption></figure>

## Using the API

Now that your Console is running on [`localhost:3262`](https://localhost:3262), you can make API requests to [`localhost:3262/api`](https://localhost:3262/api). For a full guide on the 2bttns API, [visit this page](https://docs.2bttns.com/references/apis).

**First, you'll need to generate a JWT bearer token** to authenticate your API requests.

{% openapi src="<https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/489xqKU5WXWpX6Dz2H47/openapi.json>" path="/authentication/token" method="get" %}
[openapi.json](https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/489xqKU5WXWpX6Dz2H47/openapi.json)
{% endopenapi %}

2bttns will use your `app_id` and `secret` to your Console to generate a JWT. Navigate to your **Console**, click **Settings**, and make sure you're on the **Apps** tab.

<figure><img src="https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/0NJtX0ttU9Y87XJamQiI/Screenshot%202024-02-20%20at%209.08.25%E2%80%AFPM.png" alt="" width="375"><figcaption><p>Console > Settings > Apps</p></figcaption></figure>

Here's an example fetch request:

{% code title="Example fetch request" overflow="wrap" fullWidth="false" %}

```javascript
const fetch = require('node-fetch');

const url = 'http://localhost:3262';
const endpoint = '/api/authentication/token';
const params = {
    app_id: 'your-app-id',
    secret: 'your-secret-value' 
};

fetch(`${url+endpoint}?app_id=${params.app_id}&secret=${encodeURIComponent(params.secret)}`, {
    method: 'GET' 
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

```

{% endcode %}

**Generate URL to your Game:** Now that you've generated your bearer token, you can use the full RESTful API within the Console.

Let's generate a URL to our game in our frontend Sign Up button.

```javascript
import React, { useState } from 'react';

function Signup() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');

  const getGameUrl = async () => {
    const queryParams = new URLSearchParams({
      app_id: 'example-app',
      secret: 'my-secret-value',
      game_id: 'hobbies-ranker',
      player_id: username, // assuming username is used as player_id
      callback_url: encodeURIComponent('http://localhost:3000/profile')
    });
    const requestUrl = `http://localhost:3262/api/authentication/generatePlayURL?${queryParams.toString()}`;

    try {
      const response = await fetch(requestUrl, {
          headers: {
            'Authorization': `Bearer ${process.env.BEARER_TOKEN}`
          }
        });
      const data = await response.json();
      if (data.gameUrl) {
        window.location.href = data.gameUrl; // Redirect
      } else {
        console.error('Game URL not found.');
      }
    } catch (error) {
      console.error('Failed to fetch game URL:', error);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (password !== confirmPassword) {
      alert("Passwords don't match");
      return;
    }
    // Assuming signup logic is successful
    console.log('Submitting', { username, password });

    // Call getGameUrl to redirect user to their game instance
    getGameUrl();
  };

  return (
    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', flexDirection: 'column' }}>
      <h1>Create a new account</h1>
      <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}>
        <div>
          <label>Username:</label>
          <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} required />
        </div>
        <div>
          <label>Password:</label>
          <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
        </div>
        <div>
          <label>Confirm Password:</label>
          <input type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} required />
        </div>
        <button type="submit">Sign Up</button>
      </form>
    </div>
  );
}

export default Signup;
```

Now, when users click Sign up, they'll be redirect to our newly created demo game. Let's try this out:

<figure><img src="https://2953305964-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FCK6sBMIejLVGzjC2MMwl%2Fuploads%2FQyo1sj4ltZy1YMVg2Bp7%2Fezgif-5-933a1ee7ae.gif?alt=media&#x26;token=db8afb2b-4783-47b4-ba43-57de869ca108" alt=""><figcaption><p>Clicking sign up bounces users out to 2bttns, then redirects them to profile</p></figcaption></figure>

After completing a round 2bttns will redirect you to the specified `callbackUrl` parameter in the `generatePlayUrl` endpoint. Since we haven't loaded in any data, our game is empty. It's time to build out our game🕹️.

## Create your game

We'll need to navigate to the Console and add input data (known as Game Objects) into our hobbies-ranker game we created earlier.&#x20;

### Adding inputs

Since we're generating our profiles using hobbie data, let's see what we can come up with. Click on the Game Objects page and start adding game objects at the top:

<figure><img src="https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/URsgTwufViXKI8iZ0fIS/Screenshot%202024-02-22%20at%202.16.09%E2%80%AFPM.png" alt=""><figcaption><p>Creating game input data in your Console</p></figcaption></figure>

Let's add in a bunch more. In the future, we can upload JSON's directly through the Console or import data through the API.&#x20;

#### Grouping inputs

Next, we'll need to organize our Game Objects into Tags, which are collections of Game Objects. You can then load your Tags as inputs into games. Let's create a `Hobbies` tag under Tags page

<figure><img src="https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/IPiV1hjtjADNuztRZv4j/Screenshot%202024-02-22%20at%203.20.56%E2%80%AFPM.png" alt=""><figcaption><p>Create a Tag under the Tags page by clicking <code>shift</code> + <code>enter</code></p></figcaption></figure>

#### Add data to game

After creating our Tag, tag them by returning to the Game Objects page, bulk selecting, selecting the `Hobbies` tag, and then load them as inputs into our `hobbies-ranker` game. &#x20;

<figure><img src="https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/aww4xZTfRYgnd5uwjUS6/LoadIntoTags-ezgif.com-optimize.gif" alt=""><figcaption><p>Input game objects into games by first grouping them into Tags</p></figcaption></figure>

To enhance the user experience by shortening the round length, please adjust the `numItems` parameter in your `generatePlayUrl` method within the API.&#x20;

{% hint style="info" %}
&#x20; **💡 Helpful Tip:**  Although the Console sets the default, your API call to generate a URL will override the round length.&#x20;
{% endhint %}

Let's go back to our app and try signing in now.

<figure><img src="https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/u6642ECLtubod0JKgc83/FullImpementationDemo-ezgif.com-video-to-gif-converter.gif" alt=""><figcaption><p>Now our game is populated with data, and ready to go!</p></figcaption></figure>

## Retrieve and use results&#x20;

Now let's retrieve the scores and display them on the profile page.  Let's make a GET request in our API to retrieve the round results. We're going to use the [`/games/getPlayerScores`](https://docs.2bttns.com/references/apis/games#games-getplayerscores) endpoint:

{% openapi src="<https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/489xqKU5WXWpX6Dz2H47/openapi.json>" path="/games/getPlayerScores" method="get" %}
[openapi.json](https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/489xqKU5WXWpX6Dz2H47/openapi.json)
{% endopenapi %}

{% code title="" %}

```javascript
import React, { useState, useEffect } from 'react';

function PlayerScores({ player_id }) {
  const [scores, setScores] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchScores = async () => {
      try {
        const response = await fetch(`http://localhost:3262/api/games/getPlayerScores?game_id=hobbies-ranker&player_id=${player_id}&include_game_objects=true`, {
          headers: {
            'Authorization': `Bearer ${process.env.BEARER_TOKEN}`
          }
        });
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const jsonResponse = await response.json();
        // Sort scores and then take the top 10
        const sortedScores = jsonResponse.scores
          .sort((a, b) => b.score - a.score)
          .slice(0, 10); // Take only the top 10 scores
        setScores(sortedScores);
      } catch (error) {
        setError(`Failed to fetch scores: ${error.message}`);
      } finally {
        setIsLoading(false);
      }
    };

    if (player_id) {
      fetchScores();
    } else {
      setIsLoading(false);
    }
  }, [player_id]); 

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      <h2>Player Scores</h2>
      <div style={{ padding: '10px' }}>
        {scores && scores.length > 0 ? (
          scores.map((score, index) => (
            <div key={index} style={{ marginBottom: '10px' }}>
              <div style={{ fontWeight: 'bold' }}>{score.gameObject ? score.gameObject.name : 'Unknown Game'}</div>
              <div style={{
                width: `${(score.score / 1) * 100}%`, // Adjust if necessary based on score range
                backgroundColor: 'royalblue',
                color: 'white',
                textAlign: 'right',
                padding: '5px',
                borderRadius: '5px',
                maxWidth: '100%'
              }}>
                {score.score.toFixed(2)}
              </div>
            </div>
          ))
        ) : (
          <div>No scores available</div>
        )}
      </div>
    </div>
  );
}

export default PlayerScores;
```

{% endcode %}

All we have to do now is render this component in our `Profile.js` page:

{% code title="Profile.js" overflow="wrap" fullWidth="false" %}

```javascript
import React from 'react';
import PlayerScores from './components/results/results.js'

function Profile() {

  return (
    <div style={{
      paddingTop: "75px",
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      height: '100vh',
      width: '100vw', // Ensure the div takes full width
      position: 'fixed', // Keep the div fixed during scrolling
      top: '0', // Align to the top
      left: '0', // Align to the left
      flexDirection: 'column',
      textAlign: 'center',
      overflowY: 'scroll', // Enable scrolling within the div if content overflows
    }}>
      <h1>Welcome to your profile!</h1>
      <br/>
      <PlayerScores player_id={"some-player-id"} />
    </div>
  );
}

export default Profile;
```

{% endcode %}

## Next Steps

{% hint style="success" %}
Voila! 🏁 2bttns is now fully integrated.  🎉
{% endhint %}

&#x20;Now when we play a game after signing up, our profile page should look like this:

<figure><img src="https://content.gitbook.com/content/n2L7ltGlCAKlpbJZ3edj/blobs/TZSoNHWOakD4PfS6yvkF/FULLDEMOOF2bttns-ezgif.com-speed.gif" alt=""><figcaption><p>Full integration with 2bttns to generate profiles </p></figcaption></figure>

Next, you can use game scores to personalize your app experience, sort and rank content for feeds, curate marketplaces, and more.
