srs/lib/app.dart
Aswin Biju 45fcdcebfb Initial commit
Add SRS app: results, shops, orders and users, wired to srs-api
Flutter web/PWA for the lottery agency, built on Riverpod and go_router,
talking to srs-api over HTTP (HttpApi, token kept in SharedPreferences).

- Results, Shops, Orders and Users tabs (Users for admins only), with
  phone, tablet and desktop layouts and shared widgets and theme tokens
- Users: list with search and Active/Inactive filters; user detail with
  shops, sign-in details, and an Access card
- WhatsApp results switch on each user (sendResults on
  PATCH /api/users/:id). Shows the WhatsApp logo (Simple Icons, CC0,
  drawn with flutter_svg), green only when results will really go out.
  Can't be turned on without a phone number ("Add a phone number first"
  opens the phone sheet), and is paused while the user is inactive. The
  users list marks people who get results with the logo.
- Each Access switch has a one-line description; the explanation
  paragraphs are gone
- Remove demo mode: the app always uses the real AP
  test/support and is used only by tests.
2026-11-04 23:21:50 +05:30

108 lines
3.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'core/providers.dart';
import 'core/theme.dart';
import 'features/auth/login_screen.dart';
import 'features/orders/order_detail_screen.dart';
import 'features/orders/orders_screen.dart';
import 'features/results/results_screen.dart';
import 'features/shops/shop_detail_screen.dart';
import 'features/shops/shops_screen.dart';
import 'features/shops/tickets_screen.dart';
import 'features/users/user_detail_screen.dart';
import 'features/users/user_form_screen.dart';
import 'features/users/users_screen.dart';
import 'widgets/main_shell.dart';
final routerProvider = Provider<GoRouter>((ref) {
final refresh = ValueNotifier<int>(0);
ref.listen(sessionProvider, (_, _) => refresh.value++);
ref.onDispose(refresh.dispose);
Page<void> tab(GoRouterState s, Widget w) =>
NoTransitionPage<void>(key: s.pageKey, child: w);
return GoRouter(
initialLocation: '/results',
refreshListenable: refresh,
redirect: (context, state) {
final user = ref.read(sessionProvider);
final loc = state.matchedLocation;
if (user == null) return loc == '/login' ? null : '/login';
if (loc == '/login') return '/results';
if (loc.startsWith('/users') && !user.isAdmin) return '/results';
return null;
},
routes: [
GoRoute(path: '/login', builder: (_, _) => const LoginScreen()),
// Every signed-in screen lives in the shell, so the sidebar stays in
// place on wide windows while detail screens open beside it.
ShellRoute(
builder: (_, s, child) =>
MainShell(location: s.matchedLocation, child: child),
routes: [
GoRoute(
path: '/results',
pageBuilder: (_, s) => tab(s, const ResultsScreen()),
),
GoRoute(
path: '/shops',
pageBuilder: (_, s) => tab(s, const ShopsScreen()),
routes: [
GoRoute(
path: ':id',
builder: (_, s) => ShopDetailScreen(shopId: _id(s)),
routes: [
GoRoute(
path: 'tickets',
builder: (_, s) => TicketsScreen(shopId: _id(s)),
),
],
),
],
),
GoRoute(
path: '/orders',
pageBuilder: (_, s) => tab(s, const OrdersScreen()),
routes: [
GoRoute(
path: ':id',
builder: (_, s) => OrderDetailScreen(orderId: _id(s)),
),
],
),
GoRoute(
path: '/users',
pageBuilder: (_, s) => tab(s, const UsersScreen()),
routes: [
GoRoute(path: 'new', builder: (_, _) => const UserFormScreen()),
GoRoute(
path: ':id',
builder: (_, s) => UserDetailScreen(userId: _id(s)),
),
],
),
],
),
],
);
});
int _id(GoRouterState s) => int.parse(s.pathParameters['id']!);
class SrsApp extends ConsumerWidget {
const SrsApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp.router(
title: 'SRS',
debugShowCheckedModeBanner: false,
theme: buildTheme(),
routerConfig: ref.watch(routerProvider),
);
}
}