4.4 KiB
4.4 KiB
Coding Standards — Tour Planner App
Aturan ini berlaku untuk semua kode yang ditulis dalam proyek ini. AI agent wajib mengikuti seluruh aturan di bawah ini.
1. Struktur Komponen
// ✅ BENAR — Functional component dengan arrow function
const MyComponent = ({ prop1, prop2 }) => {
// hooks di atas
// logic di tengah
// return di bawah
return <div />;
};
export default MyComponent;
// ❌ SALAH — Jangan gunakan class component
class MyComponent extends React.Component { }
2. Styling
// ✅ BENAR — CSS variables dari design system
<div style={{ color: 'var(--color-text-primary)', padding: 'var(--space-4)' }}>
// ✅ BENAR — className dengan class yang didefinisikan di CSS
<div className="glass section-title">
// ❌ SALAH — Hardcode warna
<div style={{ color: '#ffffff', backgroundColor: '#1a1a2e' }}>
// ❌ SALAH — TailwindCSS
<div className="text-white bg-gray-900 p-4">
3. State Management
// ✅ BENAR — Gunakan Zustand store untuk state yang di-share
const { schedule, addActivity } = useTourStore();
// ✅ BENAR — useState untuk state lokal (form, toggle)
const [isOpen, setIsOpen] = useState(false);
// ❌ SALAH — Prop drilling lebih dari 2 level
<A> <B prop={x}> <C prop={x}> <D prop={x} />
4. Animasi
// ✅ BENAR — Selalu gunakan Framer Motion
import { motion } from 'framer-motion';
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
// ❌ SALAH — Animasi manual
setTimeout(() => setVisible(true), 100);
element.style.transition = 'opacity 0.3s';
5. Icons
// ✅ BENAR — Lucide React
import { MapPin, Calendar, Plane } from 'lucide-react';
<MapPin size={20} />
// ❌ SALAH — Emoji sebagai icon UI
<span>📍</span>
// ❌ SALAH — SVG inline tanpa abstraksi
<svg xmlns="..." viewBox="..."><path d="..." /></svg>
6. Data & Constants
// ✅ BENAR — Data di src/data/
import { destinations } from '../data/destinations';
import { CATEGORY_COLORS } from '../data/constants';
// ❌ SALAH — Data hardcode di dalam komponen
const destinations = [{ name: 'Bali', ... }]; // dalam file .jsx
7. File Naming
| Tipe | Convention | Contoh |
|---|---|---|
| Component | PascalCase | TripCard.jsx |
| Hook | camelCase dengan "use" prefix | useBudget.js |
| Utility | camelCase | formatCurrency.js |
| Data file | camelCase | destinations.js |
| CSS | kebab-case | trip-card.css |
8. Import Order
// 1. React & hooks
import { useState, useEffect } from 'react';
// 2. Third-party libraries
import { motion, AnimatePresence } from 'framer-motion';
import { MapPin } from 'lucide-react';
// 3. Internal store & hooks
import { useTourStore } from '../store/tourStore';
import { useBudget } from '../hooks/useBudget';
// 4. Components
import Button from '../components/ui/Button';
import TripCard from '../components/trip/TripCard';
// 5. Data & utils
import { destinations } from '../data/destinations';
import { formatIDR } from '../utils/formatCurrency';
// 6. Styles (jika ada)
import './ComponentName.css';
9. Performance
- Gunakan
useMemountuk kalkulasi berat (total budget, statistik packing) - Gunakan
useCallbackuntuk handler yang di-pass sebagai prop - Gunakan
React.memountuk komponen list item yang berat - Jangan lakukan side effect tanpa
useEffect
10. Batasan Fitur (TIDAK BOLEH DILANGGAR)
| Larangan | Alasan |
|---|---|
| ❌ Login / Logout | Aplikasi ini sepenuhnya static & lokal |
| ❌ API call ke backend | Tidak ada server, semua data lokal |
| ❌ Menyimpan password atau data sensitif | Tidak ada autentikasi |
| ❌ TailwindCSS | Gunakan vanilla CSS + CSS variables |
| ❌ jQuery atau manipulasi DOM manual | Gunakan React state |
❌ console.log di production code |
Hapus sebelum commit |
11. Aksesibilitas (Minimum Requirements)
- Semua tombol punya
aria-labeljika tidak ada teks - Semua form field punya
<label>yang terhubung - Warna kontras minimum 4.5:1 untuk teks
- Keyboard navigable untuk modal (focus trap)
12. Error Handling
// Selalu handle edge case
const totalDays = trip?.totalDays ?? 0;
const activities = schedule[dayIndex]?.activities ?? [];
// Jangan biarkan crash karena undefined
// ❌
const name = trip.name.toUpperCase(); // crash jika trip.name null
// ✅
const name = trip?.name?.toUpperCase() ?? 'Unnamed Trip';