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

Apr 11, 2025 - 00:28
 0
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 )} />
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

Tambah Menu Baru
{menus.map((menu) => (

{menu.nama_menu}

Rp {menu.harga.toLocaleString()}

{menu.nama_menu}
))}
); }

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

Tambah Meja Baru
{mejas.map((meja) => (
{`Meja
Nomor Meja: {meja.no_meja}

Kapasitas: {meja.kapasitas} orang

))}
); }