Archived student lesson

Add Badges

Part of End to End React with Prisma 2

Objective: Create badge components to see tags, feeds and bundles associated with an item.

Our <OneListItem> component is off to a good start and in this step we will add the <BadgeList> component so that we can list out the bundles, feeds and tags that belong to the current item. Let’s start by creating the <BadgeList>.

To keep things a little streamlined, we will build out the component as it will eventually end up being even though we won’t use all the features right now. If all the functionality doesn’t make 100% sense, don’t worry because we’ll be coming back to it.

components/badgeList.tsx

import { Dispatch, SetStateAction } from 'react'
import {
  ActionType,
  BadgeFieldName,
  BundleState,
  FeedState,
} from '../utils/types'
import { OneBadge } from './oneBadge'

export const BadgeList = ({
  fieldName,
  action,
  setItem,
  item,
}: {
  fieldName: BadgeFieldName
  action: ActionType
  item: FeedState | BundleState
  setItem?: Dispatch<SetStateAction<FeedState | BundleState>>
}) => {
  return item[fieldName].length > 0 ? (
    <>
      {item[fieldName].map(oneBadge => (
        <OneBadge
          key={`${item['id']}-${oneBadge.name}`}
          fieldName={fieldName}
          item={oneBadge}
          action={action}
          setItem={setItem}
          currentItem={item}
        />
      ))}
    </>
  ) : (
    <p className="text-gray-400">None found</p>
  )
}

Our Badge will be used for representing feedTags, bundleTags, bundles, and feeds. We specify what kind of feed it is by using the BadgeFieldName type which will be a string that specifies what kind it is. Next, the item specifies what the parent type is that these tags belong to. In this step, we only be displaying badges, with no ability to add or remove them so we will not be using setItem yet and due to this static nature of this component at this time, action prop will be set to NONE which we’ll show you how to set in a little bit.

Aside from some of these props that aren’t quite in use yet, this component simply takes the item object and then will take from that a property, fieldName which will either be tags, feeds, or bundles depending on what we are trying to show.

To fill in those missing types, add them to the types.ts file:

We need to first add the missing types:

utils/types.ts

// other tupes above

export type BundleState = {
  name: string
  description: string
  tags: TagObject[]
  feeds: FeedState[]
}

export type FeedState = {
  id?: number
  name: string
  url: string
  tags: TagObject[]
}

export enum BadgeFieldName {
  tags = 'tags',
  feeds = 'feeds',
  bundles = 'bundles',
}

export enum ActionType {
  ADD = 'ADD',
  CREATE = 'CREATE',
  NONE = 'NONE',
}

Now that we have built out our list component, we have to build the <OneBadge> component so we can actually use it.

components/oneBadge.tsx

import { BundleTag, FeedTag } from '@prisma/client'
import { Dispatch, SetStateAction } from 'react'
import {
  ActionType,
  BadgeFieldName,
  BundleState,
  FeedObject,
  FeedState,
} from '../utils/types'

export const OneBadge = ({
  item,
  action,
  currentItem,
  fieldName,
  setItem,
}: {
  item: FeedTag | BundleTag | FeedObject
  action: ActionType
  currentItem?: FeedState | BundleState
  fieldName: BadgeFieldName
  setItem?: Dispatch<SetStateAction<FeedState | BundleState>>
}) => {
  const color =
    fieldName === BadgeFieldName.tags
      ? `blue`
      : fieldName === BadgeFieldName.feeds
      ? `green`
      : `purple`
  return (
    <div className="inline-block align-middle">
      <span
        className={`flex justify-center text-sm py-2 px-2 rounded-lg bg-${color}-200`}
      >
        <p className={`text-xs text-${color}-600 text-center`}>{item.name}</p>
      </span>
    </div>
  )
}

Here, the currentItem is the parent item that this badge belongs to, setItem will be a future setting function for adding or deleting a badge from new or edit functionality, and action just as in the <BadgeList> component will be determining whether there is any actions associated with that badge. In our case though, action will be set to NONE which won’t do anything at the moment.

The only thing that is important to pay attention to is that we will return back a tag with the name of the item. Here, you can see that it is a <p> tag nested inside a <span> and <div> tag.

Before wrapping up, let’s make the <ProfilePic> component. This will pull a picture from the auth0 user object and we will display it alongside the profile name.

components/profilePic.tsx

import { AuthorObject } from '../utils/types'
import { Question } from './svg'

export const ProfilePic = ({ author }: { author: AuthorObject }) => (
  <>
    {author.picture ? (
      <div className="rounded-full">
        <img
          className="rounded-full p-0.5 w-8 h-8 border-2 border-gray-300"
          src={author.picture}
        />
      </div>
    ) : (
      <Question className="w-6 h-6 text-gray-500" />
    )}
    <p>{author.nickname}</p>
  </>
)

components/svg.tsx

// other svgs above

export const Question = ({ className }) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke="currentColor"
    className={className}
  >
    <path
      strokeLinecap="round"
      strokeLinejoin="round"
      strokeWidth={2}
      d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
    />
  </svg>
)