Toko Kue: Halaman Login & Registrasi (Supabase Auth)

Toko Kue: Halaman Login & Registrasi (Supabase Auth)

Bikin pintu masuk untuk pelanggan Toko Kue-mu! Pelajari cara membangun komponen dan halaman untuk registrasi (sign up) dan login (sign in) pengguna menggunakan fitur Supabase Auth dan Supabase JS Client di aplikasi Next.js.

Buka Pintu Toko Kue: Bikin Halaman Login & Registrasi Pake Supabase Auth!

"Dapur" Supabase kita udah punya "satpam" (Supabase Auth) yang siap jaga. Sekarang, saatnya kita bikin "pintu masuk" dan "formulir pendaftaran" buat pelanggan Toko Kue kita di frontend Next.js!

Kita bakal bikin dua halaman utama:

  1. Halaman Registrasi (Sign Up): Tempat pengguna baru bisa bikin akun pake email dan password.
  2. Halaman Login (Sign In): Tempat pengguna yang udah punya akun bisa masuk.

Kita bakal pake Supabase JS Client (@supabase/supabase-js) buat ngobrol sama Supabase Auth.

Langkah 1: Menyiapkan Halaman dan Komponen Form Dasar

Pertama, kita bikin dulu struktur halaman dan komponen form yang bakal kita pake.

a. Halaman Registrasi (src/app/auth/signup/page.tsx)

  1. Di dalem src/app/, bikin folder auth.
  2. Di dalem src/app/auth/, bikin folder signup.
  3. Di dalem src/app/auth/signup/, bikin file page.tsx.

File src/app/auth/signup/page.tsx (Kerangka Awal):

tsxtsx
// src/app/auth/signup/page.tsx
"use client"; // Karena ini akan jadi form interaktif
 
import React, { useState, FormEvent } from 'react';
import { supabase } from '@/lib/supabase-client'; // Client Supabase kita
import { useRouter } from 'next/navigation';
import Link from 'next/link';
 
export default function SignUpPage() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [message, setMessage] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const router = useRouter();
 
  const handleSignUp = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setError(null);
    setMessage(null);
 
    if (password !== confirmPassword) {
      setError("Password dan konfirmasi password tidak cocok!");
      return;
    }
    if (password.length < 6) {
      setError("Password minimal harus 6 karakter.");
      return;
    }
 
    setLoading(true);
    // Logika Sign Up Supabase akan kita tambahkan di sini
    // ...
    setLoading(false);
  };
 
  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2 bg-gray-50">
      <div className="w-full max-w-md p-8 space-y-6 bg-white rounded-xl shadow-lg">
        <h2 className="text-3xl font-bold text-center text-gray-800">Buat Akun Baru</h2>
        <form onSubmit={handleSignUp} className="space-y-6">
          <div>
            <label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
            <input id="email" name="email" type="email" autoComplete="email" required
                   value={email} onChange={(e) => setEmail(e.target.value)}
                   className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
          </div>
          <div>
            <label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
            <input id="password" name="password" type="password" autoComplete="new-password" required
                   value={password} onChange={(e) => setPassword(e.target.value)}
                   className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
          </div>
          <div>
            <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">Konfirmasi Password</label>
            <input id="confirmPassword" name="confirmPassword" type="password" autoComplete="new-password" required
                   value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)}
                   className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
          </div>
          {error && <p className="text-sm text-red-600">{error}</p>}
          {message && <p className="text-sm text-green-600">{message}</p>}
          <div>
            <button type="submit" disabled={loading}
                    className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50">
              {loading ? 'Mendaftar...' : 'Daftar Sekarang'}
            </button>
          </div>
        </form>
        <p className="text-sm text-center text-gray-600">
          Sudah punya akun?{' '}
          <Link href="/auth/signin" className="font-medium text-indigo-600 hover:text-indigo-500">
            Masuk di sini
          </Link>
        </p>
      </div>
    </div>
  );
}

b. Halaman Login (src/app/auth/signin/page.tsx)

  1. Di dalem src/app/auth/, bikin folder signin.
  2. Di dalem src/app/auth/signin/, bikin file page.tsx.

File src/app/auth/signin/page.tsx (Kerangka Awal):

tsxtsx
// src/app/auth/signin/page.tsx
"use client";
 
import React, { useState, FormEvent } from 'react';
import { supabase } from '@/lib/supabase-client';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
 
