# Video
[class-1](https://youtu.be/M0Fsrt9V9HE)
[class-2](https://youtu.be/at07DRKHj6Q)
[class-3](https://youtu.be/G-0p_SFPuXg)
[class-4](https://youtu.be/rV4W7R3zw58)
[class-5](https://youtu.be/E9AKo0TKErA)
[class-6](https://youtu.be/fQY8B5mJHUo)
[class-7A](https://youtu.be/JGc5p9vjDE4)
[class-7B](https://youtu.be/Z1w81dObkBQ)
[class-8-Auth-1](https://youtu.be/vnpggK3y5c8)
[class-8-Auth-2](https://youtu.be/16oqQFGM30c)
[class-8-Auth-3](https://youtu.be/9iPDVs89-iM)
[class-9-Assign Issue-1](https://youtu.be/Wlj9GYuMVug)
[class-10-Assign Issue-2](https://youtu.be/fcLIpe2UfWc)
[class-10-Assign Issue-3](https://youtu.be/mmjTk05mrDw)
[class-11-Filter - 1](https://youtu.be/uJo7MGq6k1Y)
[class-12-Filter - 2](https://youtu.be/VBCEd6rVqKI)
[class-13-Pagination](https://youtu.be/CybZMdoQkMo)
[class-14-Dashboard and hosting](https://youtu.be/5Y-u5eIXAns)
# 1. Introduction (10m)
Build a full-stack production-grade application Issue-tracker
- In modern web, these are the few common features.
- Dashboard with chart
- Filtering, sorting, pagination
- Forms with client side validation
- User authentication and access control
- Model dialog box and tost notification
By the end of the course everyone should be confident in building fast, responsive fully functional web application and able to host.
**Stack**
- Nextjs, Typescript, tailwindcss, Radix UI, Prisma, NextAuth
**Learning Goal**
- How to structure your application
- How to write clean, professional quality code, that align with industry standard and best practices.
- How to think like software Engineer.
**What should you know**
- Client and Server component's
- Routing, Building API
- Database integration with Prisma
- Authentication with NextAuth
**Project Roadmap**
- May be you thinking how to start and finish the project, which component should i build first.
`Lets take simple approach, divide the project in 2 parts`
**core/Essential Features**
- Create an issue, setup database
- Viewing Issues
- Updating Issue
- Deleting Issue
**Advance Features**
- User Authentication
- Assigning issues
- Sorting, Filtering, Pagination
- Dashboard
**Important Point to remember**
- When we are building this application we are going to focus one feature at a time.
- When building feature, our goal is not to come with perfect solution, first we build the working feature, then improve the code.
**there is no such thing as `Perfect in software development`**
`Make it first work and improve it`
# 2. Setting Up the Project (45m)
- create new github repository issue-tracker
- clone
window>`git clone https://github.com/369webforall/bug-tracker.git`
- open in vscode (add extension ES7+React/redux/ Tailwind css intellisent / Prisma / Javascript/Typescript)
- install next-js, with typescript and tailwind css
`npx create-next-app@latest ./`
- clean the code.
git add .
git commit -m "project setup"
git push
**NavBar.tsx**
- crate NavBar.tsx file in app directory
- install react-icons (`npm i react-icons`)
- install classnames (`npm i classnames`)
- once navbar is complete
git add .
git commit -m "Navbar completed"
git push
# 3. Creating Issues (database setup) (80m)
**1. Database setup**
- Mongodb
- create project in mongodb atlas, save password, copy the link of your project.
**2. Setting up prisma**
- install prisma (`npm i prisma`)
- intialize prisma (`npx prisma init`)
- change provider datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
**3. Creating the issue model**
- create model as below
```javascript
model Issue {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
description String
status Status @default(OPEN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
- `npx prisma format` this will format the code
(Prisma Client is a TypeScript (or JavaScript) library that provides a type-safe and auto-completing API for interacting with your database. When you run npx prisma generate, Prisma generates the necessary code based on your Prisma schema and the database you're using.)
- `npx prisma db push` - it sync the schema with our database (creating database and table)
`git add .`
`git commit -m "setup prisma and model"`
`git push`
[Guide Prisma-mongodb](https://www.prisma.io/docs/guides/database/mongodb)
**4. Building an API**
- app>api>issues>route.tsx
- add api code to post method.
- install zod for data validation
- to store data(issue) we need to create prisma client.
- in prisma>client.ts (`add the code from nextjs-prisma client documentation`)
```javascript
import { NextRequest, NextResponse } from 'next/server';
import prisma from '@/prisma/client';
import { z } from 'zod';
const createIssueSchema = z.object({
title: z.string().min(3).max(255),
description: z.string().min(1),
});
export async function POST(request: NextRequest) {
const body = await request.json();
const validation = createIssueSchema.safeParse(body);
if (!validation.success) {
return NextResponse.json(validation.error.errors, { status: 400 });
}
const newIssue = await prisma.issue.create({
data: { title: body.title, description: body.description },
});
return NextResponse.json(newIssue, { status: 201 });
}
```
**5. Setting up Redix UI**
- To build the new issue page we will use radix UI, very popular component library.
- Radix comes in to flavour, Themes and Primitives, themes have pre build component ready to use, where Primitives have only behaviour, and we have to style ourself.
- step1 `npm install @radix-ui/themes`
- step2 `import '@radix-ui/themes/styles.css;`
- step3 ` Add the Theme component`
[install Radix](https://www.radix-ui.com/themes/docs/overview/getting-started)
- import Button component in issue page and test.
`git add . // git commit -m "setup radix ui" // git push`
**6. Building the new issue page**
- In this section we are just going to build the form with two field, title and description.
- issues>new>page.tsx
- add the form from radix ui.
```javascript
'use client';
import React from 'react';
import { TextField, TextArea, Button } from '@radix-ui/themes';
const NewIssuePage = () => {
return (
);
};
export default NewIssuePage;
```
- add button to navigate the NewIssuePage
**7. Customizing radix ui theme**
- ThemePanle to change the theme.
- Also fix the inter font to work with radix ui.
- Follow the instruction in below link.
[RadixUi-typography](https://www.radix-ui.com/themes/docs/theme/typography)
**8. Adding a Markdown editor**
- Now let's replace textarea filed with markdown component.
- For this we are going to use `react-simplemde-editor`
[React SimpleMDE (EasyMDE) Markdown Editor](https://www.npmjs.com/package/react-simplemde-editor)
- install `npm install --save react-simplemde-editor easymde`
- now to use this component, import
`import SimpleMDE from "react-simplemde-editor";`
`import "easymde/dist/easymde.min.css";`
- Replace TextArea field with SimpleMDE component.
**9. Handling form submission**
- our Form is ready now its time to handle the form submission, we are going to use very popular library call `React Hook Form`
- install `npm install react-hook-form`
- `import { useForm } from "react-hook-form"`
- Next we define the interface of our form, basically the shape of our form (all the fields)
- Call the function useForm, which return objects, destructure it.
`const {register} = useForm()`
- {...register('title')} // because we saw in console log. it return object with 4 properties. that's why we must destructure it.
- This technique will not work with simpleMDE
- We need to use the controller component.
- First we render the controller component then pass the SimpleMDE component to controller component as props.
```javascript
}
/>
```
- next we need to submit form, handleSubmit
- install axios to connect with our api. (POST)
`npm i axios`
```javascript
'use client';
import { TextField, Button } from '@radix-ui/themes';
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';
import { useForm, Controller } from 'react-hook-form';
import axios from 'axios';
import { useRouter } from 'next/navigation';
interface IssueForm {
title: string;
description: string;
}
const NewIssuePage = () => {
const { register, control, handleSubmit } = useForm();
const router = useRouter();
return (
);
};
export default NewIssuePage;
```
**10. Handling Errors**
- currently our code doen't have error handling, so next thing what we want to do is handle the potential error and provide the feedback to the user if something goes wrong.
- add `try catch` block;
- we can also format the error `return NextResponse.json(validation.error.format(), { status: 400 });`
- we can also provide the custom error message.
- This will be usefull if you want to handle error in server, for example in registraion form , user must select unique username.
- now we need to display the error in client page.
- `useState` hook to store the error.
- now let's use one of the component from Radix ui `callout` to display the error.
```javascript
'use client';
import { useState } from 'react';
import { TextField, Button, Callout } from '@radix-ui/themes';
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';
import { useForm, Controller } from 'react-hook-form';
import axios from 'axios';
import { useRouter } from 'next/navigation';
interface IssueForm {
title: string;
description: string;
}
const NewIssuePage = () => {
const { register, control, handleSubmit } = useForm();
const router = useRouter();
const [error, setError] = useState('');
return (
{error && (
{error}
)}
);
};
export default NewIssuePage;
```
**11. Implementing client side valiation**
- We have schema for validating our Object in route.tsx file, so it would be nice if we use the same shcema also in client side.
- let's extract this logic in seperate module, so we can use it in two different places.
- right click on createIssueSchema and click Refactor.
- move the file to app directory.
- change the name to more general name like `validationSchema`
- now to use the schema, install the package called `@hookform/resolver`
`npm i @hookform/resolvers`
`import { zodResolver } from '@hookform/resolvers/zod';`
- also update the interfce, we already have type check in zod
`import {z} from 'zod'`
`type IssueForm = z.infer;`
- now we can get formState object, destructure to errors object which has property for title and description.
**12. Extract the error message component**
```javascript
import { Text } from '@radix-ui/themes';
import React, { PropsWithChildren } from 'react';
const ErrorMessage = ({ children }: PropsWithChildren) => {
if (!children) return null;
return (
{children}
);
};
export default ErrorMessage;
```
**13. Adding a spinner**
- To improve user experience we want to add the spinner when submitting the form.
[react spinner elements](https://tw-elements.com/docs/standard/components/spinners/)
```javascript
import React from 'react';
const Spinner = () => {
return (
Loading...
);
};
export default Spinner;
```
- import to page.tsx add to the Button component.
- useState hook to maintain the state for displaying the spinner when submitting the form.
**14. code organization**
- when creating the issue we may move the code in seperate file, but we only have one place creating the issue so for now let's keep it there
// createIssue(data)
- move the inline function.
# 4. Viewing Issues (54m)
**1-Showing the issues**
- show the issue, in issue page. import prisma,
`prisma.issue.findMany()`
- use Table component from Radix UI
- map through the issues and display the issue
- also hide status and issueDate in mobile.
- add div block to display the status on mobile.
**2-Building the issues Status Badge**
- Now let's add the beautiful badge componet from Radix ui for our status.
- first approach
```javascript
const IssueStatusBadge = ({ status }: { status: Status }) => {
if (status === 'OPEN') return Open;
if (status === 'IN_PROGRESS') return Open;
if (status === 'CLOSE') return Open;
};
```
- Second approach using Record, utility type in TypeScript which allows us to define a key value pair.
```javascript
import { Status, Issue } from '@prisma/client';
import { Badge } from '@radix-ui/themes';
import React from 'react';
const statusMap: Record<
Status,
{ label: string, color: 'red' | 'violet' | 'green' }
> = {
OPEN: { label: 'Open', color: 'red' },
IN_PROGRESS: { label: 'In Progress', color: 'violet' },
CLOSE: { label: 'Closed', color: 'green' },
};
const IssueStatusBadge = ({ status }: { status: Status }) => {
return (
{statusMap[status].label}
);
};
export default IssueStatusBadge;
```
**3-Adding Loading Skeltons**
- To imporve the user experience, let's add the loading skelton.
- when someone visit the site, while data is loading, user can see the skelton of the page.
- In issue folder add loading.tsx
- we can install delay package to delay the page loading so that we can see it clearly.
`npm i delay`
`import delay from 'delay`
- right after data is fetch, we can add delay(2000)
- To add the modern loading skelton, React Loading Skeleton package we will use.
`npm i react-loading-skeleton`
```javascript
import { Table } from '@radix-ui/themes';
import React from 'react';
import Skeleton from 'react-loading-skeleton';
import 'react-loading-skeleton/dist/skeleton.css';
import IssueActions from './IssueActions';
const issues = [1, 2, 3, 4, 5, 6];
const LoadingIssuePage = () => {
return (
Issue
Status
Created
{issues.map((issue) => (
))}
);
};
export default LoadingIssuePage;
```
- we can move the button to seperate componet so it can be used in different component inside issues.
**4-Showing Issues Details**
- let's add [id]>page.tsx
```javascript
import React from 'react';
import prisma from '@/prisma/client';
import { notFound } from 'next/navigation';
interface Props {
params: { id: string };
}
const IssueDetailPage = async ({ params }: Props) => {
const issue = await prisma.issue.findUnique({
where: { id: params.id },
});
if (!issue) notFound();
return (
{issue.title}
{issue.description}
{issue.status}
{issue.createdAt.toDateString()}
);
};
export default IssueDetailPage;
```
**5-Styling the Issue Details Page**
- let's style the issue detail page, will use Radix UI.
```javascript
import React from 'react';
import prisma from '@/prisma/client';
import { notFound } from 'next/navigation';
import { Heading, Flex, Card } from '@radix-ui/themes';
import IssueStatusBadge from '@/app/components/IssueStatusBadge';
interface Props {
params: { id: string };
}
const IssueDetailPage = async ({ params }: Props) => {
const issue = await prisma.issue.findUnique({
where: { id: params.id },
});
if (!issue) notFound();
return (
{issue.title}
{issue.createdAt.toDateString()}
{issue.description}
);
};
export default IssueDetailPage;
```
**6-Adding Markdown Preview**
- let's install package called react-markdown
`npm i react-markdown`
- wrap the paragraph with ReactMarkdown component.
- Next install the package tailwind typograpy
[tailwind typography](https://tailwindcss.com/docs/typography-plugin)
`npm install -D @tailwindcss/typography`
```javascript
import React from 'react'; ... ...