Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Demo NextJS web app - Home admin automation

Notifications You must be signed in to change notification settings

JuroOravec/homata

Repository files navigation

Homata

A comprehensive home and life management web application for tracking documents, finances, tasks, repairs, inventory, and more.

Features

  • Dashboard - Overview of balances, upcoming reminders, and recent tasks
  • Bank Accounts - Track accounts with masked numbers (last 4 digits only)
  • Cards - Manage cards with expiry tracking (no PIN/CCV stored)
  • Documents - Upload, categorize, tag, and search files
  • Tasks - One-off, seasonal, recurring, and life-event tasks
  • Repairs - Track home repairs with costs and contractor info
  • Properties - Real estate management with tax declarations (Danove priznanie)
  • Inventory - Track owned items with warranty alerts
  • Persons - Household members with life events
  • Reminders - Payment due dates, expiry alerts, appointments

Data Model: Workspaces & Homes

Homata uses a Workspace → Home hierarchy, similar to tools like Notion or ClickUp:

Workspace (e.g., "John's workspace")
├── Home: "My Apartment"
│   ├── Bank Accounts, Cards
│   ├── Documents, Tasks, Repairs
│   ├── Persons (household members)
│   └── Inventory, Properties
├── Home: "Parents' House"
│   └── (separate set of data)
└── Home: "Vacation Cabin"
    └── (separate set of data)

Why this structure?

  • Workspaces are like tenants in a multi-user SaaS. When you register, a workspace is created for you (e.g., "John's workspace"). You can invite others to your workspace.

  • Homes represent separate living situations within a workspace. For example:

    • You live separately from your siblings, so each manages their own Home
    • You have a primary residence and a vacation property
    • You help manage your parents' household

Each Home has its own bank accounts, cards, documents, tasks, repairs, inventory, and household members. The UI provides dropdowns to switch between workspaces and homes.

Tech Stack

  • Framework: Next.js 14+ with App Router, TypeScript
  • Database: PostgreSQL with Prisma ORM
  • Auth: NextAuth.js (credentials + OAuth-ready)
  • UI: Tailwind CSS + shadcn/ui
  • i18n: next-intl (English/Slovak)
  • File Storage: Local filesystem (S3-ready abstraction)
  • Workflow Engine: Temporal.io for durable backend operations

Prerequisites

  • Node.js 18+
  • PostgreSQL 14+

Setup

  1. Clone and install dependencies
git clone <repo-url>
cd homata
npm install
  1. Configure environment
cp .env.example .env
# Edit .env with your database credentials
  1. Start services (PostgreSQL and Temporal)
docker compose up -d

