u
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
* - Brands (`brands`, `filteredBrands`, `brandFilter`)
|
||||
* - Product attributes & values (`attributes`)
|
||||
* - Payment accounts (`accounts`)
|
||||
* - Members & pending invites (`members`, `invites`; admin only, loaded on demand)
|
||||
*
|
||||
* Lives with the 1st-level navigation shell (`AppShell`) and triggers when
|
||||
* `authAutomat.state.hasCompany` is true (`hasCompany` requires `hasAuth`).
|
||||
@@ -39,6 +40,8 @@ import {
|
||||
attributename as attributenameApi,
|
||||
attributevalue as attributevalueApi,
|
||||
account as accountApi,
|
||||
companyuser as companyuserApi,
|
||||
autoinvite as autoinviteApi,
|
||||
} from '../api';
|
||||
import { requireArray, requireDefined, requireObject } from '../utils/apiShape';
|
||||
import { normalizeCompany } from '../utils/companyForm';
|
||||
@@ -330,6 +333,14 @@ const initialState = {
|
||||
accounts: [],
|
||||
accountsStatus: 'idle',
|
||||
accountsError: null,
|
||||
|
||||
// 14. Members & invites (admin only; never part of loadAll)
|
||||
members: [],
|
||||
membersStatus: 'idle',
|
||||
membersError: null,
|
||||
invites: [],
|
||||
invitesStatus: 'idle',
|
||||
invitesError: null,
|
||||
};
|
||||
|
||||
let companyAutomat;
|
||||
@@ -350,6 +361,8 @@ let inflightCarriers = null;
|
||||
let inflightBrands = null;
|
||||
let inflightAttributes = null;
|
||||
let inflightAccounts = null;
|
||||
let inflightMembers = null;
|
||||
let inflightInvites = null;
|
||||
// #endregion
|
||||
|
||||
// #region internal state updaters & fetchers
|
||||
@@ -565,6 +578,8 @@ const actions = {
|
||||
inflightBrands = null;
|
||||
inflightAttributes = null;
|
||||
inflightAccounts = null;
|
||||
inflightMembers = null;
|
||||
inflightInvites = null;
|
||||
companyAutomat.setState({
|
||||
...initialState,
|
||||
viewerPosition: companyAutomat.state.viewerPosition,
|
||||
@@ -1709,6 +1724,122 @@ const actions = {
|
||||
return accounts;
|
||||
},
|
||||
// #endregion
|
||||
|
||||
// #region 14. Members (companyuser) & invites (autoinvite) — admin only
|
||||
// MARK: calls companyuser.* / autoinvite.* from ../api/index.js
|
||||
loadMembers() {
|
||||
if (inflightMembers) return inflightMembers;
|
||||
inflightMembers = (async () => {
|
||||
companyAutomat.setState({ membersStatus: 'loading', membersError: null });
|
||||
try {
|
||||
const res = await companyuserApi.list();
|
||||
const members = requireArray(res?.companyusers, 'companyusers');
|
||||
companyAutomat.setState({ members, membersStatus: 'ready', membersError: null });
|
||||
return members;
|
||||
} catch (error) {
|
||||
companyAutomat.setState({ membersStatus: 'failed', membersError: error });
|
||||
throw error;
|
||||
} finally {
|
||||
inflightMembers = null;
|
||||
}
|
||||
})();
|
||||
return inflightMembers;
|
||||
},
|
||||
|
||||
async reloadMembers() {
|
||||
try {
|
||||
const res = await companyuserApi.list();
|
||||
const members = requireArray(res?.companyusers, 'companyusers');
|
||||
companyAutomat.setState({ members, membersStatus: 'ready', membersError: null });
|
||||
return members;
|
||||
} catch (error) {
|
||||
companyAutomat.setState({ membersError: error });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
ensureMembersLoaded() {
|
||||
if (companyAutomat.state.membersStatus === 'ready') {
|
||||
return Promise.resolve(companyAutomat.state.members);
|
||||
}
|
||||
return actions.loadMembers();
|
||||
},
|
||||
|
||||
/** Adds an already registered user by email. Rejects with 404/409 from the backend. */
|
||||
async addMember(email, role = 'user') {
|
||||
const res = await companyuserApi.create({ email, role });
|
||||
const member = requireObject(res?.companyuser, 'companyuser');
|
||||
companyAutomat.setState({ members: [...companyAutomat.state.members, member] });
|
||||
return member;
|
||||
},
|
||||
|
||||
/** Rejects with 409 when demoting the last admin. */
|
||||
async setMemberRole(id, role) {
|
||||
const res = await companyuserApi.setRole(id, role);
|
||||
const member = requireObject(res?.companyuser, 'companyuser');
|
||||
companyAutomat.setState({
|
||||
members: companyAutomat.state.members.map((m) => (m.id === member.id ? member : m)),
|
||||
});
|
||||
return member;
|
||||
},
|
||||
|
||||
/** Rejects with 409 when removing the last admin. */
|
||||
async removeMember(id) {
|
||||
await companyuserApi.delete(id);
|
||||
companyAutomat.setState({ members: companyAutomat.state.members.filter((m) => m.id !== id) });
|
||||
},
|
||||
|
||||
loadInvites() {
|
||||
if (inflightInvites) return inflightInvites;
|
||||
inflightInvites = (async () => {
|
||||
companyAutomat.setState({ invitesStatus: 'loading', invitesError: null });
|
||||
try {
|
||||
const res = await autoinviteApi.list();
|
||||
const invites = requireArray(res?.autoinvites, 'autoinvites');
|
||||
companyAutomat.setState({ invites, invitesStatus: 'ready', invitesError: null });
|
||||
return invites;
|
||||
} catch (error) {
|
||||
companyAutomat.setState({ invitesStatus: 'failed', invitesError: error });
|
||||
throw error;
|
||||
} finally {
|
||||
inflightInvites = null;
|
||||
}
|
||||
})();
|
||||
return inflightInvites;
|
||||
},
|
||||
|
||||
async reloadInvites() {
|
||||
try {
|
||||
const res = await autoinviteApi.list();
|
||||
const invites = requireArray(res?.autoinvites, 'autoinvites');
|
||||
companyAutomat.setState({ invites, invitesStatus: 'ready', invitesError: null });
|
||||
return invites;
|
||||
} catch (error) {
|
||||
companyAutomat.setState({ invitesError: error });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
ensureInvitesLoaded() {
|
||||
if (companyAutomat.state.invitesStatus === 'ready') {
|
||||
return Promise.resolve(companyAutomat.state.invites);
|
||||
}
|
||||
return actions.loadInvites();
|
||||
},
|
||||
|
||||
/** Rejects with 409 when the email is already registered (use addMember then). */
|
||||
async createInvite(email, role = 'user') {
|
||||
const res = await autoinviteApi.create({ email, role });
|
||||
const invite = requireObject(res?.autoinvite, 'autoinvite');
|
||||
companyAutomat.setState({ invites: [...companyAutomat.state.invites, invite] });
|
||||
return invite;
|
||||
},
|
||||
|
||||
async deleteInvite(id) {
|
||||
await autoinviteApi.delete(id);
|
||||
companyAutomat.setState({ invites: companyAutomat.state.invites.filter((i) => i.id !== id) });
|
||||
},
|
||||
// #endregion
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1807,6 +1938,15 @@ sseAutomat.actions.on('attributename', () => {
|
||||
sseAutomat.actions.on('account', () => {
|
||||
void companyAutomat.actions.reloadAccounts().catch(() => {});
|
||||
});
|
||||
// Admin-only lists: only refresh once an admin view has loaded them.
|
||||
sseAutomat.actions.on('companyuser', () => {
|
||||
if (companyAutomat.state.membersStatus !== 'ready') return;
|
||||
void companyAutomat.actions.reloadMembers().catch(() => {});
|
||||
});
|
||||
sseAutomat.actions.on('autoinvite', () => {
|
||||
if (companyAutomat.state.invitesStatus !== 'ready') return;
|
||||
void companyAutomat.actions.reloadInvites().catch(() => {});
|
||||
});
|
||||
sseAutomat.actions.on('company', () => {
|
||||
void companyAutomat.actions.reload().catch(() => {});
|
||||
});
|
||||
@@ -1885,6 +2025,16 @@ export const selectAttributeStatus = () => companyAutomat.state.attributesStatus
|
||||
// Accounts selectors
|
||||
export const selectAccounts = () => companyAutomat.state.accounts;
|
||||
export const selectAccountsStatus = () => companyAutomat.state.accountsStatus;
|
||||
|
||||
// Own role in the selected company ('admin' | 'user' | undefined)
|
||||
export const selectCompanyRole = () => companyAutomat.state.current?.role;
|
||||
export const selectIsCompanyAdmin = () => companyAutomat.state.current?.role === 'admin';
|
||||
|
||||
// Members & invites selectors (admin only)
|
||||
export const selectMembers = () => companyAutomat.state.members;
|
||||
export const selectMembersStatus = () => companyAutomat.state.membersStatus;
|
||||
export const selectInvites = () => companyAutomat.state.invites;
|
||||
export const selectInvitesStatus = () => companyAutomat.state.invitesStatus;
|
||||
// #endregion
|
||||
|
||||
export default companyAutomat;
|
||||
|
||||
Reference in New Issue
Block a user