Accounts, groups, invites and activity each wrote the same first load: clear the error, try, put the server's reason or a fallback in error, and stop loading. useLoad (composables/useAction.ts) is that, and its load() is also the retry. Kept: ShareDialog and Settings. Their load sets loading back to true on a reload, which the lists never did, so moving them would change what a retry shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { api } from "../api/client";
|
||||
import { errorMessage } from "../api/errors";
|
||||
import { useSessionStore } from "../stores/session";
|
||||
import { appLink } from "../router/links";
|
||||
import OneTimeLink from "./OneTimeLink.vue";
|
||||
import { useRowAction } from "../composables/useAction";
|
||||
import { useRowAction, useLoad } from "../composables/useAction";
|
||||
|
||||
// Admin: the accounts on this instance, and a password reset link for any of them
|
||||
// (#5173). There is no mail path, so the admin hands the link over.
|
||||
@@ -20,23 +19,13 @@ interface Account {
|
||||
const session = useSessionStore();
|
||||
|
||||
const accounts = ref<Account[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
const { loading, error, load } = useLoad(async () => {
|
||||
accounts.value = (await api.get<{ accounts: Account[] }>("/api/accounts")).accounts;
|
||||
}, "Couldn't load accounts.");
|
||||
const { busy, act } = useRowAction();
|
||||
// The reset link just made, and whose it is. Never retrievable again once dismissed.
|
||||
const fresh = ref<{ link: string; email: string } | null>(null);
|
||||
|
||||
async function load() {
|
||||
error.value = "";
|
||||
try {
|
||||
accounts.value = (await api.get<{ accounts: Account[] }>("/api/accounts")).accounts;
|
||||
} catch (e) {
|
||||
error.value = errorMessage(e, "Couldn't load accounts.");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function resetLink(account: Account) {
|
||||
const self = account.id === session.user?.id;
|
||||
const warning = self
|
||||
|
||||
@@ -3,6 +3,7 @@ import { onMounted, ref } from "vue";
|
||||
import { api } from "../api/client";
|
||||
import { errorMessage } from "../api/errors";
|
||||
import { formatShortDateTime } from "../notes/datetime";
|
||||
import { useLoad } from "../composables/useAction";
|
||||
|
||||
// Admin: what has happened to accounts on this instance, newest first (#2939 §5).
|
||||
// Kept for `audit_retention_days` (Settings → Security).
|
||||
@@ -45,28 +46,18 @@ const REFUSALS = new Set([
|
||||
|
||||
const events = ref<AuditEvent[]>([]);
|
||||
const more = ref(false);
|
||||
const loading = ref(true);
|
||||
const { loading, error, load } = useLoad(async () => {
|
||||
const res = await page();
|
||||
events.value = res.events;
|
||||
more.value = res.more;
|
||||
}, "Couldn't load activity.");
|
||||
const loadingMore = ref(false);
|
||||
const error = ref("");
|
||||
|
||||
async function page(before?: string) {
|
||||
const query = before ? `?before=${encodeURIComponent(before)}` : "";
|
||||
return api.get<{ events: AuditEvent[]; more: boolean }>(`/api/accounts/activity${query}`);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
error.value = "";
|
||||
try {
|
||||
const res = await page();
|
||||
events.value = res.events;
|
||||
more.value = res.more;
|
||||
} catch (e) {
|
||||
error.value = errorMessage(e, "Couldn't load activity.");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
const last = events.value[events.value.length - 1];
|
||||
if (!last) return;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { errorMessage } from "../api/errors";
|
||||
import BaseButton from "./BaseButton.vue";
|
||||
import BaseInput from "./BaseInput.vue";
|
||||
import Icon from "./Icon.vue";
|
||||
import { useRowAction } from "../composables/useAction";
|
||||
import { useRowAction, useLoad } from "../composables/useAction";
|
||||
|
||||
// Admin: groups of people a note can be shared with in one go (#5177). A group is
|
||||
// the instance's; anyone can share with it, and a note shared with it reaches whoever
|
||||
@@ -23,11 +23,16 @@ interface Group {
|
||||
members: Person[];
|
||||
}
|
||||
|
||||
|
||||
const groups = ref<Group[]>([]);
|
||||
const accounts = ref<Person[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
const { loading, error, load } = useLoad(async () => {
|
||||
const [g, a] = await Promise.all([
|
||||
api.get<{ groups: Group[] }>("/api/groups"),
|
||||
api.get<{ accounts: Person[] }>("/api/accounts"),
|
||||
]);
|
||||
groups.value = g.groups;
|
||||
accounts.value = a.accounts;
|
||||
}, "Couldn't load groups.");
|
||||
const name = ref("");
|
||||
const creating = ref(false);
|
||||
const { busy, act } = useRowAction();
|
||||
@@ -43,22 +48,6 @@ function outside(group: Group): Person[] {
|
||||
|
||||
const empty = computed(() => !loading.value && !groups.value.length);
|
||||
|
||||
async function load() {
|
||||
error.value = "";
|
||||
try {
|
||||
const [g, a] = await Promise.all([
|
||||
api.get<{ groups: Group[] }>("/api/groups"),
|
||||
api.get<{ accounts: Person[] }>("/api/accounts"),
|
||||
]);
|
||||
groups.value = g.groups;
|
||||
accounts.value = a.accounts;
|
||||
} catch (e) {
|
||||
error.value = errorMessage(e, "Couldn't load groups.");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function replace(updated: Group) {
|
||||
groups.value = groups.value.map((g) => (g.id === updated.id ? updated : g)).sort(byName);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { appLink } from "../router/links";
|
||||
import BaseButton from "./BaseButton.vue";
|
||||
import BaseInput from "./BaseInput.vue";
|
||||
import OneTimeLink from "./OneTimeLink.vue";
|
||||
import { toastOnFailure } from "../composables/useAction";
|
||||
import { toastOnFailure, useLoad } from "../composables/useAction";
|
||||
|
||||
// Admin: invite one person to register while registration stays closed (#5172).
|
||||
// The server keeps only a hash of each invite's token, so the link is shown once,
|
||||
@@ -30,25 +30,15 @@ const LIFETIMES = [
|
||||
];
|
||||
|
||||
const invites = ref<Invite[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
const { loading, error, load } = useLoad(async () => {
|
||||
invites.value = (await api.get<{ invites: Invite[] }>("/api/invites")).invites;
|
||||
}, "Couldn't load invites.");
|
||||
const email = ref("");
|
||||
const days = ref(7);
|
||||
const creating = ref(false);
|
||||
// The link for the invite just made. Never retrievable again once dismissed.
|
||||
const freshLink = ref("");
|
||||
|
||||
async function load() {
|
||||
error.value = "";
|
||||
try {
|
||||
invites.value = (await api.get<{ invites: Invite[] }>("/api/invites")).invites;
|
||||
} catch (e) {
|
||||
error.value = errorMessage(e, "Couldn't load invites.");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function create() {
|
||||
creating.value = true;
|
||||
error.value = "";
|
||||
|
||||
@@ -45,3 +45,23 @@ export function useRowAction() {
|
||||
}
|
||||
return { busy, act };
|
||||
}
|
||||
|
||||
/**
|
||||
* A list's first load: `loading` until it settles, then `error` holding what went
|
||||
* wrong (the server's reason, else `fallback`) or "". `load` is also the retry.
|
||||
*/
|
||||
export function useLoad(work: () => Promise<unknown>, fallback: string) {
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
async function load(): Promise<void> {
|
||||
error.value = "";
|
||||
try {
|
||||
await work();
|
||||
} catch (e) {
|
||||
error.value = errorMessage(e, fallback);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
return { loading, error, load };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user