This starts:

  • PostgreSQL (shared by Prisma and Temporal) - ~100-200 MB RAM
  • Temporal Server
  • Temporal UI (http://localhost:8080)

Note: The PostgreSQL instance hosts two separate databases:

  • homata - Your application data (Prisma)
  • temporal - Temporal workflow state (auto-created by Temporal on first run)

Development Setup: For development, both databases share the same PostgreSQL instance to reduce resource usage. In production, consider separate instances for better isolation and performance.

  1. Run database migrations
npx prisma migrate dev --name init
  1. Generate Prisma client
npx prisma generate
  1. Start the Temporal worker
# In a separate terminal
npm run worker:dev

Temporal Workflows

Homata uses Temporal.io for durable workflow execution. All API operations (auth, accounts, cards, documents) are executed as Temporal workflows, providing:

  • Durability: Operations survive server restarts and failures
  • Observability: View all workflows in the Temporal UI at http://localhost:8080
  • Reliability: Automatic retries with configurable retry policies
  • Traceability: Full history of every operation

See also: BUSINESS_LOGIC.md for detailed information about how business logic is structured (activities, workflows, operations) and TEMPORAL.md for error handling patterns.

Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Next.js API   │────▶│  Temporal Server│────▶│  Temporal Worker│
│   (routes)      │     │  (orchestration)│     │  (activities)   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
         │                       │                       │
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────────────────────────────────────────────────┐
│                    PostgreSQL (shared)                       │
│  ├── homata database (Prisma - application data)            │
│  └── temporal database (Temporal - workflow state)          │
└─────────────────────────────────────────────────────────────┘

Workflow Types

Workflow Description
registerUserWorkflow User registration with workspace creation
listAccountsWorkflow List bank accounts for a user's home
createAccountWorkflow Create a new bank account
updateAccountWorkflow Update bank account details
deleteAccountWorkflow Delete a bank account
listCardsWorkflow List cards for a user's home
createCardWorkflow Create a new card
updateCardWorkflow Update card details
deleteCardWorkflow Delete a card
listDocumentsWorkflow List documents for a user's home
createDocumentWorkflow Upload and create a document
downloadDocumentWorkflow Download a document file
deleteDocumentWorkflow Delete a document and its file

Running the Worker

The Temporal worker processes workflows and activities:

# Development (with auto-reload)
npm run worker:dev

# Production
npm run worker

The worker connects to Temporal Server at localhost:7233 by default. Configure with:

  • TEMPORAL_ADDRESS - Temporal server address
  • TEMPORAL_NAMESPACE - Namespace (default: "default")

Internationalization (i18n)

Homata uses next-intl for internationalization, supporting English and Slovak.

Supported Locales

  • English (en) - Default locale
  • Slovak (sk) - Secondary locale

Translation files are located in messages/:

  • messages/en.json - English translations
  • messages/sk.json - Slovak translations

Locale configuration is in src/i18n/config.ts, which defines:

  • locales - Array of supported locale IDs
  • localeNames - Object mapping locale IDs to display names

Validation: A validation script (scripts/validate/i18n-config.ts) automatically checks that all message JSON files in messages/ are properly registered in both the locales array and localeNames object. Run npm run validate to ensure all locales are properly configured.

Type-Safe Translations

The project includes full TypeScript type safety for translations, ensuring:

  • Type-safe namespaces: Only valid translation namespaces can be used

    const t = await getTranslations("dashboard"); // ✅ Valid
    const t = await getTranslations("invalid"); // ❌ TypeScript error
  • Type-safe keys: Only valid translation keys within a namespace can be used

    t("title"); // ✅ Valid
    t("invalidKey"); // ❌ TypeScript error
  • Autocomplete support: Full IDE autocomplete for both namespaces and keys

This is configured via type augmentation in src/types/next-intl.d.ts, which:

  • Infers Locale types from src/i18n/config.ts
  • Infers Messages types from messages/en.json

For more details, see the next-intl TypeScript documentation.

Changing Locale

Users can change their locale preference via the language toggle in the UI, which sets a cookie that persists across sessions. The locale is read by next-intl middleware to load the appropriate translation messages.

Development

npm run dev

Open http://localhost:3000 in your browser.

Codebase Validation

The project includes automated validation scripts to enforce code quality and consistency. Run validations manually with:

npm run validate

Validation Rules

1. RouteConfig Validation

Every page.tsx file in src/app must declare a RouteConfig entry that matches the file's path.

Path conversion rules:

  • Parenthesized directories are skipped (e.g., (auth), (dashboard))
  • Dynamic segments are preserved (e.g., [workspaceId], [homeSlug])

Examples:

  • src/app/(auth)/login/page.tsx → Route key: "/login"
  • src/app/(dashboard)/[workspaceId]/[homeSlug]/documents/upload/page.tsx → Route key: "/[workspaceId]/[homeSlug]/documents/upload"

Valid pattern:

declare module "@/routes" {
  interface RouteConfig {
    "/login": RouteConfigEntry<{
      query?: never;
      params?: never;
    }>;
  }
}

2. Client/Server Component Validation

Every page.tsx file in src/app must be either:

  • A client component (has "use client" directive at the top), OR
  • A server component (has getServerSideProps export), OR
  • An async server component (has export default async function)

Valid patterns:

// Client component
"use client";
export default function MyPage() { ... }

// Server component with getServerSideProps
export const getServerSideProps = async (context) => { ... }
export default function MyPage(props) { ... }

// Async server component (App Router)
export default async function MyPage() { ... }

3. Props Interface Validation

Every .tsx file (except src/components/ui/**) with a default export must have a corresponding Props interface or type.

Rules:

  • Component name: LoginPage → Props interface: LoginPageProps
  • Supports both interface and type definitions
  • Props can be defined before or after the component

Valid patterns:

// Pattern 1: Function component
interface LoginPageProps {
  // ...
}
export default function LoginPage(props: LoginPageProps) { ... }

// Pattern 2: Const arrow function
interface LoginPageProps {
  // ...
}
export default const LoginPage = (props: LoginPageProps) => { ... }

// Pattern 3: Async function
interface LoginPageProps {
  // ...
}
export default async function LoginPage(props: LoginPageProps) { ... }

4. Form Component Validation

Every .tsx file (except src/components/ui/**) that contains a <form> tag must be named with the -form.tsx suffix.

Rule: Forms should be extracted into separate component files.

Invalid:

  • src/app/(auth)/login/page.tsx (contains form but not named -form.tsx)

Valid:

  • src/app/(dashboard)/[workspaceId]/[homeSlug]/finances/cards/card-form.tsx
  • src/app/(dashboard)/[workspaceId]/[homeSlug]/documents/upload/upload-form.tsx

Note: This validation will flag existing files that need refactoring. Forms should be extracted into separate -form.tsx component files.

5. tRPC Router Validation

Every trpc.ts file in src/features/*/ must be imported and registered in src/lib/trpc/root.ts.

Required patterns:

  1. Import: import { <featdir>Router } from "@/features/<featdir>/trpc";
  2. Registration: <featdir>: <featdir>Router, in the appRouter object

Example:

// In src/lib/trpc/root.ts
import { authRouter } from "@/features/auth/trpc";

export const appRouter = createTRPCRouter({
  auth: authRouter,
});

6. Temporal Activities Validation

Every activities.ts file in src/features/*/ must be imported and spread in src/temporal/activities.ts.

Required patterns:

  1. Import: import * as <featdir>Activities from "@/features/<featdir>/activities";
  2. Spread: ...<featdir>Activities, in the activities object

Example:

// In src/temporal/activities.ts
import * as authActivities from "@/features/auth/activities";

export const activities = {
  ...authActivities,
};

7. Temporal Workflows Validation

Every workflows.ts file in src/features/*/ must be exported from src/temporal/workflows.ts.

Required pattern:

  • Export: export * from "@/features/<featdir>/workflows";

Example:

// In src/temporal/workflows.ts
export * from "@/features/auth/workflows";
export * from "@/features/documents/workflows";

8. Temporal Activities Import Validation

Activities (activities.ts files) can only be imported in workflows.ts or activities.ts files.

Rules:

  • ✅ Activities can be imported in workflows.ts files
  • ✅ Activities can be imported in activities.ts files
  • ❌ Activities cannot be imported in operations.ts, trpc.ts, or any other files
  • ✅ Files in src/temporal/ are exempt from this restriction

Example violations:

// ❌ Invalid: activities imported in operations.ts
import { someActivity } from "./activities";

// ❌ Invalid: activities imported in trpc.ts
import type { SomeType } from "./activities";

9. Temporal Workflows Import Validation

Workflows (workflows.ts files) can only be imported in workflows.ts or operations.ts files.

Rules:

  • ✅ Workflows can be imported in workflows.ts files
  • ✅ Workflows can be imported in operations.ts files
  • ❌ Workflows cannot be imported in activities.ts or any other files
  • ✅ Files in src/temporal/ are exempt from this restriction

Example violations:

// ❌ Invalid: workflows imported in activities.ts
import { someWorkflow } from "./workflows";

// ❌ Invalid: workflows imported in trpc.ts
import type { SomeType } from "./workflows";

10. i18n Config Validation

Every .json file in messages/ must be registered in src/i18n/config.ts.

Required patterns:

  1. Locale in locales array: export const locales = [..., "<locale>"] as const;
  2. Locale in localeNames object: export const localeNames = { ..., "<locale>": "..." };

Example:

// In src/i18n/config.ts
export const locales = ["en", "sk"] as const;

export const localeNames: Record<Locale, string> = {
  en: "English",
  sk: "Slovenčina",
};

Note: When adding a new locale file (e.g., messages/fr.json), you must:

  1. Add "fr" to the locales array
  2. Add fr: "Français" to the localeNames object

Running Validations

Validations run automatically before commits (if using git hooks) or can be run manually:

# Run all validations
npm run validate

# Run individual validations (via tsx)
tsx scripts/validate/app-page-route-config.ts
tsx scripts/validate/app-page-client-server.ts
tsx scripts/validate/tsx-props-interface.ts
tsx scripts/validate/tsx-form.ts
tsx scripts/validate/trpc-router.ts
tsx scripts/validate/temporal-activities.ts
tsx scripts/validate/temporal-workflows.ts
tsx scripts/validate/temporal-activities-import.ts
tsx scripts/validate/temporal-workflows-import.ts
tsx scripts/validate/i18n-config.ts

Bypassing Validations

If you need to bypass validations (not recommended), you can use:

git commit --no-verify

However, it's better to fix the validation errors before committing.

Build

npm run build
npm start

Database Commands

# Open Prisma Studio (database GUI)
npx prisma studio

# Reset database
npx prisma migrate reset

# Create new migration
npx prisma migrate dev --name <migration-name>

Project Structure

homata/
├── prisma/
│   └── schema.prisma       # Database schema
├── messages/
│   ├── en.json             # English translations
│   └── sk.json             # Slovak translations
├── src/
│   ├── app/
│   │   ├── (auth)/         # Login, Register
│   │   ├── (dashboard)/    # Main app pages
│   │   └── api/            # API routes
│   ├── components/
│   │   ├── ui/             # shadcn components
│   │   ├── layout/         # Sidebar, Header
│   │   └── shared/         # Language toggle, etc.
│   ├── lib/
│   │   ├── auth.ts         # NextAuth config
│   │   ├── prisma.ts       # Database client
│   │   └── storage/        # File storage abstraction
│   └── temporal/
│       ├── client.ts       # Temporal client singleton
│       ├── worker.ts       # Temporal worker entry point
│       ├── activities/     # Business logic (DB, storage ops)
│       └── workflows/      # Durable workflow definitions
└── uploads/                # Local file storage

License

Private

About

Demo NextJS web app - Home admin automation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published