Adds a Settings > Users screen where members can be added, turned off
and removed, and fixes three defects found while auditing the app on
a phone.
User management
- Settings is a real page with a Users section: add a member (name,
email, password), flip "Can sign in" to block or restore access
without deleting their history, or remove them outright.
- The app has no roles - membership is just the active flag - so the
Settings route no longer requires the "admin" role, which left it
unreachable for every real member. Settings is now in the sidebar.
- New members are created with active: true. AuthContext refuses to
sign in a user whose active flag is false, and the field had no
default, so previously created users were locked out of the app.
Live PocketBase rules for the `users` collection (see
scripts/fix-users-collection.mjs):
- createRule was "" which means PUBLIC - any anonymous visitor could
create an account on the instance. It now requires an authenticated
member.
- listRule was "id = @request.auth.id", so a member could only ever
see their own record and no member list could be rendered. It now
matches every other collection in the app: @request.auth.id != "".
Totals always showed R0.00
- form.watch("items") returns the same array instance that
react-hook-form mutates in place, so the useMemo keyed on it never
recomputed. Both the quote and purchase order totals stayed at zero
however many items were added. Switched to useWatch with compute,
which re-runs whenever the watched field really changes.
Mobile layout
- The page was zoomed out on phones: flex items default to
min-width:auto, so a wide table stretched the layout past the
viewport. Added min-w-0 to the layout chain.
- The sr-only labels inside the tables are position:absolute with no
positioned ancestor, so they escaped the table's overflow-x-auto
clip and stretched the document. Made the scroll containers
relative.
- DialogContent was w-full plus an mx-4 gutter, which overflowed the
viewport by 16px and clipped the right edge of every form. The
component now owns the gutter itself.
Verified at 390x844: all pages and the quote, purchase order and add
user dialogs fit the viewport with no horizontal overflow.
82 lines
3.4 KiB
JavaScript
82 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Harden the `users` collection and make it usable by the app's
|
|
* Settings > Users screen.
|
|
*
|
|
* Problems found on the live instance:
|
|
*
|
|
* 1. createRule was "" (empty string) => PUBLIC. Any anonymous visitor
|
|
* on the internet could create an account on this PocketBase instance.
|
|
* 2. listRule was "id = @request.auth.id" => a signed-in member could
|
|
* only ever see their own record, so there was no way to render a
|
|
* member list.
|
|
* 3. The `active` bool field had no default, so every record was created
|
|
* with active = false. AuthContext refuses to sign in a user whose
|
|
* `active` is false, so newly created users could not log in at all.
|
|
* NOTE: PocketBase's REST API strips `defaultValue` from field
|
|
* definitions (verified on this instance - a scratch collection with
|
|
* `defaultValue: true` on a bool field still stored false), so the
|
|
* default cannot be set from here. The app sets `active: true`
|
|
* explicitly when it creates a member instead.
|
|
*
|
|
* New rules (matching the posture already used by every other collection
|
|
* in this app - stock_items, customers, purchase_orders, quotes, ...):
|
|
*
|
|
* listRule @request.auth.id != "" members can list the team
|
|
* viewRule @request.auth.id != "" members can read a member
|
|
* createRule @request.auth.id != "" members can add members
|
|
* updateRule @request.auth.id != "" members can activate/deactivate
|
|
* deleteRule @request.auth.id != "" members can remove members
|
|
*
|
|
* The `role` field is intentionally left untouched: the app does not use
|
|
* roles, membership is just the `active` flag.
|
|
*
|
|
* Usage:
|
|
* node scripts/fix-users-collection.mjs # dry run (default)
|
|
* node scripts/fix-users-collection.mjs --apply # apply to the instance
|
|
*/
|
|
|
|
import PocketBase from "pocketbase";
|
|
|
|
const PB_URL = process.env.POCKETBASE_URL || "https://pb.jeistk.apphub.co.za";
|
|
const PB_EMAIL = process.env.POCKETBASE_ADMIN_EMAIL || "david@wfsales.co.za";
|
|
const PB_PASSWORD = process.env.POCKETBASE_ADMIN_PASSWORD || "1234567890";
|
|
|
|
const APPLY = process.argv.includes("--apply");
|
|
|
|
const AUTHENTICATED = '@request.auth.id != ""';
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
pb.autoCancellation(false);
|
|
|
|
await pb.admins.authWithPassword(PB_EMAIL, PB_PASSWORD);
|
|
|
|
const collection = await pb.collections.getOne("users");
|
|
|
|
console.log("=== BEFORE ===");
|
|
for (const rule of ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"]) {
|
|
console.log(` ${rule.padEnd(11)}: ${JSON.stringify(collection[rule])}`);
|
|
}
|
|
const activeField = collection.fields.find((f) => f.name === "active");
|
|
console.log(` active field: ${activeField ? "present" : "MISSING"}`);
|
|
|
|
if (!APPLY) {
|
|
console.log("\nDry run. Re-run with --apply to write these rules to the instance.");
|
|
process.exit(0);
|
|
}
|
|
|
|
collection.listRule = AUTHENTICATED;
|
|
collection.viewRule = AUTHENTICATED;
|
|
collection.createRule = AUTHENTICATED;
|
|
collection.updateRule = AUTHENTICATED;
|
|
collection.deleteRule = AUTHENTICATED;
|
|
|
|
const updated = await pb.collections.update(collection.id, collection);
|
|
|
|
console.log("\n=== AFTER ===");
|
|
for (const rule of ["listRule", "viewRule", "createRule", "updateRule", "deleteRule"]) {
|
|
console.log(` ${rule.padEnd(11)}: ${JSON.stringify(updated[rule])}`);
|
|
}
|
|
|
|
console.log("\nDone. Existing records keep their current `active` value - flip them");
|
|
console.log("from Settings > Users if they should be able to sign in.");
|