DEV Community

Andrej Tlčina
Andrej Tlčina

Posted on

Building Training Plan builder: Introduction

Hello! So, I started a new mini side project. The app is supposed to help you create a training plan and export it in PDF format. In this article, I'm going to talk about setting up the project and building authentication and DB schema.

Setting the project up

I did the setup with the help of this tutorial. Sabin Adams will help you set up the development environment with Remix, Tailwind, MongoDB, and Prisma. I don't want to take credit for that, so if you want to set up a simple project, take a look at that article/tutorial.

Authentication

This part will be pretty short. Mainly because, I did a lot of the same stuff as I did in the previous project and again there's already a great article talking about the auth in Remix with MongoDB, that I followed. You can find it here

User info in the app

When it came to keeping user information on the frontend I immediately went to creating context. But I felt a little lazy and I didn't need user data in a lot of places, so because I have the info saved in the request header I decided to create a simple function

export const authenticateUser = async (req: Request) => {
  const user = await getUser(req);

  if (!user) throw redirect("auth/login");

  return user;
};
Enter fullscreen mode Exit fullscreen mode

, which I call in loader and get the result in the component.

export const loader: LoaderFunction = async ({ request }) => {
  const user = await authenticateUser(request);

  return user;
};
...
// in Route component
const user = useLoaderData<Awaited<ReturnType<typeof authenticateUser>>>();
Enter fullscreen mode Exit fullscreen mode

The result is then passed to Layout component, which displays the name of the user in the navbar

const Navbar = (props: { user: { name: string; id: string } }) => {
  const { user } = props;

  return (
    <div className="navbar bg-base-100">
      <Link to="/" className="btn btn-ghost normal-case text-xl">
        Strongion
      </Link>
      <div className="ml-auto font-semibold text-lg">{user.name}</div>
      <form action="/auth/logout" method="POST">
        <button name="_action" value="delete" className="btn btn-nav-sm ml-2">
          Log out
        </button>
      </form>
    </div>
  );
};

const Layout: React.FC<{ user: { name: string; id: string } }> = (props) => {
  const { user, children } = props;

  return (
    <div className="h-screen bg-base-100">
      <Navbar user={user} />
      <div className="w-2/3 mx-auto">{children}</div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

DB Schema

I created the schema with four main models: User, Plan, Phase, Exercise. Each user can create multiple Plans, so we get one-to-many relations. Each plan has multiple Phases, and each Phase has multiple exercises attached to it. All relations are of type one-to-many. Here's the whole schema with attributes

model User {
  id            String   @id @default(auto()) @map("_id") @db.ObjectId
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  name          String   @unique
  password      String
  trainingPlans Plan[]
}

model Plan {
  id          String   @id @default(auto()) @map("_id") @db.ObjectId
  title       String   @unique
  description String?
  trainee     User     @relation(references: [id], fields: [traineeId], onDelete: Cascade)
  traineeId   String   @db.ObjectId
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  phases      Phase[]
}

model Phase {
  id          String     @id @default(auto()) @map("_id") @db.ObjectId
  title       String
  description String?
  exercises   Exercise[]
  plan        Plan       @relation(references: [id], fields: [planId], onDelete: Cascade)
  planId      String     @db.ObjectId

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Exercise {
  id           String       @id @default(auto()) @map("_id") @db.ObjectId
  name         String
  description  String?
  exerciseData ExerciseData
  phase        Phase        @relation(references: [id], fields: [phaseId], onDelete: Cascade)
  phaseId      String       @db.ObjectId

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

type ExerciseData {
  reps Int
  sets Int
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this section, we went through setting up a simple project, authentication, and creating a DB schema. If you ask me, pretty uninteresting things, but they have to be done 😅, so, to make the project more exciting, in the next section I'm going to explain building the Drag and Drop component with autocomplete search. See you there 😉

Github -> Strongion

Top comments (0)