NextJS Consume api
login // pages/login.js import { useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { setToken, isAuthenticated } from "@/utils/auth"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; const formSchema = z.object({ username: z.string().min(1, "Username is required"), password: z.string().min(5, "Password must be at least 5 characters long"), }); export default function LoginPage({ serverError }) { const router = useRouter(); const [errorMessage, setErrorMessage] = useState(serverError || ""); const [isLoading, setIsLoading] = useState(false); const form = useForm({ defaultValues: { username: "", password: "", }, resolver: zodResolver(formSchema), }); const onSubmit = async (data) => { setErrorMessage(""); setIsLoading(true); try { const response = await fetch("http://localhost:8000/api/login/", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), credentials: 'include', // Penting untuk cookies }); const result = await response.json(); if (!response.ok) { throw new Error(result.message || "Login failed. Please check your credentials."); } // Simpan token di cookie dan localStorage setToken(result.access); router.push("/menu"); } catch (error) { setErrorMessage(error.message); } finally { setIsLoading(false); } }; return ( Log in to RestoKu {errorMessage && ( {errorMessage} )} ( Username )} /> ( Password )} /> {isLoading ? "Logging in..." : "Login"} Forgot your password? Don't have an account? Create account ); } export async function getServerSideProps(context) { // Cek jika pengguna sudah terautentikasi if (isAuthenticated(context)) { return { redirect: { destination: '/menu', permanent: false, }, }; } const { error } = context.query; return { props: { serverError: error || null, }, }; } utils/auth // utils/auth.js import Cookies from 'js-cookie'; import { parseCookies } from 'nookies'; // Fungsi untuk menyimpan token di cookies dan localStorage (untuk kompatibilitas) export const setToken = (token) => { if (typeof window !== 'undefined') { // Client-side localStorage.setItem('adminToken', token); // Mengatur cookie dengan secure flag, httpOnly untuk produksi Cookies.set('adminToken', token, { expires: 7 }); // Expires in 7 days } }; // Fungsi untuk mendapatkan token export const getToken = (ctx) => { // Server-side if (ctx) { const cookies = parseCookies(ctx); return cookies.adminToken; } // Client-side if (typeof window !== 'undefined') { return Cookies.get('adminToken') || localStorage.getItem('adminToken'); } return null; }; // Fungsi untuk menghapus token saat logout export const removeToken = () => { if (typeof window !== 'undefined') { localStorage.removeItem('adminToken'); Cookies.remove('adminToken'); } }; // Fungsi untuk memeriksa apakah pengguna sudah terautentikasi export const isAuthenticated = (ctx) => { const token = getToken(ctx); return !!token; }; menu // pages/menu.tsx import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { PlusIcon } from "lucide-react"; import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { getToken, removeToken } from "@/utils/auth"; import { redirect } from 'next/navigation'; import { cookies } from 'next/headers'; interface Menuitem { id_menu: number; nama_menu: string; harga: number; menu_image: string; id_kategori: number; } async function getMenuData() { const token = getToken({ req: { headers: { cookie: cookies().toString() } }); if (!token) { redirect('/l

