diff --git a/.env b/.env
new file mode 100644
index 0000000..43c1433
--- /dev/null
+++ b/.env
@@ -0,0 +1,8 @@
+POSTGRES_URL="postgres://default:w3AEaGL5Necm@ep-bold-truth-a4wviweq-pooler.us-east-1.aws.neon.tech:5432/verceldb?sslmode=require"
+POSTGRES_PRISMA_URL="postgres://default:w3AEaGL5Necm@ep-bold-truth-a4wviweq-pooler.us-east-1.aws.neon.tech:5432/verceldb?sslmode=require&pgbouncer=true&connect_timeout=15"
+POSTGRES_URL_NO_SSL="postgres://default:w3AEaGL5Necm@ep-bold-truth-a4wviweq-pooler.us-east-1.aws.neon.tech:5432/verceldb"
+POSTGRES_URL_NON_POOLING="postgres://default:w3AEaGL5Necm@ep-bold-truth-a4wviweq.us-east-1.aws.neon.tech:5432/verceldb?sslmode=require"
+POSTGRES_USER="default"
+POSTGRES_HOST="ep-bold-truth-a4wviweq-pooler.us-east-1.aws.neon.tech"
+POSTGRES_PASSWORD="w3AEaGL5Necm"
+POSTGRES_DATABASE="verceldb"
\ No newline at end of file
diff --git a/.env.example b/.env.example
deleted file mode 100644
index 8a85ba7..0000000
--- a/.env.example
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copy from .env.local on the Vercel dashboard
-# https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database
-POSTGRES_URL=
-POSTGRES_PRISMA_URL=
-POSTGRES_URL_NON_POOLING=
-POSTGRES_USER=
-POSTGRES_HOST=
-POSTGRES_PASSWORD=
-POSTGRES_DATABASE=
-
-# `openssl rand -base64 32`
-AUTH_SECRET=
-AUTH_URL=http://localhost:3000/api/auth
\ No newline at end of file
diff --git a/Chapter-1.md b/Chapter-1.md
new file mode 100644
index 0000000..ffb631c
--- /dev/null
+++ b/Chapter-1.md
@@ -0,0 +1,112 @@
+# Bab 1
+
+## Memulai
+
+### Membuat Proyek Baru
+
+Kami merekomendasikan menggunakan pnpm sebagai manajer paket Anda, karena lebih cepat dan lebih efisien daripada npm atau yarn. Jika Anda belum menginstal pnpm, Anda dapat menginstalnya secara global dengan menjalankan perintah berikut:
+
+```bash
+npm install -g pnpm
+```
+
+Untuk membuat aplikasi Next.js, buka terminal Anda, masuk ke folder tempat Anda ingin menyimpan proyek, dan jalankan perintah berikut:
+
+```bash
+npx create-next-app@latest nextjs-dashboard --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example" --use-pnpm
+```
+
+Perintah ini menggunakan create-next-app, sebuah alat Command Line Interface (CLI) yang menyiapkan aplikasi Next.js untuk Anda. Dalam perintah di atas, Anda juga menggunakan flag --example dengan contoh starter untuk kursus ini.
+
+### Menjelajahi Proyek
+
+Berbeda dengan tutorial yang mengharuskan Anda menulis kode dari awal, sebagian besar kode untuk kursus ini sudah ditulis untuk Anda. Ini lebih mencerminkan pengembangan dunia nyata, di mana Anda kemungkinan besar akan bekerja dengan basis kode yang sudah ada.
+
+Tujuan kami adalah membantu Anda fokus mempelajari fitur utama Next.js, tanpa harus menulis semua kode aplikasi.
+
+Setelah instalasi, buka proyek di editor kode Anda dan navigasikan ke nextjs-dashboard.
+
+```bash
+cd nextjs-dashboard-template
+```
+
+Mari kita luangkan waktu untuk menjelajahi proyek ini.
+
+### Struktur Folder
+
+Anda akan melihat bahwa proyek ini memiliki struktur folder berikut:
+
+Struktur folder dari proyek dashboard, menunjukkan folder dan file utama: app, public, dan file konfigurasi.
+
+- /app: Berisi semua rute, komponen, dan logika untuk aplikasi Anda, di sinilah Anda akan bekerja sebagian besar.
+- /app/lib: Berisi fungsi-fungsi yang digunakan dalam aplikasi Anda, seperti fungsi utilitas yang dapat digunakan kembali dan fungsi pengambilan data.
+- /app/ui: Berisi
+
+semua komponen antarmuka pengguna untuk aplikasi Anda, seperti kartu, tabel, dan formulir. Untuk menghemat waktu, kami telah menata komponen-komponen ini untuk Anda.
+
+- /public: Berisi semua aset statis untuk aplikasi Anda, seperti gambar.
+- File Konfigurasi: Anda juga akan melihat file konfigurasi seperti next.config.js di root aplikasi Anda. Sebagian besar file ini dibuat dan dikonfigurasi sebelumnya saat Anda memulai proyek baru menggunakan create-next-app. Anda tidak perlu memodifikasinya dalam kursus ini.
+
+Jelajahi folder-folder ini dengan bebas, dan jangan khawatir jika Anda belum memahami semua kode yang ada.
+
+### Data Placeholder
+
+Saat Anda membangun antarmuka pengguna, akan sangat membantu jika Anda memiliki beberapa data placeholder. Jika basis data atau API belum tersedia, Anda dapat:
+
+- Menggunakan data placeholder dalam format JSON atau sebagai objek JavaScript.
+- Menggunakan layanan pihak ketiga seperti mockAPI.
+
+Untuk proyek ini, kami telah menyediakan beberapa data placeholder di app/lib/placeholder-data.ts. Setiap objek JavaScript dalam file tersebut mewakili tabel dalam basis data Anda. Misalnya, untuk tabel invoices:
+
+```javascript
+const invoices = [
+ {
+ customer_id: customers[0].id,
+ amount: 15795,
+ status: "pending",
+ date: "2022-12-06",
+ },
+ {
+ customer_id: customers[1].id,
+ amount: 20348,
+ status: "pending",
+ date: "2022-11-14",
+ },
+ // ...
+];
+```
+
+Pada bab tentang pengaturan basis data Anda, Anda akan menggunakan data ini untuk mengisi basis data Anda (mengisinya dengan beberapa data awal).
+
+```javascript
+// Contoh objek invoice
+const invoice = {
+ id: "1",
+ customer_id: "123",
+ amount: 15795,
+ date: "2022-12-06",
+ status: "pending", // Status ini bisa berupa 'pending' atau 'paid'
+};
+```
+
+Dengan menggunakan komentar JSDoc, Anda dapat mendokumentasikan tipe data dan memberikan informasi tambahan yang berguna saat bekerja dengan editor kode yang mendukung JSDoc, seperti Visual Studio Code.
+
+### Menjalankan Server Pengembangan
+
+Jalankan `pnpm i` untuk menginstal paket proyek.
+
+```bash
+pnpm i
+```
+
+Diikuti oleh `pnpm dev` untuk memulai server pengembangan.
+
+```bash
+pnpm dev
+```
+
+`pnpm dev` akan memulai server pengembangan Next.js Anda pada port 3000. Mari kita cek apakah servernya berfungsi.
+
+Buka http://localhost:3000 di browser Anda. Halaman beranda Anda harus terlihat seperti ini, yang sengaja tidak diberi gaya:
+
+Halaman tanpa gaya dengan judul 'Acme', deskripsi, dan tautan login.
diff --git a/Chapter-2.md b/Chapter-2.md
new file mode 100644
index 0000000..d86dc0d
--- /dev/null
+++ b/Chapter-2.md
@@ -0,0 +1,203 @@
+# Bab 2
+
+## CSS Styling
+
+Saat ini, halaman beranda Anda tidak memiliki gaya apa pun. Mari kita lihat berbagai cara untuk memberi gaya pada aplikasi Next.js Anda.
+
+### Dalam bab ini...
+
+Berikut adalah topik yang akan kita bahas:
+
+- Cara menambahkan file CSS global ke aplikasi Anda.
+- Dua cara berbeda dalam memberi gaya: Tailwind dan CSS modules.
+- Cara menambahkan nama kelas secara kondisional dengan paket utilitas clsx.
+
+### Gaya Global
+
+Jika Anda melihat di dalam folder /app/ui, Anda akan melihat file bernama global.css. Anda dapat menggunakan file ini untuk menambahkan aturan CSS ke semua rute di aplikasi Anda - seperti aturan reset CSS, gaya di seluruh situs untuk elemen HTML seperti tautan, dan lainnya.
+
+Anda dapat mengimpor global.css di komponen mana pun di aplikasi Anda, tetapi biasanya praktik yang baik adalah menambahkannya ke komponen tingkat atas Anda. Dalam Next.js, ini adalah layout root (lebih lanjut tentang ini nanti).
+
+Tambahkan gaya global ke aplikasi Anda dengan menavigasi ke /app/layout.tsx dan mengimpor file global.css:
+
+```javascript
+// /app/layout.js
+
+import "@/app/ui/global.css";
+
+export default function RootLayout({ children }) {
+ return (
+
+
{children}
+
+ );
+}
+```
+
+Dengan server pengembangan masih berjalan, simpan perubahan Anda dan lihat pratinjau di browser. Halaman beranda Anda sekarang harus terlihat seperti ini:****
+
+Halaman dengan gaya dengan logo 'Acme', deskripsi, dan tautan login.
+
+Tapi tunggu **sebentar**, Anda tidak menambahkan aturan CSS apa pun, dari mana gaya ini berasal?
+
+Jika Anda melihat di dalam global.css, Anda akan melihat beberapa direktif @tailwind:
+
+```css
+// /app/ui/global.css
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+```
+
+### Tailwind
+
+Tailwind adalah kerangka kerja CSS yang mempercepat proses pengembangan dengan memungkinkan Anda menulis kelas utilitas secara langsung di markup TSX Anda.
+
+Dalam Tailwind, Anda memberi gaya pada elemen dengan menambahkan nama kelas. Misalnya, menambahkan kelas "text-blue-500" akan mengubah teksmenjadi biru:
+
+```jsx
+
I'm blue!
+```
+
+Meskipun gaya CSS dibagikan secara global, setiap kelas diterapkan secara tunggal ke setiap elemen. Ini berarti jika Anda menambah atau menghapus elemen, Anda tidak perlu khawatir tentang mempertahankan stylesheet terpisah, tabrakan gaya, atau ukuran bundle CSS yang meningkat saat aplikasi Anda berkembang.
+
+Ketika Anda menggunakan create-next-app untuk memulai proyek baru, Next.js akan menanyakan apakah Anda ingin menggunakan Tailwind. Jika Anda memilih ya, Next.js akan secara otomatis menginstal paket yang diperlukan dan mengonfigurasi Tailwind di aplikasi Anda.
+
+Jika Anda melihat di /app/page.tsx, Anda akan melihat bahwa kami menggunakan kelas Tailwind dalam contoh tersebut.
+
+```javascript
+// /app/page.x
+import AcmeLogo from '@/app/ui/acme-logo';
+import { ArrowRightIcon } from '@heroicons/react/24/outline';
+import Link from 'next/link';
+
+export default function Page() {
+ return (
+ // Ini adalah kelas Tailwind:
+
+
+ // ...
+ )
+}
+```
+
+Jangan khawatir jika ini adalah pertama kalinya Anda menggunakan Tailwind. Untuk menghemat waktu, kami telah menata semua komponen yang akan Anda gunakan.
+
+Mari bermain dengan Tailwind! Salin kode di bawah ini dan tempelkan di atas elemen
di /app/page.tsx:
+
+```jsx
+// /app/page.tsx
+
+```
+
+### CSS Modules
+
+CSS Modules memungkinkan Anda membuat skop CSS ke komponen dengan membuat nama kelas yang unik secara otomatis, sehingga Anda tidak perlu khawatir tentang tabrakan gaya.
+
+Kami akan terus menggunakan Tailwind dalam kursus ini, tetapi mari kita lihat bagaimana Anda bisa mencapai hasil yang sama dari kuis di atas menggunakan CSS modules.
+
+Di dalam /app/ui, buat file baru bernama home.module.css dan tambahkan aturan CSS berikut:
+
+```css
+// /app/ui/home.module.css
+.shape {
+ height: 0;
+ width: 0;
+ border-bottom: 30px solid black;
+ border-left: 20px solid transparent;
+ border-right: 20px solid transparent;
+}
+```
+
+Kemudian, di dalam file /app/page.tsx Anda, impor gaya tersebut dan ganti nama kelas Tailwind dari yang telah Anda tambahkan dengan styles.shape:
+
+```javascript
+// /app/page.jsx
+import AcmeLogo from "@/app/ui/acme-logo";
+import { ArrowRightIcon } from "@heroicons/react/24/outline";
+import Link from "next/link";
+
+export default function Page() {
+ return (
+
+
+ {/* */}
+
+
+
+
+ Welcome to Acme. This is the example for the{" "}
+
+ Next.js Learn Course
+
+ , brought to you by devnolife - laboratorium-if-unismuh.
+
+
+ Log in
+
+
+
+ {/* Add Hero Images Here */}
+
+
+
+ );
+}
+```
+
+Simpan perubahan Anda dan lihat pratinjau di browser. Anda harus melihat bentuk yang sama seperti sebelumnya.
+
+Tailwind dan CSS modules adalah dua cara paling umum untuk memberi gaya pada aplikasi Next.js. Apakah Anda menggunakan salah satunya atau lainnya adalah masalah preferensi - Anda bahkan dapat menggunakan keduanya dalam aplikasi yang sama!
+
+### Menggunakan pustaka clsx untuk toggle nama kelas
+
+Mungkin ada kasus di mana Anda perlu memberi gaya pada elemen secara kondisional berdasarkan state atau kondisi lainnya.
+
+clsx adalah pustaka yang memungkinkan Anda untuk toggle nama kelas dengan mudah. Kami merekomendasikan melihat dokumentasi untuk detail lebih lanjut, tetapi berikut adalah penggunaan dasarnya:
+
+Misalkan Anda ingin membuat komponen InvoiceStatus yang menerima status. Statusnya bisa 'pending' atau 'paid'. Jika 'paid', Anda ingin warnanya menjadi hijau. Jika 'pending', Anda ingin warnanya menjadi abu-abu. Anda dapat menggunakan clsx untuk menerapkan kelas secara kondisional, seperti ini:
+
+```javascript
+// /app/ui/invoices/status.jsx
+import { CheckIcon, ClockIcon } from "@heroicons/react/24/outline";
+import clsx from "clsx";
+
+export default function InvoiceStatus({ status }) {
+ return (
+
+ {status === "pending" ? (
+ <>
+ Pending
+
+ >
+ ) : null}
+ {status === "paid" ? (
+ <>
+ Paid
+
+ >
+ ) : null}
+
+ );
+}
+```
+
+### Solusi styling lainnya
+
+Selain pendekatan yang telah kita bahas, Anda juga dapat memberi gaya pada aplikasi Next.js Anda dengan:
+
+- Sass yang memungkinkan Anda mengimpor file .css dan .scss.
+- Pustaka CSS-in-JS seperti styled-jsx, styled-components, dan emotion.
+
+Lihat dokumentasi CSS untuk informasi lebih lanjut.
diff --git a/Chapter-3.md b/Chapter-3.md
new file mode 100644
index 0000000..a2eb322
--- /dev/null
+++ b/Chapter-3.md
@@ -0,0 +1,163 @@
+# Bab 3
+
+## Optimizing Fonts and Images
+
+Pada bab sebelumnya, Anda telah belajar cara memberi gaya pada aplikasi Next.js Anda. Mari lanjutkan bekerja pada halaman beranda Anda dengan menambahkan font kustom dan gambar hero.
+
+### Dalam bab ini...
+
+Berikut adalah topik yang akan kita bahas:
+
+- Cara menambahkan font kustom dengan next/font.
+- Cara menambahkan gambar dengan next/image.
+- Bagaimana font dan gambar dioptimalkan dalam Next.js.
+
+### Mengapa Mengoptimalkan Font?
+
+Font memainkan peran penting dalam desain situs web, tetapi menggunakan font kustom dalam proyek Anda dapat memengaruhi performa jika file font perlu diambil dan dimuat.
+
+Cumulative Layout Shift adalah metrik yang digunakan oleh Google untuk mengevaluasi performa dan pengalaman pengguna dari sebuah situs web. Dengan font, pergeseran tata letak terjadi ketika browser awalnya merender teks dalam font fallback atau sistem dan kemudian menggantinya dengan font kustom setelah dimuat. Pergantian ini dapat menyebabkan ukuran teks, spasi, atau tata letak berubah, menggeser elemen di sekitarnya.
+
+Next.js secara otomatis mengoptimalkan font dalam aplikasi saat Anda menggunakan modul next/font. Ini mengunduh file font saat build time dan menghostingnya dengan aset statis lainnya. Ini berarti ketika pengguna mengunjungi aplikasi Anda, tidak ada permintaan jaringan tambahan untuk font yang akan mempengaruhi performa.
+
+### Menambahkan Font Utama
+
+Mari tambahkan font Google kustom ke aplikasi Anda untuk melihat cara kerjanya!
+
+Di folder /app/ui, buat file baru bernama fonts.ts. Anda akan menggunakan file ini untuk menyimpan font yang akan digunakan di seluruh aplikasi Anda.
+
+Impor font Inter dari modul next/font/google - ini akan menjadi font utama Anda. Kemudian, tentukan subset apa yang ingin Anda muat. Dalam hal ini, 'latin':
+
+```javascript
+// /app/ui/fonts.js
+export const lusitana = {
+ className: "font-lusitana",
+};
+```
+
+Terakhir, tambahkan font ke elemen di /app/layout.tsx:
+
+```javascript
+// /app/layout.jsx
+export default function RootLayout({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Dengan menambahkan Inter ke elemen, font akan diterapkan di seluruh aplikasi Anda. Di sini, Anda juga menambahkan kelas Tailwind `antialiased` yang memperhalus font. Ini tidak perlu digunakan, tetapi menambahkan sentuhan yang bagus.
+
+Navigasikan ke browser Anda, buka dev tools dan pilih elemen body. Anda harus melihat Inter dan Inter_Fallback sekarang diterapkan di bawah styles.
+
+Terakhir, komponen juga menggunakan Lusitana. Ini dikomentari untuk mencegah kesalahan, Anda sekarang dapat menghapus komentarnya:
+
+```javascript
+// /app/page.tsx
+export default function Page() {
+ return (
+
+
+
+ {/* ... */}
+
+
+ );
+}
+```
+
+Bagus, Anda telah menambahkan dua font kustom ke aplikasi Anda! Selanjutnya, mari tambahkan gambar hero ke halaman beranda.
+
+### Mengapa Mengoptimalkan Gambar?
+
+Next.js dapat menyajikan aset statis, seperti gambar, di bawah folder tingkat atas /public. File di dalam /public dapat direferensikan dalam aplikasi Anda.
+
+Dengan HTML biasa, Anda akan menambahkan gambar sebagai berikut:
+
+```html
+
+```
+
+Namun, ini berarti Anda harus secara manual:
+
+- Memastikan gambar Anda responsif pada berbagai ukuran layar.
+- Menentukan ukuran gambar untuk perangkat yang berbeda.
+- Mencegah pergeseran tata letak saat gambar dimuat.
+- Memuat gambar secara malas yang berada di luar viewport pengguna.
+
+Optimisasi Gambar adalah topik besar dalam pengembangan web yang bisa dianggap sebagai spesialisasi tersendiri. Alih-alih mengimplementasikan optimisasi ini secara manual, Anda dapat menggunakan komponen next/image untuk mengoptimalkan gambar Anda secara otomatis.
+
+### Komponen
+
+Komponen adalah perpanjangan dari tag HTML , dan dilengkapi dengan optimisasi gambar otomatis, seperti:
+
+- Mencegah pergeseran tata letak secara otomatis saat gambar dimuat.
+- Mengubah ukuran gambar untuk menghindari pengiriman gambar besar ke perangkat dengan viewport lebih kecil.
+- Memuat gambar secara malas secara default (gambar dimuat saat memasuki viewport).
+- Menyajikan gambar dalam format modern, seperti WebP dan AVIF, saat browser mendukungnya.
+
+### Menambahkan Gambar Hero Desktop
+
+Mari kita gunakan komponen . Jika Anda melihat di dalam folder /public, Anda akan melihat ada dua gambar: hero-desktop.png dan hero-mobile.png. Kedua gambar ini sepenuhnya berbeda, dan akan ditampilkan tergantung apakah perangkat pengguna adalah desktop atau mobile.
+
+Di file /app/page.tsx Anda, impor komponen dari next/image. Kemudian, tambahkan gambar di bawah komentar:
+
+```javascript
+// /app/page.js
+import AcmeLogo from "@/app/ui/acme-logo";
+import { ArrowRightIcon } from "@heroicons/react/24/outline";
+import Link from "next/link";
+import Image from "next/image";
+
+export default function Page() {
+ return (
+ //..
+
+ {/* Add Hero Images Here */}
+
+
+ //..
+ );
+}
+```
+
+Di sini, Anda menetapkan lebar 1000 dan tinggi 760 piksel. Adalah praktik yang baik untuk menetapkan lebar dan tinggi gambar Anda untuk menghindari pergeseran tata letak, ini harus memiliki rasio aspek yang identik dengan gambar sumber.
+
+Anda juga akan melihat kelas `hidden` untuk menghapus gambar dari DOM di layar mobile, dan `md:block` untuk menampilkan gambar di layar desktop.
+
+Inilah tampilan halaman beranda Anda sekarang:
+
+Halaman beranda yang distilisasi dengan font kustom dan gambar hero.
+
+
+### Latihan: Menambahkan Gambar Hero Mobile
+
+Sekarang giliran Anda! Di bawah gambar yang baru saja Anda tambahkan, tambahkan komponen lain untuk hero-mobile.png.
+
+- Gambar harus memiliki lebar 560 dan tinggi 620 piksel.
+- Gambar harus ditampilkan di layar mobile, dan disembunyikan di desktop - Anda dapat menggunakan dev tools untuk memeriksa apakah gambar desktop dan mobile ditukar dengan benar.
+
+Setelah Anda siap, perluas cuplikan kode di bawah ini untuk melihat solusinya.
+
+Bagus! Halaman beranda Anda sekarang memiliki font kustom dan gambar hero.
+
+### Bacaan yang Direkomendasikan
+
+Masih banyak lagi yang bisa dipelajari tentang topik ini, termasuk mengoptimalkan gambar jarak jauh dan menggunakan file font lokal. Jika Anda ingin mendalami lebih jauh tentang font dan gambar, lihat:
+
+- [Dokumentasi Optimisasi Gambar](https://nextjs.org/docs/basic-features/image-optimization)
+- [Dokumentasi Optimisasi Font](https://nextjs.org/docs/basic-features/font-optimization)
+- [Meningkatkan Performa Web dengan Gambar (MDN)](https://developer.mozilla.org/en-US/docs/Web/Performance/Optimizing_content_efficiency/Images)
+- [Web Fonts (MDN)](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face)
+- [Bagaimana Core Web Vitals Mempengaruhi SEO](https://web.dev/vitals/seo/)
diff --git a/Chapter-4.md b/Chapter-4.md
new file mode 100644
index 0000000..625e1fd
--- /dev/null
+++ b/Chapter-4.md
@@ -0,0 +1,105 @@
+# Bab 4
+
+## Creating Layouts and Pages
+
+Sejauh ini, aplikasi Anda hanya memiliki halaman beranda. Mari pelajari cara membuat lebih banyak rute dengan layout dan halaman.
+
+### Dalam bab ini...
+
+Berikut adalah topik yang akan kita bahas:
+
+- Membuat rute dashboard menggunakan file-system routing.
+- Memahami peran folder dan file saat membuat segmen rute baru.
+- Membuat layout bersarang yang dapat dibagikan antara beberapa halaman dashboard.
+- Memahami apa itu colocation, partial rendering, dan root layout.
+
+### Nested routing
+
+Next.js menggunakan file-system routing di mana folder digunakan untuk membuat rute bersarang. Setiap folder mewakili segmen rute yang memetakan ke segmen URL.
+
+Anda dapat membuat UI terpisah untuk setiap rute menggunakan file layout.tsx dan page.tsx.
+
+page.tsx adalah file khusus Next.js yang mengekspor komponen React, dan ini diperlukan agar rute dapat diakses. Dalam aplikasi Anda, Anda sudah memiliki file halaman: /app/page.tsx - ini adalah halaman beranda yang terkait dengan rute /.
+
+Untuk membuat rute bersarang, Anda dapat menyarangkan folder di dalam satu sama lain dan menambahkan file page.jsx di dalamnya. Misalnya:
+
+/app/dashboard/page.jsx terkait dengan path /dashboard. Mari kita buat halaman untuk melihat cara kerjanya!
+
+### Membuat Halaman Dashboard
+
+Buat folder baru bernama dashboard di dalam /app. Kemudian, buat file page.jsx baru di dalam folder dashboard dengan konten berikut:
+
+```javascript
+// /app/dashboard/page.jsx
+export default function Page() {
+ return
Dashboard Page
;
+}
+```
+
+Sekarang, pastikan server pengembangan berjalan dan kunjungi http://localhost:3000/dashboard. Anda harus melihat teks "Dashboard Page".
+
+Inilah cara Anda dapat membuat halaman berbeda di Next.js: buat segmen rute baru menggunakan folder, dan tambahkan file halaman di dalamnya.
+
+Dengan memiliki nama khusus untuk file halaman, Next.js memungkinkan Anda untuk menempatkan komponen UI, file uji, dan kode terkait lainnya bersama rute Anda. Hanya konten di dalam file halaman yang akan dapat diakses secara publik. Misalnya, folder /ui dan /lib ditempatkan di dalam folder /app bersama rute Anda.
+
+### Latihan: Membuat Halaman Dashboard
+
+Mari berlatih membuat lebih banyak rute. Di dashboard Anda, buat dua halaman lagi:
+
+- **Customers Page**: Halaman ini harus dapat diakses di http://localhost:3000/dashboard/customers. Untuk saat ini, halaman ini harus mengembalikan elemen
Customers Page
.
+- **Invoices Page**: Halaman invoices harus dapat diakses di http://localhost:3000/dashboard/invoices. Untuk saat ini, juga kembalikan elemen
Invoices Page
.
+
+Luangkan waktu untuk menangani latihan ini, dan ketika Anda siap, perluas toggle di bawah untuk melihat solusinya:
+
+### Membuat Layout Dashboard
+
+Dashboard memiliki beberapa jenis navigasi yang dibagikan di antara beberapa halaman. Di Next.js, Anda dapat menggunakan file layout.jsx khusus untuk membuat UI yang dibagikan antara beberapa halaman. Mari kita buat layout untuk halaman dashboard!
+
+Di dalam folder /dashboard, tambahkan file baru bernama layout.jsx dan tempelkan kode berikut:
+
+```javascript
+// /app/dashboard/layout.jsx
+iimport SideNav from '@/app/ui/dashboard/sidenav';
+
+export default function Layout({ children }) {
+ return (
+
+
+
+
+
{children}
+
+ );
+}
+```
+
+Beberapa hal terjadi dalam kode ini, jadi mari kita uraikan:
+
+- Pertama, Anda mengimpor komponen ke dalam layout Anda. Setiap komponen yang Anda impor ke file ini akan menjadi bagian dari layout.
+- Komponen menerima prop children. Anak ini bisa berupa halaman atau layout lain. Dalam kasus Anda, halaman di dalam /dashboard akan secara otomatis disarangkan di dalam seperti ini:
+ 
+ Pastikan semuanya bekerja dengan benar dengan menyimpan perubahan Anda dan memeriksa localhost Anda. Anda harus melihat:
+ 
+ Salah satu manfaat menggunakan layout di Next.js adalah bahwa pada navigasi, hanya komponen halaman yang diperbarui sementara layout tidak akan di-render ulang. Ini disebut partial rendering:
+ 
+
+### Root Layout
+
+Di Bab 3, Anda mengimpor font Inter ke layout lain: /app/layout.jsx. Sebagai pengingat:
+
+```javascript
+// /app/layout.jsx
+import "@/app/ui/global.css";
+import { lusitana } from "@/app/ui/fonts";
+export default function RootLayout({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Ini disebut root layout dan diperlukan. UI apa pun yang Anda tambahkan ke root layout akan dibagikan di semua halaman dalam aplikasi Anda. Anda dapat menggunakan root layout untuk memodifikasi tag dan , serta menambahkan metadata (Anda akan belajar lebih banyak tentang metadata di bab selanjutnya).
+
+Karena layout baru yang baru saja Anda buat (/app/dashboard/layout.jsx) unik untuk halaman dashboard, Anda tidak perlu menambahkan UI apa pun ke root layout di atas.
diff --git a/app/dashboard/(overview)/loading.js b/app/dashboard/(overview)/loading.js
new file mode 100644
index 0000000..3d9e042
--- /dev/null
+++ b/app/dashboard/(overview)/loading.js
@@ -0,0 +1,6 @@
+// /app/dashboard/loading.js
+import DashboardSkeleton from "@/app/ui/skeletons";
+
+export default function Loading() {
+ return ;
+}
\ No newline at end of file
diff --git a/app/dashboard/(overview)/page.js b/app/dashboard/(overview)/page.js
new file mode 100644
index 0000000..f5269c7
--- /dev/null
+++ b/app/dashboard/(overview)/page.js
@@ -0,0 +1,30 @@
+// /app/dashboard/(overview)/page.js
+import CardWrapper from "@/app/ui/dashboard/cards";
+import RevenueChart from "@/app/ui/dashboard/revenue-chart";
+import LatestInvoices from "@/app/ui/dashboard/latest-invoices";
+import { lusitana } from "@/app/ui/fonts";
+import { Suspense } from "react";
+import { RevenueChartSkeleton, LatestInvoicesSkeleton , CardSkeleton} from "@/app/ui/skeletons";
+
+export default async function Page() {
+ return (
+
+
+ Dashboard
+
+
+ }>
+
+
+
+
+ }>
+
+
+ }>
+
+
+
+
+ );
+}
diff --git a/app/dashboard/customers/page.js b/app/dashboard/customers/page.js
new file mode 100644
index 0000000..05badaf
--- /dev/null
+++ b/app/dashboard/customers/page.js
@@ -0,0 +1,3 @@
+export default function Page() {
+ return
Customers Page
;
+ }
\ No newline at end of file
diff --git a/app/dashboard/invoices/[id]/edit/page.js b/app/dashboard/invoices/[id]/edit/page.js
new file mode 100644
index 0000000..647a755
--- /dev/null
+++ b/app/dashboard/invoices/[id]/edit/page.js
@@ -0,0 +1,28 @@
+// /app/dashboard/invoices/[id]/edit/page.js
+import Form from "@/app/ui/invoices/edit-form";
+import Breadcrumbs from "@/app/ui/invoices/breadcrumbs";
+import { fetchInvoiceById, fetchCustomers } from "@/app/lib/data";
+
+export default async function Page({params }) {
+ const id = params.id;
+ const [invoice, customers] = await Promise.all([
+ fetchInvoiceById(id),
+ fetchCustomers(),
+ ]);
+ return (
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/dashboard/invoices/create/page.js b/app/dashboard/invoices/create/page.js
new file mode 100644
index 0000000..fd72576
--- /dev/null
+++ b/app/dashboard/invoices/create/page.js
@@ -0,0 +1,24 @@
+// /dashboard/invoices/create/page.js
+import Form from "@/app/ui/invoices/create-form";
+import Breadcrumbs from "@/app/ui/invoices/breadcrumbs";
+import { fetchCustomers } from "@/app/lib/data";
+
+export default async function Page() {
+ const customers = await fetchCustomers();
+
+ return (
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/dashboard/invoices/page.js b/app/dashboard/invoices/page.js
new file mode 100644
index 0000000..31ed7d3
--- /dev/null
+++ b/app/dashboard/invoices/page.js
@@ -0,0 +1,33 @@
+// /app/dashboard/invoices/page.js
+import Pagination from "@/app/ui/invoices/pagination";
+import Search from "@/app/ui/search";
+import Table from "@/app/ui/invoices/table";
+import { CreateInvoice } from "@/app/ui/invoices/buttons";
+import { lusitana } from "@/app/ui/fonts";
+import { Suspense } from "react";
+import { InvoicesTableSkeleton } from "@/app/ui/skeletons";
+import { fetchInvoicesPages } from '@/app/lib/data';
+
+export default async function Page({ searchParams }) {
+ const query = searchParams?.query || "";
+ const currentPage = Number(searchParams?.page) || 1;
+ const totalPages = await fetchInvoicesPages(query);
+
+ return (
+
+
+
Invoices
+
+
+
+
+
+ }>
+
+
+
+
+
+
+ );
+}
diff --git a/app/dashboard/layout.js b/app/dashboard/layout.js
new file mode 100644
index 0000000..547dc44
--- /dev/null
+++ b/app/dashboard/layout.js
@@ -0,0 +1,13 @@
+import SideNav from '@/app/ui/dashboard/sidenav';
+export const experimental_ppr = true;
+
+export default function Layout({ children }) {
+ return (
+
+
+
+
+
{children}
+
+ );
+}
\ No newline at end of file
diff --git a/app/layout.js b/app/layout.js
index 23ec8e7..7d878a3 100644
--- a/app/layout.js
+++ b/app/layout.js
@@ -1,7 +1,9 @@
+import "@/app/ui/global.css";
+import { lusitana } from "@/app/ui/fonts";
export default function RootLayout({ children }) {
return (
- {children}
+ {children}
);
-}
+}
\ No newline at end of file
diff --git a/app/lib/actions.js b/app/lib/actions.js
new file mode 100644
index 0000000..e2fcd48
--- /dev/null
+++ b/app/lib/actions.js
@@ -0,0 +1,60 @@
+// /app/lib/actions.js
+"use server";
+
+
+import { z } from "zod";
+import { sql } from "@vercel/postgres";
+import { revalidatePath } from "next/cache";
+import { redirect } from "next/navigation";
+
+const FormSchema = z.object({
+ id: z.string(),
+ customerId: z.string(),
+ amount: z.coerce.number(),
+ status: z.enum(["pending", "paid"]),
+ date: z.string(),
+});
+
+const CreateInvoice = FormSchema.omit({ id: true, date: true });
+const UpdateInvoice = FormSchema.omit({ id: true, date: true });
+
+export async function createInvoice(formData) {
+ const { customerId, amount, status } = CreateInvoice.parse({
+ customerId: formData.get("customerId"),
+ amount: formData.get("amount"),
+ status: formData.get("status"),
+ });
+ const amountInCents = amount * 100;
+ const date = new Date().toISOString().split("T")[0];
+
+ await sql`
+ INSERT INTO invoices (customer_id, amount, status, date)
+ VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
+ `;
+ revalidatePath("/dashboard/invoices");
+ redirect("/dashboard/invoices");
+}
+
+export async function updateInvoice(id, formData) {
+ const { customerId, amount, status } = UpdateInvoice.parse({
+ customerId: formData.get("customerId"),
+ amount: formData.get("amount"),
+ status: formData.get("status"),
+ });
+
+ const amountInCents = amount * 100;
+
+ await sql`
+ UPDATE invoices
+ SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
+ WHERE id = ${id}
+ `;
+
+ revalidatePath("/dashboard/invoices");
+ redirect("/dashboard/invoices");
+}
+
+export async function deleteInvoice(id) {
+ await sql`DELETE FROM invoices WHERE id = ${id}`;
+ revalidatePath("/dashboard/invoices");
+}
diff --git a/app/lib/data.js b/app/lib/data.js
index b131a39..68e4028 100644
--- a/app/lib/data.js
+++ b/app/lib/data.js
@@ -6,12 +6,12 @@ export async function fetchRevenue() {
// Artificially delay a response for demo purposes.
// Don't do this in production :)
- // console.log('Fetching revenue data...');
- // await new Promise((resolve) => setTimeout(resolve, 3000));
+ console.log('Fetching revenue data...');
+ await new Promise((resolve) => setTimeout(resolve, 3000));
const data = await sql`SELECT * FROM revenue`;
- // console.log('Data fetch completed after 3 seconds.');
+ console.log('Data fetch completed after 3 seconds.');
return data.rows;
} catch (error) {
diff --git a/app/lib/placeholder-data.js b/app/lib/placeholder-data.js
index 4b1dafb..75cda4b 100644
--- a/app/lib/placeholder-data.js
+++ b/app/lib/placeholder-data.js
@@ -49,7 +49,8 @@ const customers = [
},
];
-const invoices = [
+const invoices =
+[
{
customer_id: customers[0].id,
amount: 15795,
diff --git a/app/page.js b/app/page.js
index 50676d7..efb25bd 100644
--- a/app/page.js
+++ b/app/page.js
@@ -1,17 +1,19 @@
-import AcmeLogo from '@/app/ui/acme-logo';
-import { ArrowRightIcon } from '@heroicons/react/24/outline';
-import Link from 'next/link';
+import AcmeLogo from "@/app/ui/acme-logo";
+import { ArrowRightIcon } from "@heroicons/react/24/outline";
+import Link from "next/link";
+import Image from "next/image";
export default function Page() {
return (
+
{/* */}
- Welcome to Acme. This is the example for the{' '}
+ Welcome to Acme. This is the example for the{" "}
Next.js Learn Course
@@ -25,9 +27,16 @@ export default function Page() {
- {/* Add Hero Images Here */}
+ {/* Add Hero Images Here */}
+
);
-}
+}
\ No newline at end of file
diff --git a/app/seed/route.js b/app/seed/route.js
index 287456a..24be185 100644
--- a/app/seed/route.js
+++ b/app/seed/route.js
@@ -1,8 +1,8 @@
-// import bcrypt from 'bcrypt';
-// import { db } from '@vercel/postgres';
-// import { invoices, customers, revenue, users } from '../lib/placeholder-data';
+ import bcrypt from 'bcrypt';
+ import { db } from '@vercel/postgres';
+ import { invoices, customers, revenue, users } from '../lib/placeholder-data';
-// const client = await db.connect();
+ const client = await db.connect();
async function seedUsers() {
await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
@@ -102,24 +102,18 @@ async function seedRevenue() {
}
export async function GET() {
- return new Response(
- JSON.stringify({
- message:
- 'Uncomment this file and remove this line. You can delete this file when you are finished.',
- }),
- { status: 200, headers: { 'Content-Type': 'application/json' } }
- );
- // try {
- // await client.sql`BEGIN`;
- // await seedUsers();
- // await seedCustomers();
- // await seedInvoices();
- // await seedRevenue();
- // await client.sql`COMMIT`;
-
- // return new Response(JSON.stringify({ message: 'Database seeded successfully' }), { status: 200 });
- // } catch (error) {
- // await client.sql`ROLLBACK`;
- // return new Response(JSON.stringify({ error }), { status: 500 });
- // }
+
+ try {
+ await client.sql`BEGIN`;
+ await seedUsers();
+ await seedCustomers();
+ await seedInvoices();
+ await seedRevenue();
+ await client.sql`COMMIT`;
+
+ return new Response(JSON.stringify({ message: 'Database seeded successfully' }), { status: 200 });
+ } catch (error) {
+ await client.sql`ROLLBACK`;
+ return new Response(JSON.stringify({ error }), { status: 500 });
+ }
}
diff --git a/app/ui/dashboard/cards.js b/app/ui/dashboard/cards.js
index d8d3ee7..77e6b40 100644
--- a/app/ui/dashboard/cards.js
+++ b/app/ui/dashboard/cards.js
@@ -5,6 +5,7 @@ import {
InboxIcon,
} from '@heroicons/react/24/outline';
import { lusitana } from '@/app/ui/fonts';
+import { fetchCardData } from "@/app/lib/data";
const iconMap = {
collected: BanknotesIcon,
@@ -14,17 +15,24 @@ const iconMap = {
};
export default async function CardWrapper() {
+ const {
+ numberOfInvoices,
+ numberOfCustomers,
+ totalPaidInvoices,
+ totalPendingInvoices,
+ } = await fetchCardData();
+
return (
<>
{/* NOTE: Uncomment this code in Chapter 9 */}
- {/*
+ */}
+ />
>
);
}
diff --git a/app/ui/dashboard/latest-invoices.js b/app/ui/dashboard/latest-invoices.js
index df40e9f..1cad7bd 100644
--- a/app/ui/dashboard/latest-invoices.js
+++ b/app/ui/dashboard/latest-invoices.js
@@ -1,10 +1,11 @@
-
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import Image from 'next/image';
import { lusitana } from '@/app/ui/fonts';
+import { fetchLatestInvoices } from '@/app/lib/data';
-export default async function LatestInvoices({ latestInvoices }) {
+export default async function LatestInvoices() {
+ const latestInvoices = await fetchLatestInvoices();
return (
{/* NOTE: Uncomment this code in Chapter 7 */}
- {/*
+ {
{latestInvoices.map((invoice, i) => {
return (
);
})}
-
*/}
+
}
Updated just now
diff --git a/app/ui/dashboard/nav-links.js b/app/ui/dashboard/nav-links.js
index 72fa462..8a5d77a 100644
--- a/app/ui/dashboard/nav-links.js
+++ b/app/ui/dashboard/nav-links.js
@@ -1,8 +1,12 @@
+"use client";
import {
UserGroupIcon,
HomeIcon,
DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
// Map of links to display in the side navigation.
// Depending on the size of the application, this would be stored in a database.
@@ -17,19 +21,20 @@ const links = [
];
export default function NavLinks() {
+ const pathname = usePathname();
return (
<>
{links.map((link) => {
const LinkIcon = link.icon;
return (
-