ES7 React/Redux/React Native
JavaScript and Typescript Nightly
Tailwind CSS IntelliSence
Prisma
docker run --name issue-tracker -p 33060:3306 -v /c/users/steven.yin/AppData/Local/Issue-Tracker:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
- npx create-next-app@13.4.19
- name: issue-tracker
- rest of the options are all default
- use shortcut "rafce" to generate the template code
- use shortcut "ul>li*2" to generate
- Dashboard
- Issues
- adjust the style
- use a nice logo by react-icons library
- add hover effect "text-zinc-500 hover:text-zinc-800" and transition class "transition-colors"
-
highlight the active link. impot { usePathname } from 'next/navigation'; const currentPath = usePathname();
-
make the component as a clientComponent 'use client'
-
use "npm i classnames@2.3.2" import classnames from 'classnames'; // this is a function return a object, this object specify that the classes we wanna render and the condition that it should be render
className = { classnames({ 'text-zinc-900': link.href === currentPath, 'text-zinc-500': link.href !== currentPath, 'hover:text-zinc-800 transition-colors': true }) }
- npm i prisma@5.3.1
- npx prisma init 1.1 modify "prisma schema" and ".env" file to adjust my own configuration
- create prisma model
model Issue {
id Int @id @default(autoincrement())
title String @db.VarChar(255)// varchar(191) - variable character
description String @db.Text
status Status @default(OPEN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Status {
OPEN
IN_PROGRESS
CLOSED
}
- npx prisma format
- npx prisma migrate dev
- install zod@3.22.2 to validate data
import { z } from 'zod';
const createIssueSchema = z.object({
title: z.string().min(1).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 NextRequest.json(validation.error.errors, { status: 400 });
}
// import single prisma client
// google how to create prisma client in /prisma/client.ts.
const newIssue = await prisma.issue.create({
// ... data
});
return NextResponse.json(newIssue, { status: 201 })
}
References: Radix UI Get Started Follow the handbook to integrate the Radix UI
<TextArea />- declare inside
- select one favourite theme -> Copy the code -> replace the
- References: Radix UI typography
- React SimpleMDE Markdown Editor
npm install --save react-simplemde-editor easymde
- npm install react-hook-form@7.46.1
import { useForm, Controller } from 'react-hook-form';
interface IssueForm {
title: string;
description: string;
}
// ...
const { register, control, handleSubmit } = useForm<IssueForm>();
- install axios@1.5.0
const router = useRouter(); // next/navigation
- use try/catch
- use "Callout" in Radix UI
- npm install @hookform/resolvers@3.3.1
useForm<IssueForm>({
resolver: zodResolver(createIssueSchema)
})
- reuse type in zod
import { z } from 'zod';
type IssueForm = z.infer<typeof createIssueSchema>
- insert error alert below each form component
- create /app/components/ErrorMessage.tsx
- google "tailwind elements spinner"
- create /app/components/Spinner.tsx
- use while submitting a new issue
- Use prisma.issue.findMany() to fetch multiple data.
- Use Radix UI Table to show all data
- Issue
- Status
- Created
- Adjust the style
- responsive layout
- hide status and created only show these in wide screen
- className="hidden md:table-cell"
- npm install delay
- use delay to check loading effect
await delay(2000)
- google "react loading skeleton"
- Follow Skeleton documentation.
- Extract New Issue Button to a new component(/app/issues/IssueActions.tsx)
- /app/issues/[id]/IssueDetailPage.tsx
- Use prisma.issue.findUnique({ where: { id: parseInt(params.id) }})
- Handle the situation when can not find anything. // notFound()
- Add to wrap Issue block on /issues
- Add /app/issues/[id]/loading.tsx for IssueDetailPage
- Add /app/issues/new/loading.tsx for NewIssuePage
<Flex my="2" >
...
</Flex>
npm install react-markdown@8.0.7
1. google tailwindcss typography ( Beautiful typographic defaults for HTML you don't control )
*## Building Linked Component*?
1. use 'next/link'
2. import { Link as RadixLink } from '@radix-ui/themes'
3. combine both of them together.
4. google "nextjs link component" - passHref lagacyBehavior
## Additional Loading Skeletons
1. Use react-loading-skeleton for all pages. (details, new)
```jsx
<Box>
<Skeleton />
...
<Skeleton />
</Box>
- use Await delay to check the skeleton.
- 'navigator is not defined' Because all codes are generated on server side, import dynamic from 'next/dynamic';
const SimpleMDE = dynamic(() => import('react-simplemde-editor'), { ssr: false });
- Managing all components in index.ts
- extracting Skeleton as a custom components with import ts and css together
- Use to split the Detail Page into 2.
- Use Breakpoints to identity the responsive layout.
- Add Edit Button in the second column, redirect to
/issue/${id}/edit
- Extracting Edit Issue logic to /app/issue/[id]/EditIssueButton.tsx
- Extracting Details logic to /app/issue/[id]/IssueDetails.tsx
- create '/app/issue/[id]/edit/page.tsx'
- create '/app/issue/_components/IssueForm.tsx'
- Refactor New Issue by
interface Props {
params: {id: string}
}
- new file: '/app/api/issue/[id]/route.ts'
- Rename createIssueSchema
- validate the input
- Check whether the issue is exist
- update the issue by id
export async function PATH(request: NextRequest, { params }: { params: {id: string }}) {
const body = await request.json();
}
Reference: google "Nextjs Route Segment Config"
- Data Caching
- When we fetch data using fetch()
- Stored in the file system
- Permanent until we redeploy
- Full Route Cache (Cache on the Server)
- Used to store the output of statically rendered routes
- Router Cache (Client-side Cache)
- To store the payload of pages in broswer
- Lasts for a session
- Get a refresh when we reload
export const dynamic = 'force-dynamic';
export const revalidate = 0;
- Let Text and MDE appear together
dynamic(() => import('xx'), { ssr: false, loading: () => <>Loading...</> })
- Add Skeleton for Edit Issue page and New Issue page
- Button are inline element of HTML -> set flex to put two buttons vertically
<Flex gap="2" direction="column">
- Adjust Button layout in different resolutions(Mobile/Tablet/PC).
- Use
- Reference: AlertDialog in Radix UI.
- npm i next-auth
- Follow offical guide:
- create /api/auth/[...nextauth]/route.ts
const handler = NextAuth()
-
.env: NEXTAUTH_URL="http://localhost:3000 NEXTAUTH_SECRET="xxxxx"
-
generate a random string: `openssl rand -base64 32``
Configuring Google Provider: https://console.cloud.google.com
- Follow the documentation in website: "Google | NextAuth.js"
- Follow the documentation: @auth/prisma-adapter
- grap all models in the schema.prisma
- npx prisma migrate dev
npm install @next-auth/prisma-adapter@1.0.7
- add {session: 'jwt'} in NextAuth({});
- const { status, data } = useSession(); {status === "authenticated" && Log out} {status === "unauthenticated" && Login}
- create file "/app/auth/Provider.tsx"
const AuthProvider = () => {
return (
<SessionProvider>{children}</SessionProvider>
)
}
- wrap our website into the AuthProvider
justify-content: space-between
.- Wrap everything inside a
- Using DropdownMenu from Radix UI
- referrerPolicy='no-referrer'
- Add configuration in
next.config.ts
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'referrer-policy', value: 'no-referrer' }
]
}
]
}
}
- Extract each part into components
- control + shift + command + 'right arrow': select Expand
@layer utilities {
.nav-link {
@apply text-zinc-500 hover:text-zinc-800 transition-colors
}
}
<Link className="nav-link">
- Adding middleware function
/middleware.ts
export { default } from "next-auth/middleware"
export const config = {
matcher: [
'/issue/new',
'/issue/edit/:id+'
]
}
- Hiding Edit & Delete Buttons if there isn't a session available;
- const session = getServerSession(authOptions) to check if there is a session available;
/app/issue/[id]/_components/AssigneeSelect.tsx
- fetch data by
axios
to getusers
- Building the API
/app/api/users/route.ts
(Add NextRequest parameter to avoid caching) - Use React Query to fetch data from this API.
- React Query uses queryClient in React Context, so we should declare a Provider to wrap our application.
- connect
asignedToUser
withIssue
table by primary key - Add
assignedIssues Issue[]
inUser
Table - execute
npx prisma migrate dev
- create new zod schema to check different fields in different situations.
npm install react-hot-toast@2.4.1
- new file
/app/issue/list/IssueStatusFilter.tsx
withSelect
inRadix UI
; - Add this component inside list page.
- Register the onValueChange event to filter issues by status
- Making Columns Sortable.
- extracting columns of the table
const columns: {label: string; value: keyof Issue; className?: string}[];
- Add link for each title, href is
/issue/list?orderby=${columnValue}
, but still keep the existing parameters, so we should use href={{ query: { ...searchParams, orderBy: columnValue }}}; - Add Arrow Icon to indicate descent or ascent.
- Using URLSearchParams to assemble Params
- Summarising issues in three dimonsions: OPEN, IN_PROGRESS and CLOSED.
npm install recharts@2.8.0
const { ResponsiveContainer, BarChart, XAxis, YAxis, Bar } from 'recharts'
- Check color variables in browser, get the variable named
--accent-9
export const metadata: Metadata = {
title: '',
description: '',
};
export async function generateMetadata({ params }: Props) {
// ...
return {
title: issue?.title,
description: 'xxx'
}
}
const fetchUsers = cache((issueId: number) => {
return await prisma.issue.findUnique({
where: {
id: issueId
}
})
})
"prisma:migrate:dev": "dotenv -e .env.local -- npx prisma migrate dev"