mirror of
https://github.com/Lyxminxx/jast-frontend.git
synced 2026-10-11 20:57:44 +02:00
fixed login finally
This commit is contained in:
1 parent
f0094826e7
commit
e07036acf4
10 files changed
+116
-218
No files matched your search
+18
-14
@@ -1,5 +1,6 @@
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
from services.storage import get_setting
|
from services.storage import AppStorage
|
||||||
|
from services.api import ApiClient
|
||||||
from themes import get_theme
|
from themes import get_theme
|
||||||
from views.dashboard import DashboardView
|
from views.dashboard import DashboardView
|
||||||
from views.login import LoginView
|
from views.login import LoginView
|
||||||
@@ -8,15 +9,20 @@ from views.statistics import StatisticsView
|
|||||||
from views.account import AccountView
|
from views.account import AccountView
|
||||||
from views.register import RegisterView
|
from views.register import RegisterView
|
||||||
|
|
||||||
|
# 1. Change to async def
|
||||||
def main(page: ft.Page):
|
async def main(page: ft.Page):
|
||||||
page.theme_mode = ft.ThemeMode.DARK
|
page.theme_mode = ft.ThemeMode.DARK
|
||||||
forest = get_theme()
|
forest = get_theme()
|
||||||
page.theme = forest
|
page.theme = forest
|
||||||
page.bgcolor = forest.color_scheme.surface
|
page.bgcolor = forest.color_scheme.surface
|
||||||
|
|
||||||
page.title = "Jast Tracker"
|
page.title = "Jast Tracker"
|
||||||
|
|
||||||
|
app_storage = AppStorage(page)
|
||||||
|
# 2. Await the initial cache load BEFORE running the rest of the app
|
||||||
|
await app_storage.init()
|
||||||
|
|
||||||
|
api_client = ApiClient(app_storage)
|
||||||
|
|
||||||
def navigate(route: str):
|
def navigate(route: str):
|
||||||
page.route = route
|
page.route = route
|
||||||
route_change()
|
route_change()
|
||||||
@@ -25,30 +31,28 @@ def main(page: ft.Page):
|
|||||||
page.views.clear()
|
page.views.clear()
|
||||||
|
|
||||||
if page.route == "/server":
|
if page.route == "/server":
|
||||||
page.views.append(ServerPickerView(page, navigate))
|
page.views.append(ServerPickerView(page, navigate, app_storage, api_client))
|
||||||
elif page.route == "/login":
|
elif page.route == "/login":
|
||||||
page.views.append(LoginView(page, navigate))
|
page.views.append(LoginView(page, navigate, app_storage, api_client))
|
||||||
elif page.route == "/register":
|
elif page.route == "/register":
|
||||||
page.views.append(RegisterView(page, navigate))
|
page.views.append(RegisterView(page, navigate, app_storage, api_client))
|
||||||
elif page.route == "/dashboard":
|
elif page.route == "/dashboard":
|
||||||
page.views.append(DashboardView(page, navigate))
|
page.views.append(DashboardView(page, navigate, app_storage, api_client))
|
||||||
elif page.route == "/statistics":
|
elif page.route == "/statistics":
|
||||||
page.views.append(StatisticsView(page, navigate))
|
page.views.append(StatisticsView(page, navigate, app_storage, api_client))
|
||||||
elif page.route == "/account":
|
elif page.route == "/account":
|
||||||
page.views.append(AccountView(page, navigate))
|
page.views.append(AccountView(page, navigate, app_storage, api_client))
|
||||||
|
|
||||||
page.update()
|
page.update()
|
||||||
|
|
||||||
page.on_route_change = route_change
|
page.on_route_change = route_change
|
||||||
|
|
||||||
# Startup route guard
|
if not app_storage.get_setting("server_url"):
|
||||||
if not get_setting("server_url"):
|
|
||||||
navigate("/server")
|
navigate("/server")
|
||||||
elif not get_setting("auth_token"):
|
elif not app_storage.get_setting("auth_token"):
|
||||||
navigate("/login")
|
navigate("/login")
|
||||||
else:
|
else:
|
||||||
navigate("/dashboard")
|
navigate("/dashboard")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
ft.run(main)
|
ft.run(main)
|
||||||
+47
-137
@@ -1,25 +1,23 @@
|
|||||||
import httpx
|
import httpx
|
||||||
from services.storage import get_setting, remove_setting, save_setting
|
|
||||||
|
|
||||||
class ApiClient:
|
class ApiClient:
|
||||||
def __init__(self, timeout: float = 10.0):
|
def __init__(self, storage, timeout: float = 10.0):
|
||||||
|
self.storage = storage
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_url(self) -> str | None:
|
def base_url(self) -> str | None:
|
||||||
return get_setting("server_url")
|
return self.storage.get_setting("server_url")
|
||||||
|
|
||||||
def get_headers(self) -> dict[str, str]:
|
def get_headers(self) -> dict[str, str]:
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
token = get_setting("auth_token")
|
token = self.storage.get_setting("auth_token")
|
||||||
if token:
|
if token:
|
||||||
headers["Authorization"] = f"Bearer {token}"
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def get_me(self) -> tuple[bool, dict | str]:
|
def get_me(self) -> tuple[bool, dict | str]:
|
||||||
"""Fetches profile info for current authenticated user."""
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.get(f"{self.base_url}/auth/me", headers=self.get_headers())
|
res = client.get(f"{self.base_url}/auth/me", headers=self.get_headers())
|
||||||
@@ -30,22 +28,18 @@ class ApiClient:
|
|||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def get_categories(self) -> tuple[bool, list]:
|
def get_categories(self) -> tuple[bool, list]:
|
||||||
"""Fetches available categories from Django Ninja."""
|
if not self.base_url: return False, []
|
||||||
if not self.base_url:
|
|
||||||
return False, []
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.get(f"{self.base_url}/categories", headers=self.get_headers())
|
res = client.get(f"{self.base_url}/categories", headers=self.get_headers())
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
return True, res.json()
|
return True, res.json()
|
||||||
return False, []
|
return False, []
|
||||||
except Exception as err:
|
except Exception:
|
||||||
return False, []
|
return False, []
|
||||||
|
|
||||||
def get_transactions(self) -> tuple[bool, list | str]:
|
def get_transactions(self) -> tuple[bool, list | str]:
|
||||||
"""Fetches transactions list from Django Ninja."""
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.get(f"{self.base_url}/transactions", headers=self.get_headers())
|
res = client.get(f"{self.base_url}/transactions", headers=self.get_headers())
|
||||||
@@ -55,142 +49,81 @@ class ApiClient:
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def create_transaction(
|
def create_transaction(self, title: str, amount: str | float, date_str: str, category_id: int | None = None) -> tuple[bool, str]:
|
||||||
self, title: str, amount: str | float, date_str: str, category_id: int | None = None
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
) -> tuple[bool, str]:
|
payload = {"title": title, "amount": str(amount), "date": date_str, "category_id": category_id}
|
||||||
"""Posts a new transaction."""
|
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
payload = {
|
|
||||||
"title": title,
|
|
||||||
"amount": str(amount),
|
|
||||||
"date": date_str,
|
|
||||||
"category_id": category_id,
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.post(
|
res = client.post(f"{self.base_url}/transactions", json=payload, headers=self.get_headers())
|
||||||
f"{self.base_url}/transactions",
|
if res.status_code == 200: return True, "Transaction created"
|
||||||
json=payload,
|
|
||||||
headers=self.get_headers(),
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
return True, "Transaction created"
|
|
||||||
return False, f"Error creating transaction ({res.status_code})"
|
return False, f"Error creating transaction ({res.status_code})"
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def update_transaction(
|
def update_transaction(self, transaction_id: int, title: str, amount: str | float, date_str: str, category_id: int | None = None) -> tuple[bool, str]:
|
||||||
self, transaction_id: int, title: str, amount: str | float, date_str: str, category_id: int | None = None
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
) -> tuple[bool, str]:
|
payload = {"title": title, "amount": str(amount), "date": date_str, "category_id": category_id}
|
||||||
"""Updates an existing transaction by ID."""
|
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
payload = {
|
|
||||||
"title": title,
|
|
||||||
"amount": str(amount),
|
|
||||||
"date": date_str,
|
|
||||||
"category_id": category_id,
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.put(
|
res = client.put(f"{self.base_url}/transactions/{transaction_id}", json=payload, headers=self.get_headers())
|
||||||
f"{self.base_url}/transactions/{transaction_id}",
|
if res.status_code == 200: return True, "Transaction updated"
|
||||||
json=payload,
|
|
||||||
headers=self.get_headers(),
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
return True, "Transaction updated"
|
|
||||||
return False, f"Error updating transaction ({res.status_code})"
|
return False, f"Error updating transaction ({res.status_code})"
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def delete_transaction(self, transaction_id: int) -> tuple[bool, str]:
|
def delete_transaction(self, transaction_id: int) -> tuple[bool, str]:
|
||||||
"""Deletes a transaction by ID."""
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.delete(
|
res = client.delete(f"{self.base_url}/transactions/{transaction_id}", headers=self.get_headers())
|
||||||
f"{self.base_url}/transactions/{transaction_id}",
|
if res.status_code == 200: return True, "Transaction deleted"
|
||||||
headers=self.get_headers(),
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
return True, "Transaction deleted"
|
|
||||||
return False, f"Error deleting transaction ({res.status_code})"
|
return False, f"Error deleting transaction ({res.status_code})"
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def login(self, username: str, password: str) -> tuple[bool, str]:
|
def login(self, username: str, password: str) -> tuple[bool, str]:
|
||||||
if not self.base_url:
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
return False, "Server URL is not configured."
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
response = client.post(
|
response = client.post(f"{self.base_url}/auth/login", json={"username": username, "password": password})
|
||||||
f"{self.base_url}/auth/login",
|
|
||||||
json={"username": username, "password": password},
|
|
||||||
)
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
save_setting("auth_token", data.get("access_token"))
|
self.storage.save_setting("auth_token", data.get("access_token"))
|
||||||
if data.get("refresh_token"):
|
if data.get("refresh_token"):
|
||||||
save_setting("refresh_token", data.get("refresh_token"))
|
self.storage.save_setting("refresh_token", data.get("refresh_token"))
|
||||||
return True, "Login successful"
|
return True, "Login successful"
|
||||||
return False, "Invalid username or password"
|
return False, "Invalid username or password"
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def logout(self) -> None:
|
def logout(self) -> None:
|
||||||
remove_setting("auth_token")
|
self.storage.remove_setting("auth_token")
|
||||||
remove_setting("refresh_token")
|
self.storage.remove_setting("refresh_token")
|
||||||
|
|
||||||
def update_me(
|
def update_me(self, username: str | None = None, email: str | None = None, first_name: str | None = None, last_name: str | None = None, password: str | None = None) -> tuple[bool, dict | str]:
|
||||||
self,
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
username: str | None = None,
|
|
||||||
email: str | None = None,
|
|
||||||
first_name: str | None = None,
|
|
||||||
last_name: str | None = None,
|
|
||||||
password: str | None = None,
|
|
||||||
) -> tuple[bool, dict | str]:
|
|
||||||
"""Updates profile details for the authenticated user."""
|
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
payload = {}
|
payload = {}
|
||||||
if username:
|
if username: payload["username"] = username
|
||||||
payload["username"] = username
|
if email is not None: payload["email"] = email
|
||||||
if email is not None:
|
if first_name is not None: payload["first_name"] = first_name
|
||||||
payload["email"] = email
|
if last_name is not None: payload["last_name"] = last_name
|
||||||
if first_name is not None:
|
if password: payload["password"] = password
|
||||||
payload["first_name"] = first_name
|
|
||||||
if last_name is not None:
|
|
||||||
payload["last_name"] = last_name
|
|
||||||
if password:
|
|
||||||
payload["password"] = password
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.put(
|
res = client.put(f"{self.base_url}/auth/me", json=payload, headers=self.get_headers())
|
||||||
f"{self.base_url}/auth/me",
|
|
||||||
json=payload,
|
|
||||||
headers=self.get_headers(),
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
|
try: return True, res.json()
|
||||||
|
except Exception: return True, {}
|
||||||
try:
|
try:
|
||||||
return True, res.json()
|
return False, res.json().get("detail", f"Update failed ({res.status_code})")
|
||||||
except Exception:
|
|
||||||
return True, {}
|
|
||||||
try:
|
|
||||||
err_json = res.json()
|
|
||||||
return False, err_json.get("detail", f"Update failed ({res.status_code})")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return False, f"Update failed ({res.status_code}): {res.text}"
|
return False, f"Update failed ({res.status_code}): {res.text}"
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def delete_me(self) -> tuple[bool, str]:
|
def delete_me(self) -> tuple[bool, str]:
|
||||||
"""Deletes the current user account."""
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.delete(f"{self.base_url}/auth/me", headers=self.get_headers())
|
res = client.delete(f"{self.base_url}/auth/me", headers=self.get_headers())
|
||||||
@@ -201,44 +134,21 @@ class ApiClient:
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
|
|
||||||
def register(
|
def register(self, username: str, password: str, email: str | None = None, first_name: str | None = None, last_name: str | None = None) -> tuple[bool, str]:
|
||||||
self,
|
if not self.base_url: return False, "Server URL is not configured."
|
||||||
username: str,
|
payload = {"username": username, "password": password, "email": email or "", "first_name": first_name or "", "last_name": last_name or ""}
|
||||||
password: str,
|
|
||||||
email: str | None = None,
|
|
||||||
first_name: str | None = None,
|
|
||||||
last_name: str | None = None,
|
|
||||||
) -> tuple[bool, str]:
|
|
||||||
"""Registers a new user account and saves the auth tokens."""
|
|
||||||
if not self.base_url:
|
|
||||||
return False, "Server URL is not configured."
|
|
||||||
payload = {
|
|
||||||
"username": username,
|
|
||||||
"password": password,
|
|
||||||
"email": email or "",
|
|
||||||
"first_name": first_name or "",
|
|
||||||
"last_name": last_name or "",
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
with httpx.Client(timeout=self.timeout) as client:
|
with httpx.Client(timeout=self.timeout) as client:
|
||||||
res = client.post(
|
res = client.post(f"{self.base_url}/auth/register", json=payload)
|
||||||
f"{self.base_url}/auth/register",
|
|
||||||
json=payload,
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
data = res.json()
|
data = res.json()
|
||||||
save_setting("auth_token", data.get("access_token"))
|
self.storage.save_setting("auth_token", data.get("access_token"))
|
||||||
if data.get("refresh_token"):
|
if data.get("refresh_token"):
|
||||||
save_setting("refresh_token", data.get("refresh_token"))
|
self.storage.save_setting("refresh_token", data.get("refresh_token"))
|
||||||
return True, "Registration successful"
|
return True, "Registration successful"
|
||||||
|
|
||||||
try:
|
try: err_detail = res.json().get("detail", "Registration failed")
|
||||||
err_detail = res.json().get("detail", "Registration failed")
|
except: err_detail = f"Registration failed ({res.status_code})"
|
||||||
except Exception:
|
|
||||||
err_detail = f"Registration failed ({res.status_code})"
|
|
||||||
return False, err_detail
|
return False, err_detail
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return False, str(err)
|
return False, str(err)
|
||||||
api_client = ApiClient()
|
|
||||||
|
|
||||||
|
|
||||||
+29
-34
@@ -1,44 +1,39 @@
|
|||||||
import json
|
import flet as ft
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
STORAGE_FILE = Path("settings.json")
|
class AppStorage:
|
||||||
|
def __init__(self, page: ft.Page):
|
||||||
|
self.page = page
|
||||||
|
self.prefs = ft.SharedPreferences()
|
||||||
|
self._cache = {}
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
# Only look for our exact keys, ignoring hidden browser data
|
||||||
|
app_keys = ["server_url", "auth_token", "refresh_token", "currency_symbol"]
|
||||||
|
|
||||||
def save_setting(key: str, value: str) -> None:
|
for k in app_keys:
|
||||||
data = {}
|
if await self.prefs.contains_key(k):
|
||||||
if STORAGE_FILE.exists():
|
self._cache[k] = str(await self.prefs.get(k))
|
||||||
try:
|
|
||||||
data = json.loads(STORAGE_FILE.read_text())
|
|
||||||
except Exception:
|
|
||||||
data = {}
|
|
||||||
data[key] = value
|
|
||||||
STORAGE_FILE.write_text(json.dumps(data, indent=2))
|
|
||||||
|
|
||||||
|
def save_setting(self, key: str, value: str) -> None:
|
||||||
|
self._cache[key] = value
|
||||||
|
self.page.run_task(self._async_save, key, value)
|
||||||
|
|
||||||
def get_setting(key: str, default: str | None = None) -> str | None:
|
async def _async_save(self, key, value):
|
||||||
if not STORAGE_FILE.exists():
|
await self.prefs.set(key, str(value))
|
||||||
return default
|
|
||||||
try:
|
|
||||||
data = json.loads(STORAGE_FILE.read_text())
|
|
||||||
return data.get(key, default)
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
def get_setting(self, key: str, default: str | None = None) -> str | None:
|
||||||
|
return self._cache.get(key, default)
|
||||||
|
|
||||||
def remove_setting(key: str) -> None:
|
def remove_setting(self, key: str) -> None:
|
||||||
if STORAGE_FILE.exists():
|
if key in self._cache:
|
||||||
try:
|
del self._cache[key]
|
||||||
data = json.loads(STORAGE_FILE.read_text())
|
self.page.run_task(self._async_remove, key)
|
||||||
if key in data:
|
|
||||||
del data[key]
|
|
||||||
STORAGE_FILE.write_text(json.dumps(data, indent=2))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
async def _async_remove(self, key):
|
||||||
|
await self.prefs.remove(key)
|
||||||
|
|
||||||
def get_currency_symbol() -> str:
|
def get_currency_symbol(self) -> str:
|
||||||
return get_setting("currency_symbol") or "kr"
|
return self.get_setting("currency_symbol") or "kr"
|
||||||
|
|
||||||
|
def set_currency_symbol(self, symbol: str) -> None:
|
||||||
def set_currency_symbol(symbol: str) -> None:
|
self.save_setting("currency_symbol", symbol)
|
||||||
save_setting("currency_symbol", symbol)
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
from services.api import api_client
|
|
||||||
from services.storage import get_currency_symbol, set_currency_symbol
|
|
||||||
|
|
||||||
def AccountView(page: ft.Page, navigate) -> ft.View:
|
def AccountView(page: ft.Page, navigate, app_storage, api_client) -> ft.View:
|
||||||
user_info = {}
|
user_info = {}
|
||||||
|
|
||||||
# Form Fields: Dark containers with crisp white text
|
# Form Fields: Dark containers with crisp white text
|
||||||
@@ -22,7 +20,7 @@ def AccountView(page: ft.Page, navigate) -> ft.View:
|
|||||||
|
|
||||||
currency_in = ft.TextField(
|
currency_in = ft.TextField(
|
||||||
label="Currency Symbol (e.g. kr, $, €)",
|
label="Currency Symbol (e.g. kr, $, €)",
|
||||||
value=get_currency_symbol(),
|
value=app_storage.get_currency_symbol(),
|
||||||
**input_style,
|
**input_style,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -92,7 +90,7 @@ def AccountView(page: ft.Page, navigate) -> ft.View:
|
|||||||
c_val = currency_in.value.strip() or "kr"
|
c_val = currency_in.value.strip() or "kr"
|
||||||
|
|
||||||
# Save currency locally
|
# Save currency locally
|
||||||
set_currency_symbol(c_val)
|
app_storage.set_currency_symbol(c_val)
|
||||||
|
|
||||||
ok, res = api_client.update_me(
|
ok, res = api_client.update_me(
|
||||||
username=u_val or None,
|
username=u_val or None,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import flet as ft
|
import flet as ft
|
||||||
from services.api import api_client
|
|
||||||
from services.storage import get_currency_symbol
|
|
||||||
|
|
||||||
def DashboardView(page: ft.Page, navigate) -> ft.View:
|
def DashboardView(page: ft.Page, navigate, app_storage, api_client) -> ft.View:
|
||||||
transactions = []
|
transactions = []
|
||||||
categories = []
|
categories = []
|
||||||
user_info = {}
|
user_info = {}
|
||||||
@@ -43,7 +41,7 @@ def DashboardView(page: ft.Page, navigate) -> ft.View:
|
|||||||
)
|
)
|
||||||
|
|
||||||
amount_in = ft.TextField(
|
amount_in = ft.TextField(
|
||||||
label=f"Amount ({get_currency_symbol()})",
|
label=f"Amount ({app_storage.get_currency_symbol()})",
|
||||||
keyboard_type=ft.KeyboardType.NUMBER,
|
keyboard_type=ft.KeyboardType.NUMBER,
|
||||||
expand=True
|
expand=True
|
||||||
)
|
)
|
||||||
@@ -126,7 +124,7 @@ def DashboardView(page: ft.Page, navigate) -> ft.View:
|
|||||||
nonlocal editing_tx_id
|
nonlocal editing_tx_id
|
||||||
editing_tx_id = None
|
editing_tx_id = None
|
||||||
modal_title.value = "Add Transaction"
|
modal_title.value = "Add Transaction"
|
||||||
amount_in.label = f"Amount ({get_currency_symbol()})"
|
amount_in.label = f"Amount ({app_storage.get_currency_symbol()})"
|
||||||
|
|
||||||
title_in.value = ""
|
title_in.value = ""
|
||||||
amount_in.value = ""
|
amount_in.value = ""
|
||||||
@@ -148,7 +146,7 @@ def DashboardView(page: ft.Page, navigate) -> ft.View:
|
|||||||
nonlocal editing_tx_id
|
nonlocal editing_tx_id
|
||||||
editing_tx_id = tx["id"]
|
editing_tx_id = tx["id"]
|
||||||
modal_title.value = "Edit Transaction"
|
modal_title.value = "Edit Transaction"
|
||||||
amount_in.label = f"Amount ({get_currency_symbol()})"
|
amount_in.label = f"Amount ({app_storage.get_currency_symbol()})"
|
||||||
|
|
||||||
title_in.value = tx.get("title", "")
|
title_in.value = tx.get("title", "")
|
||||||
amt_val = float(tx.get("amount", 0))
|
amt_val = float(tx.get("amount", 0))
|
||||||
@@ -252,7 +250,7 @@ def DashboardView(page: ft.Page, navigate) -> ft.View:
|
|||||||
render_dashboard()
|
render_dashboard()
|
||||||
|
|
||||||
def render_dashboard():
|
def render_dashboard():
|
||||||
curr_symbol = get_currency_symbol()
|
curr_symbol = app_storage.get_currency_symbol()
|
||||||
total_spent = sum(float(t.get("amount", 0)) for t in transactions)
|
total_spent = sum(float(t.get("amount", 0)) for t in transactions)
|
||||||
current_balance_text.value = f"{total_spent:,.2f} {curr_symbol}"
|
current_balance_text.value = f"{total_spent:,.2f} {curr_symbol}"
|
||||||
|
|
||||||
|
|||||||
+4
-7
@@ -1,10 +1,7 @@
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
from services.api import api_client
|
|
||||||
from services.storage import get_setting, remove_setting
|
|
||||||
|
|
||||||
|
def LoginView(page: ft.Page, navigate, app_storage, api_client) -> ft.View:
|
||||||
def LoginView(page: ft.Page, navigate) -> ft.View:
|
server_url = app_storage.get_setting("server_url") or "Not Configured"
|
||||||
server_url = get_setting("server_url") or "Not Configured"
|
|
||||||
|
|
||||||
# Input controls
|
# Input controls
|
||||||
username_input = ft.TextField(
|
username_input = ft.TextField(
|
||||||
@@ -41,7 +38,7 @@ def LoginView(page: ft.Page, navigate) -> ft.View:
|
|||||||
status_text.value = ""
|
status_text.value = ""
|
||||||
page.update()
|
page.update()
|
||||||
|
|
||||||
# Call Django authentication endpoint
|
# Call Django authentication endpoint using the passed client
|
||||||
success, message = api_client.login(username, password)
|
success, message = api_client.login(username, password)
|
||||||
|
|
||||||
loading_ring.visible = False
|
loading_ring.visible = False
|
||||||
@@ -62,7 +59,7 @@ def LoginView(page: ft.Page, navigate) -> ft.View:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def change_server(e):
|
def change_server(e):
|
||||||
remove_setting("server_url")
|
app_storage.remove_setting("server_url")
|
||||||
navigate("/server")
|
navigate("/server")
|
||||||
|
|
||||||
return ft.View(
|
return ft.View(
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
from services.api import api_client
|
|
||||||
from services.storage import get_setting, remove_setting
|
|
||||||
|
|
||||||
|
def RegisterView(page: ft.Page, navigate, app_storage, api_client) -> ft.View:
|
||||||
def RegisterView(page: ft.Page, navigate) -> ft.View:
|
server_url = app_storage.get_setting("server_url") or "Not Configured"
|
||||||
server_url = get_setting("server_url") or "Not Configured"
|
|
||||||
|
|
||||||
# Input controls
|
# Input controls
|
||||||
username_input = ft.TextField(
|
username_input = ft.TextField(
|
||||||
@@ -104,7 +101,7 @@ def RegisterView(page: ft.Page, navigate) -> ft.View:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def change_server(e):
|
def change_server(e):
|
||||||
remove_setting("server_url")
|
app_storage.remove_setting("server_url")
|
||||||
navigate("/server")
|
navigate("/server")
|
||||||
|
|
||||||
return ft.View(
|
return ft.View(
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
from services.storage import save_setting
|
|
||||||
|
|
||||||
|
# Including api_client parameter here to match the routing mapping in main.py seamlessly
|
||||||
def ServerPickerView(page: ft.Page, navigate) -> ft.View:
|
def ServerPickerView(page: ft.Page, navigate, app_storage, api_client=None) -> ft.View:
|
||||||
# 1. Set width to 140px so "https://" fits comfortably alongside the dropdown arrow
|
# 1. Set width to 140px so "https://" fits comfortably alongside the dropdown arrow
|
||||||
protocol_dropdown = ft.Dropdown(
|
protocol_dropdown = ft.Dropdown(
|
||||||
value="https://",
|
value="https://",
|
||||||
@@ -45,7 +44,7 @@ def ServerPickerView(page: ft.Page, navigate) -> ft.View:
|
|||||||
protocol = protocol_dropdown.value
|
protocol = protocol_dropdown.value
|
||||||
final_url = f"{protocol}{raw_host}/api"
|
final_url = f"{protocol}{raw_host}/api"
|
||||||
|
|
||||||
save_setting("server_url", final_url)
|
app_storage.save_setting("server_url", final_url)
|
||||||
navigate("/login")
|
navigate("/login")
|
||||||
|
|
||||||
return ft.View(
|
return ft.View(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
import flet_charts as fch
|
import flet_charts as fch
|
||||||
from services.api import api_client
|
|
||||||
from services.storage import get_currency_symbol
|
|
||||||
|
|
||||||
def StatisticsView(page: ft.Page, navigate) -> ft.View:
|
def StatisticsView(page: ft.Page, navigate, app_storage, api_client) -> ft.View:
|
||||||
transactions = []
|
transactions = []
|
||||||
is_loading = True
|
is_loading = True
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ def StatisticsView(page: ft.Page, navigate) -> ft.View:
|
|||||||
render_statistics()
|
render_statistics()
|
||||||
|
|
||||||
def render_statistics():
|
def render_statistics():
|
||||||
curr_symbol = get_currency_symbol()
|
curr_symbol = app_storage.get_currency_symbol()
|
||||||
category_totals = {}
|
category_totals = {}
|
||||||
total_expenses = 0.0
|
total_expenses = 0.0
|
||||||
|
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "flet" },
|
{ name = "flet" },
|
||||||
{ name = "flet-charts" },
|
{ name = "flet-charts" },
|
||||||
|
{ name = "httpx" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -560,6 +561,7 @@ dev = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "flet", specifier = ">=1.0.0" },
|
{ name = "flet", specifier = ">=1.0.0" },
|
||||||
{ name = "flet-charts", specifier = ">=1.0.0" },
|
{ name = "flet-charts", specifier = ">=1.0.0" },
|
||||||
|
{ name = "httpx" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
|
|||||||
Reference in new issue
Block a user