Adding Custom Branding to a User App

Milecia - Sep 14 '21 - - Dev Community

Many organizations want some ability to use a service to handle some of their functionality and customize the interface users are shown. This includes things like the names they see displayed, data they want to be shown, or some images they want to see. Giving them the ability to add their own branding is one way to add value to your own products.

In this Redwood tutorial, we'll make an app that will change formats depending on what user is associated with the page.

Create the Redwood app

First thing we need to do is spin up a new app. In a terminal, run:

yarn create redwood-app branding
Enter fullscreen mode Exit fullscreen mode

When this is done, you'll have a bunch of new files and folders in a branding directory. The main folders we'll be working in are the api and web folders. We'll start with some work in the api folder first.

Setting up the model

Building our app by making the models for the database schema works really well in Redwood. I usually like to start here because it's one way you can start thinking about your business logic from the beginning.

We'll be using a Postgres database. Here are the docs to get Postgres installed locally. Let's start by updating the .env file with a connection string for your local instance. Uncomment the DATABASE_URL line and update the value. It might look something like this.

DATABASE_URL=postgres://admin:password@localhost:5432/branding
Enter fullscreen mode Exit fullscreen mode

Now we can go to api > db and open the schema.prisam file. This is where we'll add our models. One thing we need to do is update the provider value at the top to postgresql instead of sqlite. Next, you can delete the existing example model and add these.

model User {
  id     Int      @id @default(autoincrement())
  name   String
  info   Info[]
  image  Image[]
  layout Layout[]
}

model Image {
  id     Int    @id @default(autoincrement())
  name   String
  url    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int
}

model Info {
  id        Int      @id @default(autoincrement())
  balance   Float
  lastLogin DateTime
  endDate   DateTime
  user      User     @relation(fields: [userId], references: [id])
  userId    Int
}

model Layout {
  id           Int    @id @default(autoincrement())
  name         String
  dataLocation String
  imageUrl     String
  user         User   @relation(fields: [userId], references: [id])
  userId       Int
}
Enter fullscreen mode Exit fullscreen mode

Usually, when you have relationships between tables like we have here, it's a good idea to seed your database with some initial values. You'll see this pretty often with apps that have dropdown menus or pre-defined user roles.

We'll be adding our own seed data in the seed.js file. You can open that and delete all of the commented-out code in the main function and replace it with this.

await db.user.create({
  data: { name: 'Nimothy' },
})

await db.image.create({
  data: {
    name: 'Nimothy Profile',
    url: 'https://res.cloudinary.com/milecia/image/upload/v1606580774/fish-vegetables.jpg',
    userId: 1,
  },
})

await db.info.create({
  data: {
    balance: 7.89,
    lastLogin: new Date(),
    endDate: new Date(),
    userId: 1,
  },
})

await db.layout.create({
  data: {
    name: 'MidLeft',
    dataLocation: 'mid-left',
    imageUrl:
      'https://res.cloudinary.com/milecia/image/upload/v1606580774/fish-vegetables.jpg',
    userId: 1,
  },
})
Enter fullscreen mode Exit fullscreen mode

Run migration

With our models and seed data in place, we can migrate the database with this command:

yarn rw prisma migrate dev
Enter fullscreen mode Exit fullscreen mode

That will add the tables and columns with the defined relationships to your Postgres instance. To seed the database, we'll need to run:

yarn rw prisma db seed
Enter fullscreen mode Exit fullscreen mode

This will add the placeholder data we created in seed.js so that the relationships between tables and columns are met and don't cause errors with our app.

Since we've run the migration and seeding, we can move on to the back-end and front-end.

Making the back-end and front-end

We're going to make the functionality to add new layouts and new users to the app for now so that we can show how things update for the user. We'll also be adding a special page to show how these updates would actually affect users.

For the sake of this project, we're going to assume that adding new users and layouts is admin functionality that users of the app won't be able to see. Later on, we'll add the user view that applies the custom branding.

Adding the ability to create and update users and layouts only takes a couple of commands in Redwood. Let's start by making the users functionality with this:

yarn rw g scaffold user
Enter fullscreen mode Exit fullscreen mode

This will generate the back-end GraphQL types and resolvers as well as adding new components to the front-end. We'll run this command one more time for the layouts functionality:

yarn rw g scaffold layout
Enter fullscreen mode Exit fullscreen mode

You can take a look at the code Redwood generated to make all of this work on the front-end by going through the web > src directory. There are new files under components, layouts, and pages, plus Routes.js has been updated. All of the new files you see were created by that scaffold command for those two models.

The back-end code that supports new user and layout creation and the edit and delete functionality can be found in the api > src directory. You'll see new files under graphql and services that hold the GraphQL types and resolvers that make all of the CRUD work and persists the data.

