diff --git a/.gitignore b/.gitignore
index a547bf3..b5cb4a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,8 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+# env files
+.env
+.env.dev
+.env.prod
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
index 33e12c7..1683e38 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -4,6 +4,9 @@ import vue from 'eslint-plugin-vue'
import tseslint from 'typescript-eslint'
export default [
+ {
+ ignores: ['node_modules', 'dist', '.vite', 'coverage', 'supabase', 'src/types/supabase.ts'],
+ },
js.configs.recommended,
...tseslint.configs.recommended,
...vue.configs['flat/recommended'],
@@ -14,4 +17,4 @@ export default [
parserOptions: { parser: tseslint.parser },
},
},
-]
\ No newline at end of file
+]
diff --git a/package-lock.json b/package-lock.json
index 3ccf29a..ff9f344 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "simple-budget-app",
"version": "0.0.0",
"dependencies": {
+ "@supabase/supabase-js": "^2.112.3",
"@tailwindcss/vite": "^4.3.3",
"pinia": "^4.0.3",
"tailwindcss": "^4.3.3",
@@ -690,6 +691,98 @@
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"license": "MIT"
},
+ "node_modules/@supabase/auth-js": {
+ "version": "2.112.3",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.112.3.tgz",
+ "integrity": "sha512-NA0rsgAlWZPvbhw8aUdmgfpHVgUAcd8zK5ov43l++o1bLIPXZhRiAlRobhwF5AatQuovpqxsMH50F4oyyV4XZw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.112.3",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.112.3.tgz",
+ "integrity": "sha512-gfv481mTOVWtZIJgXupxZpni2V2UWPf6jeF/jOK7HdMHdH+mt6sU0sHHwf0POsPip8ltlulu9OUHgwVzl5ddRw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@supabase/phoenix": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.5.tgz",
+ "integrity": "sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==",
+ "license": "MIT"
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "2.112.3",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.112.3.tgz",
+ "integrity": "sha512-+Mf6uCpzr00bqxwX8hTK2X2L9eAL/1vuOjdEjx6upz9ulb0RmQT16XeU/JkMUlVHw/B46ZnPa2busY4Kd9YCzw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.112.3",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.112.3.tgz",
+ "integrity": "sha512-E6wljXWs7DUOloyIB69i3YFInWE6IyCvgTAbQ0KYxOHv26FdA1KzEXTuzxrYEdf70t406Z9BRwUlGyclGF2FXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/phoenix": "0.4.5",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.112.3",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.112.3.tgz",
+ "integrity": "sha512-oSK61tzlUvg+BWPqpKQCu9qqonsO26btaoAR9D6Gest2aj7xUqToj9rKyaoYOJczkhg9BjqA1REbYy9tPI4bDA==",
+ "license": "MIT",
+ "dependencies": {
+ "iceberg-js": "^0.8.1",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.112.3",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.112.3.tgz",
+ "integrity": "sha512-Jv1bxVQmEJNkjvPEhFaKjPzsh+Ozyew6lWGD+SoYcsclDEP1z7yEvKvfUQfzy0DkxRIQnZNxmmWtAzw5XLTQoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.112.3",
+ "@supabase/functions-js": "2.112.3",
+ "@supabase/postgrest-js": "2.112.3",
+ "@supabase/realtime-js": "2.112.3",
+ "@supabase/storage-js": "2.112.3"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@tailwindcss/node": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
@@ -2480,6 +2573,15 @@
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
+ "node_modules/iceberg-js": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
+ "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3555,6 +3657,12 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index ffb898c..35854a4 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"format": "prettier --write ."
},
"dependencies": {
+ "@supabase/supabase-js": "^2.112.3",
"@tailwindcss/vite": "^4.3.3",
"pinia": "^4.0.3",
"tailwindcss": "^4.3.3",
diff --git a/src/App.vue b/src/App.vue
index 013363c..9f7a593 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,5 +1,5 @@
-
+
diff --git a/src/composables/useSupabase.ts b/src/composables/useSupabase.ts
new file mode 100644
index 0000000..2ab178b
--- /dev/null
+++ b/src/composables/useSupabase.ts
@@ -0,0 +1,15 @@
+import type { Database } from '@/types/supabase'
+import { createClient } from '@supabase/supabase-js'
+
+const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
+const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
+
+if (!supabaseUrl || !supabasePublishableKey) {
+ throw new Error('Missing Supabase env variables. Check your .env file.')
+}
+
+const supabase = createClient(supabaseUrl, supabasePublishableKey)
+
+export function useSupabase() {
+ return supabase
+}
diff --git a/src/main.ts b/src/main.ts
index 2425c0f..fc37965 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,5 +1,12 @@
+import { createPinia } from 'pinia'
import { createApp } from 'vue'
-import './style.css'
import App from './App.vue'
+import router from './router/index.ts'
+import './style.css'
-createApp(App).mount('#app')
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
diff --git a/src/pages/DashboardPage.vue b/src/pages/DashboardPage.vue
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/HistoryPage.vue b/src/pages/HistoryPage.vue
new file mode 100644
index 0000000..e69de29
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..903dac0
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,13 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import DashboardPage from '../pages/DashboardPage.vue'
+import HistoryPage from '../pages/HistoryPage.vue'
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes: [
+ { path: '/', name: 'dashboard', component: DashboardPage },
+ { path: '/history', name: 'history', component: HistoryPage },
+ ],
+})
+
+export default router
\ No newline at end of file
diff --git a/src/stores/budget.ts b/src/stores/budget.ts
new file mode 100644
index 0000000..afdffb4
--- /dev/null
+++ b/src/stores/budget.ts
@@ -0,0 +1,29 @@
+import { defineStore } from 'pinia'
+
+export type EntryType = 'revenue' | 'investments' | 'mandatory' | 'personal'
+
+export interface BudgetEntry {
+ id: string
+ label: string
+ amount: number
+}
+
+export const useBudgetStore = defineStore('budget', {
+ state: () => ({
+ revenue: [] as BudgetEntry[],
+ investments: [] as BudgetEntry[],
+ mandatory: [] as BudgetEntry[],
+ personal: [] as BudgetEntry[],
+ }),
+
+ getters: {
+ // TODO: sum each column
+ // TODO: remainingMoney = revenue - investments - mandatory - personal
+ },
+
+ actions: {
+ // TODO: addEntry(type: EntryType, entry: BudgetEntry)
+ // TODO: removeEntry(type: EntryType, id: string)
+ // TODO: saveMonth() -> snapshot current state to DB, then reset or roll over
+ },
+})
diff --git a/src/types/supabase.ts b/src/types/supabase.ts
new file mode 100644
index 0000000..ef0143d
--- /dev/null
+++ b/src/types/supabase.ts
@@ -0,0 +1,234 @@
+export type Json =
+ | string
+ | number
+ | boolean
+ | null
+ | { [key: string]: Json | undefined }
+ | Json[]
+
+export type Database = {
+ graphql_public: {
+ Tables: {
+ [_ in never]: never
+ }
+ Views: {
+ [_ in never]: never
+ }
+ Functions: {
+ graphql: {
+ Args: {
+ extensions?: Json
+ operationName?: string
+ query?: string
+ variables?: Json
+ }
+ Returns: Json
+ }
+ }
+ Enums: {
+ [_ in never]: never
+ }
+ CompositeTypes: {
+ [_ in never]: never
+ }
+ }
+ public: {
+ Tables: {
+ budget_entries: {
+ Row: {
+ amount: number
+ created_at: string
+ id: string
+ label: string
+ month_id: string | null
+ type: string
+ }
+ Insert: {
+ amount: number
+ created_at?: string
+ id?: string
+ label: string
+ month_id?: string | null
+ type: string
+ }
+ Update: {
+ amount?: number
+ created_at?: string
+ id?: string
+ label?: string
+ month_id?: string | null
+ type?: string
+ }
+ Relationships: [
+ {
+ foreignKeyName: "budget_entries_month_id_fkey"
+ columns: ["month_id"]
+ isOneToOne: false
+ referencedRelation: "months"
+ referencedColumns: ["id"]
+ },
+ ]
+ }
+ months: {
+ Row: {
+ created_at: string
+ id: string
+ label: string
+ snapshot: Json
+ }
+ Insert: {
+ created_at?: string
+ id?: string
+ label: string
+ snapshot: Json
+ }
+ Update: {
+ created_at?: string
+ id?: string
+ label?: string
+ snapshot?: Json
+ }
+ Relationships: []
+ }
+ }
+ Views: {
+ [_ in never]: never
+ }
+ Functions: {
+ [_ in never]: never
+ }
+ Enums: {
+ [_ in never]: never
+ }
+ CompositeTypes: {
+ [_ in never]: never
+ }
+ }
+}
+
+type DatabaseWithoutInternals = Omit
+
+type DefaultSchema = DatabaseWithoutInternals[Extract]
+
+export type Tables<
+ DefaultSchemaTableNameOrOptions extends
+ | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
+ | { schema: keyof DatabaseWithoutInternals },
+ TableName extends DefaultSchemaTableNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+ }
+ ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
+ DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
+ : never = never,
+> = DefaultSchemaTableNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+}
+ ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
+ DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] &
+ DefaultSchema["Views"])
+ ? (DefaultSchema["Tables"] &
+ DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : never
+
+export type TablesInsert<
+ DefaultSchemaTableNameOrOptions extends
+ | keyof DefaultSchema["Tables"]
+ | { schema: keyof DatabaseWithoutInternals },
+ TableName extends DefaultSchemaTableNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+ }
+ ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = DefaultSchemaTableNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+}
+ ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
+ ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : never
+
+export type TablesUpdate<
+ DefaultSchemaTableNameOrOptions extends
+ | keyof DefaultSchema["Tables"]
+ | { schema: keyof DatabaseWithoutInternals },
+ TableName extends DefaultSchemaTableNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+ }
+ ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = DefaultSchemaTableNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+}
+ ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
+ ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : never
+
+export type Enums<
+ DefaultSchemaEnumNameOrOptions extends
+ | keyof DefaultSchema["Enums"]
+ | { schema: keyof DatabaseWithoutInternals },
+ EnumName extends DefaultSchemaEnumNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+ }
+ ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
+ : never = never,
+> = DefaultSchemaEnumNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+}
+ ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
+ : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
+ ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
+ : never
+
+export type CompositeTypes<
+ PublicCompositeTypeNameOrOptions extends
+ | keyof DefaultSchema["CompositeTypes"]
+ | { schema: keyof DatabaseWithoutInternals },
+ CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+ }
+ ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
+ : never = never,
+> = PublicCompositeTypeNameOrOptions extends {
+ schema: keyof DatabaseWithoutInternals
+}
+ ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
+ : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
+ ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
+ : never
+
+export const Constants = {
+ graphql_public: {
+ Enums: {},
+ },
+ public: {
+ Enums: {},
+ },
+} as const
+
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..70efb1a
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,10 @@
+///
+
+interface ImportMetaEnv {
+ readonly VITE_SUPABASE_URL: string
+ readonly VITE_SUPABASE_PUBLISHABLE_KEY: string
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv
+}
diff --git a/supabase/.gitignore b/supabase/.gitignore
new file mode 100644
index 0000000..ad9264f
--- /dev/null
+++ b/supabase/.gitignore
@@ -0,0 +1,8 @@
+# Supabase
+.branches
+.temp
+
+# dotenvx
+.env.keys
+.env.local
+.env.*.local
diff --git a/supabase/config.toml b/supabase/config.toml
new file mode 100644
index 0000000..03c3979
--- /dev/null
+++ b/supabase/config.toml
@@ -0,0 +1,414 @@
+# For detailed configuration reference documentation, visit:
+# https://supabase.com/docs/guides/local-development/cli/config
+# A string used to distinguish different Supabase projects on the same host. Defaults to the
+# working directory name when running `supabase init`.
+project_id = "simple-budget-app"
+
+[api]
+enabled = true
+# Port to use for the API URL.
+port = 54321
+# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
+# endpoints. `public` and `graphql_public` schemas are included by default.
+schemas = ["public", "graphql_public"]
+# Extra schemas to add to the search_path of every request.
+extra_search_path = ["public", "extensions"]
+# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
+# for accidental or malicious requests.
+max_rows = 1000
+# Controls whether new tables, views, sequences and functions created in the `public` schema by
+# `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`)
+# without explicit GRANTs. When unset, new entities are NOT auto-exposed, matching the new cloud
+# default. Set to `true` to keep the legacy behaviour of auto-exposing new entities; this is
+# deprecated and the field is removed on 2026-10-30 once the always-revoked behaviour is permanent.
+# auto_expose_new_tables = true
+
+[api.tls]
+# Enable HTTPS endpoints locally using a self-signed certificate.
+enabled = false
+# Paths to self-signed certificate pair.
+# cert_path = "../certs/my-cert.pem"
+# key_path = "../certs/my-key.pem"
+
+[db]
+# Port to use for the local database URL.
+port = 54322
+# Port used by db diff command to initialize the shadow database.
+shadow_port = 54320
+# Maximum amount of time to wait for health check when starting the local database.
+health_timeout = "2m"
+# The database major version to use. This has to be the same as your remote database's. Run `SHOW
+# server_version;` on the remote database to check.
+major_version = 17
+
+[db.pooler]
+enabled = false
+# Port to use for the local connection pooler.
+port = 54329
+# Specifies when a server connection can be reused by other clients.
+# Configure one of the supported pooler modes: `transaction`, `session`.
+pool_mode = "transaction"
+# How many server connections to allow per user/database pair.
+default_pool_size = 20
+# Maximum number of client connections allowed.
+max_client_conn = 100
+
+# [db.vault]
+# secret_key = "env(SECRET_VALUE)"
+
+[db.migrations]
+# If disabled, migrations will be skipped during a db push or reset.
+enabled = true
+# Specifies an ordered list of schema files, directories, or glob patterns that describe your database.
+# Supports paths relative to supabase directory: "./schemas/*.sql", "./database".
+schema_paths = []
+
+[db.seed]
+# If enabled, seeds the database after migrations during a db reset.
+enabled = true
+# Specifies an ordered list of seed files to load during db reset.
+# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
+sql_paths = ["./seed.sql"]
+
+[db.network_restrictions]
+# Enable management of network restrictions.
+enabled = false
+# List of IPv4 CIDR blocks allowed to connect to the database.
+# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
+allowed_cidrs = ["0.0.0.0/0"]
+# List of IPv6 CIDR blocks allowed to connect to the database.
+# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
+allowed_cidrs_v6 = ["::/0"]
+
+# Uncomment to reject non-secure connections to the database.
+# [db.ssl_enforcement]
+# enabled = true
+
+[realtime]
+enabled = true
+# Bind realtime via either IPv4 or IPv6. (default: IPv4)
+# ip_version = "IPv6"
+# The maximum length in bytes of HTTP request headers. (default: 4096)
+# max_header_length = 4096
+
+[studio]
+enabled = true
+# Port to use for Supabase Studio.
+port = 54323
+# External URL of the API server that frontend connects to.
+api_url = "http://127.0.0.1"
+# OpenAI API Key to use for Supabase AI in the Supabase Studio.
+openai_api_key = "env(OPENAI_API_KEY)"
+
+# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
+# are monitored, and you can view the emails that would have been sent from the web interface.
+[local_smtp]
+enabled = true
+# Port to use for the email testing server web interface.
+port = 54324
+# Uncomment to expose additional ports for testing user applications that send emails.
+# smtp_port = 54325
+# pop3_port = 54326
+# admin_email = "admin@email.com"
+# sender_name = "Admin"
+
+[storage]
+enabled = true
+# The maximum file size allowed (e.g. "5MB", "500KB").
+file_size_limit = "50MiB"
+
+# Uncomment to configure local storage buckets
+# [storage.buckets.images]
+# public = false
+# file_size_limit = "50MiB"
+# allowed_mime_types = ["image/png", "image/jpeg"]
+# objects_path = "./images"
+
+# Allow connections via S3 compatible clients
+[storage.s3_protocol]
+enabled = true
+
+# Image transformation API is available to Supabase Pro plan.
+# [storage.image_transformation]
+# enabled = true
+
+# Store analytical data in S3 for running ETL jobs over Iceberg Catalog
+# This feature is only available on the hosted platform.
+[storage.analytics]
+enabled = false
+max_namespaces = 5
+max_tables = 10
+max_catalogs = 2
+
+# Analytics Buckets is available to Supabase Pro plan.
+# [storage.analytics.buckets.my-warehouse]
+
+# Store vector embeddings in S3 for large and durable datasets
+[storage.vector]
+enabled = true
+max_buckets = 10
+max_indexes = 5
+
+# Vector Buckets is available to Supabase Pro plan.
+# [storage.vector.buckets.documents-openai]
+
+[auth]
+enabled = true
+# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
+# in emails.
+site_url = "http://127.0.0.1:3000"
+# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended.
+# external_url = ""
+# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
+additional_redirect_urls = ["https://127.0.0.1:3000"]
+# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
+jwt_expiry = 3600
+# JWT issuer URL. If not set, defaults to auth.external_url.
+# jwt_issuer = ""
+# Path to JWT signing key. DO NOT commit your signing keys file to git.
+# signing_keys_path = "./signing_keys.json"
+# If disabled, the refresh token will never expire.
+enable_refresh_token_rotation = true
+# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
+# Requires enable_refresh_token_rotation = true.
+refresh_token_reuse_interval = 10
+# Allow/disallow new user signups to your project.
+enable_signup = true
+# Allow/disallow anonymous sign-ins to your project.
+enable_anonymous_sign_ins = false
+# Allow/disallow testing manual linking of accounts
+enable_manual_linking = false
+# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
+minimum_password_length = 6
+# Passwords that do not meet the following requirements will be rejected as weak. Supported values
+# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
+password_requirements = ""
+
+# Configure passkey sign-ins.
+# [auth.passkey]
+# enabled = false
+
+# Configure WebAuthn relying party settings (required when passkey is enabled).
+# [auth.webauthn]
+# rp_display_name = "Supabase"
+# rp_id = "localhost"
+# rp_origins = ["http://127.0.0.1:3000"]
+
+[auth.rate_limit]
+# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
+email_sent = 2
+# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
+sms_sent = 30
+# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
+anonymous_users = 30
+# Number of sessions that can be refreshed in a 5 minute interval per IP address.
+token_refresh = 150
+# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
+sign_in_sign_ups = 30
+# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
+token_verifications = 30
+# Number of Web3 logins that can be made in a 5 minute interval per IP address.
+web3 = 30
+
+# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
+# [auth.captcha]
+# enabled = true
+# provider = "hcaptcha"
+# secret = ""
+
+[auth.email]
+# Allow/disallow new user signups via email to your project.
+enable_signup = true
+# If enabled, a user will be required to confirm any email change on both the old, and new email
+# addresses. If disabled, only the new email is required to confirm.
+double_confirm_changes = true
+# If enabled, users need to confirm their email address before signing in.
+enable_confirmations = false
+# If enabled, users will need to reauthenticate or have logged in recently to change their password.
+secure_password_change = false
+# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
+max_frequency = "1s"
+# Number of characters used in the email OTP.
+otp_length = 6
+# Number of seconds before the email OTP expires (defaults to 1 hour).
+otp_expiry = 3600
+
+# Use a production-ready SMTP server
+# [auth.email.smtp]
+# enabled = true
+# host = "smtp.sendgrid.net"
+# port = 587
+# user = "apikey"
+# pass = "env(SENDGRID_API_KEY)"
+# admin_email = "admin@email.com"
+# sender_name = "Admin"
+
+# Uncomment to customize email template
+# [auth.email.template.invite]
+# subject = "You have been invited"
+# content_path = "./supabase/templates/invite.html"
+
+# Uncomment to customize notification email template
+# [auth.email.notification.password_changed]
+# enabled = true
+# subject = "Your password has been changed"
+# content_path = "./supabase/templates/password_changed_notification.html"
+
+[auth.sms]
+# Allow/disallow new user signups via SMS to your project.
+enable_signup = false
+# If enabled, users need to confirm their phone number before signing in.
+enable_confirmations = false
+# Template for sending OTP to users
+template = "Your code is {{ .Code }}"
+# Controls the minimum amount of time that must pass before sending another sms otp.
+max_frequency = "5s"
+
+# Use pre-defined map of phone number to OTP for testing.
+# [auth.sms.test_otp]
+# 4152127777 = "123456"
+
+# Configure logged in session timeouts.
+# [auth.sessions]
+# Force log out after the specified duration.
+# timebox = "24h"
+# Force log out if the user has been inactive longer than the specified duration.
+# inactivity_timeout = "8h"
+
+# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
+# [auth.hook.before_user_created]
+# enabled = true
+# uri = "pg-functions://postgres/auth/before-user-created-hook"
+
+# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
+# [auth.hook.custom_access_token]
+# enabled = true
+# uri = "pg-functions:////"
+
+# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
+[auth.sms.twilio]
+enabled = false
+account_sid = ""
+message_service_sid = ""
+# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
+auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
+
+# Multi-factor-authentication is available to Supabase Pro plan.
+[auth.mfa]
+# Control how many MFA factors can be enrolled at once per user.
+max_enrolled_factors = 10
+
+# Control MFA via App Authenticator (TOTP)
+[auth.mfa.totp]
+enroll_enabled = false
+verify_enabled = false
+
+# Configure MFA via Phone Messaging
+[auth.mfa.phone]
+enroll_enabled = false
+verify_enabled = false
+otp_length = 6
+template = "Your code is {{ .Code }}"
+max_frequency = "5s"
+
+# Configure MFA via WebAuthn
+# [auth.mfa.web_authn]
+# enroll_enabled = true
+# verify_enabled = true
+
+# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
+# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
+# `twitter`, `x`, `slack`, `spotify`, `workos`, `zoom`.
+[auth.external.apple]
+enabled = false
+client_id = ""
+# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
+secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
+# Overrides the default auth callback URL derived from auth.external_url.
+redirect_uri = ""
+# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
+# or any other third-party OIDC providers.
+url = ""
+# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
+skip_nonce_check = false
+# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address.
+email_optional = false
+
+# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
+# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
+[auth.web3.solana]
+enabled = false
+
+# Use Firebase Auth as a third-party provider alongside Supabase Auth.
+[auth.third_party.firebase]
+enabled = false
+# project_id = "my-firebase-project"
+
+# Use Auth0 as a third-party provider alongside Supabase Auth.
+[auth.third_party.auth0]
+enabled = false
+# tenant = "my-auth0-tenant"
+# tenant_region = "us"
+
+# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
+[auth.third_party.aws_cognito]
+enabled = false
+# user_pool_id = "my-user-pool-id"
+# user_pool_region = "us-east-1"
+
+# Use Clerk as a third-party provider alongside Supabase Auth.
+[auth.third_party.clerk]
+enabled = false
+# Obtain from https://clerk.com/setup/supabase
+# domain = "example.clerk.accounts.dev"
+
+# OAuth server configuration
+[auth.oauth_server]
+# Enable OAuth server functionality
+enabled = false
+# Path for OAuth consent flow UI
+authorization_url_path = "/oauth/consent"
+# Allow dynamic client registration
+allow_dynamic_registration = false
+
+[edge_runtime]
+enabled = true
+# Supported request policies: `oneshot`, `per_worker`.
+# `per_worker` (default) — enables hot reload during local development.
+# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks).
+policy = "per_worker"
+# Port to attach the Chrome inspector for debugging edge functions.
+inspector_port = 8083
+# The Deno major version to use.
+deno_version = 2
+
+# [edge_runtime.secrets]
+# secret_key = "env(SECRET_VALUE)"
+
+[analytics]
+enabled = true
+port = 54327
+# Configure one of the supported backends: `postgres`, `bigquery`.
+backend = "postgres"
+
+# Experimental features may be deprecated any time
+[experimental]
+# Configures Postgres storage engine to use OrioleDB (S3)
+orioledb_version = ""
+# Configures S3 bucket URL, eg. .s3-.amazonaws.com
+s3_host = "env(S3_HOST)"
+# Configures S3 bucket region, eg. us-east-1
+s3_region = "env(S3_REGION)"
+# Configures AWS_ACCESS_KEY_ID for S3 bucket
+s3_access_key = "env(S3_ACCESS_KEY)"
+# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
+s3_secret_key = "env(S3_SECRET_KEY)"
+
+# pg-delta is the schema diff engine for db diff / db pull / db remote commit.
+# Set enabled = false to fall back to the legacy migra engine.
+[experimental.pgdelta]
+enabled = true
+# Directory under `supabase/` where declarative files are written.
+# declarative_schema_path = "./schemas"
+# JSON string passed through to pg-delta SQL formatting.
+# format_options = "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}"
diff --git a/supabase/migrations/20260821135107_create_budget_tables.sql b/supabase/migrations/20260821135107_create_budget_tables.sql
new file mode 100644
index 0000000..b6e033a
--- /dev/null
+++ b/supabase/migrations/20260821135107_create_budget_tables.sql
@@ -0,0 +1,21 @@
+create table months (
+ id uuid primary key default gen_random_uuid(),
+ label text not null,
+ snapshot jsonb not null,
+ created_at timestamptz not null default now()
+);
+
+create table budget_entries (
+ id uuid primary key default gen_random_uuid(),
+ type text not null check (type in ('revenue', 'investments', 'mandatory', 'personal')),
+ label text not null,
+ amount numeric not null,
+ month_id uuid references months(id) on delete set null,
+ created_at timestamptz not null default now()
+);
+
+alter table budget_entries enable row level security;
+alter table months enable row level security;
+
+create policy "Allow all" on budget_entries for all using (true);
+create policy "Allow all" on months for all using (true);
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
index d72aa75..60ab997 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -4,6 +4,9 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
"allowArbitraryExtensions": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ },
/* Linting */
"noUnusedLocals": true,
diff --git a/tsconfig.json b/tsconfig.json
index 1ffef60..389bd92 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
{
"files": [],
- "references": [
- { "path": "./tsconfig.app.json" },
- { "path": "./tsconfig.node.json" }
- ]
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
+ "compilerOptions": {
+
+ }
}
diff --git a/vite.config.ts b/vite.config.ts
index 4815a12..a64f29a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,14 @@
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
+import vue from '@vitejs/plugin-vue'
+import { fileURLToPath, URL } from 'node:url'
+import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), tailwindcss()],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
+ },
+ },
})