Selasa, 05 Mei 2026

React - Deploy Aplikasi

 


Panduan Deploy Aplikasi Marketplace Siswa

Dokumen ini menjelaskan langkah-langkah untuk mempublikasikan (deploy) aplikasi React Anda ke internet agar dapat diakses secara publik.

1. Persiapan Awal

Pastikan Anda sudah memiliki lingkungan pengembangan lokal yang siap:

  • Node.js terinstall di komputer.

  • Git terinstall untuk mengelola kode.

  • Akun di GitHub, Vercel, atau Netlify.

2. Membuat Proyek React Lokal

Jika Anda belum memiliki struktur proyek yang lengkap, gunakan Vite (rekomendasi terbaru):

  1. Buka terminal/CMD dan jalankan:

    npm create vite@latest marketplace-siswa -- --template react
    cd marketplace-siswa
    npm install
    
  2. Install dependensi yang dibutuhkan oleh aplikasi:

    npm install lucide-react
    npm install -D tailwindcss border-color postcss autoprefixer
    npx tailwindcss init -p
    
  3. Salin kode dari file marketplace_siswa.jsx ke dalam src/App.jsx di proyek baru Anda.

3. Konfigurasi Tailwind CSS

Agar tampilan sesuai, pastikan file tailwind.config.js Anda diatur seperti ini:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Dan tambahkan direktif Tailwind di src/index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

4. Opsi Deployment

Opsi A: Menggunakan Vercel (Paling Mudah)

  1. Buat repositori baru di GitHub dan unggah (push) kode Anda ke sana.

  2. Buka Vercel.

  3. Klik "Add New" > "Project".

  4. Hubungkan akun GitHub Anda dan pilih repositori marketplace-siswa.

  5. Klik "Deploy". Vercel akan mendeteksi Vite secara otomatis.

Opsi B: Menggunakan Netlify

  1. Buka Netlify.

  2. Login dan pilih "Add new site" > "Import from existing project".

  3. Pilih GitHub dan tentukan repositorinya.

  4. Pastikan Build Command adalah npm run build dan Publish directory adalah dist.

  5. Klik "Deploy site".

Opsi C: Drag & Drop (Tanpa GitHub)

  1. Jalankan perintah build di terminal lokal Anda:

    npm run build
    
  2. Folder bernama dist akan muncul di proyek Anda.

  3. Buka Netlify, masuk ke menu Sites, lalu tarik dan lepas (drag & drop) folder dist tersebut ke area yang disediakan.

5. Tips Tambahan

  • Custom Domain: Baik Vercel maupun Netlify memberikan subdomain gratis (contoh: marketplace-siswa.vercel.app). Anda bisa menghubungkan domain .com atau .id milik sendiri di menu pengaturan.

  • Update Kode: Jika Anda menggunakan metode GitHub, setiap kali Anda melakukan git push, situs Anda akan diperbarui secara otomatis.

Dibuat untuk mempermudah siswa dalam mempublikasikan karya mereka.

import React, { useState, useEffect } from 'react';
import {
  Home,
  User,
  Plus,
  Package,
  ChevronLeft,
  Loader2,
  CheckCircle2,
  Tag,
  FileText
} from 'lucide-react';

// Mock data awal untuk produk
const INITIAL_PRODUCTS = [
  { id: 1, name: 'Brownies Lumer', price: 15000, description: 'Cokelat melimpah, cocok untuk camilan sore.', category: 'Makanan' },
  { id: 2, name: 'Kaos Kelas Custom', price: 85000, description: 'Bahan katun combed 30s, adem dan nyaman.', category: 'Fashion' },
  { id: 3, name: 'Jasa Desain Poster', price: 25000, description: 'Desain kreatif untuk tugas atau acara.', category: 'Jasa' },
];

