FinSight — Personal Finance Dashboard
Aplikasi keuangan pribadi full-stack siap produksi dengan AI insights (Gemini), OCR scan struk belanja, pelacakan tujuan finansial, PWA installable, dan ekspor PDF/CSV. Dilengkapi 165 unit test & CI/CD.
- Next.js 16
- TypeScript
- Prisma 7
- PostgreSQL
- Supabase
- Tailwind CSS
- Gemini AI
- NextAuth
- Vitest

Latar Belakang
FinSight dibangun untuk membantu pengguna mencatat dan memahami keuangan pribadinya dalam satu dashboard. Selain pencatatan transaksi dan tujuan finansial, aplikasi ini memanfaatkan Gemini untuk memberikan insight dari pola pengeluaran dan OCR untuk membaca struk belanja secara otomatis, sehingga input manual dapat dikurangi.
Tanggung Jawab
- Merancang arsitektur full-stack dengan Next.js 16, Prisma 7, dan PostgreSQL (Supabase).
- Mengimplementasikan autentikasi dengan NextAuth serta pengelolaan transaksi dan tujuan finansial.
- Mengintegrasikan Gemini AI untuk insight keuangan dan OCR untuk scan struk.
- Menyiapkan pengujian dengan Vitest dan pipeline CI/CD.
Pencapaian Utama
- 165 unit test yang berjalan otomatis melalui CI/CD.
- Aplikasi dapat di-install sebagai PWA.
- Laporan keuangan dapat diekspor ke PDF dan CSV.
Cuplikan Kode
TypeScript
Server Action yang menerima gambar struk belanja, mengekstrak teks menggunakan Gemini Vision, lalu memparsing hasilnya menjadi transaksi terstruktur siap simpan ke database.
// src/app/actions/scanReceipt.ts
"use server";
import { GoogleGenerativeAI } from "@google/generative-ai";
import { z } from "zod";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const ReceiptSchema = z.object({
merchant: z.string(),
date: z.string(),
total: z.number(),
items: z.array(z.object({
name: z.string(),
qty: z.number(),
unitPrice: z.number(),
subtotal: z.number(),
})),
category: z.string().default("food_beverage"),
});
export type ParsedReceipt = z.infer<typeof ReceiptSchema>;
export async function scanReceipt(
base64Image: string,
mimeType: string
): Promise<ParsedReceipt> {
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
const prompt = `Ekstrak data dari struk belanja ini.
Return HANYA JSON valid tanpa markdown, dengan struktur:
{ merchant, date (ISO), total (number), items: [{name, qty, unitPrice, subtotal}], category }
Kategori: food_beverage | transport | shopping | health | entertainment | other`;
const result = await model.generateContent([
prompt,
{ inlineData: { mimeType, data: base64Image } },
]);
const rawText = result.response.text().trim();
const json = JSON.parse(rawText.replace(/```json?|```/g, "").trim());
return ReceiptSchema.parse(json);
}Kode nyata dari proyek ini — bukan ilustrasi.
UTF-8TypeScript