Forms
Bilko Forms Documentation
Current State: Native HTML forms with React state Validation: Client-side JavaScript validation (no schema validation yet) Future State: Zod schemas for validation, react-hook-form for form management
Invoice Creation Wizard (6-Step Multi-Page Form)
Route: /invoices/new
File: app/(dashboard)/invoices/new/page.tsx
Form Structure
Step 1: Customer Selection
Fields:
- Customer (required)
- Type: Select (dropdown)
- Options: All customers from contacts (type='customer')
- Can add new customer via dialog
- Validation: Required before proceeding to step 2
Add Customer Dialog:
- Name (required)
- Type: Text
- Validation: Required
- Email (required)
- Type: Email
- Validation: Required, valid email format
- Phone (optional)
- Type: Tel
- Validation: None
- Tax ID (optional)
- Type: Text
- Validation: None
Validation:
- Alert shown if user tries to proceed without selecting customer
- Form submission triggers inline alert (no schema validation)
Step 2: Invoice Details
Fields:
-
Invoice Number
- Type: Text
- Default: Auto-generated (e.g., "INV-2026-009")
- Validation: None (can be edited)
-
Issue Date
- Type: Date
- Default: Today's date
- Validation: None
-
Due Date
- Type: Date
- Default: 30 days from issue date
- Validation: None
-
Net Terms (shortcut selector)
- Type: Select
- Options: Net 15, Net 30, Net 60
- Behavior: Auto-calculates due date when selected
- Validation: None
-
Currency
- Type: Select
- Options: EUR, RSD, BAM
- Default: EUR
- Validation: None
Behavior:
- Net terms selector auto-updates due date field
- All fields can be manually overridden
Step 3: Line Items
Repeating Fields (Line Items):
Each line item contains:
-
Description (required)
- Type: Text
- Placeholder: "Service or product description"
- Validation: At least one item must have description
-
Quantity
- Type: Number
- Default: 1
- Min: 1
- Validation: Positive number
-
Unit Price
- Type: Number
- Default: 0
- Min: 0
- Step: 0.01
- Validation: Non-negative
-
VAT Rate
- Type: Select
- Options: 0%, 10%, 17%, 20%, 25%
- Default: 20%
- Validation: None
-
Total (calculated, read-only)
- Type: Text (disabled input)
- Calculation:
quantity * unitPrice * (1 + vatRate/100) - Display: Formatted currency
Actions:
Totals Display (read-only):
- Subtotal (before VAT)
- VAT Total
- Grand Total (with VAT)
Validation:
- Alert shown if user tries to proceed with all empty descriptions
- Form submission requires at least one item with description
Step 4: Customization
Fields:
-
Notes (optional)
- Type: Textarea
- Default: "Thank you for your business!"
- Placeholder: "Add a note for your customer..."
- Validation: None
-
Terms (optional)
- Type: Textarea
- Default: "Payment due within 30 days."
- Placeholder: "Payment terms and conditions..."
- Validation: None
Behavior:
- Both fields are optional
- Default values pre-populated but can be cleared
Step 5: Preview (Read-Only)
No form fields. Displays formatted invoice preview with all data from previous steps.
Preview Elements:
- Invoice title ("INVOICE")
- From/To addresses
- Invoice number, date, due date
- Line items table
- Subtotal, VAT, Total
- Notes (if provided)
- Terms (if provided)
No validation. Step is purely visual review.
Step 6: Send/Save
Email Form:
-
To (required)
- Type: Email
- Default: Pre-filled with customer email
- Validation: Valid email format (no schema yet)
-
Subject (required)
- Type: Text
- Default: "Invoice {invoiceNumber}"
- Validation: Required
-
Message (required)
- Type: Textarea
- Default: Pre-filled template
- Rows: 6
- Validation: Required
-
Send Me a Copy (optional)
- Type: Checkbox
- Default: Unchecked
- Validation: None
- Save as Draft — Alert placeholder (no API)
- Download PDF — Alert placeholder (no API)
- Send Invoice — Alert + redirect to
/invoices(no API)
Validation:
- No schema validation
- Form submission triggers alert "Invoice sent!"
Form State Management
Local State:
const [step, setStep] = useState(1)
const [customer, setCustomer] = useState<Contact | null>(null)
const [showAddCustomer, setShowAddCustomer] = useState(false)
const [invoiceDetails, setInvoiceDetails] = useState<InvoiceDetails>({
number: 'INV-2026-009',
issueDate: '2026-02-20',
dueDate: '2026-03-22',
currency: 'EUR',
})
const [lineItems, setLineItems] = useState<LineItem[]>([
{ description: '', quantity: 1, unitPrice: 0, vatRate: 20, total: 0 },
])
const [notes, setNotes] = useState('Thank you for your business!')
const [terms, setTerms] = useState('Payment due within 30 days.')
const [emailData, setEmailData] = useState({
to: '',
subject: '',
message: '',
sendCopy: false,
})
No persistence: All state lost on page refresh or navigation away.
No Zod schemas: Validation is inline JavaScript (alert boxes).
Expense Form (Dialog)
Route: /expenses
Component: Dialog triggered by "Add Expense" button
Form Fields
-
Amount (required)
- Type: Number
- Placeholder: "0.00"
- Validation: Required (no schema)
-
Currency (required)
- Type: Select
- Options: EUR, RSD, BAM
- Default: EUR
- Width: 24px (narrow select next to amount)
- Validation: None
-
Category (required)
- Type: Select
- Options: Office, Travel, Meals, Utilities, Marketing, Infrastructure, Software, Professional Services
- Placeholder: "Select category"
- Validation: Required
-
Date (required)
- Type: Date
- Default: Today's date
- Validation: None
-
Vendor (optional)
- Type: Text
- Placeholder: "Search vendor..."
- Validation: None
- Note: Not a searchable autocomplete yet — plain text input
-
Payment Method (optional)
- Type: Select
- Options: Cash, Card, Bank Transfer
- Placeholder: "Select method"
- Validation: None
-
Receipt (optional)
- Type: File upload (placeholder UI only)
- Display: Dashed border div with "📷 Upload or Drag" text
- Behavior: No actual upload implemented
- Validation: None
-
Description (optional)
- Type: Text
- Placeholder: "Additional notes..."
- Validation: None
Form Actions
- Cancel — Closes dialog, resets form
- Save Expense — Logs form data to console, closes dialog, resets form
No API submission. Form data not persisted.
No Zod schemas. Validation is JavaScript logic in form submit handler.
Settings Forms
Route: /settings
File: app/(dashboard)/settings/page.tsx
Company Profile Form
Fields:
-
Company Name (required)
- Type: Text
- Default: "SnowIT d.o.o."
-
Legal Form
- Type: Select
- Options: d.o.o., a.d., Preduzetnik
- Default: "d.o.o."
-
Address
- Type: Text
- Default: "Zmaja od Bosne"
-
City
- Type: Text
- Default: "Sarajevo"
-
Postal Code
- Type: Text
- Default: "71000"
-
Country
- Type: Text
- Default: "BiH"
-
Tax ID / PIB / JIB
- Type: Text
- Default: "4200000000"
-
Base Currency
- Type: Select
- Options: EUR, RSD, BAM
- Default: "EUR"
-
Fiscal Year Start
- Type: Select
- Options: Jan 1, Apr 1, Jul 1, Oct 1
- Default: "Jan 1"
Action:
- Save Changes — Alert placeholder (no API)
Validation: None (no required fields enforced)
Tax & Compliance Form
Fields:
-
Country
- Type: Select
- Options: Serbia, BiH, Croatia
- Default: "Serbia"
-
VAT Registered
- Type: Checkbox
- Default: Checked
-
VAT Number (conditional, shown only if VAT registered)
- Type: Text
- Default: "RS123456789"
- Placeholder: "Enter VAT number"
-
VAT Rate (conditional, shown only if VAT registered)
- Type: Select
- Options: 17% (BiH), 20% (Serbia), 25% (Croatia)
- Default: "20"
Compliance Reminders:
- VAT filing deadlines — Checkbox (default: checked)
- Annual tax returns — Checkbox (default: checked)
- Payroll tax deadlines — Checkbox (default: unchecked)
Action:
- Save Settings — Alert placeholder (no API)
Validation: None
Notification Preferences
Email Notifications:
- Invoice paid — Checkbox (default: checked)
- Invoice overdue — Checkbox (default: checked)
- Expense approved — Checkbox (default: unchecked)
- Bank account synced — Checkbox (default: checked)
In-App Notifications:
- Invoice updates — Checkbox (default: checked)
- Expense updates — Checkbox (default: checked)
- Reconciliation matches — Checkbox (default: unchecked)
Action:
- Save Preferences — Alert placeholder (no API)
Validation: None
Security Settings
Two-Factor Authentication:
Session Timeout:
- Type: Select
- Options: 15 minutes, 30 minutes, 1 hour, 4 hours
- Default: 30 minutes
Password Policy:
- Minimum 12 characters — Checkbox (default: checked)
- Require special characters — Checkbox (default: checked)
- Expire passwords after 90 days — Checkbox (default: unchecked)
Actions:
- View Audit Log — No functionality yet
- Request Data Export — No functionality yet
- Delete Company (Danger Zone) — No functionality yet
Validation: None
Future Form Enhancements (Phase 2)
Zod Schema Validation
Planned: Replace inline validation with Zod schemas
Example (Invoice Wizard Step 1):
import { z } from 'zod'
const customerSchema = z.object({
id: z.string(),
name: z.string().min(1, 'Customer name required'),
email: z.string().email('Valid email required'),
phone: z.string().optional(),
taxId: z.string().optional(),
})
Benefits:
- Type-safe validation
- Reusable schemas for API/DB
- Better error messages
- Centralized validation logic
react-hook-form Integration
Planned: Replace useState with react-hook-form
Example (Expense Form):
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
const expenseSchema = z.object({
amount: z.number().positive('Amount must be positive'),
currency: z.enum(['EUR', 'RSD', 'BAM']),
category: z.string().min(1, 'Category required'),
date: z.string(),
vendor: z.string().optional(),
paymentMethod: z.string().optional(),
description: z.string().optional(),
})
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(expenseSchema),
})
Benefits:
- Automatic error handling
- Less boilerplate
- Better performance (no re-renders on every keystroke)
- Built-in dirty/touched state
Field-Level Validation
Planned: Real-time validation as user types
Example (Email Field):
<Input
{...register("email")}
type="email"
error={errors.email?.message}
/>
{errors.email && (
<span className="text-error text-sm">{errors.email.message}</span>
)}
Current State: No real-time validation, only on form submit.
Form Persistence
Planned: Save draft forms to localStorage
Use Cases:
- Invoice wizard state saved between page refreshes
- Expense form data saved if user closes dialog accidentally
Implementation:
// Save to localStorage on every state change
useEffect(() => {
localStorage.setItem('invoice-draft', JSON.stringify(invoiceState))
}, [invoiceState])
// Load from localStorage on mount
useEffect(() => {
const draft = localStorage.getItem('invoice-draft')
if (draft) setInvoiceState(JSON.parse(draft))
}, [])
File Upload (Receipt Attachment)
Current State: Placeholder UI only (dashed border div)
Future Implementation:
- Drag-and-drop file upload
- File size validation (max 5MB)
- File type validation (PDF, JPG, PNG)
- Preview uploaded file
- Remove uploaded file
- Upload to backend API
API Endpoint (planned):
POST /api/expenses/:id/receipt
Content-Type: multipart/form-data
Autocomplete/Search Fields
Current State: Plain text inputs
Future Enhancement (Vendor Field):
- Searchable dropdown
- Autocomplete from existing vendors
- Create new vendor inline
- Match by partial name
Library: Radix UI Combobox or react-select
Multi-Currency Conversion
Current State: User manually selects currency
Future Enhancement:
- Fetch live exchange rates
- Auto-convert amounts for display
- Store both original currency and base currency
- Show converted amounts in tooltips
Summary
Current Forms:
- Invoice Wizard (6-step) — Customer, Details, Line Items, Customization, Preview, Send
- Expense Form (dialog) — Amount, Category, Date, Vendor, Receipt, etc.
- Company Profile — All company settings
- Tax & Compliance — VAT settings
- Notification Preferences — Email/in-app notification toggles
- Security Settings — 2FA, session timeout, password policy
Validation:
- Inline JavaScript (alert boxes)
- No schema validation
- No real-time validation
- No error state styling
State Management:
- React useState for all forms
- No persistence (lost on refresh)
- No form libraries (native HTML forms)
Future (Phase 2):
- Zod schemas for validation
- react-hook-form for form management
- Field-level validation
- Form persistence (localStorage)
- File upload functionality
- Autocomplete/search fields
- API integration for submission