diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..5908863
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/backend-development.md b/docs/backend-development.md
new file mode 100644
index 0000000..85b2177
--- /dev/null
+++ b/docs/backend-development.md
@@ -0,0 +1,150 @@
+## Backend Development Guide
+
+This guide documents how we build backend features in this repository to ensure consistency, type-safety, and security.
+
+### Tech Stack and Principles
+
+- **Runtime**: Node/TypeScript with strict typing. Avoid `any`.
+- **API**: tRPC routers under `src/server/routers` using `protectedProcedure`.
+- **ORM**: Drizzle ORM with PostgreSQL; schema in `src/db/schema.ts`.
+- **Validation**: Zod (`import { z } from "zod"`).
+- **Errors**: TRPCError helpers in `src/errors/trpc.ts` and message keys in `src/errors/messages.ts`.
+- **Types**: Shared API-facing types in `src/types/index.ts`.
+
+### Directory Layout
+
+- `src/server/routers/*`: One router per domain (e.g., `accounts.ts`, `transactions.ts`).
+- `src/server/api/*`: tRPC setup (`trpc.ts`, `root.ts`).
+- `src/db/*`: Drizzle DB client and schema.
+- `src/errors/*`: Error helpers and message keys.
+- `src/types/*`: Shared TypeScript interfaces for API responses.
+- `drizzle/*`: SQL migrations and snapshot metadata.
+
+### Authentication and Authorization
+
+- **Always use `protectedProcedure`** for endpoints that require a logged-in user; access the user via `ctx.userId`.
+- **Never trust client-provided user IDs**. Filter by `ctx.userId` on the server.
+- **Ownership checks are mandatory** for any resource scoped to a user.
+ - Example: Before creating a `transaction` for an `account`, verify that `account.userId === ctx.userId`.
+ - Example: When listing transactions, always restrict results to accounts owned by the current user.
+
+### Error Handling Conventions
+
+- Use the error factory in `src/errors/trpc.ts` to throw typed errors (e.g., `errors.forbidden(...)`).
+- **Do not hardcode strings**. Use `src/errors/messages.ts` for error message keys.
+- **Message values are camelCase** and represent i18n keys, e.g., `currencyNotFound`, `accountNotOwned`.
+- Prefer reusing generic keys where possible (`forbidden`, `notFound`, `badRequest`). Add new keys when necessary.
+
+### Drizzle ORM Guidelines
+
+- Import comparison helpers from `drizzle-orm`: `eq`, `gte`, `lte`, `and`.
+- Prefer typed selects with aliases:
+ - Use `select({ transactions: transactionsTable, categories: categoriesTable, accounts: accountsTable })`.
+ - Use `innerJoin` when related rows must exist; use `leftJoin` only when nulls are expected.
+- Compose filters using a single `where` expression (build an `and(...)` chain) instead of mutating the builder repeatedly.
+- For conditional filtering, build a `whereExpr` (`SQL | undefined`) and apply it once: `const rows = whereExpr ? await qb.where(whereExpr).execute() : await qb.execute()`.
+
+### Date/Time and Numeric Types
+
+- Use `timestamp({ withTimezone: true })` in schema; pass and compare as `Date` objects in code.
+- Use `gte`/`lte` with `Date` values (not milliseconds). Example: `gte(transactionsTable.datetime, input.startDate)`.
+- For currency amounts, use Drizzle `decimal({ precision, scale, mode: "string" })` and handle values as strings at the API boundary to avoid floating-point issues.
+
+### Zod Validation Patterns
+
+- Import with `import { z } from "zod"`.
+- Validate timestamps as `z.date()`.
+- Validate amounts as `z.string()` (paired with `decimal(..., mode: "string")`).
+- Optional fields use `.optional()`; keep inputs minimal and explicit.
+
+### Types and Response Shapes
+
+- Use `src/types/index.ts` interfaces for API responses (e.g., `AccountWithCurrencyAndType`, `TransactionWithCategoryAndAccount`).
+- Build responses by mapping typed Drizzle rows to these interfaces. Avoid returning raw Drizzle shapes if they do not match the shared types.
+- Avoid `any`, `unknown`, and explicit `PgSelectBase` casts. Let Drizzle infer types via `select({ ... })` with aliases.
+
+### Ownership Enforcement Patterns
+
+- Create operations (example: transactions):
+ - First, verify ownership of the parent resource via Drizzle `select` on the owner table (e.g., `accountsTable.userId === ctx.userId`).
+ - If not owned, throw `errors.forbidden(error_messages.accountNotOwned)`.
+- Read/List operations:
+ - Scope always results by joining the owner table first and applying `where(eq(ownerTable.userId, ctx.userId))`.
+ - If the client passes an ID filter (e.g., `accountId`), optionally pre-validate ownership and throw `forbidden` if not owned.
+
+### Example: Secure and Typed List Query
+
+```ts
+import type { SQL } from "drizzle-orm";
+const conditions: (SQL | undefined)[] = [
+ eq(accountsTable.userId, ctx.userId!),
+ input.accountId
+ ? eq(transactionsTable.accountId, input.accountId)
+ : undefined,
+ input.categoryId
+ ? eq(transactionsTable.categoryId, input.categoryId)
+ : undefined,
+ input.startDate
+ ? gte(transactionsTable.datetime, input.startDate)
+ : undefined,
+ input.endDate ? lte(transactionsTable.datetime, input.endDate) : undefined,
+];
+const typed = conditions.filter((c): c is SQL => Boolean(c));
+const whereExpr = typed.length ? and(...typed) : undefined;
+
+const qb = db
+ .select({
+ transactions: transactionsTable,
+ categories: categoriesTable,
+ accounts: accountsTable,
+ })
+ .from(transactionsTable)
+ .innerJoin(accountsTable, eq(transactionsTable.accountId, accountsTable.id))
+ .innerJoin(
+ categoriesTable,
+ eq(transactionsTable.categoryId, categoriesTable.id),
+ );
+
+const rows = whereExpr
+ ? await qb.where(whereExpr).execute()
+ : await qb.execute();
+return rows.map((row) => ({
+ ...row.transactions,
+ category: row.categories,
+ account: row.accounts,
+}));
+```
+
+### Error Messages (i18n-ready)
+
+- Add new keys to `src/errors/messages.ts` with camelCase values, e.g.:
+ - `currencyNotFound`, `accountTypeNotFound`, `accountNotOwned`, `transactionNotFound`.
+- UI will translate these keys later via i18n; backend should only emit the keys.
+
+### Router Conventions
+
+- Name operations clearly: `addX` for create, `getX` for list, `updateX`, `deleteX` if needed.
+- Keep each router focused on a single domain, and register it in `src/server/api/root.ts`.
+- Inputs: Zod schemas colocated in the procedure.
+- Outputs: Typed return values using shared interfaces.
+
+### Migrations and Seeding
+
+- Manage schema changes with Drizzle migrations in `drizzle/`.
+- Keep `src/scripts/seed.ts` aligned with current schema and business rules (ownership, required fields).
+- Prefer deterministic seeds for development.
+
+### Linting and Quality
+
+- No `any` in production code. Let types flow from Drizzle and Zod.
+- Keep functions small and focused. Use early returns.
+- Avoid catching errors unless adding meaningful handling or mapping to typed TRPC errors.
+
+### Adding a New Feature (Checklist)
+
+- Define/extend schema in `src/db/schema.ts` and generate a migration.
+- Add router or extend existing one under `src/server/routers`.
+- Validate inputs with Zod; enforce ownership with Drizzle where applicable.
+- Use `errors` + `error_messages` for failures (camelCase message keys).
+- Return typed responses using `src/types/index.ts`.
+- Add seeds/tests if relevant.
diff --git a/drizzle/0000_mute_thunderbolt_ross.sql b/drizzle/0000_mute_thunderbolt_ross.sql
deleted file mode 100644
index a658123..0000000
--- a/drizzle/0000_mute_thunderbolt_ross.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-
---> statement-breakpoint
-CREATE TABLE "accounts" (
- "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "accounts_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
- "bankName" varchar(255) NOT NULL,
- "currentAmount" numeric(10, 2) NOT NULL,
- "reference" varchar(255) NOT NULL,
- "usage" varchar(255) NOT NULL,
- "userId" varchar(255) NOT NULL,
- "createdAt" timestamp with time zone DEFAULT now() NOT NULL
-);
diff --git a/drizzle/0000_unique_mercury.sql b/drizzle/0000_unique_mercury.sql
new file mode 100644
index 0000000..cc72706
--- /dev/null
+++ b/drizzle/0000_unique_mercury.sql
@@ -0,0 +1,49 @@
+CREATE TABLE "accounts" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "userId" varchar(255) NOT NULL,
+ "bankName" varchar(255) NOT NULL,
+ "currentBalance" numeric(18, 2) NOT NULL,
+ "reference" varchar(255) NOT NULL,
+ "usage" varchar(255) NOT NULL,
+ "currencyId" uuid NOT NULL,
+ "createdAt" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "categories" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "name" varchar(100) NOT NULL,
+ "slug" varchar(120) NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "currencies" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "name" varchar(100) NOT NULL,
+ "code" varchar(10) NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "transactions" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "accountId" uuid NOT NULL,
+ "categoryId" uuid NOT NULL,
+ "company" varchar(255) NOT NULL,
+ "amount" numeric(18, 2) NOT NULL,
+ "datetime" timestamp with time zone NOT NULL,
+ "description" text NOT NULL,
+ "createdAt" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "users" (
+ "id" varchar(255) PRIMARY KEY NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "accounts" ADD CONSTRAINT "accounts_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "accounts" ADD CONSTRAINT "accounts_currencyId_currencies_id_fk" FOREIGN KEY ("currencyId") REFERENCES "public"."currencies"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "transactions" ADD CONSTRAINT "transactions_accountId_accounts_id_fk" FOREIGN KEY ("accountId") REFERENCES "public"."accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "transactions" ADD CONSTRAINT "transactions_categoryId_categories_id_fk" FOREIGN KEY ("categoryId") REFERENCES "public"."categories"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "accounts_user_idx" ON "accounts" USING btree ("userId");--> statement-breakpoint
+CREATE INDEX "accounts_currency_idx" ON "accounts" USING btree ("currencyId");--> statement-breakpoint
+CREATE UNIQUE INDEX "categories_slug_unique_idx" ON "categories" USING btree ("slug");--> statement-breakpoint
+CREATE UNIQUE INDEX "currencies_code_unique_idx" ON "currencies" USING btree ("code");--> statement-breakpoint
+CREATE INDEX "transactions_account_idx" ON "transactions" USING btree ("accountId");--> statement-breakpoint
+CREATE INDEX "transactions_category_idx" ON "transactions" USING btree ("categoryId");--> statement-breakpoint
+CREATE INDEX "transactions_datetime_idx" ON "transactions" USING btree ("datetime");
\ No newline at end of file
diff --git a/drizzle/0001_nostalgic_master_mold.sql b/drizzle/0001_nostalgic_master_mold.sql
new file mode 100644
index 0000000..d663e7a
--- /dev/null
+++ b/drizzle/0001_nostalgic_master_mold.sql
@@ -0,0 +1,41 @@
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
+
+CREATE TABLE "account_types" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "name" varchar(100) NOT NULL,
+ "slug" varchar(120) NOT NULL
+);
+--> statement-breakpoint
+
+-- 1) Add column nullable
+ALTER TABLE "accounts" ADD COLUMN "typeId" uuid;--> statement-breakpoint
+
+-- 2) (Optional but recommended) Ensure a default account type exists for backfilling
+INSERT INTO "account_types" ("name", "slug")
+SELECT 'Private', 'private'
+ON CONFLICT ("slug") DO NOTHING;--> statement-breakpoint
+
+-- 3) Backfill existing rows (choose the appropriate default type)
+UPDATE "accounts" a
+SET "typeId" = at.id
+FROM "account_types" at
+WHERE a."typeId" IS NULL
+ AND at."slug" = 'private';--> statement-breakpoint
+
+-- 4) Enforce NOT NULL
+ALTER TABLE "accounts" ALTER COLUMN "typeId" SET NOT NULL;--> statement-breakpoint
+
+-- 5) Keep the unique index on account_types.slug
+CREATE UNIQUE INDEX "account_types_slug_unique_idx"
+ ON "account_types" USING btree ("slug");--> statement-breakpoint
+
+-- 6) Add the foreign key constraint
+ALTER TABLE "accounts"
+ ADD CONSTRAINT "accounts_typeId_account_types_id_fk"
+ FOREIGN KEY ("typeId")
+ REFERENCES "public"."account_types"("id")
+ ON DELETE RESTRICT
+ ON UPDATE NO ACTION;--> statement-breakpoint
+
+-- 7) Add an index on accounts.typeId for performance
+CREATE INDEX "accounts_type_idx" ON "accounts" USING btree ("typeId");
\ No newline at end of file
diff --git a/drizzle/0001_tense_satana.sql b/drizzle/0001_tense_satana.sql
deleted file mode 100644
index eb7ac8d..0000000
--- a/drizzle/0001_tense_satana.sql
+++ /dev/null
@@ -1,12 +0,0 @@
-CREATE TABLE "welcomes" (
- "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "welcomes_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
- "bankName" varchar(255) NOT NULL,
- "currentAmount" numeric(10, 2) NOT NULL,
- "reference" varchar(255) NOT NULL,
- "usage" varchar(255) NOT NULL,
- "userId" varchar(255) NOT NULL,
- "createdAt" timestamp with time zone DEFAULT now() NOT NULL
-);
---> statement-breakpoint
-DROP TABLE "users" CASCADE;--> statement-breakpoint
-DROP TABLE "accounts" CASCADE;
\ No newline at end of file
diff --git a/drizzle/0002_careless_gladiator.sql b/drizzle/0002_careless_gladiator.sql
new file mode 100644
index 0000000..645c35c
--- /dev/null
+++ b/drizzle/0002_careless_gladiator.sql
@@ -0,0 +1,7 @@
+CREATE TYPE "public"."references" AS ENUM('private', 'business', 'savings', 'shared');--> statement-breakpoint
+ALTER TABLE "account_types" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
+DROP TABLE "account_types" CASCADE;--> statement-breakpoint
+--> statement-breakpoint
+DROP INDEX "accounts_type_idx";--> statement-breakpoint
+ALTER TABLE "accounts" ALTER COLUMN "reference" SET DATA TYPE "public"."references" USING "reference"::"public"."references";--> statement-breakpoint
+ALTER TABLE "accounts" DROP COLUMN "typeId";
diff --git a/drizzle/0002_icy_madrox.sql b/drizzle/0002_icy_madrox.sql
deleted file mode 100644
index 247beb7..0000000
--- a/drizzle/0002_icy_madrox.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-ALTER TABLE "welcomes" ALTER COLUMN "createdAt" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
-ALTER TABLE "welcomes" ALTER COLUMN "createdAt" DROP DEFAULT;
\ No newline at end of file
diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json
index 40cae6a..3df20b1 100644
--- a/drizzle/meta/0000_snapshot.json
+++ b/drizzle/meta/0000_snapshot.json
@@ -1,120 +1,355 @@
{
- "id": "7b572c16-9880-4dd2-a708-6db7a9ab2bfd",
+ "id": "9f87a7a3-7c20-4245-a39d-f4e17eb70443",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
- "public.users": {
- "name": "users",
+ "public.accounts": {
+ "name": "accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
- "type": "integer",
+ "type": "uuid",
"primaryKey": true,
"notNull": true,
- "identity": {
- "type": "always",
- "name": "users_id_seq",
- "schema": "public",
- "increment": "1",
- "startWith": "1",
- "minValue": "1",
- "maxValue": "2147483647",
- "cache": "1",
- "cycle": false
- }
+ "default": "gen_random_uuid()"
},
- "name": {
- "name": "name",
+ "userId": {
+ "name": "userId",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
- "age": {
- "name": "age",
- "type": "integer",
+ "bankName": {
+ "name": "bankName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "currentBalance": {
+ "name": "currentBalance",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference": {
+ "name": "reference",
+ "type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
- "email": {
- "name": "email",
+ "usage": {
+ "name": "usage",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
+ },
+ "currencyId": {
+ "name": "currencyId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_user_idx": {
+ "name": "accounts_user_idx",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounts_currency_idx": {
+ "name": "accounts_currency_idx",
+ "columns": [
+ {
+ "expression": "currencyId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_userId_users_id_fk": {
+ "name": "accounts_userId_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "accounts_currencyId_currencies_id_fk": {
+ "name": "accounts_currencyId_currencies_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "currencies",
+ "columnsFrom": ["currencyId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.categories": {
+ "name": "categories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "categories_slug_unique_idx": {
+ "name": "categories_slug_unique_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
}
},
- "indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
- "uniqueConstraints": {
- "users_email_unique": {
- "name": "users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.currencies": {
+ "name": "currencies",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code": {
+ "name": "code",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "currencies_code_unique_idx": {
+ "name": "currencies_code_unique_idx",
+ "columns": [
+ {
+ "expression": "code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
}
},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
- "public.accounts": {
- "name": "accounts",
+ "public.transactions": {
+ "name": "transactions",
"schema": "",
"columns": {
"id": {
"name": "id",
- "type": "integer",
+ "type": "uuid",
"primaryKey": true,
"notNull": true,
- "identity": {
- "type": "always",
- "name": "accounts_id_seq",
- "schema": "public",
- "increment": "1",
- "startWith": "1",
- "minValue": "1",
- "maxValue": "2147483647",
- "cache": "1",
- "cycle": false
- }
+ "default": "gen_random_uuid()"
},
- "bankName": {
- "name": "bankName",
- "type": "varchar(255)",
+ "accountId": {
+ "name": "accountId",
+ "type": "uuid",
"primaryKey": false,
"notNull": true
},
- "currentAmount": {
- "name": "currentAmount",
- "type": "numeric(10, 2)",
+ "categoryId": {
+ "name": "categoryId",
+ "type": "uuid",
"primaryKey": false,
"notNull": true
},
- "reference": {
- "name": "reference",
+ "company": {
+ "name": "company",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
- "usage": {
- "name": "usage",
- "type": "varchar(255)",
+ "amount": {
+ "name": "amount",
+ "type": "numeric(18, 2)",
"primaryKey": false,
"notNull": true
},
- "userId": {
- "name": "userId",
- "type": "varchar(255)",
+ "datetime": {
+ "name": "datetime",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
- "type": "varchar(255)",
+ "type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
- "default": "'now()'"
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "transactions_account_idx": {
+ "name": "transactions_account_idx",
+ "columns": [
+ {
+ "expression": "accountId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "transactions_category_idx": {
+ "name": "transactions_category_idx",
+ "columns": [
+ {
+ "expression": "categoryId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "transactions_datetime_idx": {
+ "name": "transactions_datetime_idx",
+ "columns": [
+ {
+ "expression": "datetime",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "transactions_accountId_accounts_id_fk": {
+ "name": "transactions_accountId_accounts_id_fk",
+ "tableFrom": "transactions",
+ "tableTo": "accounts",
+ "columnsFrom": ["accountId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transactions_categoryId_categories_id_fk": {
+ "name": "transactions_categoryId_categories_id_fk",
+ "tableFrom": "transactions",
+ "tableTo": "categories",
+ "columnsFrom": ["categoryId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
}
},
"indexes": {},
diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json
index 1610920..647de6c 100644
--- a/drizzle/meta/0001_snapshot.json
+++ b/drizzle/meta/0001_snapshot.json
@@ -1,29 +1,73 @@
{
- "id": "b61453c7-755e-4503-ad8a-e539246cacb6",
- "prevId": "7b572c16-9880-4dd2-a708-6db7a9ab2bfd",
+ "id": "ccc0eca7-766c-431b-930d-bdfb833c2101",
+ "prevId": "9f87a7a3-7c20-4245-a39d-f4e17eb70443",
"version": "7",
"dialect": "postgresql",
"tables": {
- "public.welcomes": {
- "name": "welcomes",
+ "public.account_types": {
+ "name": "account_types",
"schema": "",
"columns": {
"id": {
"name": "id",
- "type": "integer",
+ "type": "uuid",
"primaryKey": true,
"notNull": true,
- "identity": {
- "type": "always",
- "name": "welcomes_id_seq",
- "schema": "public",
- "increment": "1",
- "startWith": "1",
- "minValue": "1",
- "maxValue": "2147483647",
- "cache": "1",
- "cycle": false
- }
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_types_slug_unique_idx": {
+ "name": "account_types_slug_unique_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.accounts": {
+ "name": "accounts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
},
"bankName": {
"name": "bankName",
@@ -31,9 +75,9 @@
"primaryKey": false,
"notNull": true
},
- "currentAmount": {
- "name": "currentAmount",
- "type": "numeric(10, 2)",
+ "currentBalance": {
+ "name": "currentBalance",
+ "type": "numeric(18, 2)",
"primaryKey": false,
"notNull": true
},
@@ -49,18 +93,341 @@
"primaryKey": false,
"notNull": true
},
- "userId": {
- "name": "userId",
- "type": "varchar(255)",
+ "typeId": {
+ "name": "typeId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "currencyId": {
+ "name": "currencyId",
+ "type": "uuid",
"primaryKey": false,
"notNull": true
},
"createdAt": {
"name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_user_idx": {
+ "name": "accounts_user_idx",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounts_currency_idx": {
+ "name": "accounts_currency_idx",
+ "columns": [
+ {
+ "expression": "currencyId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounts_type_idx": {
+ "name": "accounts_type_idx",
+ "columns": [
+ {
+ "expression": "typeId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_userId_users_id_fk": {
+ "name": "accounts_userId_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "accounts_typeId_account_types_id_fk": {
+ "name": "accounts_typeId_account_types_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "account_types",
+ "columnsFrom": ["typeId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "accounts_currencyId_currencies_id_fk": {
+ "name": "accounts_currencyId_currencies_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "currencies",
+ "columnsFrom": ["currencyId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.categories": {
+ "name": "categories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "categories_slug_unique_idx": {
+ "name": "categories_slug_unique_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.currencies": {
+ "name": "currencies",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code": {
+ "name": "code",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "currencies_code_unique_idx": {
+ "name": "currencies_code_unique_idx",
+ "columns": [
+ {
+ "expression": "code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.transactions": {
+ "name": "transactions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "accountId": {
+ "name": "accountId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "categoryId": {
+ "name": "categoryId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "company": {
+ "name": "company",
"type": "varchar(255)",
"primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "datetime": {
+ "name": "datetime",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
"notNull": true,
- "default": "'now()'"
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "transactions_account_idx": {
+ "name": "transactions_account_idx",
+ "columns": [
+ {
+ "expression": "accountId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "transactions_category_idx": {
+ "name": "transactions_category_idx",
+ "columns": [
+ {
+ "expression": "categoryId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "transactions_datetime_idx": {
+ "name": "transactions_datetime_idx",
+ "columns": [
+ {
+ "expression": "datetime",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "transactions_accountId_accounts_id_fk": {
+ "name": "transactions_accountId_accounts_id_fk",
+ "tableFrom": "transactions",
+ "tableTo": "accounts",
+ "columnsFrom": ["accountId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transactions_categoryId_categories_id_fk": {
+ "name": "transactions_categoryId_categories_id_fk",
+ "tableFrom": "transactions",
+ "tableTo": "categories",
+ "columnsFrom": ["categoryId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
+ "notNull": true
}
},
"indexes": {},
diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json
index fa30aa0..a6e8fcb 100644
--- a/drizzle/meta/0002_snapshot.json
+++ b/drizzle/meta/0002_snapshot.json
@@ -1,29 +1,25 @@
{
- "id": "c747747e-f4bd-42a3-a570-8a4d02fd5253",
- "prevId": "b61453c7-755e-4503-ad8a-e539246cacb6",
+ "id": "91f744b4-b913-457a-abc5-f5a7ac42e454",
+ "prevId": "ccc0eca7-766c-431b-930d-bdfb833c2101",
"version": "7",
"dialect": "postgresql",
"tables": {
- "public.welcomes": {
- "name": "welcomes",
+ "public.accounts": {
+ "name": "accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
- "type": "integer",
+ "type": "uuid",
"primaryKey": true,
"notNull": true,
- "identity": {
- "type": "always",
- "name": "welcomes_id_seq",
- "schema": "public",
- "increment": "1",
- "startWith": "1",
- "minValue": "1",
- "maxValue": "2147483647",
- "cache": "1",
- "cycle": false
- }
+ "default": "gen_random_uuid()"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
},
"bankName": {
"name": "bankName",
@@ -31,15 +27,16 @@
"primaryKey": false,
"notNull": true
},
- "currentAmount": {
- "name": "currentAmount",
- "type": "numeric(10, 2)",
+ "currentBalance": {
+ "name": "currentBalance",
+ "type": "numeric(18, 2)",
"primaryKey": false,
"notNull": true
},
"reference": {
"name": "reference",
- "type": "varchar(255)",
+ "type": "references",
+ "typeSchema": "public",
"primaryKey": false,
"notNull": true
},
@@ -49,16 +46,310 @@
"primaryKey": false,
"notNull": true
},
- "userId": {
- "name": "userId",
+ "currencyId": {
+ "name": "currencyId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_user_idx": {
+ "name": "accounts_user_idx",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounts_currency_idx": {
+ "name": "accounts_currency_idx",
+ "columns": [
+ {
+ "expression": "currencyId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_userId_users_id_fk": {
+ "name": "accounts_userId_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "accounts_currencyId_currencies_id_fk": {
+ "name": "accounts_currencyId_currencies_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "currencies",
+ "columnsFrom": ["currencyId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.categories": {
+ "name": "categories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "categories_slug_unique_idx": {
+ "name": "categories_slug_unique_idx",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.currencies": {
+ "name": "currencies",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code": {
+ "name": "code",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "currencies_code_unique_idx": {
+ "name": "currencies_code_unique_idx",
+ "columns": [
+ {
+ "expression": "code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.transactions": {
+ "name": "transactions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "accountId": {
+ "name": "accountId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "categoryId": {
+ "name": "categoryId",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "company": {
+ "name": "company",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
+ "amount": {
+ "name": "amount",
+ "type": "numeric(18, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "datetime": {
+ "name": "datetime",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
"createdAt": {
"name": "createdAt",
"type": "timestamp with time zone",
"primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "transactions_account_idx": {
+ "name": "transactions_account_idx",
+ "columns": [
+ {
+ "expression": "accountId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "transactions_category_idx": {
+ "name": "transactions_category_idx",
+ "columns": [
+ {
+ "expression": "categoryId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "transactions_datetime_idx": {
+ "name": "transactions_datetime_idx",
+ "columns": [
+ {
+ "expression": "datetime",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "transactions_accountId_accounts_id_fk": {
+ "name": "transactions_accountId_accounts_id_fk",
+ "tableFrom": "transactions",
+ "tableTo": "accounts",
+ "columnsFrom": ["accountId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "transactions_categoryId_categories_id_fk": {
+ "name": "transactions_categoryId_categories_id_fk",
+ "tableFrom": "transactions",
+ "tableTo": "categories",
+ "columnsFrom": ["categoryId"],
+ "columnsTo": ["id"],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(255)",
+ "primaryKey": true,
"notNull": true
}
},
@@ -71,7 +362,13 @@
"isRLSEnabled": false
}
},
- "enums": {},
+ "enums": {
+ "public.references": {
+ "name": "references",
+ "schema": "public",
+ "values": ["private", "business", "savings", "shared"]
+ }
+ },
"schemas": {},
"sequences": {},
"roles": {},
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index d4211ac..88876b3 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -5,15 +5,22 @@
{
"idx": 0,
"version": "7",
- "when": 1749731867682,
- "tag": "0000_mute_thunderbolt_ross",
+ "when": 1760046554794,
+ "tag": "0000_unique_mercury",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
- "when": 1749733185632,
- "tag": "0001_tense_satana",
+ "when": 1760204489591,
+ "tag": "0001_nostalgic_master_mold",
+ "breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "7",
+ "when": 1761913195425,
+ "tag": "0002_careless_gladiator",
"breakpoints": true
}
]
diff --git a/localization/ar/common.json b/localization/ar/common.json
index 5464335..7460c9c 100644
--- a/localization/ar/common.json
+++ b/localization/ar/common.json
@@ -94,7 +94,8 @@
"dashboardDailyAccounts": "الحسابات اليومية",
"dashboardPrivate": "خاص",
"dashboardBusiness": "تجاري",
- "dashboardSafeAccounts": "الحسابات المحمية",
+ "dashboardSavings": "مدخرات",
+ "dashboardShared": "مشترك",
"navigationDashboard": "لوحة المعلومات",
"navigationTransactions": "المعاملات",
diff --git a/localization/bn/common.json b/localization/bn/common.json
index efcd57e..5b2f852 100644
--- a/localization/bn/common.json
+++ b/localization/bn/common.json
@@ -92,7 +92,8 @@
"dashboardDailyAccounts": "দৈনিক অ্যাকাউন্টসমূহ",
"dashboardPrivate": "ব্যক্তিগত",
"dashboardBusiness": "ব্যবসা",
- "dashboardSafeAccounts": "সেভিংস অ্যাকাউন্ট",
+ "dashboardSavings": "সঞ্চয়",
+ "dashboardShared": "যৌথ",
"navigationDashboard": "ড্যাশবোর্ড",
"navigationTransactions": "লেনদেন",
"navigationInsights": "বিশ্লেষণ",
diff --git a/localization/de/common.json b/localization/de/common.json
index 3739c8d..4679903 100644
--- a/localization/de/common.json
+++ b/localization/de/common.json
@@ -110,10 +110,11 @@
"verify": "Bestätigen",
"verifying": "Wird überprüft...",
- "dashboardDailyAccounts": "Tägliche Konten",
+ "dashboardDailyAccounts": "Tageskonten",
"dashboardPrivate": "Privat",
"dashboardBusiness": "Geschäftlich",
- "dashboardSafeAccounts": "Sichere Konten",
+ "dashboardSavings": "Ersparnisse",
+ "dashboardShared": "Gemeinsam",
"navigationDashboard": "Dashboard",
"navigationTransactions": "Transaktionen",
diff --git a/localization/en/common.json b/localization/en/common.json
index 1746e06..f9c80d7 100644
--- a/localization/en/common.json
+++ b/localization/en/common.json
@@ -93,7 +93,8 @@
"dashboardDailyAccounts": "Daily Accounts",
"dashboardPrivate": "Private",
"dashboardBusiness": "Business",
- "dashboardSafeAccounts": "Safe Accounts",
+ "dashboardSavings": "Savings",
+ "dashboardShared": "Shared",
"navigationDashboard": "Dashboard",
"navigationTransactions": "Transactions",
diff --git a/localization/es/common.json b/localization/es/common.json
index e941b72..4c9ec2b 100644
--- a/localization/es/common.json
+++ b/localization/es/common.json
@@ -101,7 +101,8 @@
"dashboardDailyAccounts": "Cuentas Diarias",
"dashboardPrivate": "Privado",
"dashboardBusiness": "Empresarial",
- "dashboardSafeAccounts": "Cuentas Protegidas",
+ "dashboardSavings": "Ahorros",
+ "dashboardShared": "Compartido",
"navigationDashboard": "Panel",
"navigationTransactions": "Transacciones",
diff --git a/localization/fa/common.json b/localization/fa/common.json
index 0f4c257..f14b43b 100644
--- a/localization/fa/common.json
+++ b/localization/fa/common.json
@@ -173,7 +173,8 @@
"dashboardDailyAccounts": "حسابهای روزانه",
"dashboardPrivate": "خصوصی",
"dashboardBusiness": "تجاری",
- "dashboardSafeAccounts": "حسابهای محفوظ",
+ "dashboardSavings": "پسانداز",
+ "dashboardShared": "مشترک",
"navigationDashboard": "داشبورد",
"navigationTransactions": "تراکنشها",
diff --git a/localization/fr/common.json b/localization/fr/common.json
index 97eeba7..b8b0dce 100644
--- a/localization/fr/common.json
+++ b/localization/fr/common.json
@@ -93,7 +93,8 @@
"dashboardDailyAccounts": "Comptes Quotidiens",
"dashboardPrivate": "Privé",
"dashboardBusiness": "Professionnel",
- "dashboardSafeAccounts": "Comptes Protégés",
+ "dashboardSavings": "Économies",
+ "dashboardShared": "Partagé",
"navigationDashboard": "Tableau de Bord",
"navigationTransactions": "Transactions",
diff --git a/localization/ha/common.json b/localization/ha/common.json
index da5132b..cc5adc3 100644
--- a/localization/ha/common.json
+++ b/localization/ha/common.json
@@ -89,10 +89,13 @@
"usageTooLong": "Amfani dole ya kasance ƙasa da haruffa 50",
"verify": "Tabbatar",
"verifying": "Ana tabbatarwa...",
+
"dashboardDailyAccounts": "Asusun yau da kullum",
"dashboardPrivate": "Na kaina",
"dashboardBusiness": "Kasuwanci",
- "dashboardSafeAccounts": "Asusun ajiya",
+ "dashboardSavings": "Ajiya",
+ "dashboardShared": "Haɗin gwiwa",
+
"navigationDashboard": "Dashibod",
"navigationTransactions": "Ma'amaloli",
"navigationInsights": "Haske",
diff --git a/localization/hi/common.json b/localization/hi/common.json
index 24b8cde..3629973 100644
--- a/localization/hi/common.json
+++ b/localization/hi/common.json
@@ -101,7 +101,8 @@
"dashboardDailyAccounts": "दैनिक खाते",
"dashboardPrivate": "निजी",
"dashboardBusiness": "व्यापारिक",
- "dashboardSafeAccounts": "सुरक्षित खाते",
+ "dashboardSavings": "बचत",
+ "dashboardShared": "साझा",
"navigationDashboard": "डैशबोर्ड",
"navigationTransactions": "लेन-देन",
diff --git a/localization/id/common.json b/localization/id/common.json
index 201002c..3506965 100644
--- a/localization/id/common.json
+++ b/localization/id/common.json
@@ -89,10 +89,13 @@
"usageTooLong": "Penggunaan maksimal 50 karakter",
"verify": "Verifikasi",
"verifying": "Memverifikasi...",
+
"dashboardDailyAccounts": "Akun Harian",
"dashboardPrivate": "Pribadi",
"dashboardBusiness": "Bisnis",
- "dashboardSafeAccounts": "Akun Tabungan",
+ "dashboardSavings": "Tabungan",
+ "dashboardShared": "Bersama",
+
"navigationDashboard": "Dasbor",
"navigationTransactions": "Transaksi",
"navigationInsights": "Wawasan",
diff --git a/localization/it/common.json b/localization/it/common.json
index 232393a..39b4d15 100644
--- a/localization/it/common.json
+++ b/localization/it/common.json
@@ -45,7 +45,7 @@
"currentAmountPlaceholder": "2000.00",
"enterCurrentAmount": "Inserisci l'importo attuale",
"reference": "Riferimento",
- "referencePlaceholder": "Business, Privato, altro",
+ "referencePlaceholder": "Aziendale, Privato, altro",
"emailRequired": "L'email è obbligatoria",
"invalidEmail": "Per favore inserisci un indirizzo email valido",
"firstNameRequired": "Il nome è obbligatorio",
@@ -100,7 +100,8 @@
"dashboardDailyAccounts": "Conti Quotidiani",
"dashboardPrivate": "Privato",
"dashboardBusiness": "Aziendale",
- "dashboardSafeAccounts": "Conti protetti",
+ "dashboardSavings": "Risparmi",
+ "dashboardShared": "Condiviso",
"navigationDashboard": "Dashboard",
"navigationTransactions": "Transazioni",
diff --git a/localization/ja/common.json b/localization/ja/common.json
index e8115d0..77bccfd 100644
--- a/localization/ja/common.json
+++ b/localization/ja/common.json
@@ -101,7 +101,8 @@
"dashboardDailyAccounts": "日常口座",
"dashboardPrivate": "プライベート",
"dashboardBusiness": "ビジネス",
- "dashboardSafeAccounts": "保護口座",
+ "dashboardSavings": "貯蓄",
+ "dashboardShared": "共有",
"navigationDashboard": "ダッシュボード",
"navigationTransactions": "取引履歴",
diff --git a/localization/ko/common.json b/localization/ko/common.json
index f955ec8..c26b11f 100644
--- a/localization/ko/common.json
+++ b/localization/ko/common.json
@@ -101,7 +101,8 @@
"dashboardDailyAccounts": "일상 계좌",
"dashboardPrivate": "개인",
"dashboardBusiness": "비즈니스",
- "dashboardSafeAccounts": "보호 계좌",
+ "dashboardSavings": "저축",
+ "dashboardShared": "공유",
"navigationDashboard": "대시보드",
"navigationTransactions": "거래내역",
diff --git a/localization/ps/common.json b/localization/ps/common.json
index a472341..60a5613 100644
--- a/localization/ps/common.json
+++ b/localization/ps/common.json
@@ -103,7 +103,8 @@
"dashboardDailyAccounts": "ورځني حسابونه",
"dashboardPrivate": "شخصي",
"dashboardBusiness": "سوداګریز",
- "dashboardSafeAccounts": "خوندي حسابونه",
+ "dashboardSavings": "سپما",
+ "dashboardShared": "شریک شوی",
"navigationDashboard": "کنټرول پینل",
"navigationTransactions": "معاملې",
diff --git a/localization/pt/common.json b/localization/pt/common.json
index 64e6b22..74c4158 100644
--- a/localization/pt/common.json
+++ b/localization/pt/common.json
@@ -100,7 +100,8 @@
"dashboardDailyAccounts": "Contas Diárias",
"dashboardPrivate": "Privado",
"dashboardBusiness": "Empresarial",
- "dashboardSafeAccounts": "Contas Protegidas",
+ "dashboardSavings": "Poupança",
+ "dashboardShared": "Compartilhado",
"navigationDashboard": "Painel",
"navigationTransactions": "Transações",
diff --git a/localization/ru/common.json b/localization/ru/common.json
index f673662..5da850c 100644
--- a/localization/ru/common.json
+++ b/localization/ru/common.json
@@ -100,7 +100,8 @@
"dashboardDailyAccounts": "Ежедневные счета",
"dashboardPrivate": "Личный",
"dashboardBusiness": "Бизнес",
- "dashboardSafeAccounts": "Защищенные счета",
+ "dashboardSavings": "Сбережения",
+ "dashboardShared": "Совместный",
"navigationDashboard": "Панель",
"navigationTransactions": "Транзакции",
diff --git a/localization/sw/common.json b/localization/sw/common.json
index 806b32f..0fabc1e 100644
--- a/localization/sw/common.json
+++ b/localization/sw/common.json
@@ -89,10 +89,13 @@
"usageTooLong": "Matumizi yasizidi herufi 50",
"verify": "Thibitisha",
"verifying": "Inathibitisha...",
+
"dashboardDailyAccounts": "Akaunti za kila siku",
"dashboardPrivate": "Binafsi",
"dashboardBusiness": "Biashara",
- "dashboardSafeAccounts": "Akaunti za akiba",
+ "dashboardSavings": "Akiba",
+ "dashboardShared": "Pamoja",
+
"navigationDashboard": "Dashibodi",
"navigationTransactions": "Miamala",
"navigationInsights": "Uchambuzi",
diff --git a/localization/th/common.json b/localization/th/common.json
index 1f76029..306c053 100644
--- a/localization/th/common.json
+++ b/localization/th/common.json
@@ -89,10 +89,13 @@
"usageTooLong": "รูปแบบการใช้งานต้องไม่เกิน 50 อักขระ",
"verify": "ยืนยัน",
"verifying": "กำลังยืนยัน...",
+
"dashboardDailyAccounts": "บัญชีรายวัน",
"dashboardPrivate": "ส่วนบุคคล",
"dashboardBusiness": "ธุรกิจ",
- "dashboardSafeAccounts": "บัญชีออมทรัพย์",
+ "dashboardSavings": "ออมทรัพย์",
+ "dashboardShared": "ร่วม",
+
"navigationDashboard": "แดชบอร์ด",
"navigationTransactions": "ธุรกรรม",
"navigationInsights": "ข้อมูลเชิงลึก",
diff --git a/localization/tr/common.json b/localization/tr/common.json
index 77a2484..a1e3724 100644
--- a/localization/tr/common.json
+++ b/localization/tr/common.json
@@ -100,7 +100,8 @@
"dashboardDailyAccounts": "Günlük Hesaplar",
"dashboardPrivate": "Özel",
"dashboardBusiness": "İş",
- "dashboardSafeAccounts": "Korumalı Hesaplar",
+ "dashboardSavings": "Biriktirme",
+ "dashboardShared": "Paylaşılan",
"navigationDashboard": "Kontrol Paneli",
"navigationTransactions": "İşlemler",
diff --git a/localization/ur/common.json b/localization/ur/common.json
index 4023ae8..b279196 100644
--- a/localization/ur/common.json
+++ b/localization/ur/common.json
@@ -101,7 +101,8 @@
"dashboardDailyAccounts": "روزانہ کھاتے",
"dashboardPrivate": "نجی",
"dashboardBusiness": "کاروباری",
- "dashboardSafeAccounts": "محفوظ کھاتے",
+ "dashboardSavings": "بچت",
+ "dashboardShared": "مشترکہ",
"navigationDashboard": "ڈیش بورڈ",
"navigationTransactions": "لین دین",
diff --git a/localization/vi/common.json b/localization/vi/common.json
index 4b90b40..eb9e5d0 100644
--- a/localization/vi/common.json
+++ b/localization/vi/common.json
@@ -89,10 +89,13 @@
"usageTooLong": "Mục đích sử dụng không quá 50 ký tự",
"verify": "Xác minh",
"verifying": "Đang xác minh...",
+
"dashboardDailyAccounts": "Tài khoản chi tiêu",
"dashboardPrivate": "Cá nhân",
"dashboardBusiness": "Doanh nghiệp",
- "dashboardSafeAccounts": "Tài khoản tiết kiệm",
+ "dashboardSavings": "Tiết kiệm",
+ "dashboardShared": "Chung",
+
"navigationDashboard": "Bảng điều khiển",
"navigationTransactions": "Giao dịch",
"navigationInsights": "Thông tin chi tiết",
diff --git a/localization/zh/common.json b/localization/zh/common.json
index c836fc7..f735837 100644
--- a/localization/zh/common.json
+++ b/localization/zh/common.json
@@ -100,7 +100,8 @@
"dashboardDailyAccounts": "日常账户",
"dashboardPrivate": "个人",
"dashboardBusiness": "商务",
- "dashboardSafeAccounts": "安全账户",
+ "dashboardSavings": "储蓄",
+ "dashboardShared": "共享",
"navigationDashboard": "仪表板",
"navigationTransactions": "交易记录",
diff --git a/package.json b/package.json
index ef223d9..3b7ee08 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,10 @@
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
+ "seed": "tsx scripts/seed.ts",
+ "db:migrate": "drizzle-kit migrate",
+ "db:studio": "drizzle-kit studio",
+ "db:generate": "drizzle-kit generate",
"test": "jest --watchAll",
"lint": "expo lint",
"typecheck": "tsc --noEmit",
diff --git a/scripts/seed.ts b/scripts/seed.ts
new file mode 100644
index 0000000..a924395
--- /dev/null
+++ b/scripts/seed.ts
@@ -0,0 +1,69 @@
+import "dotenv/config";
+import { db } from "~/db";
+import { categoriesTable, currenciesTable } from "~/db/schema";
+import { inArray } from "drizzle-orm";
+
+async function seedCurrencies() {
+ const currencies = [
+ { code: "USD", name: "US Dollar" },
+ { code: "EUR", name: "Euro" },
+ { code: "ARS", name: "Argentine Peso" },
+ ];
+
+ const codes = currencies.map((c) => c.code);
+
+ const existing = await db
+ .select({ code: currenciesTable.code })
+ .from(currenciesTable)
+ .where(inArray(currenciesTable.code, codes));
+
+ const existingCodes = new Set(existing.map((e) => e.code));
+ const toInsert = currencies.filter((c) => !existingCodes.has(c.code));
+
+ if (toInsert.length > 0) {
+ await db.insert(currenciesTable).values(toInsert);
+ console.log(
+ `Inserted currencies: ${toInsert.map((c) => c.code).join(", ")}`,
+ );
+ } else {
+ console.log("Currencies already seeded");
+ }
+}
+
+async function seedCategories() {
+ const categories = [
+ { name: "Groceries", slug: "groceries" },
+ { name: "Restaurants", slug: "restaurants" },
+ { name: "Transport", slug: "transport" },
+ { name: "Bills", slug: "bills" },
+ { name: "Entertainment", slug: "entertainment" },
+ ];
+
+ const slugs = categories.map((c) => c.slug);
+ const existing = await db
+ .select({ slug: categoriesTable.slug })
+ .from(categoriesTable)
+ .where(inArray(categoriesTable.slug, slugs));
+
+ const existingSlugs = new Set(existing.map((e) => e.slug));
+ const toInsert = categories.filter((c) => !existingSlugs.has(c.slug));
+
+ if (toInsert.length > 0) {
+ await db.insert(categoriesTable).values(toInsert);
+ console.log(
+ `Inserted categories: ${toInsert.map((c) => c.slug).join(", ")}`,
+ );
+ } else {
+ console.log("Categories already seeded");
+ }
+}
+
+async function main() {
+ await seedCurrencies();
+ await seedCategories();
+}
+
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
diff --git a/src/app/(auth)/sign-in.tsx b/src/app/(auth)/sign-in.tsx
index b9be1f5..d2ac9bc 100644
--- a/src/app/(auth)/sign-in.tsx
+++ b/src/app/(auth)/sign-in.tsx
@@ -99,7 +99,10 @@ export default function Page() {
});
if (signInAttempt.status === "complete") {
await setActive({ session: signInAttempt.createdSessionId });
- router.replace("/start");
+ // The / route is currently managing redirects based on authentication state.
+ // After signing in, we navigate there to let it handle the redirect.
+ // We can consider changing this in the future if needed so that the user directly lands on the intended page after sign-in.
+ router.replace("/");
} else {
setError(
t("signInFailed", "Sign in failed. Please check your credentials"),
@@ -110,7 +113,7 @@ export default function Page() {
}
}
} catch (err) {
- // Type guard to check if err is an object and has 'errors' property
+ // Type guard to check if err is an object and has an 'errors' property
if (
typeof err === "object" &&
err !== null &&
diff --git a/src/app/(auth)/sign-up.tsx b/src/app/(auth)/sign-up.tsx
index 37e32e5..9ac22f0 100644
--- a/src/app/(auth)/sign-up.tsx
+++ b/src/app/(auth)/sign-up.tsx
@@ -17,6 +17,7 @@ import { languageService } from "~/services/languageService";
import Button from "~/components/ui/button";
import AppImage from "~/components/ui/AppImage";
import { z } from "zod";
+import { trpc } from "~/utils/trpc";
type newErrorType = {
firstname?: string;
@@ -31,7 +32,10 @@ export default function SignUpScreen() {
const router = useRouter();
const { t } = useTranslation();
- // Initialize language when component mounts with better error handling
+ // Create a user mutation
+ const createUser = trpc.users.createUser.useMutation();
+
+ // Initialize language when a component mounts with better error handling
useEffect(() => {
const initLanguage = async () => {
try {
@@ -140,6 +144,8 @@ export default function SignUpScreen() {
});
if (completeSignUp.status === "complete") {
await setActive({ session: completeSignUp.createdSessionId });
+ // Add the user in the database
+ await createUser.mutateAsync();
router.replace("/start");
} else {
setError("Verification failed. Please try again.");
@@ -155,6 +161,8 @@ export default function SignUpScreen() {
});
if (signInAttempt?.status === "complete") {
await setActive({ session: signInAttempt.createdSessionId });
+ // Add the user in the database
+ await createUser.mutateAsync();
router.replace("/start");
} else {
setError("Email already verified. Please sign in.");
diff --git a/src/app/(tabs)/banking/index.tsx b/src/app/(tabs)/banking/index.tsx
index 41f1cd3..e2aa82b 100644
--- a/src/app/(tabs)/banking/index.tsx
+++ b/src/app/(tabs)/banking/index.tsx
@@ -1,24 +1,82 @@
import React from "react";
import { useTheme } from "~/contexts/ThemeContext";
import { ScrollView, StatusBar } from "react-native";
-import { mockDashboardData } from "~/data/mockData";
import { SectionHeader } from "~/components/SectionHeader";
import { AccountItem } from "~/components/AccountItem";
import { useTranslation } from "react-i18next";
import { SafeAreaView } from "react-native-safe-area-context";
+import { trpc } from "~/utils/trpc";
+import type { References } from "~/schemas/welcomeSchema";
+import type { Account } from "~/types";
const Dashboard = () => {
const { colors, isDark } = useTheme();
const { t } = useTranslation();
+ const { data: accounts } = trpc.accounts.getAccounts.useQuery();
+
+ // TODO: Implement loading and error states
+
const handleAccountPress = (accountName: string, accountId: string) => {
console.log(`Pressed ${accountName} with ID: ${accountId}`);
// modal logic here
};
- const handleCardPress = (cardName: string, cardId: string) => {
- console.log(`Pressed ${cardName} card with ID: ${cardId}`);
- // modal logic here
+ // Type-safe reference constants
+ const REFERENCE = React.useMemo(
+ () =>
+ ({
+ PRIVATE: "private" as References,
+ BUSINESS: "business" as References,
+ SAVINGS: "savings" as References,
+ SHARED: "shared" as References,
+ }) as const,
+ [],
+ );
+
+ // Pre-filter accounts once per reference type
+ const privateAccounts = React.useMemo(
+ () => (accounts ?? []).filter((a) => a.reference === REFERENCE.PRIVATE),
+ [accounts, REFERENCE],
+ );
+ const businessAccounts = React.useMemo(
+ () => (accounts ?? []).filter((a) => a.reference === REFERENCE.BUSINESS),
+ [accounts, REFERENCE],
+ );
+ const savingsAccounts = React.useMemo(
+ () => (accounts ?? []).filter((a) => a.reference === REFERENCE.SAVINGS),
+ [accounts, REFERENCE],
+ );
+ const sharedAccounts = React.useMemo(
+ () => (accounts ?? []).filter((a) => a.reference === REFERENCE.SHARED),
+ [accounts, REFERENCE],
+ );
+
+ const hasDailyAccounts =
+ privateAccounts.length > 0 || businessAccounts.length > 0;
+
+ // Reusable section renderer
+ const AccountSection: React.FC<{
+ title: string;
+ accounts: Account[];
+ baseDelay?: number; // delay for SectionHeader; items will start at baseDelay + 100
+ onPress: (accountName: string, accountId: string) => void;
+ }> = ({ title, accounts, baseDelay = 0, onPress }) => {
+ if (!accounts || accounts.length === 0) return null;
+ return (
+ <>
+
+ {accounts.map((account, index) => (
+ onPress(account.bankName, account.id)}
+ />
+ ))}
+ >
+ );
};
return (
@@ -55,61 +113,42 @@ const Dashboard = () => {
onPress={() => handleCardPress(card.title, card.id)}
/>
))} */}
-
-
- {mockDashboardData.accounts.private.map((account, index) => (
- handleAccountPress(account.name, account.id)}
- />
- ))}
-
- {mockDashboardData.accounts.business.map((account, index) => (
- handleAccountPress(account.name, account.id)}
- />
- ))}
+ {hasDailyAccounts ? (
+
+ ) : null}
- {/* Safe Accounts Section */}
-
- {mockDashboardData.accounts.safe.map((account, index) => (
- handleAccountPress(account.name, account.id)}
- />
- ))}
+ {/* Private Accounts Section */}
+
-
- {mockDashboardData.sharedFunds && (
-
- handleAccountPress(
- mockDashboardData.sharedFunds.title,
- mockDashboardData.sharedFunds.id,
- )
- }
- />
- )}
+ {/* Business Accounts Section */}
+
+
+ {/* Savings Accounts Section */}
+
+
+ {/* Shared Funds Section */}
+
);
diff --git a/src/app/index.tsx b/src/app/index.tsx
index c939364..91c4a8a 100644
--- a/src/app/index.tsx
+++ b/src/app/index.tsx
@@ -20,6 +20,7 @@ import { useTranslation } from "react-i18next";
import { languageService } from "~/services/languageService";
import Button from "~/components/ui/button";
import AppImage from "~/components/ui/AppImage";
+import { trpc } from "~/utils/trpc";
const LanguageDropdown = () => {
const [visible, setVisible] = useState(false);
@@ -142,13 +143,48 @@ const LanguageDropdown = () => {
};
export default function Index() {
- const scheme = useColorScheme();
- const { isSignedIn } = useAuth();
- const { t } = useTranslation();
+ const scheme = useColorScheme(); // Used later for theming (icon colors, etc.)
+ const { isSignedIn } = useAuth(); // Clerk auth state; true when a session is present
+ const { t } = useTranslation(); // i18n translations
- if (isSignedIn) {
- return ;
+ // ----------------------------------------------------------------------------
+ // Redirect decision flow
+ // ----------------------------------------------------------------------------
+ // High-level:
+ // - We only query accounts once the user is authenticated (enabled: isSignedIn).
+ // - While the "accounts" query is loading, we DO NOT redirect to avoid flicker.
+ // - After loading:
+ // - If authenticated and no accounts exist -> send user to onboarding (`./start`).
+ // - If authenticated and accounts exist -> send the user to the main app (`./(tabs)/banking`).
+ // - If not authenticated, we fall through and render the marketing/landing UI below.
+ // ----------------------------------------------------------------------------
+
+ const { data: accounts, isLoading: isLoadingAccounts } =
+ trpc.accounts.getAccounts.useQuery(undefined, {
+ // Only fetch accounts when signed in to avoid unauthorized requests
+ enabled: isSignedIn,
+ // Note: You could tune caching behavior here (e.g., `staleTime`) if needed
+ });
+
+ // Redirect once we have both an auth state AND a settled accounts query
+ // Case A: Signed in, accounts finished loading, and none found -> onboarding
+ if (
+ isSignedIn &&
+ !isLoadingAccounts &&
+ (!accounts || accounts?.length === 0)
+ ) {
+ return ;
+ }
+ // Case B: Signed in, accounts finished loading, and at least one found -> the main app
+ else if (
+ isSignedIn &&
+ !isLoadingAccounts &&
+ accounts &&
+ accounts.length > 0
+ ) {
+ return ;
}
+ // Else: Not signed in OR still loading -> render the rest of this screen
const iconColor = scheme === "dark" ? "#E0E0E0" : "#111827";
const iconBackground = scheme === "dark" ? "black" : "white";
diff --git a/src/app/start/index.tsx b/src/app/start/index.tsx
index 6fef10f..00d9649 100644
--- a/src/app/start/index.tsx
+++ b/src/app/start/index.tsx
@@ -21,6 +21,7 @@ import Button from "~/components/ui/button";
import { useRouter } from "expo-router";
import AppImage from "~/components/ui/AppImage";
import { WelcomeFormValues, welcomeSchema } from "~/schemas/welcomeSchema";
+import Select from "~/components/ui/select";
const Home = () => {
const { user, isLoaded } = useUser();
@@ -44,7 +45,7 @@ const Home = () => {
initLanguage();
}, []);
- const createAccount = trpc.account.create.useMutation({
+ const createAccount = trpc.accounts.addAccount.useMutation({
onSuccess: () => {
Alert.alert(t("success"), t("accountCreated"));
reset();
@@ -67,7 +68,7 @@ const Home = () => {
defaultValues: {
bankName: "",
currentAmount: 0,
- reference: "",
+ reference: undefined,
usage: "",
},
mode: "onChange", // Validate on change
@@ -75,11 +76,21 @@ const Home = () => {
const handleCreateAccount = async (data: WelcomeFormValues) => {
await createAccount.mutateAsync({
- ...data,
- currentAmount: data.currentAmount,
+ bankName: data.bankName,
+ currentBalance: data.currentAmount.toString(),
+ reference: data.reference,
+ usage: data.usage,
});
};
+ // Reference options must match backend enum: ["private","business","savings","shared"]
+ const referenceOptions = [
+ { value: "private", label: t("dashboardPrivate", "Private") },
+ { value: "business", label: t("dashboardBusiness", "Business") },
+ { value: "savings", label: t("dashboardSafeAccounts", "Savings") },
+ { value: "shared", label: t("sharedFunds", "Shared") },
+ ];
+
return (
{
(
-
-
-
-
-
-
+ render={({ field: { onChange, value } }) => (
+