diff --git a/README.md b/README.md index 75fc820..c634dd3 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ I'm a small self-hosted accountant app that can help you deal with invoices, rec ## 👋🏻 Getting Started -TaxHacker is a self-hosted accounting app for freelancers and small businesses who want to save time and automate expences and income tracking with power of GenAI. It can recognise uploaded photos, receipts or PDFs and extract important data (e.g. name, total amount, date, merchant, VAT) and save it as structured transactions to a table. You can also create your own custom fields to extract with your LLM prompts. +TaxHacker is a self-hosted accounting app for freelancers, indie-hackers and small businesses who want to save time and automate expences and income tracking with power of GenAI. It can recognise uploaded photos, receipts or PDFs and extract important data (e.g. name, total amount, date, merchant, VAT) and save it as structured transactions to a table. You can also create your own custom fields to extract with your LLM prompts. It supports automatic currency conversion on a day of transaction. Even for crypto! Built-in system of filters, support for multiple projects, import-export of transactions for a certain time (along with attached files) and custom categories, allows you to simplify reporting and tax filing. -![Dashboard](docs/screenshots/title.png) +![Dashboard](public/landing/main-page.webp) > \[!IMPORTANT] > @@ -37,6 +37,8 @@ Built-in system of filters, support for multiple projects, import-export of tran ### `1` Upload photos or documents to analyze with LLM +![Currency Conversion](public/landing/ai-scanner-big.webp) + https://github.com/user-attachments/assets/3326d0e3-0bf6-4c39-9e00-4bf0983d9b7a > 🎥 [Watch the video](https://github.com/user-attachments/assets/3326d0e3-0bf6-4c39-9e00-4bf0983d9b7a) @@ -53,7 +55,7 @@ TaxHacker recognizes a wide variety of documents including store receipts, resta ### `2` Multi-currency support with automatic conversion (even for crypto) -![Currency Conversion](docs/screenshots/currency_conversion.png) +![Currency Conversion](public/landing/multi-currency.webp) TaxHacker automatically converts foreign currencies and even knows the historical exchange rates on the invoice date. @@ -62,9 +64,20 @@ TaxHacker automatically converts foreign currencies and even knows the historica - Historical exchange rate lookup for past transactions - Support for over 170 world currencies and 14 popular cryptocurrencies (BTC, ETC, LTC, DOT, etc)! -### `3` Customize any LLM prompt +### `3` Create custom fields, projects, categories -![Transactions Table](docs/screenshots/transactions.png) +![Transactions Table](public/landing/transactions-big.webp) + +Adapt TaxHacker to your specific tracking needs. You can create new fields, projects or categories to extract additional information from documents. For example, if you need to save emails, addresses, and any custom information into separate fields, you can do it. Custom fields will be available when exporting too. + +- Create unlimited custom fields for transaction tracking +- Automatically extract custom field data using AI +- Include custom fields in exports and reports +- Create new categories or projects to organise your transactions and filter by them + +### `4` `Customize any LLM prompt + +![Custom Categories](public/landing/custom-llm.webp) You can customize LLM Prompts for built-in fields, categories, and projects, as well as modify global templates in the application settings. This allows to customize the quality of recognizing specific things to your specific use-cases. @@ -75,20 +88,9 @@ You can customize LLM Prompts for built-in fields, categories, and projects, as The whole extraction process is under your contoll all the time! -### `4` Create custom fields, projects, categories - -![Custom Categories](docs/screenshots/fields.png) - -Adapt TaxHacker to your specific tracking needs. You can create new fields, projects or categories to extract additional information from documents. For example, if you need to save emails, addresses, and any custom information into separate fields, you can do it. Custom fields will be available when exporting too. - -- Create unlimited custom fields for transaction tracking -- Automatically extract custom field data using AI -- Include custom fields in exports and reports -- Create new categories or projects to organise your transactions and filter by them - ### `5` Flexible data filtering and export -![Data Export](docs/screenshots/export.png) +![Data Export](public/landing/export.webp) Once all documents have been uploaded and analyzed, you can view, filter and export your transaction history. diff --git a/app/landing/landing.tsx b/app/landing/landing.tsx index b1d975e..cc38038 100644 --- a/app/landing/landing.tsx +++ b/app/landing/landing.tsx @@ -25,13 +25,13 @@ export default function LandingPage() {
Log In Sign Up @@ -48,14 +48,14 @@ export default function LandingPage() {
-
+
🚀 Under Active Development

Let AI finally care about your taxes, scan your receipts and analyze your expenses

- A self-hosted accounting app crafted with love for freelancers and small businesses + Self-hosted accounting app crafted for freelancers, indie-hackers and small businesses

Contact Us 💌
-
-
-
@@ -113,16 +113,20 @@ export default function LandingPage() {
  • - Extract key information like dates, amounts, and vendors + Extract key information like dates, items, and vendors
  • - Works with any language, format and photo quality + Works with any language and any photo quality
  • Automatically organize everything into a structured database
  • +
  • + + Bulk upload and analyze multiple files at once +
  • @@ -137,24 +141,28 @@ export default function LandingPage() { 💱 Currency Converter

    - Automatically convert currencies + Automatically convert currencies (even crypto!)

    • 💰 - Detects foreign currencies and coverts it to yours + Detects foreign currencies and converts it to yours
    • 💰 - Historical exchange rate lookup on a date of transaction + Knows historical exchange rates on a date of transaction
    • 💰 - Support for 170+ world currencies + Supports 170+ world currencies
    • 💰 - Even works with cryptocurrencies (BTC, ETH, LTC, etc.) + Works with popular cryptocurrencies (BTC, ETH, LTC, etc.) +
    • +
    • + 💰 + Still allows you to fill it manually
    @@ -170,12 +178,16 @@ export default function LandingPage() {
    - 🔍 Filters + 🔍 Filters & Categories

    - Organize expenses and income + Organize your transactions using fully customizable categories, projects and fields

    @@ -231,20 +247,21 @@ export default function LandingPage() { {/* Custom Fields & Categories */}
    -
    - Custom LLM promts -
    - 🎨 Customization + 🎨 Control over AI

    - Create custom LLM promts to extract anything + Write custom LLM promts to extract anything

    • 🔧 - Create custom fields and categories with your own LLM prompts + Expand and improve your TaxHacker instance with custom LLM prompts +
    • +
    • + 🔧 + Create custom fields and categories and tell AI how to parse them for you
    • 🔧 @@ -260,16 +277,16 @@ export default function LandingPage() {
    +
    + Custom LLM promts +
    {/* Data Export */}
    -
    - Export -
    - 📦 Export + 📦 Self-hosting & Export

    Your Data — Your Rules @@ -277,11 +294,15 @@ export default function LandingPage() {
    • 📤 - Flexible filters to export your data for tax prep + Deploy your own instance of TaxHacker if you want 100% privacy
    • 📤 - Full-text search across documents + Export your transactions to CSV for tax prep or any other purpose +
    • +
    • + 📤 + Full-text search across documents and invoices
    • 📤 @@ -289,10 +310,14 @@ export default function LandingPage() {
    • 📤 - Download full data archive to migrate to another service + Download full data archive to migrate to another service. We don't take away or limit what you do with + your data

    +
    + Export +
    @@ -311,8 +336,8 @@ export default function LandingPage() {
    {/* Self-Hosted Version */} -
    -
    +
    +
    🏠 Use Your Own Server

    @@ -346,8 +371,8 @@ export default function LandingPage() {

    {/* Cloud Version */} -
    -
    +
    +
    ☁️ We Host It For You

    @@ -356,15 +381,15 @@ export default function LandingPage() {
    • 🎯 - SaaS version for those who prefer less hassle + SaaS version if you don't want to hassle with own servers
    • 🤖 - We provide AI keys and storage + We provide you with AI keys and storage
    • 💳 - Yearly subscription plans + Yearly subscription plans. No hidden fees
    • 🚀 diff --git a/docs/screenshots/analyze.png b/docs/screenshots/analyze.png deleted file mode 100644 index deb2cf8..0000000 Binary files a/docs/screenshots/analyze.png and /dev/null differ diff --git a/docs/screenshots/currency_conversion.png b/docs/screenshots/currency_conversion.png deleted file mode 100644 index 46248c0..0000000 Binary files a/docs/screenshots/currency_conversion.png and /dev/null differ diff --git a/docs/screenshots/dashboard.png b/docs/screenshots/dashboard.png deleted file mode 100644 index fefb7c0..0000000 Binary files a/docs/screenshots/dashboard.png and /dev/null differ diff --git a/docs/screenshots/fields.png b/docs/screenshots/fields.png deleted file mode 100644 index 07e0b53..0000000 Binary files a/docs/screenshots/fields.png and /dev/null differ diff --git a/docs/screenshots/title.png b/docs/screenshots/title.png deleted file mode 100644 index c345cc8..0000000 Binary files a/docs/screenshots/title.png and /dev/null differ diff --git a/docs/screenshots/transactions.png b/docs/screenshots/transactions.png deleted file mode 100644 index 9365901..0000000 Binary files a/docs/screenshots/transactions.png and /dev/null differ diff --git a/hooks/use-progress.tsx b/hooks/use-progress.tsx index 9243708..2cb016f 100644 --- a/hooks/use-progress.tsx +++ b/hooks/use-progress.tsx @@ -1,3 +1,4 @@ +import { generateUUID } from "@/lib/utils" import { useEffect, useState } from "react" interface Progress { @@ -38,7 +39,7 @@ export function useProgress(options: UseProgressOptions = {}) { } try { - const progressId = crypto.randomUUID() + const progressId = generateUUID() const source = new EventSource(`/api/progress/${progressId}?type=${type}`) setEventSource(source) diff --git a/lib/utils.ts b/lib/utils.ts index 01745cb..3210919 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -80,3 +80,39 @@ export function encodeFilename(filename: string): string { const encoded = encodeURIComponent(filename) return `UTF-8''${encoded}` } + +export function generateUUID(): string { + // Try to use crypto.randomUUID() if available (modern browsers and Node.js 14.17+) + if (typeof crypto !== "undefined" && crypto.randomUUID) { + try { + return crypto.randomUUID() + } catch (error) { + // Fall through to next method + } + } + + // Fallback to crypto.getRandomValues() for UUID v4 generation + if (typeof crypto !== "undefined" && crypto.getRandomValues) { + try { + const bytes = new Uint8Array(16) + crypto.getRandomValues(bytes) + + // Set version (4) and variant bits according to RFC 4122 + bytes[6] = (bytes[6] & 0x0f) | 0x40 // Version 4 + bytes[8] = (bytes[8] & 0x3f) | 0x80 // Variant 10 + + // Convert to UUID string format + const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("") + return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20, 32)].join("-") + } catch (error) { + // Fall through to Math.random() fallback + } + } + + // Final fallback using Math.random() (RFC 4122 compliant UUID v4) + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c === "x" ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} diff --git a/models/categories.ts b/models/categories.ts index 8bd0bcf..9ba828e 100644 --- a/models/categories.ts +++ b/models/categories.ts @@ -46,6 +46,16 @@ export const updateCategory = async (userId: string, code: string, category: Cat } export const deleteCategory = async (userId: string, code: string) => { + await prisma.transaction.updateMany({ + where: { + userId, + categoryCode: code, + }, + data: { + categoryCode: null, + }, + }) + return await prisma.category.delete({ where: { userId_code: { userId, code } }, }) diff --git a/models/projects.ts b/models/projects.ts index 4435eb5..612bd32 100644 --- a/models/projects.ts +++ b/models/projects.ts @@ -46,6 +46,16 @@ export const updateProject = async (userId: string, code: string, project: Proje } export const deleteProject = async (userId: string, code: string) => { + await prisma.transaction.updateMany({ + where: { + userId, + projectCode: code, + }, + data: { + projectCode: null, + }, + }) + return await prisma.project.delete({ where: { userId_code: { code, userId } }, }) diff --git a/public/landing/ai-scanner-big.webp b/public/landing/ai-scanner-big.webp new file mode 100644 index 0000000..be88165 Binary files /dev/null and b/public/landing/ai-scanner-big.webp differ diff --git a/public/landing/ai-scanner.webp b/public/landing/ai-scanner.webp index 772b04b..040d0ae 100644 Binary files a/public/landing/ai-scanner.webp and b/public/landing/ai-scanner.webp differ diff --git a/public/landing/invoice-generator.webp b/public/landing/invoice-generator.webp new file mode 100644 index 0000000..339d983 Binary files /dev/null and b/public/landing/invoice-generator.webp differ diff --git a/public/landing/main-page.webp b/public/landing/main-page.webp new file mode 100644 index 0000000..bc00292 Binary files /dev/null and b/public/landing/main-page.webp differ diff --git a/public/landing/title.webp b/public/landing/title.webp deleted file mode 100644 index 80a4a87..0000000 Binary files a/public/landing/title.webp and /dev/null differ diff --git a/public/landing/transactions-big.webp b/public/landing/transactions-big.webp new file mode 100644 index 0000000..7c6eb31 Binary files /dev/null and b/public/landing/transactions-big.webp differ diff --git a/public/landing/video.mp4 b/public/landing/video.mp4 index 27db69b..a738663 100644 Binary files a/public/landing/video.mp4 and b/public/landing/video.mp4 differ