added login
This commit is contained in:
@@ -15,42 +15,91 @@ class AppRoutes {
|
||||
|
||||
static const String login = '/login';
|
||||
|
||||
/// The terminal itself. Signing in lands here directly — customer capture
|
||||
/// happens at checkout, not before the sale.
|
||||
/// The admin shell: billing plus catalogue import, promos, staff, settings.
|
||||
static const String adminDashboard = '/admin';
|
||||
|
||||
/// The cashier shell: billing, and nothing else.
|
||||
static const String cashierDashboard = '/cashier';
|
||||
|
||||
/// Alias for "wherever this session lives".
|
||||
///
|
||||
/// Kept because everything that returns to billing — finishing a receipt,
|
||||
/// abandoning a payment — should land on the caller's own dashboard without
|
||||
/// having to know which one that is. It never renders; [routerProvider]
|
||||
/// resolves it to one of the two above.
|
||||
static const String pos = '/';
|
||||
|
||||
static const String payment = '/payment';
|
||||
static const String receipt = '/receipt';
|
||||
|
||||
/// Drawer count and hand-over. Reached from the session-end chooser, never
|
||||
/// linked to directly, and guarded like every other signed-in route.
|
||||
static const String endShift = '/end-shift';
|
||||
|
||||
static const String receipt = '/receipt';
|
||||
|
||||
/// Which dashboard a session owns.
|
||||
///
|
||||
/// The single place the role-to-screen decision is written down. `null` —
|
||||
/// nobody signed in — resolves to the cashier till, which is the smaller of
|
||||
/// the two; the guard sends an unauthenticated terminal to [login] before
|
||||
/// this is ever reached.
|
||||
static String homeFor(TerminalLogin? login) =>
|
||||
login == TerminalLogin.admin ? adminDashboard : cashierDashboard;
|
||||
|
||||
/// True for the two dashboards, so the guard can spot a session sitting on
|
||||
/// the wrong one.
|
||||
static bool isDashboard(String location) =>
|
||||
location == adminDashboard || location == cashierDashboard;
|
||||
}
|
||||
|
||||
/// Router with an authentication guard.
|
||||
/// Router with an authentication and role guard.
|
||||
///
|
||||
/// Every route except [AppRoutes.login] requires a signed-in store, and an
|
||||
/// already-signed-in terminal is bounced away from the login screen.
|
||||
/// Three rules, in order:
|
||||
///
|
||||
/// 1. Every route except [AppRoutes.login] requires a signed-in session.
|
||||
/// 2. A signed-in terminal is bounced off the login screen onto its own
|
||||
/// dashboard — admin for Admin/Supervisor/Manager/Owner accounts, cashier
|
||||
/// for everything else. See [PosSession.isCashier].
|
||||
/// 3. A session on the *other* role's dashboard is moved to its own. Typing
|
||||
/// `/admin` on a cashier till must not open the back office, and the guard
|
||||
/// is what makes that true regardless of how the route was reached.
|
||||
final routerProvider = Provider<GoRouter>((ref) {
|
||||
// GoRouter re-evaluates `redirect` whenever this notifier fires.
|
||||
final authChanged = ValueNotifier<bool>(
|
||||
ref.read(authControllerProvider).isAuthenticated,
|
||||
// GoRouter re-evaluates `redirect` whenever this notifier fires. It carries
|
||||
// the destination rather than a bare bool, so a role change — a cashier
|
||||
// signing out and a supervisor signing in — also moves the terminal, which
|
||||
// watching `isAuthenticated` alone would miss.
|
||||
String? home(AuthState state) => state is Authenticated
|
||||
? AppRoutes.homeFor(state.login)
|
||||
: null;
|
||||
|
||||
final destination = ValueNotifier<String?>(
|
||||
home(ref.read(authControllerProvider)),
|
||||
);
|
||||
ref.listen<AuthState>(
|
||||
authControllerProvider,
|
||||
(_, next) => authChanged.value = next.isAuthenticated,
|
||||
(_, next) => destination.value = home(next),
|
||||
);
|
||||
ref.onDispose(authChanged.dispose);
|
||||
ref.onDispose(destination.dispose);
|
||||
|
||||
return GoRouter(
|
||||
initialLocation: AppRoutes.login,
|
||||
refreshListenable: authChanged,
|
||||
refreshListenable: destination,
|
||||
debugLogDiagnostics: false,
|
||||
redirect: (context, state) {
|
||||
final signedIn = ref.read(authControllerProvider).isAuthenticated;
|
||||
final atLogin = state.matchedLocation == AppRoutes.login;
|
||||
final auth = ref.read(authControllerProvider);
|
||||
final location = state.matchedLocation;
|
||||
final atLogin = location == AppRoutes.login;
|
||||
|
||||
if (auth is! Authenticated) return atLogin ? null : AppRoutes.login;
|
||||
|
||||
final myHome = AppRoutes.homeFor(auth.login);
|
||||
|
||||
// Signed in and still on the login screen, or on the `/` alias.
|
||||
if (atLogin || location == AppRoutes.pos) return myHome;
|
||||
|
||||
// On the other role's dashboard.
|
||||
if (AppRoutes.isDashboard(location) && location != myHome) return myHome;
|
||||
|
||||
if (!signedIn) return atLogin ? null : AppRoutes.login;
|
||||
if (atLogin) return AppRoutes.pos;
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
@@ -59,12 +108,35 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
name: 'login',
|
||||
pageBuilder: (context, state) => _fade(state, const LoginScreen()),
|
||||
),
|
||||
|
||||
// Redirect-only. `/` is an alias, never a screen — the guard above has
|
||||
// already resolved it, and this exists so the path matches a route at
|
||||
// all rather than falling through to [errorBuilder].
|
||||
GoRoute(
|
||||
path: AppRoutes.pos,
|
||||
name: 'pos',
|
||||
name: 'home',
|
||||
redirect: (context, state) => AppRoutes.homeFor(
|
||||
ref.read(terminalLoginProvider) ?? TerminalLogin.cashier,
|
||||
),
|
||||
),
|
||||
|
||||
// Both dashboards are the same shell. It reads `isCashierModeProvider`
|
||||
// and hides the sidebar, the catalogue and the back-office modules in
|
||||
// cashier mode — so the two routes are the *addresses* of two shapes of
|
||||
// one screen, not two screens to keep in step with each other.
|
||||
GoRoute(
|
||||
path: AppRoutes.adminDashboard,
|
||||
name: 'adminDashboard',
|
||||
pageBuilder: (context, state) =>
|
||||
_fade(state, const PosDashboardScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.cashierDashboard,
|
||||
name: 'cashierDashboard',
|
||||
pageBuilder: (context, state) =>
|
||||
_fade(state, const PosDashboardScreen()),
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
path: AppRoutes.endShift,
|
||||
name: 'endShift',
|
||||
|
||||
Reference in New Issue
Block a user