Add first-login password setup flow

Accounts created by tenant/store onboarding (createtenantuser,
createtenantlocation) have no password set. Previously the login form
treated the backend's "please setup a password" response as a
successful login instead of prompting for one. Now auth.ts recognizes
it (PasswordSetupRequiredError) and LoginView routes to a create-
password step, then signs in immediately with the new password.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-07-21 16:46:07 +05:30
parent f62668e54e
commit fef75a1cc2
4 changed files with 173 additions and 13 deletions

View File

@@ -139,6 +139,22 @@ export interface LoginResult {
email: string;
}
/**
* Thrown by `loginRequest` when the account exists but has never had a password
* set (a freshly onboarded tenant admin or store login — see AppLogin's
* "No password set" branch, `{ code: 409, status: true, details: { setup: true } }`).
* Distinct from a plain login failure: the caller should route to a
* create-password step, not show an error.
*/
export class PasswordSetupRequiredError extends Error {
userid: number;
constructor(userid: number, message = 'Please set up a password to continue.') {
super(message);
this.name = 'PasswordSetupRequiredError';
this.userid = userid;
}
}
/**
* POST the credentials to the Fiesta web-login endpoint. Resolves with the raw
* user record on success; throws an Error with a user-facing message on invalid
@@ -168,6 +184,15 @@ export async function loginRequest(email: string, password: string): Promise<Log
| { code?: number; status?: boolean; message?: string; details?: unknown }
| null;
// Account exists but has no password yet — server-side this fires before it
// even looks at the password the caller sent, so it happens on any attempt
// (including checkEmailRequest's password-less probe). `status: true` here,
// so this must be checked before the generic status===false failure branch.
const details = json?.details as { userid?: number; setup?: boolean } | undefined;
if (json?.code === 409 && details?.setup === true && typeof details.userid === 'number') {
throw new PasswordSetupRequiredError(details.userid, json.message);
}
// Failure: HTTP error, or the Fiesta `status: false` envelope (e.g. wrong
// email/password → { code: 409, message: "Invalid Email", status: false }).
if (!res.ok || (json && json.status === false)) {

View File

@@ -1015,6 +1015,15 @@ export async function updateUser(input: UpdateUserInput): Promise<Row> {
return fiestaSend<Row>('users/update', 'PUT', input);
}
/**
* PUT /users/update — set (or reset) a user's login password. Same endpoint as
* `updateUser`, called with only userid + password so every other column is
* left untouched (the backend's Updates() skips zero-value fields).
*/
export async function setUserPassword(userid: number, password: string): Promise<Row> {
return fiestaSend<Row>('users/update', 'PUT', { userid, password });
}
export interface CreateTenantLocationPayload {
locationname: string;
email?: string;

View File

@@ -54,6 +54,7 @@ import {
getUserById,
createUser,
updateUser,
setUserPassword,
assignRiderToOrders,
CreateUserInput,
createTenantUser,
@@ -892,6 +893,18 @@ export function useLogin() {
});
}
/**
* Set (or reset) a user's login password — used by the login flow's
* create-password step (see PasswordSetupRequiredError in ./auth) for a
* first-time tenant admin or store login.
*/
export function useSetPassword() {
return useMutation({
mutationFn: (input: { userid: number; password: string }) =>
setUserPassword(input.userid, input.password),
});
}
export function useFiestaDeleteLocation() {
const qc = useQueryClient();
return useMutation({