feat: add profile screen and update routing; enhance login validation and logout functionality
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Flutter Frontend - User Guide
|
||||
|
||||
This README describes how to use the Flutter frontend for Recipe App from a user and operator perspective.
|
||||
|
||||
Related documents:
|
||||
- [next_steps_flutter.md](../next_steps_flutter.md)
|
||||
- [teknisk_beskrivning_flutter.md](../teknisk_beskrivning_flutter.md)
|
||||
|
||||
## What this app is
|
||||
This is a Flutter Web frontend for Recipe App, served in Docker and exposed through Caddy.
|
||||
It is intended to behave like the existing web frontend, but built in Flutter to support future Android and iOS clients.
|
||||
|
||||
## Current user flows
|
||||
- Login with username and password.
|
||||
- Recipe list view after login.
|
||||
- Profile page (base version).
|
||||
- Logout from recipe/profile pages.
|
||||
|
||||
## Where to access it
|
||||
- Test environment: `https://test.gynther.se`
|
||||
|
||||
## Login details
|
||||
- Login expects username, not email.
|
||||
- Example seeded admin user in backend bootstrap: `Nadmin`.
|
||||
- Password is controlled by server environment variable (`ADMIN_NADMIN_PASSWORD`).
|
||||
|
||||
## Known current scope
|
||||
This is an active migration track. Not all pages from the existing frontend are moved yet.
|
||||
Planned migration sequence is documented in [next_steps_flutter.md](../next_steps_flutter.md).
|
||||
|
||||
## Troubleshooting (user level)
|
||||
1. If page shows old behavior after deploy: hard refresh or open in incognito.
|
||||
2. If login fails: verify username/password (not email).
|
||||
3. If recipes do not load: report browser console/network errors to the dev team.
|
||||
|
||||
## Release expectation
|
||||
This frontend is available for iterative testing. Feature parity with the current production frontend is delivered step by step.
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../features/recipes/presentation/recipes_screen.dart';
|
||||
import '../../features/auth/presentation/login_screen.dart';
|
||||
import '../../features/profile/presentation/profile_screen.dart';
|
||||
|
||||
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
@@ -15,6 +16,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
path: '/recipes',
|
||||
builder: (context, state) => const RecipesScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile',
|
||||
builder: (context, state) => const ProfileScreen(),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -22,6 +22,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (_usernameCtrl.text.trim().isEmpty || _passwordCtrl.text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
await ref.read(authStateProvider.notifier).login(
|
||||
_usernameCtrl.text.trim(),
|
||||
_passwordCtrl.text,
|
||||
@@ -47,12 +50,15 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
TextField(
|
||||
controller: _usernameCtrl,
|
||||
decoration: const InputDecoration(labelText: 'Anvandarnamn'),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _passwordCtrl,
|
||||
decoration: const InputDecoration(labelText: 'Lösenord'),
|
||||
obscureText: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: (_) => _submit(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (authState is AsyncLoading)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
|
||||
class ProfileScreen extends ConsumerWidget {
|
||||
const ProfileScreen({super.key});
|
||||
|
||||
Future<void> _logout(BuildContext context, WidgetRef ref) async {
|
||||
await ref.read(authStateProvider.notifier).logout();
|
||||
if (context.mounted) context.go('/login');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Profil'),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'Recept',
|
||||
icon: const Icon(Icons.restaurant_menu),
|
||||
onPressed: () => context.go('/recipes'),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Logga ut',
|
||||
icon: const Icon(Icons.logout),
|
||||
onPressed: () => _logout(context, ref),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Profilsida (grundversion)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../data/recipe_providers.dart';
|
||||
|
||||
class RecipesScreen extends ConsumerWidget {
|
||||
const RecipesScreen({super.key});
|
||||
|
||||
Future<void> _logout(BuildContext context, WidgetRef ref) async {
|
||||
await ref.read(authStateProvider.notifier).logout();
|
||||
if (context.mounted) context.go('/login');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final recipesAsync = ref.watch(recipesProvider);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Recept')),
|
||||
appBar: AppBar(
|
||||
title: const Text('Recept'),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'Profil',
|
||||
icon: const Icon(Icons.person),
|
||||
onPressed: () => context.go('/profile'),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Logga ut',
|
||||
icon: const Icon(Icons.logout),
|
||||
onPressed: () => _logout(context, ref),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: recipesAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(child: Text('Fel: $e')),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Next Steps: Flutter-migrering (Alternativ 3)
|
||||
|
||||
Relaterade dokument:
|
||||
- [flutter/README.md](flutter/README.md)
|
||||
- [teknisk_beskrivning_flutter.md](teknisk_beskrivning_flutter.md)
|
||||
|
||||
## 1. Definiera malbild och scope forst
|
||||
- Bestam vilka floden som maste vara parity i v1: login, receptlista, receptdetalj, inventarie, matsedel, profil.
|
||||
- Satt tydlig definition of done per feature: UI, navigation, API, felhantering, loading states, auth-skydd.
|
||||
|
||||
## 2. Bygg gemensam app-shell innan fler sidor
|
||||
- Stabil routingstruktur.
|
||||
- Gemensam navigation (top/bottom/nav drawer).
|
||||
- Auth-gate och logout-flode.
|
||||
- Standardkomponenter for tomma lagen, felmeddelanden och laddning.
|
||||
|
||||
Det gor att varje ny sida gar snabbare och mer konsekvent.
|
||||
|
||||
## 3. Migrera i denna ordning (hogst affarsvarde forst)
|
||||
- Auth: login, session, logout.
|
||||
- Recept: lista -> detalj -> skapa/andra.
|
||||
- Inventarie: lista -> skapa -> uppdatera -> forbrukning.
|
||||
- Matsedel.
|
||||
- Profil/admin.
|
||||
|
||||
Ordningen minimerar blockerare eftersom recept + auth ofta anvands av allt annat.
|
||||
|
||||
## 4. Kor API-contract first per feature
|
||||
- Verifiera exakt request/response mot backend innan UI putsas.
|
||||
- Mappa datamodeller robust (null, typskillnader, fallback-falt).
|
||||
- Lagg in central felhantering for 401/403/500 tidigt.
|
||||
|
||||
## 5. Satt enhetliga kvalitetsgrindar per migrerad feature
|
||||
- Manuell testlista for kritiska scenarier.
|
||||
- En liten smoke-test efter varje deploy.
|
||||
- Kontroll att web + mobilanpassning fungerar (utan web-specifika genvagar).
|
||||
|
||||
## 6. Leverera i korta iterationer
|
||||
- 1 feature at gangen till testmiljo.
|
||||
- Demo + snabb feedback.
|
||||
- Justera innan nasta feature.
|
||||
|
||||
Det minskar risken att du bygger fel saker for langt.
|
||||
|
||||
## 7. Avveckla gamla frontend stegvis
|
||||
- Kor dubbel drift under en period.
|
||||
- Peka en testdoman mot Flutter tills parity ar bekräftad.
|
||||
- Flytta trafik gradvis nar karnfloden ar stabila.
|
||||
|
||||
## Tumregel
|
||||
- Sikta pa funktionell parity forst, pixel-perfect parity senare.
|
||||
- Det ger snabbare nytta och farre regressionsproblem.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Teknisk Beskrivning - Flutter Frontend
|
||||
|
||||
Detta dokument beskriver vad som ar byggt, arkitekturval och teknisk kontext for Flutter-frontenden.
|
||||
Malgrupp: utvecklare och AI-assistent i denna chat.
|
||||
|
||||
Relaterade dokument:
|
||||
- [next_steps_flutter.md](next_steps_flutter.md)
|
||||
- [flutter/README.md](flutter/README.md)
|
||||
|
||||
## Syfte
|
||||
- Isolerad Flutter-baserad frontend i separat Docker-service.
|
||||
- Web forst, men med arkitektur som kan ateranvandas for Android/iOS.
|
||||
- Stegvis migrering av funktioner fran befintlig Next.js-frontend.
|
||||
|
||||
## Vad som ar gjort
|
||||
- Ny compose override: [compose.flutter.yml](compose.flutter.yml).
|
||||
- Ny Flutter-service: `recipe-flutter`.
|
||||
- Service ansluten till natverk:
|
||||
- `recipe-internal` (backend access)
|
||||
- `proxy` (external Caddy reachability)
|
||||
- Flutter-container serverar web build via intern Caddy.
|
||||
- Exponering via extern Caddy med site `test.gynther.se` -> `recipe-flutter:5000`.
|
||||
- API anrop gar same-origin via `/api` och proxas internt till `recipe-api:8080`.
|
||||
|
||||
## Arkitektur
|
||||
### Lager
|
||||
- Presentation: skarmar och widgets i `flutter/lib/features/*/presentation`.
|
||||
- State/Application: Riverpod providers/notifiers i `flutter/lib/features/*/data`.
|
||||
- Data/API: `ApiClient` i `flutter/lib/core/api`.
|
||||
- Platform abstraction: token storage interface i `flutter/lib/core/platform`.
|
||||
|
||||
### Routing
|
||||
- GoRouter i [flutter/lib/core/router/app_router.dart](flutter/lib/core/router/app_router.dart).
|
||||
- Nuvarande routes:
|
||||
- `/login`
|
||||
- `/recipes`
|
||||
- `/profile`
|
||||
|
||||
### Auth
|
||||
- Login endpoint: `POST /api/auth/login`.
|
||||
- Backend kontrakt anvander `username` + `password`.
|
||||
- Token-falt i svar: `accessToken`.
|
||||
- Token lagras via `ITokenStorage` (web implementation med SharedPreferences).
|
||||
|
||||
### Recipes
|
||||
- Recipes endpoint: `GET /api/recipes`.
|
||||
- Flutter model ar hardad for backend-falt (`name` fallback till `title`) och null-safe parsing.
|
||||
|
||||
## Drift och deploy
|
||||
### Build/run
|
||||
- Build argument i compose: `API_BASE_URL=/api`.
|
||||
- Build command:
|
||||
- `docker compose -f compose.yml -f compose.flutter.yml build recipe-flutter`
|
||||
- Run command:
|
||||
- `docker compose -f compose.yml -f compose.flutter.yml up -d --no-deps recipe-flutter`
|
||||
|
||||
### Viktiga verifieringar
|
||||
- Compose merge valid:
|
||||
- `docker compose -f compose.yml -f compose.flutter.yml config`
|
||||
- Container reachable from external Caddy:
|
||||
- `docker exec caddy wget -S -O- http://recipe-flutter:5000`
|
||||
- Public endpoint:
|
||||
- `curl -I https://test.gynther.se`
|
||||
|
||||
## Viktiga beslut
|
||||
- `compose.yml` lamnas orord; Flutter ligger i separat override-fil.
|
||||
- Flutter-web anvander same-origin API (`/api`) for att undvika mixed-content.
|
||||
- Intern Caddy i Flutter-container hanterar static hosting + `/api` proxy.
|
||||
- Feature migration sker stegvis enligt [next_steps_flutter.md](next_steps_flutter.md).
|
||||
|
||||
## Kanda fallgropar
|
||||
- Om `recipe-flutter` inte ar i `proxy` natverket blir det 502 fran extern Caddy.
|
||||
- Om browser visar gammal JS kan gamla API-URL:er leva kvar i cache/service worker.
|
||||
- Login med email fungerar inte om backend forvantar username.
|
||||
|
||||
## Nasta tekniska steg
|
||||
Fortsatt migrering enligt prioritering i [next_steps_flutter.md](next_steps_flutter.md):
|
||||
1. Auth parity (session behavior refinements).
|
||||
2. Recipes parity (list -> detail -> create/edit).
|
||||
3. Inventory and meal plan pages.
|
||||
4. Profile/admin parity.
|
||||
Reference in New Issue
Block a user