export default function SignInPage() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const router = useRouter();
 
  const handleSignIn = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setError(null);
    setLoading(true);
    // Logika Sign In Supabase akan kita tambahkan di sini
    // ...
    setLoading(false);
  };
 
  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2 bg-gray-50">
      <div className="w-full max-w-md p-8 space-y-6 bg-white rounded-xl shadow-lg">
        <h2 className="text-3xl font-bold text-center text-gray-800">Masuk ke Akun Anda</h2>
        <form onSubmit={handleSignIn} className="space-y-6">
          <div>
            <label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
            <input id="email" name="email" type="email" autoComplete="email" required
                   value={email} onChange={(e) => setEmail(e.target.value)}
                   className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
          </div>
          <div>
            <label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
            <input id="password" name="password" type="password" autoComplete="current-password" required
                   value={password} onChange={(e) => setPassword(e.target.value)}
                   className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
          </div>
          {error && <p className="text-sm text-red-600">{error}</p>}
          <div>
            <button type="submit" disabled={loading}
                    className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50">
              {loading ? 'Masuk...' : 'Masuk'}
            </button>
          </div>
        </form>
        <p className="text-sm text-center text-gray-600">
          Belum punya akun?{' '}
          <Link href="/auth/signup" className="font-medium text-indigo-600 hover:text-indigo-500">
            Daftar di sini
          </Link>
        </p>
        {/* Opsional: Link Lupa Password */}
      </div>
    </div>
  );
}

Kedua halaman ini mirip, bedanya di logika handleSignUp dan handleSignIn serta jumlah field. Kita pake state buat ngontrol input, dan ada state buat error, message (buat signup), dan loading.

Langkah 2: Implementasi Logika Supabase Auth

Sekarang, kita isi bagian yang kosong di fungsi handleSignUp dan handleSignIn.

a. Logika handleSignUp di SignUpPage

tsxtsx
// Di dalam SignUpPage komponen, fungsi handleSignUp:
  const handleSignUp = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setError(null);
    setMessage(null);
    setLoading(true);
 
    if (password !== confirmPassword) {
      setError("Password dan konfirmasi password tidak cocok!");
      setLoading(false);
      return;
    }
    if (password.length < 6) { // Supabase punya aturan minimal password (default 6)
      setError("Password minimal harus 6 karakter.");
      setLoading(false);
      return;
    }
 
    // Panggil Supabase Auth untuk sign up
    const { data, error: signUpError } = await supabase.auth.signUp({
      email: email,
      password: password,
      // Kamu bisa nambahin data tambahan (metadata) di sini kalau mau:
      // options: {
      //   data: {
      //     nama_lengkap: 'Budi Awal', // Ini akan masuk ke user_metadata di Supabase
      //   }
      // }
    });
 
    setLoading(false);
 
    if (signUpError) {
      setError(signUpError.message);
      console.error("Error Sign Up:", signUpError);
    } else if (data.user && data.user.identities && data.user.identities.length === 0) {
      // Kasus user sudah ada tapi belum konfirmasi email (jika konfirmasi email diaktifkan)
      // Atau kasus lain di mana user object ada tapi identities kosong
      setMessage("User mungkin sudah terdaftar tapi belum dikonfirmasi. Silakan cek email Anda atau coba login.");
      // Atau setError("Pengguna sudah ada atau butuh konfirmasi.")
    } else if (data.user) {
      // Cek apakah konfirmasi email diperlukan (dari settingan Supabase Auth-mu)
      if (data.user.email_confirmed_at) {
         setMessage("Pendaftaran berhasil! Anda akan diarahkan ke halaman login.");
         setTimeout(() => router.push('/auth/signin'), 2000); // Arahkan ke login setelah 2 detik
      } else {
         setMessage("Pendaftaran berhasil! Silakan cek email Anda untuk konfirmasi akun sebelum login.");
      }
    } else if (!data.session && !data.user) {
      // Kasus aneh jika user dan session null tapi tidak ada error eksplisit dari signUpError
      // Seringkali ini karena email sudah terdaftar atau konfigurasi lain
      setError("Email mungkin sudah terdaftar atau ada masalah lain. Coba login atau gunakan email lain.");
    }
  };
  • supabase.auth.signUp({ email, password }): Ini fungsi utama buat daftarin pengguna baru.
  • Penanganan Response:
    • Kalau ada signUpError, kita tampilin pesannya.
    • Supabase signUp bisa ngembaliin data.user yang null tapi data.session juga null jika, misalnya, user sudah ada tapi belum terkonfirmasi (jika konfirmasi email aktif).
    • Jika konfirmasi email aktif di Supabase, pengguna baru akan ada di tabel auth.users tapi email_confirmed_at akan null. Kita kasih pesan buat cek email.
    • Jika tidak ada konfirmasi email (atau user langsung terkonfirmasi), kita bisa arahkan ke halaman login atau langsung ke dashboard.

b. Logika handleSignIn di SignInPage