const App = () => {
  // State Management
  const [view, setView] = useState('home'); // 'home', 'add', 'profile'
  const [products, setProducts] = useState(INITIAL_PRODUCTS);
  const [loading, setLoading] = useState(false);
  const [snackbar, setSnackbar] = useState({ show: false, message: '' });
 
  // Form State
  const [formData, setFormData] = useState({ name: '', price: '', description: '' });

  // Fungsi untuk menampilkan snackbar secara otomatis menghilang
  const showSnackbar = (message) => {
    setSnackbar({ show: true, message });
    setTimeout(() => setSnackbar({ show: false, message: '' }), 3000);
  };

  // Handler Simpan Produk
  const handleAddProduct = (e) => {
    e.preventDefault();
    if (!formData.name || !formData.price || !formData.description) return;

    setLoading(true);
   
    // Simulasi loading/proses simpan
    setTimeout(() => {
      const newProduct = {
        id: Date.now(),
        name: formData.name,
        price: parseInt(formData.price),
        description: formData.description,
        category: 'Umum'
      };
     
      setProducts([newProduct, ...products]);
      setLoading(false);
      setFormData({ name: '', price: '', description: '' });
      setView('home');
      showSnackbar('Produk berhasil ditambahkan!');
    }, 1000);
  };

  // Komponen Navigasi Bawah
  const BottomNav = () => (
    <div className="fixed bottom-0 left-0 right-0 bg-surface-container border-t border-outline-variant flex justify-around items-center h-20 pb-4 px-4 bg-white shadow-[0_-1px_3px_rgba(0,0,0,0.1)]">
      <button
        onClick={() => setView('home')}
        className={`flex flex-col items-center gap-1 transition-colors ${view === 'home' ? 'text-primary' : 'text-outline'}`}
      >
        <div className={`p-2 rounded-full ${view === 'home' ? 'bg-primary-container text-primary' : ''}`}>
          <Home size={24} fill={view === 'home' ? 'currentColor' : 'none'} />
        </div>
        <span className="text-xs font-medium">Beranda</span>
      </button>
     
      <button
        onClick={() => setView('profile')}
        className={`flex flex-col items-center gap-1 transition-colors ${view === 'profile' ? 'text-primary' : 'text-outline'}`}
      >
        <div className={`p-2 rounded-full ${view === 'profile' ? 'bg-primary-container text-primary' : ''}`}>
          <User size={24} fill={view === 'profile' ? 'currentColor' : 'none'} />
        </div>
        <span className="text-xs font-medium">Profil</span>
      </button>
    </div>
  );

  // Komponen Snackbar
  const GlobalSnackbar = () => (
    <div className={`fixed bottom-24 left-1/2 -translate-x-1/2 z-50 transition-all duration-300 transform ${snackbar.show ? 'translate-y-0 opacity-100' : 'translate-y-10 opacity-0 pointer-events-none'}`}>
      <div className="bg-inverse-surface text-inverse-on-surface px-6 py-3 rounded-xl shadow-lg flex items-center gap-3 min-w-[300px]">
        <CheckCircle2 size={20} className="text-green-400" />
        <span className="text-sm font-medium">{snackbar.message}</span>
      </div>
    </div>
  );

  return (
    <div className="min-h-screen bg-slate-50 font-sans text-slate-900 pb-24">
     
      {/* HEADER */}
      <header className="sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-slate-100 px-6 h-16 flex items-center justify-between">
        {view === 'add' ? (
          <button onClick={() => setView('home')} className="p-2 -ml-2 hover:bg-slate-100 rounded-full transition-colors">
            <ChevronLeft size={24} />
          </button>
        ) : (
          <div className="flex items-center gap-2">
            <div className="bg-indigo-600 p-1.5 rounded-lg">
              <Package size={20} className="text-white" />
            </div>
            <h1 className="text-xl font-bold tracking-tight text-indigo-950">MarketSiswa</h1>
          </div>
        )}
        <div className="w-8 h-8 rounded-full bg-slate-200 overflow-hidden border border-slate-300">
           {/* Placeholder Avatar */}
           <div className="w-full h-full flex items-center justify-center text-[10px] font-bold text-slate-500">JS</div>
        </div>
      </header>

      <main className="max-w-3xl mx-auto p-6">
       
        {/* VIEW: HOME */}
        {view === 'home' && (
          <div className="space-y-6">
            <div className="flex flex-col gap-1">
              <h2 className="text-2xl font-bold text-slate-900">Halo, Siswa! 👋</h2>
              <p className="text-slate-500 text-sm">Temukan produk kreatif dari teman-temanmu.</p>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              {products.map((product) => (
                <div key={product.id} className="bg-white p-4 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-shadow">
                  <div className="flex justify-between items-start mb-2">
                    <span className="px-2 py-0.5 bg-indigo-50 text-indigo-600 text-[10px] font-bold uppercase tracking-wider rounded-md">
                      {product.category}
                    </span>
                    <h3 className="text-lg font-bold text-slate-900">
                      Rp {product.price.toLocaleString('id-ID')}
                    </h3>
                  </div>
                  <h4 className="font-semibold text-slate-800 mb-1">{product.name}</h4>
                  <p className="text-slate-500 text-sm line-clamp-2 leading-relaxed">
                    {product.description}
                  </p>
                  <button className="mt-4 w-full py-2 bg-slate-50 text-slate-700 font-semibold text-sm rounded-xl hover:bg-slate-100 transition-colors border border-slate-200">
                    Lihat Detail
                  </button>
                </div>
              ))}
            </div>

            {/* FAB - Floating Action Button */}
            <button
              onClick={() => setView('add')}
              className="fixed bottom-28 right-6 w-14 h-14 bg-indigo-600 text-white rounded-2xl shadow-xl shadow-indigo-200 flex items-center justify-center hover:scale-110 active:scale-95 transition-all z-40"
            >
              <Plus size={32} strokeWidth={2.5} />
            </button>
          </div>
        )}

        {/* VIEW: ADD PRODUCT */}
        {view === 'add' && (
          <div className="max-w-md mx-auto space-y-6">
            <div className="text-center space-y-2">
              <h2 className="text-2xl font-bold text-slate-900">Tambah Produk Baru</h2>
              <p className="text-slate-500 text-sm">Bagikan karyamu ke seluruh sekolah</p>
            </div>

            <form onSubmit={handleAddProduct} className="space-y-5">
              {/* Input Nama */}
              <div className="space-y-1.5">
                <label className="text-sm font-semibold text-slate-700 ml-1 flex items-center gap-2">
                  <Tag size={16} /> Nama Produk
                </label>
                <input
                  type="text"
                  value={formData.name}
                  onChange={(e) => setFormData({...formData, name: e.target.value})}
                  placeholder="Contoh: Pisang Goreng Keju"
                  className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-50/50 outline-none transition-all"
                  required
                />
              </div>

              {/* Input Harga */}
              <div className="space-y-1.5">
                <label className="text-sm font-semibold text-slate-700 ml-1 flex items-center gap-2">
                  <span className="font-bold">Rp</span> Harga Produk
                </label>
                <input
                  type="number"
                  value={formData.price}
                  onChange={(e) => setFormData({...formData, price: e.target.value})}
                  placeholder="Masukkan nominal harga"
                  className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-50/50 outline-none transition-all"
                  required
                />
              </div>

              {/* Input Deskripsi */}
              <div className="space-y-1.5">
                <label className="text-sm font-semibold text-slate-700 ml-1 flex items-center gap-2">
                  <FileText size={16} /> Deskripsi Produk
                </label>
                <textarea
                  rows="4"
                  value={formData.description}
                  onChange={(e) => setFormData({...formData, description: e.target.value})}
                  placeholder="Ceritakan kelebihan produkmu..."
                  className="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-indigo-500 focus:ring-4 focus:ring-indigo-50/50 outline-none transition-all resize-none"
                  required
                ></textarea>
              </div>

              {/* Submit Button */}
              <button
                type="submit"
                disabled={loading}
                className={`w-full py-4 rounded-2xl bg-indigo-600 text-white font-bold text-lg shadow-lg shadow-indigo-100 hover:bg-indigo-700 active:scale-[0.98] transition-all flex items-center justify-center gap-3 ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
              >
                {loading ? (
                  <>
                    <Loader2 className="animate-spin" size={20} />
                    Menyimpan...
                  </>
                ) : (
                  'Simpan Produk'
                )}
              </button>
             
              <button
                type="button"
                onClick={() => setView('home')}
                className="w-full py-3 rounded-2xl bg-white text-slate-500 font-semibold text-sm hover:text-slate-700 transition-colors"
              >
                Batal
              </button>
            </form>
          </div>
        )}

        {/* VIEW: PROFILE */}
        {view === 'profile' && (
          <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
            <div className="flex flex-col items-center text-center space-y-4">
              <div className="w-24 h-24 rounded-3xl bg-indigo-100 flex items-center justify-center border-4 border-white shadow-md">
                <User size={48} className="text-indigo-600" />
              </div>
              <div>
                <h2 className="text-2xl font-bold text-slate-900">John Siswa</h2>
                <p className="text-slate-500">Kelas XII - Teknik Komputer</p>
              </div>
            </div>

            <div className="bg-white rounded-3xl p-2 border border-slate-100 shadow-sm">
              <div className="p-4 flex items-center justify-between border-b border-slate-50">
                <div className="flex items-center gap-3">
                  <div className="p-2 bg-blue-50 text-blue-600 rounded-lg"><Package size={20}/></div>
                  <span className="font-semibold">Produk Saya</span>
                </div>
                <span className="bg-slate-100 px-3 py-1 rounded-full text-xs font-bold text-slate-600">
                  {products.length}
                </span>
              </div>
              <div className="p-4 flex items-center justify-between">
                <div className="flex items-center gap-3">
                  <div className="p-2 bg-orange-50 text-orange-600 rounded-lg"><Tag size={20}/></div>
                  <span className="font-semibold">Pengaturan Toko</span>
                </div>
                <ChevronLeft size={20} className="rotate-180 text-slate-300" />
              </div>
            </div>

            <button className="w-full py-4 text-red-500 font-bold bg-red-50 rounded-2xl hover:bg-red-100 transition-colors">
              Keluar Akun
            </button>
          </div>
        )}

      </main>

      <GlobalSnackbar />
      <BottomNav />
    </div>
  );
};

export default App;

Tidak ada komentar:

Posting Komentar

React - Deploy

  https://www.youtube.com/watch?v=Ey_90l9GaAw https://www.youtube.com/watch?v=G6D9cBaLViA https://www.youtube.com/watch?v=hn1IkJk24ow https:...