update footer
This commit is contained in:
parent
d3a336b5a0
commit
5cf07e2f8e
301
DEPLOY_VERCEL.md
301
DEPLOY_VERCEL.md
|
|
@ -1,301 +0,0 @@
|
|||
# 🚀 Deploy Web Javaherbal ke Vercel
|
||||
|
||||
## ✅ Persiapan Selesai!
|
||||
|
||||
File-file yang sudah disiapkan:
|
||||
- ✅ `.gitignore` - Exclude node_modules, dist, .env
|
||||
- ✅ `vercel.json` - Konfigurasi Vercel
|
||||
- ✅ `package.json` - Script build sudah ada
|
||||
|
||||
---
|
||||
|
||||
## 📋 LANGKAH-LANGKAH DEPLOY
|
||||
|
||||
### 1️⃣ Push ke GitHub
|
||||
|
||||
#### A. Buat Repository Baru di GitHub
|
||||
1. Buka https://github.com
|
||||
2. Klik tombol **"New"** (repository baru)
|
||||
3. Isi:
|
||||
- Repository name: `frondend-web`
|
||||
- Description: `Web dashboard untuk sistem pengeringan cabai jawa`
|
||||
- Visibility: **Public** atau **Private**
|
||||
4. **JANGAN** centang "Add a README file"
|
||||
5. Klik **"Create repository"**
|
||||
|
||||
#### B. Push Code ke GitHub
|
||||
```bash
|
||||
cd javaherbal-v2/javaherbal2
|
||||
|
||||
# Initialize git
|
||||
git init
|
||||
|
||||
# Add all files
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "Initial commit - Web Javaherbal"
|
||||
|
||||
# Add remote
|
||||
git remote add origin https://github.com/flemss/frondend-web.git
|
||||
|
||||
# Push
|
||||
git branch -M main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ Deploy ke Vercel
|
||||
|
||||
#### A. Buka Vercel
|
||||
1. Buka https://vercel.com
|
||||
2. Klik **"Sign Up"** atau **"Login"**
|
||||
3. Login dengan **GitHub**
|
||||
|
||||
#### B. Import Project
|
||||
1. Klik **"Add New..."** → **"Project"**
|
||||
2. Pilih **"Import Git Repository"**
|
||||
3. Cari repository **"frondend-web"**
|
||||
4. Klik **"Import"**
|
||||
|
||||
#### C. Configure Project
|
||||
1. **Project Name:** `frondend-web` (atau nama lain)
|
||||
2. **Framework Preset:** Vercel akan auto-detect **Vite**
|
||||
3. **Root Directory:** `./` (default)
|
||||
4. **Build Command:** `npm run build` (auto-filled)
|
||||
5. **Output Directory:** `dist` (auto-filled)
|
||||
6. **Install Command:** `npm install` (auto-filled)
|
||||
|
||||
#### D. Environment Variables (Opsional)
|
||||
Jika ada environment variables, tambahkan di sini.
|
||||
Untuk saat ini, tidak perlu karena API_URL sudah hardcoded.
|
||||
|
||||
#### E. Deploy!
|
||||
1. Klik **"Deploy"**
|
||||
2. Tunggu 1-2 menit
|
||||
3. **Selesai!** 🎉
|
||||
|
||||
---
|
||||
|
||||
## 🌐 URL VERCEL
|
||||
|
||||
Setelah deploy selesai, Vercel akan memberikan URL seperti:
|
||||
```
|
||||
https://frondend-web.vercel.app
|
||||
```
|
||||
|
||||
atau
|
||||
|
||||
```
|
||||
https://frondend-web-flemss.vercel.app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 TESTING
|
||||
|
||||
### 1. Buka URL Vercel
|
||||
```
|
||||
https://frondend-web.vercel.app
|
||||
```
|
||||
|
||||
### 2. Test Register
|
||||
1. Klik "Daftar di sini"
|
||||
2. Isi form registrasi
|
||||
3. Klik "Daftar"
|
||||
4. **Expected:** Alert "Registrasi berhasil!" → Redirect ke login
|
||||
|
||||
### 3. Test Login
|
||||
1. Isi form login
|
||||
2. Klik "Masuk"
|
||||
3. **Expected:** Redirect ke beranda
|
||||
4. **Expected:** Floating stat suhu muncul
|
||||
|
||||
### 4. Test Control Panel
|
||||
1. Klik menu "Control"
|
||||
2. **Expected:** Panel kontrol muncul
|
||||
3. **Expected:** Data suhu real-time
|
||||
4. **Expected:** Toggle heater berfungsi
|
||||
|
||||
---
|
||||
|
||||
## 🔄 UPDATE WEB
|
||||
|
||||
Setiap kali ada perubahan code:
|
||||
|
||||
```bash
|
||||
cd javaherbal-v2/javaherbal2
|
||||
|
||||
# Add changes
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "Update: deskripsi perubahan"
|
||||
|
||||
# Push
|
||||
git push origin main
|
||||
```
|
||||
|
||||
**Vercel akan otomatis:**
|
||||
- Detect perubahan di GitHub
|
||||
- Build ulang
|
||||
- Deploy versi baru
|
||||
- Zero downtime!
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ CUSTOM DOMAIN (Opsional)
|
||||
|
||||
Jika ingin pakai domain sendiri (contoh: `javaherbal.com`):
|
||||
|
||||
### 1. Beli Domain
|
||||
- Namecheap, GoDaddy, Niagahoster, dll
|
||||
|
||||
### 2. Tambah Domain di Vercel
|
||||
1. Buka project di Vercel dashboard
|
||||
2. Klik tab **"Settings"**
|
||||
3. Klik **"Domains"**
|
||||
4. Klik **"Add"**
|
||||
5. Masukkan domain Anda
|
||||
6. Follow instruksi untuk update DNS
|
||||
|
||||
### 3. Update DNS
|
||||
Di provider domain Anda, tambahkan record:
|
||||
```
|
||||
Type: CNAME
|
||||
Name: www
|
||||
Value: cname.vercel-dns.com
|
||||
```
|
||||
|
||||
Tunggu 5-60 menit untuk propagasi DNS.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 ENVIRONMENT VARIABLES (Jika Diperlukan)
|
||||
|
||||
Jika ingin API_URL bisa diubah tanpa rebuild:
|
||||
|
||||
### 1. Buat file `.env.production`
|
||||
```bash
|
||||
VITE_API_URL=https://web-production-32384.up.railway.app/api
|
||||
```
|
||||
|
||||
### 2. Update code untuk pakai env variable
|
||||
```typescript
|
||||
// Sebelum
|
||||
const API_URL = 'https://web-production-32384.up.railway.app/api'
|
||||
|
||||
// Sesudah
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'https://web-production-32384.up.railway.app/api'
|
||||
```
|
||||
|
||||
### 3. Set di Vercel Dashboard
|
||||
1. Buka project di Vercel
|
||||
2. Klik tab **"Settings"**
|
||||
3. Klik **"Environment Variables"**
|
||||
4. Tambah:
|
||||
- Key: `VITE_API_URL`
|
||||
- Value: `https://web-production-32384.up.railway.app/api`
|
||||
5. Save
|
||||
|
||||
### 4. Redeploy
|
||||
Vercel akan auto redeploy dengan env variable baru.
|
||||
|
||||
---
|
||||
|
||||
## 📊 MONITORING
|
||||
|
||||
### Vercel Dashboard
|
||||
https://vercel.com/dashboard
|
||||
|
||||
**Fitur:**
|
||||
- ✅ Deployment history
|
||||
- ✅ Build logs
|
||||
- ✅ Analytics (traffic, performance)
|
||||
- ✅ Error tracking
|
||||
- ✅ Custom domains
|
||||
- ✅ Environment variables
|
||||
|
||||
---
|
||||
|
||||
## 🔍 TROUBLESHOOTING
|
||||
|
||||
### Problem: Build Failed
|
||||
|
||||
**Solusi:**
|
||||
1. Cek build logs di Vercel dashboard
|
||||
2. Test build lokal:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
3. Fix error yang muncul
|
||||
4. Push lagi ke GitHub
|
||||
|
||||
### Problem: 404 Not Found
|
||||
|
||||
**Solusi:**
|
||||
1. Pastikan `vercel.json` ada dan benar
|
||||
2. Pastikan rewrites sudah diset untuk SPA routing
|
||||
3. Redeploy
|
||||
|
||||
### Problem: API Not Working
|
||||
|
||||
**Solusi:**
|
||||
1. Cek backend Railway masih online
|
||||
2. Cek CORS di backend
|
||||
3. Cek API_URL di code
|
||||
4. Cek browser console untuk error
|
||||
|
||||
---
|
||||
|
||||
## ✅ CHECKLIST
|
||||
|
||||
### Sebelum Deploy:
|
||||
- [x] `.gitignore` sudah dibuat
|
||||
- [x] `vercel.json` sudah dibuat
|
||||
- [x] `package.json` script build ada
|
||||
- [x] API_URL sudah diset ke Railway
|
||||
- [ ] Test build lokal (`npm run build`)
|
||||
- [ ] Push ke GitHub
|
||||
|
||||
### Setelah Deploy:
|
||||
- [ ] URL Vercel bisa diakses
|
||||
- [ ] Register berfungsi
|
||||
- [ ] Login berfungsi
|
||||
- [ ] Control panel berfungsi
|
||||
- [ ] Data real-time muncul
|
||||
|
||||
---
|
||||
|
||||
## 🎉 SELESAI!
|
||||
|
||||
Web Javaherbal sekarang online di Vercel!
|
||||
|
||||
**Keuntungan:**
|
||||
- ✅ Gratis unlimited
|
||||
- ✅ Auto deploy dari GitHub
|
||||
- ✅ HTTPS otomatis
|
||||
- ✅ CDN global (cepat)
|
||||
- ✅ Zero downtime deployment
|
||||
- ✅ Custom domain support
|
||||
|
||||
**Selamat! Web Anda sekarang production-ready!** 🚀
|
||||
|
||||
---
|
||||
|
||||
## 📞 SUPPORT
|
||||
|
||||
### Vercel Documentation
|
||||
https://vercel.com/docs
|
||||
|
||||
### Vite Documentation
|
||||
https://vitejs.dev/guide/
|
||||
|
||||
### React Router Documentation
|
||||
https://reactrouter.com/
|
||||
|
||||
---
|
||||
|
||||
**Created:** May 21, 2026
|
||||
**Status:** ✅ Ready to Deploy
|
||||
|
|
@ -1,397 +0,0 @@
|
|||
# 📚 Penjelasan Sistem Login Web Javaherbal
|
||||
|
||||
## 🔐 Sistem Penyimpanan Akun
|
||||
|
||||
Web Javaherbal menggunakan **localStorage** browser untuk menyimpan data akun dan status login.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Apa itu localStorage?
|
||||
|
||||
**localStorage** adalah fitur browser yang memungkinkan website menyimpan data di komputer user secara permanen (tidak hilang saat browser ditutup).
|
||||
|
||||
### Karakteristik localStorage:
|
||||
- ✅ Data disimpan di **browser user** (bukan di server)
|
||||
- ✅ Data **tidak hilang** saat browser ditutup
|
||||
- ✅ Data **tetap ada** sampai dihapus manual
|
||||
- ✅ Kapasitas: ~5-10 MB per domain
|
||||
- ✅ Format: Key-Value (string)
|
||||
- ❌ **TIDAK AMAN** untuk data sensitif (password terlihat plain text)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Alur Sistem Login
|
||||
|
||||
### 1️⃣ REGISTRASI (Register)
|
||||
|
||||
**File:** `src/pages/Register.tsx`
|
||||
|
||||
#### Langkah-langkah:
|
||||
1. User mengisi form:
|
||||
- Nama lengkap
|
||||
- Email
|
||||
- Password (minimal 6 karakter)
|
||||
- Konfirmasi password
|
||||
|
||||
2. Validasi:
|
||||
```typescript
|
||||
// Cek password cocok
|
||||
if (password !== confirmPassword) {
|
||||
setError('Password tidak cocok')
|
||||
return
|
||||
}
|
||||
|
||||
// Cek panjang password
|
||||
if (password.length < 6) {
|
||||
setError('Password minimal 6 karakter')
|
||||
return
|
||||
}
|
||||
|
||||
// Cek email sudah terdaftar
|
||||
const users = JSON.parse(localStorage.getItem('users') || '[]')
|
||||
if (users.find((u: any) => u.email === email)) {
|
||||
setError('Email sudah terdaftar')
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
3. Simpan ke localStorage:
|
||||
```typescript
|
||||
// Buat user baru
|
||||
const newUser = { name, email, password }
|
||||
|
||||
// Ambil array users yang sudah ada
|
||||
const users = JSON.parse(localStorage.getItem('users') || '[]')
|
||||
|
||||
// Tambahkan user baru
|
||||
users.push(newUser)
|
||||
|
||||
// Simpan kembali ke localStorage
|
||||
localStorage.setItem('users', JSON.stringify(users))
|
||||
```
|
||||
|
||||
4. Redirect ke halaman login
|
||||
|
||||
#### Contoh Data di localStorage:
|
||||
```json
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"password": "123456"
|
||||
},
|
||||
{
|
||||
"name": "Jane Smith",
|
||||
"email": "jane@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ LOGIN
|
||||
|
||||
**File:** `src/pages/Login.tsx`
|
||||
|
||||
#### Langkah-langkah:
|
||||
1. User mengisi form:
|
||||
- Email
|
||||
- Password
|
||||
|
||||
2. Validasi:
|
||||
```typescript
|
||||
// Ambil semua users dari localStorage
|
||||
const users = JSON.parse(localStorage.getItem('users') || '[]')
|
||||
|
||||
// Cari user dengan email dan password yang cocok
|
||||
const user = users.find((u: any) =>
|
||||
u.email === email && u.password === password
|
||||
)
|
||||
|
||||
if (user) {
|
||||
// Login berhasil
|
||||
localStorage.setItem('isAuthenticated', 'true')
|
||||
localStorage.setItem('currentUser', JSON.stringify(user))
|
||||
navigate('/')
|
||||
} else {
|
||||
// Login gagal
|
||||
setError('Email atau password salah')
|
||||
}
|
||||
```
|
||||
|
||||
3. Jika berhasil:
|
||||
- Set `isAuthenticated` = `'true'`
|
||||
- Simpan data user ke `currentUser`
|
||||
- Redirect ke halaman beranda
|
||||
|
||||
#### Data di localStorage Setelah Login:
|
||||
```json
|
||||
{
|
||||
"users": [...],
|
||||
"isAuthenticated": "true",
|
||||
"currentUser": {
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"password": "123456"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ CEK STATUS LOGIN
|
||||
|
||||
**File:** `src/components/ProtectedRoute.tsx`
|
||||
|
||||
#### Cara Kerja:
|
||||
```typescript
|
||||
const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true'
|
||||
|
||||
if (!isAuthenticated) {
|
||||
// Redirect ke login
|
||||
return <Navigate to="/login" replace />
|
||||
}
|
||||
|
||||
// Tampilkan halaman yang dilindungi
|
||||
return <>{children}</>
|
||||
```
|
||||
|
||||
#### Penggunaan:
|
||||
```tsx
|
||||
// Halaman yang perlu login
|
||||
<Route
|
||||
path="/control"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Control />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ LOGOUT
|
||||
|
||||
**File:** `src/components/Navbar.tsx` (kemungkinan)
|
||||
|
||||
#### Cara Kerja:
|
||||
```typescript
|
||||
function handleLogout() {
|
||||
// Hapus status login
|
||||
localStorage.removeItem('isAuthenticated')
|
||||
localStorage.removeItem('currentUser')
|
||||
|
||||
// Redirect ke login
|
||||
navigate('/login')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Struktur Data di localStorage
|
||||
|
||||
### Key-Value Pairs:
|
||||
|
||||
| Key | Value | Deskripsi |
|
||||
|-----|-------|-----------|
|
||||
| `users` | `[{name, email, password}, ...]` | Array semua user yang terdaftar |
|
||||
| `isAuthenticated` | `'true'` atau `'false'` | Status login user saat ini |
|
||||
| `currentUser` | `{name, email, password}` | Data user yang sedang login |
|
||||
|
||||
### Contoh Lengkap:
|
||||
```json
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "Admin",
|
||||
"email": "admin@javaherbal.com",
|
||||
"password": "admin123"
|
||||
},
|
||||
{
|
||||
"name": "User Test",
|
||||
"email": "test@example.com",
|
||||
"password": "test123"
|
||||
}
|
||||
],
|
||||
"isAuthenticated": "true",
|
||||
"currentUser": {
|
||||
"name": "Admin",
|
||||
"email": "admin@javaherbal.com",
|
||||
"password": "admin123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Cara Lihat Data di Browser
|
||||
|
||||
### Chrome / Edge:
|
||||
1. Buka web di browser
|
||||
2. Tekan **F12** (Developer Tools)
|
||||
3. Klik tab **Application**
|
||||
4. Di sidebar kiri, klik **Local Storage**
|
||||
5. Pilih domain web (contoh: `http://localhost:5173`)
|
||||
6. Lihat semua key-value yang tersimpan
|
||||
|
||||
### Firefox:
|
||||
1. Buka web di browser
|
||||
2. Tekan **F12** (Developer Tools)
|
||||
3. Klik tab **Storage**
|
||||
4. Di sidebar kiri, klik **Local Storage**
|
||||
5. Pilih domain web
|
||||
6. Lihat semua key-value yang tersimpan
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ KELEMAHAN SISTEM INI
|
||||
|
||||
### 1. **Password Tidak Terenkripsi**
|
||||
```json
|
||||
{
|
||||
"password": "123456" // ❌ Plain text, bisa dilihat siapa saja!
|
||||
}
|
||||
```
|
||||
|
||||
**Solusi:**
|
||||
- Gunakan hashing (bcrypt, SHA-256)
|
||||
- Simpan password di backend, bukan localStorage
|
||||
|
||||
### 2. **Data Bisa Dihapus User**
|
||||
User bisa hapus localStorage via Developer Tools → semua akun hilang!
|
||||
|
||||
**Solusi:**
|
||||
- Simpan data di backend (database)
|
||||
- localStorage hanya untuk token/session
|
||||
|
||||
### 3. **Tidak Aman untuk Production**
|
||||
localStorage bisa diakses oleh JavaScript → rentan XSS attack
|
||||
|
||||
**Solusi:**
|
||||
- Gunakan backend API untuk autentikasi
|
||||
- Simpan token di httpOnly cookie
|
||||
- Implementasi JWT (JSON Web Token)
|
||||
|
||||
### 4. **Data Lokal per Browser**
|
||||
Akun di Chrome tidak bisa diakses di Firefox (data terpisah)
|
||||
|
||||
**Solusi:**
|
||||
- Gunakan backend database (MongoDB, PostgreSQL)
|
||||
- Sinkronisasi data via API
|
||||
|
||||
---
|
||||
|
||||
## 🚀 REKOMENDASI UNTUK PRODUCTION
|
||||
|
||||
### Sistem Login yang Aman:
|
||||
|
||||
#### 1. **Backend API**
|
||||
```
|
||||
Frontend (React) → Backend API (Express) → Database (MongoDB)
|
||||
```
|
||||
|
||||
#### 2. **Registrasi:**
|
||||
```typescript
|
||||
// Frontend
|
||||
const response = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, email, password })
|
||||
})
|
||||
|
||||
// Backend
|
||||
const hashedPassword = await bcrypt.hash(password, 10)
|
||||
await User.create({ name, email, password: hashedPassword })
|
||||
```
|
||||
|
||||
#### 3. **Login:**
|
||||
```typescript
|
||||
// Frontend
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password })
|
||||
})
|
||||
const { token } = await response.json()
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// Backend
|
||||
const user = await User.findOne({ email })
|
||||
const isValid = await bcrypt.compare(password, user.password)
|
||||
if (isValid) {
|
||||
const token = jwt.sign({ userId: user._id }, SECRET_KEY)
|
||||
return { token }
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. **Protected Routes:**
|
||||
```typescript
|
||||
// Frontend
|
||||
const token = localStorage.getItem('token')
|
||||
const response = await fetch('/api/protected', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
|
||||
// Backend
|
||||
const token = req.headers.authorization?.split(' ')[1]
|
||||
const decoded = jwt.verify(token, SECRET_KEY)
|
||||
const user = await User.findById(decoded.userId)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 KESIMPULAN
|
||||
|
||||
### Sistem Saat Ini (localStorage):
|
||||
- ✅ **Kelebihan:**
|
||||
- Mudah implementasi
|
||||
- Tidak perlu backend
|
||||
- Cocok untuk prototype/demo
|
||||
- Cepat
|
||||
|
||||
- ❌ **Kekurangan:**
|
||||
- Tidak aman (password plain text)
|
||||
- Data lokal (tidak sinkron antar device)
|
||||
- Bisa dihapus user
|
||||
- Tidak cocok untuk production
|
||||
|
||||
### Untuk Production:
|
||||
- ✅ Gunakan backend API (Express + MongoDB)
|
||||
- ✅ Hash password (bcrypt)
|
||||
- ✅ Gunakan JWT untuk token
|
||||
- ✅ Simpan token di httpOnly cookie
|
||||
- ✅ Implementasi refresh token
|
||||
- ✅ Rate limiting untuk prevent brute force
|
||||
- ✅ HTTPS untuk enkripsi data
|
||||
|
||||
---
|
||||
|
||||
## 🎯 NEXT STEPS
|
||||
|
||||
Jika ingin upgrade ke sistem yang lebih aman:
|
||||
|
||||
1. **Buat Backend API untuk Auth**
|
||||
- Endpoint: `/api/auth/register`
|
||||
- Endpoint: `/api/auth/login`
|
||||
- Endpoint: `/api/auth/logout`
|
||||
- Endpoint: `/api/auth/me` (get current user)
|
||||
|
||||
2. **Gunakan MongoDB untuk Simpan User**
|
||||
- Collection: `users`
|
||||
- Schema: `{ name, email, password (hashed), createdAt }`
|
||||
|
||||
3. **Implementasi JWT**
|
||||
- Generate token saat login
|
||||
- Verify token di protected routes
|
||||
- Refresh token untuk extend session
|
||||
|
||||
4. **Update Frontend**
|
||||
- Ganti localStorage dengan API calls
|
||||
- Simpan hanya token (bukan password)
|
||||
- Handle token expiration
|
||||
|
||||
---
|
||||
|
||||
**Untuk saat ini, sistem localStorage sudah cukup untuk development/testing, tapi TIDAK AMAN untuk production!** 🔒
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# 🚀 Push Web Javaherbal ke GitHub
|
||||
|
||||
## 📋 COMMAND SIAP PAKAI
|
||||
|
||||
Copy-paste command ini satu per satu:
|
||||
|
||||
### 1. Masuk ke Folder Web
|
||||
```bash
|
||||
cd javaherbal-v2/javaherbal2
|
||||
```
|
||||
|
||||
### 2. Initialize Git
|
||||
```bash
|
||||
git init
|
||||
```
|
||||
|
||||
### 3. Add All Files
|
||||
```bash
|
||||
git add .
|
||||
```
|
||||
|
||||
### 4. Commit
|
||||
```bash
|
||||
git commit -m "Initial commit - Web Javaherbal dengan auth backend"
|
||||
```
|
||||
|
||||
### 5. Add Remote (Repository: frondend-web)
|
||||
```bash
|
||||
git remote add origin https://github.com/flemss/frondend-web.git
|
||||
```
|
||||
|
||||
### 6. Set Branch ke Main
|
||||
```bash
|
||||
git branch -M main
|
||||
```
|
||||
|
||||
### 7. Push ke GitHub
|
||||
```bash
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ SELESAI!
|
||||
|
||||
Repository: https://github.com/flemss/frondend-web
|
||||
|
||||
**Next:** Deploy ke Vercel!
|
||||
|
||||
---
|
||||
|
||||
## 🔄 UPDATE SETELAH PUSH PERTAMA
|
||||
|
||||
Jika ada perubahan code:
|
||||
|
||||
```bash
|
||||
cd javaherbal-v2/javaherbal2
|
||||
|
||||
# Add changes
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "Update: deskripsi perubahan"
|
||||
|
||||
# Push
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Vercel akan auto deploy! 🚀
|
||||
31
README.md
31
README.md
|
|
@ -1,31 +0,0 @@
|
|||
# JavaHerbal 🌿
|
||||
|
||||
Website herbal modern dengan React + TypeScript + Vite.
|
||||
|
||||
## Cara Menjalankan
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
npm install
|
||||
|
||||
# 2. Jalankan dev server
|
||||
npm run dev
|
||||
# → buka http://localhost:5173
|
||||
|
||||
# 3. Build production
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Fitur
|
||||
- Hero section dengan animasi
|
||||
- Panel Kontrol: gauge SVG, toggle power, slider suhu, chart 24 jam (Recharts)
|
||||
- Grid 6 produk dengan hover effects & keranjang belanja
|
||||
- Footer lengkap dengan navigasi
|
||||
- TypeScript strict-free (tidak ada error)
|
||||
|
||||
## Stack
|
||||
- React 18 + TypeScript
|
||||
- Vite 5
|
||||
- Recharts
|
||||
- Lucide React
|
||||
- Google Fonts (Playfair Display + Plus Jakarta Sans)
|
||||
245
README_UPDATE.md
245
README_UPDATE.md
|
|
@ -1,245 +0,0 @@
|
|||
# ✅ Update Web Javaherbal - Connected to Railway Backend
|
||||
|
||||
## 🎉 Yang Sudah Diupdate:
|
||||
|
||||
### 1. API Connection
|
||||
- **Backend URL:** `https://web-production-32384.up.railway.app/api`
|
||||
- **File:** `src/components/ControlPanel.tsx`
|
||||
|
||||
### 2. Fitur Baru:
|
||||
|
||||
#### ✅ Real-time Data
|
||||
- Suhu update otomatis setiap 5 detik
|
||||
- Data dari ESP32 via backend Railway
|
||||
- Grafik menampilkan history 24 jam terakhir
|
||||
|
||||
#### ✅ Kontrol Manual Heater
|
||||
- Toggle HEATER: ON/OFF
|
||||
- Kirim kontrol ke backend → MQTT → ESP32
|
||||
- Status heater real-time
|
||||
|
||||
#### ✅ Target Suhu (Setpoint)
|
||||
- Slider untuk set target suhu (30-80°C)
|
||||
- Kirim setpoint ke backend
|
||||
- Update otomatis
|
||||
|
||||
### 3. Tampilan
|
||||
- **TIDAK DIUBAH** - Tampilan tetap sama seperti sebelumnya
|
||||
- Hanya fungsi backend yang ditambahkan
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Cara Menjalankan
|
||||
|
||||
### 1. Install Dependencies (jika belum)
|
||||
```bash
|
||||
cd javaherbal-v2/javaherbal2
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. Jalankan Development Server
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 3. Buka Browser
|
||||
```
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
### 4. Login & Akses Control Panel
|
||||
- Login dengan akun yang sudah terdaftar
|
||||
- Klik menu "Control" atau akses `/control`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Fitur Control Panel
|
||||
|
||||
### Gauge Suhu
|
||||
- Menampilkan suhu real-time dari ESP32
|
||||
- Update setiap 5 detik
|
||||
- Range: 0-100°C
|
||||
|
||||
### Toggle HEATER
|
||||
- **ON:** Heater aktif (hijau)
|
||||
- **OFF:** Heater nonaktif (abu-abu)
|
||||
- Klik untuk toggle ON/OFF
|
||||
- Kirim kontrol ke ESP32 via backend
|
||||
|
||||
### Slider Target Suhu
|
||||
- Set target suhu: 30-80°C
|
||||
- Drag slider untuk ubah setpoint
|
||||
- Otomatis kirim ke backend
|
||||
|
||||
### Grafik Riwayat
|
||||
- Menampilkan data 24 jam terakhir
|
||||
- Update setiap 5 detik
|
||||
- Hover untuk lihat detail
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Data Flow
|
||||
|
||||
```
|
||||
ESP32 (Publish MQTT)
|
||||
↓
|
||||
MQTT Broker (broker.hivemq.com)
|
||||
↓
|
||||
Backend Railway (Subscribe & Save)
|
||||
↓
|
||||
MongoDB Atlas (Store data)
|
||||
↓
|
||||
API Endpoint (/api/sensor/latest)
|
||||
↓
|
||||
Web Javaherbal (Fetch & Display)
|
||||
```
|
||||
|
||||
### Kontrol Flow
|
||||
|
||||
```
|
||||
Web Javaherbal (Toggle Heater)
|
||||
↓
|
||||
POST /api/sensor/control
|
||||
↓
|
||||
Backend Railway (Publish MQTT)
|
||||
↓
|
||||
MQTT Broker
|
||||
↓
|
||||
ESP32 (Receive & Execute)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test 1: Data Real-time
|
||||
1. Buka web di browser
|
||||
2. Login dan akses Control Panel
|
||||
3. Lihat gauge suhu
|
||||
4. **Expected:** Suhu muncul dan update setiap 5 detik
|
||||
|
||||
### Test 2: Kontrol Heater
|
||||
1. Klik toggle HEATER
|
||||
2. Lihat perubahan warna (hijau = ON, abu = OFF)
|
||||
3. Cek di aplikasi mobile atau ESP32
|
||||
4. **Expected:** Heater ON/OFF sesuai toggle
|
||||
|
||||
### Test 3: Set Target Suhu
|
||||
1. Drag slider target suhu
|
||||
2. Lihat angka berubah
|
||||
3. **Expected:** Setpoint terkirim ke backend
|
||||
|
||||
### Test 4: Grafik History
|
||||
1. Lihat grafik di panel kanan
|
||||
2. Hover untuk lihat detail
|
||||
3. **Expected:** Grafik menampilkan data 24 jam
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Problem: "Memuat data..." terus menerus
|
||||
|
||||
**Solusi:**
|
||||
1. Cek backend Railway masih online:
|
||||
```bash
|
||||
curl https://web-production-32384.up.railway.app/api/sensor/latest
|
||||
```
|
||||
2. Cek browser console (F12) untuk error
|
||||
3. Pastikan CORS enabled di backend
|
||||
|
||||
### Problem: Toggle heater tidak berfungsi
|
||||
|
||||
**Solusi:**
|
||||
1. Cek network tab di browser (F12)
|
||||
2. Lihat response dari POST /api/sensor/control
|
||||
3. Cek backend logs di Railway
|
||||
|
||||
### Problem: Grafik kosong
|
||||
|
||||
**Solusi:**
|
||||
1. Tunggu beberapa menit untuk data terkumpul
|
||||
2. Cek ESP32 masih publish data
|
||||
3. Cek MongoDB ada data
|
||||
|
||||
---
|
||||
|
||||
## 📊 API Endpoints yang Digunakan
|
||||
|
||||
### GET /api/sensor/latest
|
||||
Fetch data sensor terbaru
|
||||
```typescript
|
||||
Response: {
|
||||
success: true,
|
||||
data: {
|
||||
device: "esp32_cabai_01",
|
||||
suhu: 27.8,
|
||||
heater: true,
|
||||
setpoint: 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/sensor/esp32_cabai_01?limit=24
|
||||
Fetch history 24 data terakhir
|
||||
```typescript
|
||||
Response: {
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
suhu: 27.8,
|
||||
createdAt: "2026-05-19T14:45:58.139Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/sensor/control
|
||||
Kirim kontrol heater
|
||||
```typescript
|
||||
Body: {
|
||||
mode: "MANUAL",
|
||||
heater: true
|
||||
}
|
||||
|
||||
Response: {
|
||||
success: true,
|
||||
message: "Kontrol relay berhasil dikirim"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/sensor/setpoint
|
||||
Set target suhu
|
||||
```typescript
|
||||
Body: {
|
||||
setpoint: 60
|
||||
}
|
||||
|
||||
Response: {
|
||||
success: true,
|
||||
message: "Setpoint berhasil diatur"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [x] Connect ke backend Railway
|
||||
- [x] Fetch data real-time
|
||||
- [x] Display suhu di gauge
|
||||
- [x] Toggle heater ON/OFF
|
||||
- [x] Set target suhu
|
||||
- [x] Display grafik history
|
||||
- [x] Auto refresh setiap 5 detik
|
||||
- [x] Error handling
|
||||
- [x] Loading state
|
||||
- [x] Tampilan tidak berubah
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Selesai!
|
||||
|
||||
Web Javaherbal sekarang sudah terhubung dengan backend Railway dan bisa kontrol heater secara manual!
|
||||
|
||||
**Selamat menggunakan!** 🌶️
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
# ✅ Update Hero - Sembunyikan Status Suhu Jika Belum Login
|
||||
|
||||
## 🎯 Perubahan
|
||||
|
||||
### File yang Diubah:
|
||||
- **`src/components/Hero.tsx`**
|
||||
|
||||
### Apa yang Diubah:
|
||||
Floating stat "SUHU OPTIMAL 50°C – 70°C" di hero section sekarang **hanya tampil jika user sudah login**.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Logika
|
||||
|
||||
### Sebelum:
|
||||
```tsx
|
||||
{/* Floating stat */}
|
||||
<div style={{ ... }}>
|
||||
🌡️ SUHU OPTIMAL 50°C – 70°C
|
||||
</div>
|
||||
```
|
||||
|
||||
**Masalah:** Floating stat selalu tampil, bahkan jika user belum login.
|
||||
|
||||
### Sesudah:
|
||||
```tsx
|
||||
{/* Floating stat - Hanya tampil jika sudah login */}
|
||||
{isAuthenticated && (
|
||||
<div style={{ ... }}>
|
||||
🌡️ SUHU OPTIMAL 50°C – 70°C
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**Solusi:** Floating stat hanya tampil jika `isAuthenticated === true`.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test 1: User Belum Login
|
||||
1. Buka web di browser
|
||||
2. Akses halaman beranda (/)
|
||||
3. **Expected:** Floating stat suhu **TIDAK TAMPIL**
|
||||
4. Hero section tetap tampil normal tanpa floating stat
|
||||
|
||||
### Test 2: User Sudah Login
|
||||
1. Login dengan akun yang valid
|
||||
2. Kembali ke halaman beranda (/)
|
||||
3. **Expected:** Floating stat suhu **TAMPIL**
|
||||
4. Menampilkan "SUHU OPTIMAL 50°C – 70°C"
|
||||
|
||||
### Test 3: Logout
|
||||
1. Sudah login dan floating stat tampil
|
||||
2. Logout dari aplikasi
|
||||
3. Kembali ke halaman beranda
|
||||
4. **Expected:** Floating stat suhu **TIDAK TAMPIL**
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Cara Kerja
|
||||
|
||||
### Autentikasi
|
||||
Web menggunakan `localStorage` untuk menyimpan status login:
|
||||
```typescript
|
||||
const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true'
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
React akan render floating stat hanya jika `isAuthenticated === true`:
|
||||
```tsx
|
||||
{isAuthenticated && <FloatingStat />}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Status
|
||||
|
||||
- ✅ Floating stat disembunyikan jika belum login
|
||||
- ✅ Floating stat tampil jika sudah login
|
||||
- ✅ Tampilan hero section tidak berubah
|
||||
- ✅ Tidak ada perubahan pada halaman lain
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Selesai!
|
||||
|
||||
Floating stat suhu sekarang hanya tampil untuk user yang sudah login!
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
# ✅ Update Responsive - Web Javaherbal
|
||||
|
||||
## 🎯 Perubahan
|
||||
|
||||
Web javaherbal sekarang sudah **responsive** dan tidak terpotong di HP!
|
||||
|
||||
---
|
||||
|
||||
## 📱 Yang Sudah Diupdate
|
||||
|
||||
### 1. **index.css**
|
||||
- ✅ Prevent horizontal scroll
|
||||
- ✅ Prevent text size adjustment di mobile
|
||||
- ✅ Max-width 100% untuk semua elemen
|
||||
- ✅ Responsive images
|
||||
- ✅ Font size adjustment untuk mobile
|
||||
- ✅ Prevent zoom on input focus (font-size 16px)
|
||||
|
||||
### 2. **Navbar.tsx**
|
||||
- ✅ Responsive height: 64px → 56px (mobile)
|
||||
- ✅ Responsive padding: 2rem → 1rem (mobile)
|
||||
- ✅ **Hamburger menu di mobile** (icon Menu/X dari lucide-react)
|
||||
- ✅ Mobile menu dropdown dengan semua navigation links
|
||||
- ✅ Show user info di mobile menu
|
||||
- ✅ Full-width login/logout button di mobile menu
|
||||
- ✅ Auto-close menu saat route berubah
|
||||
- ✅ Auto-close menu saat resize ke desktop
|
||||
- ✅ Smooth transitions dan hover effects
|
||||
- ✅ Active link highlighting di mobile menu
|
||||
|
||||
### 3. **Footer.tsx**
|
||||
- ✅ Grid responsive: 4 kolom → 1 kolom (mobile)
|
||||
- ✅ Padding responsive: 4rem 2rem → 3rem 1rem (mobile)
|
||||
- ✅ Bottom bar: row → column (mobile)
|
||||
- ✅ Smaller gaps di mobile
|
||||
|
||||
### 4. **Hero.tsx**
|
||||
- ✅ Grid responsive: 2 kolom → 1 kolom (mobile)
|
||||
- ✅ Padding responsive: 2rem → 1rem
|
||||
|
||||
### 5. **Dashboard.tsx**
|
||||
- ✅ Section padding responsive: 5rem 2rem → 3rem 1rem (mobile)
|
||||
- ✅ Grid auto-fit untuk cards
|
||||
|
||||
### 6. **ControlPanel.tsx**
|
||||
- ✅ Grid responsive: 2 kolom → 1 kolom (mobile)
|
||||
- ✅ Section padding responsive: 5rem 2rem → 3rem 1rem (mobile)
|
||||
|
||||
### 7. **Login.tsx & Register.tsx**
|
||||
- ✅ Container padding responsive: 3rem → 2rem 1.5rem (mobile)
|
||||
- ✅ Border radius responsive: 24px → 16px (mobile)
|
||||
- ✅ Wrapper padding responsive: 2rem → 1rem (mobile)
|
||||
|
||||
### 8. **Products.tsx**
|
||||
- ✅ Hero padding responsive: 4rem 2rem → 3rem 1rem (mobile)
|
||||
- ✅ Section padding responsive: 4rem 2rem → 3rem 1rem (mobile)
|
||||
- ✅ Filter layout: row → column (mobile)
|
||||
- ✅ Grid responsive: auto-fill → 1 kolom (mobile)
|
||||
- ✅ PaddingTop adjust dengan navbar mobile (56px)
|
||||
|
||||
### 9. **About.tsx**
|
||||
- ✅ All section padding responsive: 5rem 2rem → 3rem 1rem (mobile)
|
||||
- ✅ Timeline layout: 3 kolom → horizontal (mobile)
|
||||
- ✅ Hide timeline line di mobile
|
||||
- ✅ PaddingTop adjust dengan navbar mobile (56px)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Breakpoints
|
||||
|
||||
### Desktop (> 768px)
|
||||
- Navbar: 64px height
|
||||
- Grid: 2-4 kolom
|
||||
- Padding: 4-5rem 2rem
|
||||
- Font size: Normal
|
||||
- Show all navigation links
|
||||
|
||||
### Mobile (≤ 768px)
|
||||
- Navbar: 56px height
|
||||
- **Hamburger menu icon** (Menu/X toggle)
|
||||
- **Mobile dropdown menu** dengan semua navigation links
|
||||
- Grid: 1 kolom
|
||||
- Padding: 3rem 1rem
|
||||
- Font size: Smaller
|
||||
- Input font: 16px (prevent zoom)
|
||||
- Compact buttons
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Implementasi
|
||||
|
||||
Semua komponen menggunakan:
|
||||
```typescript
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768)
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleResize = () => setIsMobile(window.innerWidth <= 768)
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
```
|
||||
|
||||
Kemudian conditional rendering:
|
||||
```typescript
|
||||
style={{
|
||||
padding: isMobile ? '1rem' : '2rem',
|
||||
gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr'
|
||||
}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Desktop
|
||||
1. Buka web di browser
|
||||
2. Resize window
|
||||
3. **Expected:** Layout adjust smoothly
|
||||
|
||||
### Tablet
|
||||
1. Buka web di tablet atau resize browser ke 768px
|
||||
2. **Expected:** Grid jadi 1 kolom
|
||||
3. **Expected:** Padding lebih kecil
|
||||
|
||||
### Mobile
|
||||
1. Buka web di HP
|
||||
2. **Expected:** Hamburger menu icon (☰) muncul di kanan atas
|
||||
3. **Expected:** Klik hamburger, dropdown menu muncul dengan semua links
|
||||
4. **Expected:** Klik link, menu auto-close dan navigate
|
||||
5. **Expected:** Tidak ada horizontal scroll
|
||||
6. **Expected:** UI tidak terpotong
|
||||
7. **Expected:** Text readable
|
||||
8. **Expected:** Input tidak zoom saat focus
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [x] Prevent horizontal scroll
|
||||
- [x] Responsive navbar (hide links, compact buttons)
|
||||
- [x] **Hamburger menu untuk mobile navigation**
|
||||
- [x] Mobile menu dropdown dengan semua links
|
||||
- [x] Auto-close menu on route change
|
||||
- [x] Responsive footer (4 col → 1 col)
|
||||
- [x] Responsive grid (2-4 col → 1 col)
|
||||
- [x] Responsive padding (all pages)
|
||||
- [x] Responsive font size
|
||||
- [x] Prevent zoom on input
|
||||
- [x] Max-width 100% semua elemen
|
||||
- [x] Responsive images
|
||||
- [x] Responsive timeline (About page)
|
||||
- [x] Responsive filter (Products page)
|
||||
- [x] Responsive login/register forms
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Selesai!
|
||||
|
||||
Web sekarang **fully responsive** dan mobile-friendly! 📱
|
||||
|
||||
Semua halaman sudah disesuaikan:
|
||||
- ✅ Home (Hero, Dashboard)
|
||||
- ✅ Products
|
||||
- ✅ About
|
||||
- ✅ Control Panel (gauge tidak terpotong)
|
||||
- ✅ Login
|
||||
- ✅ Register
|
||||
- ✅ **Navbar dengan Hamburger Menu** 🍔
|
||||
- ✅ Footer
|
||||
|
||||
## 🍔 Fitur Hamburger Menu
|
||||
|
||||
Di mobile (≤ 768px):
|
||||
1. **Icon hamburger** (☰) muncul di kanan atas navbar
|
||||
2. **Klik icon** → Dropdown menu muncul dari atas
|
||||
3. **Menu berisi:**
|
||||
- User info (jika login)
|
||||
- Semua navigation links (Beranda, Produk, Tentang Kami)
|
||||
- Control Panel link (jika login)
|
||||
- Button Login/Logout
|
||||
4. **Auto-close** saat:
|
||||
- Klik link (navigate ke halaman lain)
|
||||
- Resize window ke desktop
|
||||
- Klik icon X
|
||||
5. **Smooth animations** dan hover effects
|
||||
6. **Active link highlighting** (warna hijau untuk halaman aktif)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Leaf, Mail, Phone, MapPin, Instagram, Facebook, Youtube, LucideProps } from 'lucide-react'
|
||||
import { Leaf, Mail, Phone, MapPin, LucideProps } from 'lucide-react'
|
||||
|
||||
// ── types ────────────────────────────────────────────────────────────────────
|
||||
type IconComponent = React.FC<LucideProps>
|
||||
|
|
@ -8,10 +8,8 @@ interface LinkColData { title: string; links: string[] }
|
|||
interface ContactData { Icon: IconComponent; text: string }
|
||||
|
||||
// ── data ─────────────────────────────────────────────────────────────────────
|
||||
const SOCIAL_ICONS: IconComponent[] = [Instagram, Facebook, Youtube]
|
||||
|
||||
const LINK_COLS: LinkColData[] = [
|
||||
{ title: 'Produk', links: ['Rempah Kering','Jamu Tradisional', 'Paket Bundle'] },
|
||||
{ title: 'Produk', links: ['Rempah Kering','Jamu Tradisional'] },
|
||||
]
|
||||
|
||||
const CONTACT_ITEMS: ContactData[] = [
|
||||
|
|
@ -46,27 +44,6 @@ function HoverLink({ children, dimColor = 'rgba(255,255,255,0.6)', size = '0.85r
|
|||
)
|
||||
}
|
||||
|
||||
function SocialBtn({ Icon }: { Icon: IconComponent }) {
|
||||
const [hovered, setHovered] = useState(false)
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
width: '36px', height: '36px',
|
||||
borderRadius: '8px',
|
||||
background: hovered ? 'rgba(122,182,72,0.28)' : 'rgba(255,255,255,0.1)',
|
||||
border: '1px solid rgba(255,255,255,0.15)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
color: hovered ? '#7ab648' : 'rgba(255,255,255,0.7)',
|
||||
cursor: 'pointer', transition: 'all 0.2s',
|
||||
}}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<Icon size={16} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function LinkCol({ title, links }: LinkColData) {
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -152,11 +129,6 @@ export default function Footer() {
|
|||
>
|
||||
Teknologi pengeringan untuk menjaga khasiat alami rempah Nusantara.
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
{SOCIAL_ICONS.map((Icon, i) => (
|
||||
<SocialBtn key={i} Icon={Icon} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Link columns */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue