How we built the dynamic tickets feature for Builder Accelerate

How we built the dynamic tickets feature for Builder Accelerate

On March 13th, we will host Builder Accelerate, an AI launch event focused on leveraging AI to convert designs into code with your design system. As part of the registration experience, we’ve introduced a feature which allows the generation of a personalized event ticket based on the user's domain. For example, twitch.com will produce a ticket infused with Twitch's brand colors:

In this blog post, I’ll break down this feature using React for the frontend and Express for the backend (though it was originally built with Qwik). We will focus primarily on the concept without diving too deeply into the specifics of frameworks being used. You can find the source code on my GitHub repo.

Light bulb tip icon.

If you're someone who learns best through visual content, check out the video tutorial.

Setting up your project

We'll start by setting up a React application using Vite and a Node.js Express server. The goal is to create an API endpoint in Express that returns necessary ticket information based on a given domain, focusing primarily on brand colors.

The Express server

First, let's dive into the Express server setup. We'll use the express and cors packages to create a basic server:

import express from "express"; import cors from "cors";

const app = express(); app.use(cors());

app.get("/", (_, res) => { res.send("Hello World"); });

app.listen(3000, () => { console.log("Server is running on port 3000"); });

Our server runs on port 3000, and visiting localhost:3000 displays a "Hello World" message.

Generating primary and palette colors

We introduce a /ticket route that takes a domain as a query parameter. We then construct the URL to fetch the favicon associated with the domain. Google provides a convenient URL format to retrieve a site's favicon:

app.get("/ticket", async (req, res) => { const domain = req.query.domain; const faviconUrl = https://www.google.com/s2/favicons?domain=${domain}&sz=128; });

Using the [**colorthief**](https://lokeshdhakar.com/projects/color-thief/) package, we extract the primary color and a palette of colors from the favicon:

const [primaryColorRGB, paletteRGB] = await Promise.all([ colorthief.getColor(faviconUrl), colorthief.getPalette(faviconUrl), ]);

Here, primaryColorRGB holds the RGB values of the dominant color, and paletteRGB contains an array of colors representing the favicon's color palette.

Generating the secondary color

The secondary color is chosen to provide a good contrast with the primary color.

Define a function to calculate the distance between two colors in RGB space. This helps in finding a color in the palette that is most dissimilar to the primary color:

function rgbDistance(color1, color2) { let rDiff = Math.pow(color1[0] - color2[0], 2); let gDiff = Math.pow(color1[1] - color2[1], 2); let bDiff = Math.pow(color1[2] - color2[2], 2); return Math.sqrt(rDiff + gDiff + bDiff); }

Iterate through the palette to find the color that is furthest away from the primary color:

function findDissimilarColor(primaryColor, colorPalette) { let maxDistance = -1; let secondaryColor = null;

colorPalette.forEach((color) => { let distance = rgbDistance(primaryColor, color); if (distance > maxDistance) { maxDistance = distance; secondaryColor = color; } });

return secondaryColor; }

Call this function with the primary color and the palette to determine the secondary color:

const secondaryColorRGB = findDissimilarColor(primaryColorRGB, paletteRGB);

Determining if a color is dark or light

To ensure the text on the ticket is readable regardless of the background color, we need to determine whether the color is dark or light. Implement a function to check the luminance of a color:

1. Calculate relative luminance: The luminance of a color is a measure of the intensity of the light that it emits. Use the following function to calculate it:

function isDarkColor(color) { let luminance = 0.2126 (color[0] / 255) + 0.7152 (color[1] / 255) + 0.0722 * (color[2] / 255); return luminance < 0.5; }

2. Apply the function: Use this function to determine if the primary and secondary colors are dark or light. This information is crucial for setting the text color on the ticket for optimal readability:

const isPrimaryColorDark = isDarkColor(primaryColorRGB); const isSecondaryColorDark = isDarkColor(secondaryColorRGB);

Now that all the ticket information has been calculated, return the domain, the favicon URL, the primary color in both RGB and hex formats, the secondary color in both RGB and hex formats, and whether the two colors are dark.

res.json({ domain, faviconUrl, primaryColorRGB, primaryColorHex: rgbToHex(...primaryColorRGB), isPrimaryColorDark: isDarkColor(primaryColorRGB), secondaryColorRGB, secondaryColorHex: rgbToHex(...secondaryColorRGB), isSecondaryColorDark: isDarkColor(secondaryColorRGB), });

If you navigate to localhost:3000/ticket?domain=builder.io, you should see the API returning the ticket information.

Connecting the React frontend

In the React frontend, create an input field to accept the domain and a button to generate the ticket. The frontend communicates with the Express backend to fetch and display the ticket information. I’ve taken the good-enough-is-fine approach to writing the React code. Here’s the most basic code to get it working:

// App.jsx

import { useState } from "react"; import { Ticket } from "./Ticket";

export default function App() { const [domain, setDomain] = useState("builder.io"); const [ticketInfo, setTicketInfo] = useState({});

const fetchTicketInfo = async () => { const response = await fetch( http://localhost:3000/ticket?domain=${domain} ); const data = await response.json(); setTicketInfo(data); };

return (

setDomain(e.target.value)} onKeyDown={(e) => e.key === "Enter" && fetchTicketInfo()} /> {!!ticketInfo.faviconUrl && }
); }

The ticket component

Create a Ticket component in React to display the ticket and use the fetched color data to dynamically style the ticket based on the input domain's brand colors:

// Ticket.jsx

import { BuilderTicketLogo } from "./assets/BuilderLogo";

const padStartWithZero = (num = 0) => { return num.toString().padStart(7, "0"); };

export const Ticket = (props) => { const primaryColor = props.ticketInfo.primaryColorHex; const secondaryColor = props.ticketInfo.secondaryColorHex; const isPrimaryColorDark = props.ticketInfo.isPrimaryColorDark; const isSecondaryColorDark = props.ticketInfo.isSecondaryColorDark; const favicon = props.ticketInfo.faviconUrl; const companyName = props.ticketInfo.domain; const ticketNo = padStartWithZero("12345");

return (

An AI launch event accelerate.builder.io
Accelerate >>>
March 13, 2024 / 10 AM PST
{companyName}
ADMIT ONE
Ticket No.

#{ticketNo}

); };

With TailwindCSS, remember that dynamic class names that aren't composed properly won't be compiled. You can address this in a clean way, but for simplicity, I've chosen to use the style attribute.

We add extra zeros to the start of the ticket number to ensure it's 7 digits long using the padStart method on a string.

Conclusion

This feature is a great addition to event registration processes, adding a personalized touch for attendees. Experiment with the code, adapt it to your needs, and integrate it into your projects to see how dynamic ticket generation can elevate your user experience.

Read the full post on the Builder.io blog