tsxtsx
// Di dalam SignInPage komponen, fungsi handleSignIn:
  const handleSignIn = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setError(null);
    setLoading(true);
 
    // Panggil Supabase Auth untuk sign in
    const { data, error: signInError } = await supabase.auth.signInWithPassword({
      email: email,
      password: password,
    });
 
    setLoading(false);
 
    if (signInError) {
      setError(signInError.message);
      console.error("Error Sign In:", signInError);
    } else if (data.user) {
      // Login berhasil!
      // 'data.session' juga bakal ada di sini
      console.log("Login berhasil:", data.user);
      // Arahkan ke halaman utama atau dashboard setelah login
      router.push('/'); // Ganti dengan path halaman setelah login yang sesuai
      // Kamu mungkin juga mau nge-refresh halaman atau data di app setelah ini
    } else {
        setError("Login gagal. Cek kembali email dan password Anda.");
    }
  };
  • supabase.auth.signInWithPassword({ email, password }): Ini fungsi utama buat login.
  • Penanganan Response:
    • Kalau ada signInError (misal, email/password salah), kita tampilin pesannya.
    • Kalau data.user ada, berarti login berhasil! Kita bisa arahin pengguna ke halaman lain (router.push('/')). Sesi pengguna (session) juga otomatis dibuat dan dikelola sama Supabase JS Client.

Langkah 3: Mengelola Sesi Pengguna & Menampilkan Status Login (Singkat)

Supabase JS Client otomatis ngelola sesi pengguna pake localStorage. Kita bisa cek status login pengguna atau dengerin perubahan status autentikasi. Ini biasanya dilakuin di komponen level atas (kayak RootLayout atau di CartProvider kita kalau mau digabung).

Contoh di RootLayout atau komponen navigasi (misalnya, Navbar.tsx):

tsxtsx
// src/app/components/navbar.tsx (Contoh)
"use client";
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import { supabase } from '@/lib/supabase-client';
import { User } from '@supabase/supabase-js';
import { useRouter } from 'next/navigation';
 
export default function Navbar() {
  const [user, setUser] = useState<User | null>(null);
  const router = useRouter();
 
  useEffect(() => {
    async function checkUser() {
      const { data: { session } } = await supabase.auth.getSession();
      setUser(session?.user ?? null);
    }
    checkUser();
 
    const { data: authListener } = supabase.auth.onAuthStateChange((_event, session) => {
      setUser(session?.user ?? null);
    });
 
    return () => {
      authListener?.unsubscribe();
    };
  }, []);
 
  const handleSignOut = async () => {
    const { error } = await supabase.auth.signOut();
    if (error) console.error('Error signing out:', error);
    else router.push('/auth/signin'); // Arahkan ke halaman login setelah logout
  };
 
  return (
    <nav className="bg-gray-800 p-4 text-white flex justify-between items-center">
      <Link href="/" className="font-bold text-xl">Toko Kue</Link>
      <div>
        {user ? (
          <>
            <span className="mr-4">Halo, {user.email}</span>
            <button onClick={handleSignOut} className="bg-red-500 hover:bg-red-600 px-3 py-1 rounded">
              Logout
            </button>
          </>
        ) : (
          <>
            <Link href="/auth/signin" className="mr-3 hover:underline">Masuk</Link>
            <Link href="/auth/signup" className="bg-green-500 hover:bg-green-600 px-3 py-1 rounded">Daftar</Link>
          </>
        )}
      </div>
    </nav>
  );
}
  • supabase.auth.getSession(): Buat ngecek sesi yang aktif pas komponen dimuat.
  • supabase.auth.onAuthStateChange(...): "Ngedengerin" perubahan status login/logout secara real-time.
  • supabase.auth.signOut(): Buat logout pengguna.

Pastikan kamu pasang komponen Navbar ini di src/app/layout.tsx biar tampil di semua halaman.

Jangan Lupa Settingan Email di Supabase!

  • Kalau kamu ngaktifin konfirmasi email di Supabase Auth, pastikan settingan SMTP email di Supabase udah bener (Dashboard Supabase > Authentication > Settings > Email Templates / SMTP Settings). Supabase nyediain layanan email bawaan buat development, tapi buat produksi mungkin kamu mau pake provider SMTP sendiri.
  • Pengguna baru gak bakal bisa login sampe mereka ngeklik link konfirmasi di email mereka.

Dengan ini, Toko Kue kita sekarang udah punya sistem pendaftaran dan login pengguna pake Supabase Auth! Ini ngebuka banyak banget kemungkinan buat fitur-fitur yang lebih personal dan aman.

Kamu bisa ngembangin lagi, misalnya:

  • Nambahin validasi input form yang lebih canggih.
  • Bikin halaman profil pengguna.
  • Nambahin fitur "Lupa Password".
  • Integrasi sama login OAuth (Google, GitHub).

Yang pasti, Supabase Auth udah ngurusin banyak kerumitan di belakang layar buatmu!

Untuk hasil akhirnya bisa kamu lihat disini Signin Page dan Signup Page

Uji Pemahamanmu!

Memeriksa status login...