Now we have the CRUD for the front-end and back-end for these two models. You can run the scaffold command to create the CRUD for the other models, but we don't actually need it. What we do need are the types for those models. We can generate those with a couple of Redwood commands:

yarn rw g sdl info
yarn rw g sdl image
Enter fullscreen mode Exit fullscreen mode

The sdl generator makes all of the GraphQL types and a resolver for the specified model. If you check out api > src > graphql, you'll see the new types that were generated for info and images. Then if you look in api > src > service, you'll see that a resolver has been made to handle a query for us for both info and images.

The reason we're adding these types is so the user types reference these so we need them to be available, even if we aren't adding the front-end piece.

Running the updated app

If you run your app with yarn rw dev and navigate to localhost:8910/users, you'll see a table and buttons for different ways to interact with the data. You should see something similar to this:

users table

Go ahead and add a new user by clicking the "New User" button. This will open the form like this:

new user form

Now you can add a new layout for this new user by going to localhost:8910/layouts and clicking the "New Layout" button. It'll bring up this form:

new layout form

Show the user their custom view

Now that we've got the core functionality together to create users and associate layouts with them, we can create the custom view that they will see. To do that, we'll use Redwood to generate a page that will load a specific user's layout. Make a new page with this command:

yarn rw g page option
Enter fullscreen mode Exit fullscreen mode

This will add a new page to the web > src > pages directory and it will update the Routes.js file with a new /option route. If you navigate to localhost:8910/option, you'll see this:

option page

We need to update this page to show the user's layout by pulling some data from the back-end.

Querying for the user layout

In the web > src > pages > OptionPage directory, open the OptionPage.js file and add the following import to get your GraphQL query ready.

import { useQuery } from '@redwoodjs/web'
Enter fullscreen mode Exit fullscreen mode

Then at the bottom of the file, right above the export statement, add this code for the query.

const LAYOUT = gql`
  query layout($id: Int!) {
    layout(id: $id) {
      id
      name
      dataLocation
      imageUrl
      userId
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

This will give us a specific layout based on the id we pass to the query. We'll be manually setting this id to mimic what we might get from a prop from a different component. We'll add the variable for the id in our query hook. This will be added inside of the OptionPage component:

const { loading, data } = useQuery(LAYOUT, {
  variables: { id: 1 },
})

if (loading) {
  return <div>Loading...</div>
}
Enter fullscreen mode Exit fullscreen mode

We're using the useQuery hook to execute the query we made earlier and we're manually setting the id of the layout we want to use. Then we're checking the load status for the data and rendering an indicator that the page is loading the content so that the user doesn't see an error before the fetch finishes.

The last thing we'll do is update the elements to show in the layout format we currently have loaded.

Updating the page

To show the right layout, we're going to install the styled-components package. That way we'll be able to pass props to update the layout based on the user viewing the page. So in the web directory in your terminal, run:

yarn add styled-components
Enter fullscreen mode Exit fullscreen mode

Now we're going to import that package in the OptionPage.js file.

import styled from 'styled-components'
Enter fullscreen mode Exit fullscreen mode

Then we need to add a new styled component to handle the location of the image based on that user layout. We'll add this right above the OptionPage component.

const Img = styled.img`
  display: block;
  position: absolute;
  top: ${(props) => (props.dataLocation === 'mid-left' ? '35%' : 'unset')};
  right: ${(props) => (props.dataLocation === 'mid-left' ? 'unset' : '0')};
  width: 360px;
`
Enter fullscreen mode Exit fullscreen mode

We're doing a simple update of the image location with an absolute position setup. This will let the image move independently of the other elements on the page so that the user sees it in the place they've selected. We're passing in the dataLocation value as a prop.

Cleaning things up

Just a few finishing touches and we'll have this layout working. First, we need to add the Img to OptionPage. We'll delete the existing Link from the return statement and add this image instead.

<Img src={data.layout.imageUrl} dataLocation={data.layout.dataLocation} />
Enter fullscreen mode Exit fullscreen mode

We'll also add a little line to show the name of the current layout. This will go below the description of the file location.

<p>{data.layout.name}</p>
Enter fullscreen mode Exit fullscreen mode

That's it! We've finished up this app. Now if you run the app with yarn rw dev, you should see something similar to this.

user one layout

If you update the id in the query variable to 2 and reload the browser, you'll see something like this.

user two layout

Finished code

If you want to check out the complete code, you can check it out in the custom-app-branding folder of this repo. You can also check out the front-end in this Code Sandbox.

Conclusion

If you're interested in a deeper dive on how Redwood handles scaffolding or the general way it creates files for you, make sure to go through their docs.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player