refactor: let the billing extension own the plan picker

The Teams tab was asking which tier with a UIAlert, which looked nothing like
the personal plan modal. It now dispatches the seat and lets the extension
render the grid, so the two match and OSS stays free of prices.

Carries `username` and `currentTier` so the picker can title itself and mark
the plan the account is already on.
This commit is contained in:
Juan Castro
2026-09-11 10:51:57 -04:00
parent c2a975b3f7
commit 06bdc26efd
+5 -55
View File
@@ -316,70 +316,20 @@ const loadPlan = async (teamUid) => {
}
};
/** Asks which tier, then hands the purchase to the billing extension. */
const changeSeatPlan = async ($el_window, username, uuid) => {
const plan = state.plan;
if ( ! plan || plan.status !== 'ready' ) return;
const current = plan.seatTiers?.[uuid] ?? null;
const choices = plan.offerings.filter(o => o.available && o.tier !== current);
if ( choices.length === 0 ) return;
const answer = await UIAlert({
type: 'confirm',
message: i18n('teams_plan_change_for', { username }),
buttons: [
...choices.map(o => ({
label: `${o.name_en || o.tier}${o.amountPerSeat} ${o.currency}`,
value: o.itemId,
})),
{ label: i18n('cancel'), value: 'no' },
],
...modalOptions($el_window),
});
if ( ! answer || answer === 'no' ) return;
/** The billing extension owns the picker, so the look matches personal plans. */
const changeSeatPlan = ($el_window, username, uuid) => {
if ( state.plan?.status !== 'ready' || ! uuid ) return;
window.dispatchEvent(new CustomEvent('team-plan-purchase', {
detail: {
teamUid: state.selected.uid,
itemId: answer,
seatUuid: uuid,
username,
currentTier: state.plan.seatTiers?.[uuid] ?? null,
onDone: () => refresh($el_window),
},
}));
};
const load = async ($el_window) => {
// The API can be on while the interface is not; same effect as no route.
if ( ! window.teams_ui ) {
state.status = 'unavailable';
setTabVisible($el_window, false);
return paint($el_window);
}
try {
const teams = await puter.teams.list();
state.teams = teams;
state.selected = teams.find(t => t.uid === state.selected?.uid) ?? teams[0] ?? null;
await loadSelected();
state.status = 'ready';
setTabVisible($el_window, true);
} catch (e) {
// A deployment with teams off registers no `/teams` route, so the
// 404 is the feature gate rather than a failure worth reporting.
state.status = e?.code === 'not_found' ? 'unavailable' : 'error';
state.teams = [];
state.selected = null;
setTabVisible($el_window, false);
}
paint($el_window);
};
const refresh = ($el_window) => {
if ( ! loadPromise ) {
loadPromise = load($el_window).finally(() => { loadPromise = null; });
}
return loadPromise;
};
// -- Actions --------------------------------------------------------------
const showError = ($el_window, e) => UIAlert({