login
// pages/login.js
import { useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { setToken, isAuthenticated } from "@/utils/auth";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
const formSchema = z.object({
username: z.string().min(1, "Username is required"),
password: z.string().min(5, "Password must be at least 5 characters long"),
});
export default function LoginPage({ serverError }) {
const router = useRouter();
const [errorMessage, setErrorMessage] = useState(serverError || "");
const [isLoading, setIsLoading] = useState(false);
const form = useForm({
defaultValues: {
username: "",
password: "",
},
resolver: zodResolver(formSchema),
});
const onSubmit = async (data) => {
setErrorMessage("");
setIsLoading(true);
try {
const response = await fetch("http://localhost:8000/api/login/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
credentials: 'include', // Penting untuk cookies
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || "Login failed. Please check your credentials.");
}
// Simpan token di cookie dan localStorage
setToken(result.access);
router.push("/menu");
} catch (error) {
setErrorMessage(error.message);
} finally {
setIsLoading(false);
}
};
return (
Log in to RestoKu
{errorMessage && (
{errorMessage}
)}
Forgot your password?
Don't have an account?
Create account
);
}
export async function getServerSideProps(context) {
// Cek jika pengguna sudah terautentikasi
if (isAuthenticated(context)) {
return {
redirect: {
destination: '/menu',
permanent: false,
},
};
}
const { error } = context.query;
return {
props: {
serverError: error || null,
},
};
}
utils/auth
// utils/auth.js
import Cookies from 'js-cookie';
import { parseCookies } from 'nookies';
// Fungsi untuk menyimpan token di cookies dan localStorage (untuk kompatibilitas)
export const setToken = (token) => {
if (typeof window !== 'undefined') {
// Client-side
localStorage.setItem('adminToken', token);
// Mengatur cookie dengan secure flag, httpOnly untuk produksi
Cookies.set('adminToken', token, { expires: 7 }); // Expires in 7 days
}
};
// Fungsi untuk mendapatkan token
export const getToken = (ctx) => {
// Server-side
if (ctx) {
const cookies = parseCookies(ctx);
return cookies.adminToken;
}
// Client-side
if (typeof window !== 'undefined') {
return Cookies.get('adminToken') || localStorage.getItem('adminToken');
}
return null;
};
// Fungsi untuk menghapus token saat logout
export const removeToken = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('adminToken');
Cookies.remove('adminToken');
}
};
// Fungsi untuk memeriksa apakah pengguna sudah terautentikasi
export const isAuthenticated = (ctx) => {
const token = getToken(ctx);
return !!token;
};
menu
// pages/menu.tsx
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { PlusIcon } from "lucide-react";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { getToken, removeToken } from "@/utils/auth";
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
interface Menuitem {
id_menu: number;
nama_menu: string;
harga: number;
menu_image: string;
id_kategori: number;
}
async function getMenuData() {
const token = getToken({ req: { headers: { cookie: cookies().toString() } });
if (!token) {
redirect('/login');
}
try {
const response = await fetch("http://127.0.0.1:8000/api/menu/", {
headers: {
Authorization: `Bearer ${token}`,
},
cache: 'no-store'
});
if (response.status === 401) {
removeToken();
redirect('/login');
}
if (!response.ok) {
throw new Error('Failed to fetch menu data');
}
return await response.json();
} catch (error) {
console.error("Error fetching menu data:", error);
return [];
}
}
export default async function MenuPage() {
const menus: Menuitem[] = await getMenuData();
async function handleAddMenu(formData: FormData) {
'use server';
const token = getToken({ req: { headers: { cookie: cookies().toString() } });
if (!token) {
redirect('/login');
}
const namaMenu = formData.get('nama_menu') as string;
const harga = formData.get('harga') as string;
const kategori = formData.get('id_kategori') as string;
const imageFile = formData.get('menu_image') as File;
const postData = new FormData();
postData.append('nama_menu', namaMenu);
postData.append('harga', harga);
postData.append('id_kategori', kategori);
if (imageFile.size > 0) {
postData.append('menu_image', imageFile);
}
try {
const response = await fetch("http://127.0.0.1:8000/api/menu/", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: postData,
});
if (response.status === 401) {
removeToken();
redirect('/login');
}
if (!response.ok) {
throw new Error('Failed to add menu');
}
redirect('/menu');
} catch (error) {
console.error("Error adding menu:", error);
throw error;
}
}
return (
Daftar Menu
{menus.map((menu) => (
{menu.nama_menu}
Rp {menu.harga.toLocaleString()}
))}
);
}
meja
// app/features-02/page.tsx
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PlusIcon } from "lucide-react";
import { getToken, removeToken } from "@/utils/auth";
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
interface MejaItem {
id_meja: number;
no_meja: number;
kapasitas: number;
image_meja: string;
}
async function getMejaData() {
const token = getToken({ req: { headers: { cookie: cookies().toString() } });
if (!token) {
redirect('/login');
}
try {
const response = await fetch("http://127.0.0.1:8000/api/meja/", {
headers: {
Authorization: `Bearer ${token}`,
},
cache: 'no-store'
});
if (response.status === 401) {
removeToken();
redirect('/login');
}
if (!response.ok) {
throw new Error('Failed to fetch table data');
}
return await response.json();
} catch (error) {
console.error("Error fetching table data:", error);
return [];
}
}
export default async function Features02Page() {
const mejas: MejaItem[] = await getMejaData();
async function handleAddTable(formData: FormData) {
'use server';
const token = getToken({ req: { headers: { cookie: cookies().toString() } });
if (!token) {
redirect('/login');
}
const no_meja = formData.get('no_meja') as string;
const kapasitas = formData.get('kapasitas') as string;
const imageFile = formData.get('image_meja') as File;
const postData = new FormData();
postData.append('no_meja', no_meja);
postData.append('kapasitas', kapasitas);
if (imageFile.size > 0) {
postData.append('image_meja', imageFile);
}
try {
const response = await fetch("http://127.0.0.1:8000/api/meja/", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: postData,
});
if (response.status === 401) {
removeToken();
redirect('/login');
}
if (!response.ok) {
throw new Error('Failed to add table');
}
redirect('/features-02');
} catch (error) {
console.error("Error adding table:", error);
throw error;
}
}
return (
Daftar Meja
{mejas.map((meja) => (
Nomor Meja: {meja.no_meja}
Kapasitas: {meja.kapasitas} orang
))}
);
}