kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'mastodon-client' into 'main'
Use a custom API client for fetching data See merge request soapbox-pub/soapbox!3146environments/review-main-yi2y9f/deployments/4887
commit
c3655d085c
|
@ -0,0 +1,10 @@
|
|||
export class HTTPError extends Error {
|
||||
|
||||
response: Response;
|
||||
|
||||
constructor(response: Response) {
|
||||
super(response.statusText);
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import { HTTPError } from './HTTPError';
|
||||
|
||||
interface Opts {
|
||||
searchParams?: Record<string, string | number | boolean>;
|
||||
headers?: Record<string, string>;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export class MastodonClient {
|
||||
|
||||
readonly baseUrl: string;
|
||||
|
||||
private fetch: typeof fetch;
|
||||
private accessToken?: string;
|
||||
|
||||
constructor(baseUrl: string, accessToken?: string, fetch = globalThis.fetch.bind(globalThis)) {
|
||||
this.fetch = fetch;
|
||||
this.baseUrl = baseUrl;
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
async get(path: string, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('GET', path, undefined, opts);
|
||||
}
|
||||
|
||||
async post(path: string, data?: unknown, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('POST', path, data, opts);
|
||||
}
|
||||
|
||||
async put(path: string, data?: unknown, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('PUT', path, data, opts);
|
||||
}
|
||||
|
||||
async delete(path: string, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('DELETE', path, undefined, opts);
|
||||
}
|
||||
|
||||
async patch(path: string, data: unknown, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('PATCH', path, data, opts);
|
||||
}
|
||||
|
||||
async head(path: string, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('HEAD', path, undefined, opts);
|
||||
}
|
||||
|
||||
async options(path: string, opts: Opts = {}): Promise<Response> {
|
||||
return this.request('OPTIONS', path, undefined, opts);
|
||||
}
|
||||
|
||||
async request(method: string, path: string, data: unknown, opts: Opts = {}): Promise<Response> {
|
||||
const url = new URL(path, this.baseUrl);
|
||||
|
||||
if (opts.searchParams) {
|
||||
const params = Object
|
||||
.entries(opts.searchParams)
|
||||
.map(([key, value]) => ([key, String(value)]));
|
||||
|
||||
url.search = new URLSearchParams(params).toString();
|
||||
}
|
||||
|
||||
const headers = new Headers(opts.headers);
|
||||
|
||||
if (this.accessToken) {
|
||||
headers.set('Authorization', `Bearer ${this.accessToken}`);
|
||||
}
|
||||
|
||||
let body: BodyInit | undefined;
|
||||
|
||||
if (data instanceof FormData) {
|
||||
headers.set('Content-Type', 'multipart/form-data');
|
||||
body = data;
|
||||
} else if (data !== undefined) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const request = new Request(url, {
|
||||
method,
|
||||
headers,
|
||||
signal: opts.signal,
|
||||
body,
|
||||
});
|
||||
|
||||
const response = await this.fetch(request);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new HTTPError(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,8 +3,7 @@ import { useHistory } from 'react-router-dom';
|
|||
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntity } from 'soapbox/entity-store/hooks';
|
||||
import { useFeatures, useLoggedIn } from 'soapbox/hooks';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useApi, useFeatures, useLoggedIn } from 'soapbox/hooks';
|
||||
import { type Account, accountSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useRelationship } from './useRelationship';
|
||||
|
|
|
@ -3,8 +3,7 @@ import { useHistory } from 'react-router-dom';
|
|||
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntityLookup } from 'soapbox/entity-store/hooks';
|
||||
import { useFeatures, useLoggedIn } from 'soapbox/hooks';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useApi, useFeatures, useLoggedIn } from 'soapbox/hooks';
|
||||
import { type Account, accountSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useRelationship } from './useRelationship';
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { importEntities } from 'soapbox/entity-store/actions';
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useTransaction } from 'soapbox/entity-store/hooks';
|
||||
import { useAppDispatch, useLoggedIn } from 'soapbox/hooks';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useApi, useAppDispatch, useLoggedIn } from 'soapbox/hooks';
|
||||
import { relationshipSchema } from 'soapbox/schemas';
|
||||
|
||||
interface FollowOpts {
|
||||
|
@ -57,7 +56,7 @@ function useFollow() {
|
|||
|
||||
try {
|
||||
const response = await api.post(`/api/v1/accounts/${accountId}/follow`, options);
|
||||
const result = relationshipSchema.safeParse(response.data);
|
||||
const result = relationshipSchema.safeParse(await response.json());
|
||||
if (result.success) {
|
||||
dispatch(importEntities([result.data], Entities.RELATIONSHIPS));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntity } from 'soapbox/entity-store/hooks';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useApi } from 'soapbox/hooks';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig';
|
||||
import { type PatronUser, patronUserSchema } from 'soapbox/schemas';
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities';
|
||||
import { useLoggedIn } from 'soapbox/hooks';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useApi, useLoggedIn } from 'soapbox/hooks';
|
||||
import { type Relationship, relationshipSchema } from 'soapbox/schemas';
|
||||
|
||||
function useRelationships(listKey: string[], ids: string[]) {
|
||||
|
|
|
@ -6,8 +6,6 @@ import { adminAnnouncementSchema, type AdminAnnouncement } from 'soapbox/schemas
|
|||
|
||||
import { useAnnouncements as useUserAnnouncements } from '../announcements';
|
||||
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
interface CreateAnnouncementParams {
|
||||
content: string;
|
||||
starts_at?: string | null;
|
||||
|
@ -24,7 +22,8 @@ const useAnnouncements = () => {
|
|||
const userAnnouncements = useUserAnnouncements();
|
||||
|
||||
const getAnnouncements = async () => {
|
||||
const { data } = await api.get<AdminAnnouncement[]>('/api/v1/pleroma/admin/announcements');
|
||||
const response = await api.get('/api/v1/pleroma/admin/announcements');
|
||||
const data: AdminAnnouncement[] = await response.json();
|
||||
|
||||
const normalizedData = data.map((announcement) => adminAnnouncementSchema.parse(announcement));
|
||||
return normalizedData;
|
||||
|
@ -42,10 +41,12 @@ const useAnnouncements = () => {
|
|||
} = useMutation({
|
||||
mutationFn: (params: CreateAnnouncementParams) => api.post('/api/v1/pleroma/admin/announcements', params),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
|
||||
[...prevResult, adminAnnouncementSchema.parse(data)],
|
||||
),
|
||||
);
|
||||
},
|
||||
onSettled: () => userAnnouncements.refetch(),
|
||||
});
|
||||
|
||||
|
@ -55,10 +56,12 @@ const useAnnouncements = () => {
|
|||
} = useMutation({
|
||||
mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api.patch(`/api/v1/pleroma/admin/announcements/${id}`, params),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
|
||||
prevResult.map((announcement) => announcement.id === data.id ? adminAnnouncementSchema.parse(data) : announcement),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSettled: () => userAnnouncements.refetch(),
|
||||
});
|
||||
|
||||
|
|
|
@ -11,8 +11,11 @@ interface CreateDomainParams {
|
|||
const useCreateDomain = () => {
|
||||
const api = useApi();
|
||||
|
||||
const { createEntity, ...rest } = useCreateEntity([Entities.DOMAINS], (params: CreateDomainParams) =>
|
||||
api.post('/api/v1/pleroma/admin/domains', params), { schema: domainSchema });
|
||||
const { createEntity, ...rest } = useCreateEntity(
|
||||
[Entities.DOMAINS],
|
||||
(params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', params),
|
||||
{ schema: domainSchema },
|
||||
);
|
||||
|
||||
return {
|
||||
createDomain: createEntity,
|
||||
|
|
|
@ -4,8 +4,6 @@ import { useApi } from 'soapbox/hooks';
|
|||
import { queryClient } from 'soapbox/queries/client';
|
||||
import { domainSchema, type Domain } from 'soapbox/schemas';
|
||||
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
interface CreateDomainParams {
|
||||
domain: string;
|
||||
public: boolean;
|
||||
|
@ -20,7 +18,8 @@ const useDomains = () => {
|
|||
const api = useApi();
|
||||
|
||||
const getDomains = async () => {
|
||||
const { data } = await api.get<Domain[]>('/api/v1/pleroma/admin/domains');
|
||||
const response = await api.get('/api/v1/pleroma/admin/domains');
|
||||
const data: Domain[] = await response.json();
|
||||
|
||||
const normalizedData = data.map((domain) => domainSchema.parse(domain));
|
||||
return normalizedData;
|
||||
|
@ -38,10 +37,12 @@ const useDomains = () => {
|
|||
} = useMutation({
|
||||
mutationFn: (params: CreateDomainParams) => api.post('/api/v1/pleroma/admin/domains', params),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray<Domain>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray<Domain>) =>
|
||||
[...prevResult, domainSchema.parse(data)],
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
|
@ -50,10 +51,12 @@ const useDomains = () => {
|
|||
} = useMutation({
|
||||
mutationFn: ({ id, ...params }: UpdateDomainParams) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, params),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray<Domain>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'domains'], (prevResult: ReadonlyArray<Domain>) =>
|
||||
prevResult.map((domain) => domain.id === data.id ? domainSchema.parse(data) : domain),
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
|
|
|
@ -32,7 +32,8 @@ export const useManageZapSplit = () => {
|
|||
*/
|
||||
const fetchZapSplitData = async () => {
|
||||
try {
|
||||
const { data } = await api.get<ZapSplitData[]>('/api/v1/ditto/zap_splits');
|
||||
const response = await api.get('/api/v1/ditto/zap_splits');
|
||||
const data: ZapSplitData[] = await response.json();
|
||||
if (data) {
|
||||
const normalizedData = data.map((dataSplit) => baseZapAccountSchema.parse(dataSplit));
|
||||
setFormattedData(normalizedData);
|
||||
|
@ -132,9 +133,7 @@ export const useManageZapSplit = () => {
|
|||
* @param accountId - The ID of the account to be removed.
|
||||
*/
|
||||
const removeAccount = async (accountId: string) => {
|
||||
const isToDelete = [(formattedData.find(item => item.account.id === accountId))?.account.id];
|
||||
|
||||
await api.delete('/api/v1/admin/ditto/zap_splits/', { data: isToDelete });
|
||||
await api.request('DELETE', '/api/v1/admin/ditto/zap_splits', [accountId]);
|
||||
await fetchZapSplitData();
|
||||
};
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ const useModerationLog = () => {
|
|||
const api = useApi();
|
||||
|
||||
const getModerationLog = async (page: number): Promise<ModerationLogResult> => {
|
||||
const { data } = await api.get<ModerationLogResult>('/api/v1/pleroma/admin/moderation_log', { params: { page } });
|
||||
const response = await api.get('/api/v1/pleroma/admin/moderation_log', { searchParams: { page } });
|
||||
const data: ModerationLogResult = await response.json();
|
||||
|
||||
const normalizedData = data.items.map((domain) => moderationLogEntrySchema.parse(domain));
|
||||
|
||||
|
|
|
@ -4,15 +4,14 @@ import { useApi } from 'soapbox/hooks';
|
|||
import { queryClient } from 'soapbox/queries/client';
|
||||
import { relaySchema, type Relay } from 'soapbox/schemas';
|
||||
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
const useRelays = () => {
|
||||
const api = useApi();
|
||||
|
||||
const getRelays = async () => {
|
||||
const { data } = await api.get<{ relays: Relay[] }>('/api/v1/pleroma/admin/relay');
|
||||
const response = await api.get('/api/v1/pleroma/admin/relay');
|
||||
const relays: Relay[] = await response.json();
|
||||
|
||||
const normalizedData = data.relays?.map((relay) => relaySchema.parse(relay));
|
||||
const normalizedData = relays?.map((relay) => relaySchema.parse(relay));
|
||||
return normalizedData;
|
||||
};
|
||||
|
||||
|
@ -28,19 +27,21 @@ const useRelays = () => {
|
|||
} = useMutation({
|
||||
mutationFn: (relayUrl: string) => api.post('/api/v1/pleroma/admin/relays', { relay_url: relayUrl }),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'relays'], (prevResult: ReadonlyArray<Relay>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'relays'], (prevResult: ReadonlyArray<Relay>) =>
|
||||
[...prevResult, relaySchema.parse(data)],
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
mutate: unfollowRelay,
|
||||
isPending: isPendingUnfollow,
|
||||
} = useMutation({
|
||||
mutationFn: (relayUrl: string) => api.delete('/api/v1/pleroma/admin/relays', {
|
||||
data: { relay_url: relayUrl },
|
||||
}),
|
||||
mutationFn: async (relayUrl: string) => {
|
||||
await api.request('DELETE', '/api/v1/pleroma/admin/relays', { relay_url: relayUrl });
|
||||
},
|
||||
retry: false,
|
||||
onSuccess: (_, relayUrl) =>
|
||||
queryClient.setQueryData(['admin', 'relays'], (prevResult: ReadonlyArray<Relay>) =>
|
||||
|
|
|
@ -4,8 +4,6 @@ import { useApi } from 'soapbox/hooks';
|
|||
import { queryClient } from 'soapbox/queries/client';
|
||||
import { adminRuleSchema, type AdminRule } from 'soapbox/schemas';
|
||||
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
interface CreateRuleParams {
|
||||
priority?: number;
|
||||
text: string;
|
||||
|
@ -23,7 +21,8 @@ const useRules = () => {
|
|||
const api = useApi();
|
||||
|
||||
const getRules = async () => {
|
||||
const { data } = await api.get<AdminRule[]>('/api/v1/pleroma/admin/rules');
|
||||
const response = await api.get('/api/v1/pleroma/admin/rules');
|
||||
const data: AdminRule[] = await response.json();
|
||||
|
||||
const normalizedData = data.map((rule) => adminRuleSchema.parse(rule));
|
||||
return normalizedData;
|
||||
|
@ -41,10 +40,12 @@ const useRules = () => {
|
|||
} = useMutation({
|
||||
mutationFn: (params: CreateRuleParams) => api.post('/api/v1/pleroma/admin/rules', params),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray<AdminRule>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray<AdminRule>) =>
|
||||
[...prevResult, adminRuleSchema.parse(data)],
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
|
@ -53,10 +54,12 @@ const useRules = () => {
|
|||
} = useMutation({
|
||||
mutationFn: ({ id, ...params }: UpdateRuleParams) => api.patch(`/api/v1/pleroma/admin/rules/${id}`, params),
|
||||
retry: false,
|
||||
onSuccess: ({ data }: AxiosResponse) =>
|
||||
queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray<AdminRule>) =>
|
||||
onSuccess: async (response: Response) => {
|
||||
const data = await response.json();
|
||||
return queryClient.setQueryData(['admin', 'rules'], (prevResult: ReadonlyArray<AdminRule>) =>
|
||||
prevResult.map((rule) => rule.id === data.id ? adminRuleSchema.parse(data) : rule),
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
|
|
|
@ -8,8 +8,11 @@ import type { CreateDomainParams } from './useCreateDomain';
|
|||
const useUpdateDomain = (id: string) => {
|
||||
const api = useApi();
|
||||
|
||||
const { createEntity, ...rest } = useCreateEntity([Entities.DOMAINS], (params: Omit<CreateDomainParams, 'domain'>) =>
|
||||
api.patch(`/api/v1/pleroma/admin/domains/${id}`, params), { schema: domainSchema });
|
||||
const { createEntity, ...rest } = useCreateEntity(
|
||||
[Entities.DOMAINS],
|
||||
(params: Omit<CreateDomainParams, 'domain'>) => api.patch(`/api/v1/pleroma/admin/domains/${id}`, params),
|
||||
{ schema: domainSchema },
|
||||
);
|
||||
|
||||
return {
|
||||
updateDomain: createEntity,
|
||||
|
|
|
@ -46,7 +46,7 @@ function useVerify() {
|
|||
const accts = accountIdsToAccts(getState(), accountIds);
|
||||
verifyEffect(accountIds, false);
|
||||
try {
|
||||
await api.delete('/api/v1/pleroma/admin/users/tag', { data: { nicknames: accts, tags: ['verified'] } });
|
||||
await api.request('DELETE', '/api/v1/pleroma/admin/users/tag', { nicknames: accts, tags: ['verified'] });
|
||||
callbacks?.onSuccess?.();
|
||||
} catch (e) {
|
||||
callbacks?.onError?.(e);
|
||||
|
|
|
@ -24,7 +24,8 @@ const useAnnouncements = () => {
|
|||
const api = useApi();
|
||||
|
||||
const getAnnouncements = async () => {
|
||||
const { data } = await api.get<Announcement[]>('/api/v1/announcements');
|
||||
const response = await api.get('/api/v1/announcements');
|
||||
const data: Announcement[] = await response.json();
|
||||
|
||||
const normalizedData = data?.map((announcement) => announcementSchema.parse(announcement));
|
||||
return normalizedData;
|
||||
|
@ -39,8 +40,10 @@ const useAnnouncements = () => {
|
|||
const {
|
||||
mutate: addReaction,
|
||||
} = useMutation({
|
||||
mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) =>
|
||||
api.put<Announcement>(`/api/v1/announcements/${announcementId}/reactions/${name}`),
|
||||
mutationFn: async ({ announcementId, name }: { announcementId: string; name: string }): Promise<Announcement> => {
|
||||
const response = await api.put(`/api/v1/announcements/${announcementId}/reactions/${name}`);
|
||||
return response.json();
|
||||
},
|
||||
retry: false,
|
||||
onMutate: ({ announcementId: id, name }) => {
|
||||
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
|
||||
|
@ -63,8 +66,10 @@ const useAnnouncements = () => {
|
|||
const {
|
||||
mutate: removeReaction,
|
||||
} = useMutation({
|
||||
mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) =>
|
||||
api.delete<Announcement>(`/api/v1/announcements/${announcementId}/reactions/${name}`),
|
||||
mutationFn: async ({ announcementId, name }: { announcementId: string; name: string }): Promise<Announcement> => {
|
||||
const response = await api.delete(`/api/v1/announcements/${announcementId}/reactions/${name}`);
|
||||
return response.json();
|
||||
},
|
||||
retry: false,
|
||||
onMutate: ({ announcementId: id, name }) => {
|
||||
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
|
||||
|
|
|
@ -35,14 +35,11 @@ const useCaptcha = () => {
|
|||
try {
|
||||
const topI = getRandomNumber(0, (356 - 61));
|
||||
const leftI = getRandomNumber(0, (330 - 61));
|
||||
const { data } = await api.get('/api/v1/ditto/captcha');
|
||||
if (data) {
|
||||
const normalizedData = captchaSchema.parse(data);
|
||||
setCaptcha(normalizedData);
|
||||
setYPosition(topI);
|
||||
setXPosition(leftI);
|
||||
}
|
||||
|
||||
const response = await api.get('/api/v1/ditto/captcha');
|
||||
const data = captchaSchema.parse(await response.json());
|
||||
setCaptcha(data);
|
||||
setYPosition(topI);
|
||||
setXPosition(leftI);
|
||||
} catch (error) {
|
||||
toast.error('Error loading captcha:');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useDismissEntity, useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useApi } from 'soapbox/hooks';
|
||||
import { accountSchema } from 'soapbox/schemas';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ function useGroupSearch(search: string) {
|
|||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, 'discover', 'search', search],
|
||||
() => api.get('/api/v1/groups/search', {
|
||||
params: {
|
||||
searchParams: {
|
||||
q: search,
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures';
|
||||
|
||||
|
@ -16,19 +17,17 @@ function useGroupValidation(name: string = '') {
|
|||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
|
||||
const getValidation = async() => {
|
||||
const { data } = await api.get<Validation>('/api/v1/groups/validate', {
|
||||
params: { name },
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 422) {
|
||||
return { data: error.response.data };
|
||||
}
|
||||
const getValidation = async () => {
|
||||
try {
|
||||
const response = await api.get('/api/v1/groups/validate', { searchParams: { name } });
|
||||
return response.json();
|
||||
} catch (e) {
|
||||
if (e instanceof HTTPError && e.response.status === 422) {
|
||||
return e.response.json();
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
return data;
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const queryInfo = useQuery<Validation>({
|
||||
|
|
|
@ -12,7 +12,7 @@ function useGroups(q: string = '') {
|
|||
|
||||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, 'search', q],
|
||||
() => api.get('/api/v1/groups', { params: { q } }),
|
||||
() => api.get('/api/v1/groups', { searchParams: { q } }),
|
||||
{ enabled: features.groups, schema: groupSchema },
|
||||
);
|
||||
const { relationships } = useGroupRelationships(
|
||||
|
|
|
@ -11,7 +11,7 @@ function usePendingGroups() {
|
|||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, account?.id!, 'pending'],
|
||||
() => api.get('/api/v1/groups', {
|
||||
params: {
|
||||
searchParams: {
|
||||
pending: true,
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -13,12 +13,7 @@ function useUpdateBookmarkFolder(folderId: string) {
|
|||
|
||||
const { createEntity, ...rest } = useCreateEntity(
|
||||
[Entities.BOOKMARK_FOLDERS],
|
||||
(params: UpdateBookmarkFolderParams) =>
|
||||
api.patch(`/api/v1/pleroma/bookmark_folders/${folderId}`, params, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}),
|
||||
(params: UpdateBookmarkFolderParams) => api.patch(`/api/v1/pleroma/bookmark_folders/${folderId}`, params),
|
||||
{ schema: bookmarkFolderSchema },
|
||||
);
|
||||
|
||||
|
|
|
@ -30,15 +30,14 @@ const useZapSplit = (status: StatusEntity | undefined, account: AccountEntity) =
|
|||
const [zapArrays, setZapArrays] = useState<ZapSplitData[]>([]);
|
||||
const [zapSplitData, setZapSplitData] = useState<{splitAmount: number; receiveAmount: number; splitValues: SplitValue[]}>({ splitAmount: Number(), receiveAmount: Number(), splitValues: [] });
|
||||
|
||||
const fetchZapSplit = async (id: string) => {
|
||||
return await api.get(`/api/v1/ditto/${id}/zap_splits`);
|
||||
};
|
||||
const fetchZapSplit = (id: string) => api.get(`/api/v1/ditto/${id}/zap_splits`);
|
||||
|
||||
const loadZapSplitData = async () => {
|
||||
if (status) {
|
||||
const data = (await fetchZapSplit(status.id)).data;
|
||||
const response = await fetchZapSplit(status.id);
|
||||
const data: ZapSplitData[] = await response.json();
|
||||
if (data) {
|
||||
const normalizedData = data.map((dataSplit: ZapSplitData) => baseZapAccountSchema.parse(dataSplit));
|
||||
const normalizedData = data.map((dataSplit) => baseZapAccountSchema.parse(dataSplit));
|
||||
setZapArrays(normalizedData);
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +52,7 @@ const useZapSplit = (status: StatusEntity | undefined, account: AccountEntity) =
|
|||
const receiveAmount = (zapAmount: number) => {
|
||||
if (zapArrays.length > 0) {
|
||||
const zapAmountPrincipal = zapArrays.find((zapSplit: ZapSplitData) => zapSplit.account.id === account.id);
|
||||
const formattedZapAmountPrincipal = {
|
||||
const formattedZapAmountPrincipal = {
|
||||
account: zapAmountPrincipal?.account,
|
||||
message: zapAmountPrincipal?.message,
|
||||
weight: zapArrays.filter((zapSplit: ZapSplitData) => zapSplit.account.id === account.id).reduce((acc:number, zapData: ZapSplitData) => acc + zapData.weight, 0),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { Entity } from '../types';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type z from 'zod';
|
||||
|
||||
type EntitySchema<TEntity extends Entity = Entity> = z.ZodType<TEntity, z.ZodTypeDef, any>;
|
||||
|
@ -35,7 +34,7 @@ interface EntityCallbacks<Value, Error = unknown> {
|
|||
* Passed into hooks to make requests.
|
||||
* Must return an Axios response.
|
||||
*/
|
||||
type EntityFn<T> = (value: T) => Promise<AxiosResponse>
|
||||
type EntityFn<T> = (value: T) => Promise<Response>
|
||||
|
||||
export type {
|
||||
EntitySchema,
|
||||
|
|
|
@ -54,7 +54,8 @@ function useBatchedEntities<TEntity extends Entity>(
|
|||
dispatch(entitiesFetchRequest(entityType, listKey));
|
||||
try {
|
||||
const response = await entityFn(filteredIds);
|
||||
const entities = filteredArray(schema).parse(response.data);
|
||||
const json = await response.json();
|
||||
const entities = filteredArray(schema).parse(json);
|
||||
dispatch(entitiesFetchSuccess(entities, entityType, listKey, 'end', {
|
||||
next: undefined,
|
||||
prev: undefined,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AxiosError } from 'axios';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch';
|
||||
import { useLoading } from 'soapbox/hooks/useLoading';
|
||||
|
||||
|
@ -25,11 +25,11 @@ function useCreateEntity<TEntity extends Entity = Entity, Data = unknown>(
|
|||
const [isSubmitting, setPromise] = useLoading();
|
||||
const { entityType, listKey } = parseEntitiesPath(expandedPath);
|
||||
|
||||
async function createEntity(data: Data, callbacks: EntityCallbacks<TEntity, AxiosError> = {}): Promise<void> {
|
||||
async function createEntity(data: Data, callbacks: EntityCallbacks<TEntity, HTTPError> = {}): Promise<void> {
|
||||
try {
|
||||
const result = await setPromise(entityFn(data));
|
||||
const schema = opts.schema || z.custom<TEntity>();
|
||||
const entity = schema.parse(result.data);
|
||||
const entity = schema.parse(await result.json());
|
||||
|
||||
// TODO: optimistic updating
|
||||
dispatch(importEntities([entity], entityType, listKey, 'start'));
|
||||
|
@ -38,7 +38,7 @@ function useCreateEntity<TEntity extends Entity = Entity, Data = unknown>(
|
|||
callbacks.onSuccess(entity);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
if (error instanceof HTTPError) {
|
||||
if (callbacks.onError) {
|
||||
callbacks.onError(error);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import LinkHeader from 'http-link-header';
|
||||
import { useEffect } from 'react';
|
||||
import z from 'zod';
|
||||
|
||||
import { getNextLink, getPrevLink } from 'soapbox/api';
|
||||
import { useApi } from 'soapbox/hooks/useApi';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector';
|
||||
|
@ -66,13 +66,16 @@ function useEntities<TEntity extends Entity>(
|
|||
dispatch(entitiesFetchRequest(entityType, listKey));
|
||||
try {
|
||||
const response = await req();
|
||||
const entities = filteredArray(schema).parse(response.data);
|
||||
const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']);
|
||||
const json = await response.json();
|
||||
const entities = filteredArray(schema).parse(json);
|
||||
const parsedCount = realNumberSchema.safeParse(response.headers.get('x-total-count'));
|
||||
const totalCount = parsedCount.success ? parsedCount.data : undefined;
|
||||
const linkHeader = response.headers.get('link');
|
||||
const links = linkHeader ? new LinkHeader(linkHeader) : undefined;
|
||||
|
||||
dispatch(entitiesFetchSuccess(entities, entityType, listKey, pos, {
|
||||
next: getNextLink(response),
|
||||
prev: getPrevLink(response),
|
||||
next: links?.refs.find((link) => link.rel === 'next')?.uri,
|
||||
prev: links?.refs.find((link) => link.rel === 'prev')?.uri,
|
||||
totalCount: Number(totalCount) >= entities.length ? totalCount : undefined,
|
||||
fetching: false,
|
||||
fetched: true,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AxiosError } from 'axios';
|
||||
import { useEffect, useState } from 'react';
|
||||
import z from 'zod';
|
||||
|
||||
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector';
|
||||
import { useLoading } from 'soapbox/hooks/useLoading';
|
||||
|
@ -46,7 +46,8 @@ function useEntity<TEntity extends Entity>(
|
|||
const fetchEntity = async () => {
|
||||
try {
|
||||
const response = await setPromise(entityFn());
|
||||
const entity = schema.parse(response.data);
|
||||
const json = await response.json();
|
||||
const entity = schema.parse(json);
|
||||
dispatch(importEntities([entity], entityType));
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
|
@ -67,8 +68,8 @@ function useEntity<TEntity extends Entity>(
|
|||
isLoading,
|
||||
isLoaded,
|
||||
error,
|
||||
isUnauthorized: error instanceof AxiosError && error.response?.status === 401,
|
||||
isForbidden: error instanceof AxiosError && error.response?.status === 403,
|
||||
isUnauthorized: error instanceof HTTPError && error.response.status === 401,
|
||||
isForbidden: error instanceof HTTPError && error.response.status === 403,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AxiosError } from 'axios';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector';
|
||||
import { useLoading } from 'soapbox/hooks/useLoading';
|
||||
|
@ -36,7 +36,8 @@ function useEntityLookup<TEntity extends Entity>(
|
|||
const fetchEntity = async () => {
|
||||
try {
|
||||
const response = await setPromise(entityFn());
|
||||
const entity = schema.parse(response.data);
|
||||
const json = await response.json();
|
||||
const entity = schema.parse(json);
|
||||
setFetchedEntity(entity);
|
||||
dispatch(importEntities([entity], entityType));
|
||||
} catch (e) {
|
||||
|
@ -57,8 +58,8 @@ function useEntityLookup<TEntity extends Entity>(
|
|||
fetchEntity,
|
||||
isFetching,
|
||||
isLoading,
|
||||
isUnauthorized: error instanceof AxiosError && error.response?.status === 401,
|
||||
isForbidden: error instanceof AxiosError && error.response?.status === 403,
|
||||
isUnauthorized: error instanceof HTTPError && error.response.status === 401,
|
||||
isForbidden: error instanceof HTTPError && error.response.status === 403,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -101,8 +101,9 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
const data = error.response?.data as any;
|
||||
toast.error(data?.error);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
history.push(`/chats/${response.data.id}`);
|
||||
onSuccess: async (response) => {
|
||||
const data = await response.json();
|
||||
history.push(`/chats/${data.id}`);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ChatKeys.chatSearch(),
|
||||
});
|
||||
|
|
|
@ -14,7 +14,8 @@ export function useAdminNostrRelays() {
|
|||
return useQuery({
|
||||
queryKey: ['NostrRelay'],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.get('/api/v1/admin/ditto/relays');
|
||||
const response = await api.get('/api/v1/admin/ditto/relays');
|
||||
const data = await response.json();
|
||||
return relayEntitySchema.array().parse(data);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -49,11 +49,13 @@ const ChatSearch = (props: IChatSearch) => {
|
|||
const data = error.response?.data as any;
|
||||
toast.error(data?.error);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
onSuccess: async (response) => {
|
||||
const data = await response.json();
|
||||
|
||||
if (isMainPage) {
|
||||
history.push(`/chats/${response.data.id}`);
|
||||
history.push(`/chats/${data.id}`);
|
||||
} else {
|
||||
changeScreen(ChatWidgetScreens.CHAT, response.data.id);
|
||||
changeScreen(ChatWidgetScreens.CHAT, data.id);
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() });
|
||||
|
|
|
@ -197,7 +197,8 @@ function useNames() {
|
|||
return useQuery({
|
||||
queryKey: ['names', 'approved'],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.get('/api/v1/ditto/names?approved=true');
|
||||
const response = await api.get('/api/v1/ditto/names?approved=true');
|
||||
const data = await response.json();
|
||||
return adminAccountSchema.array().parse(data);
|
||||
},
|
||||
placeholderData: [],
|
||||
|
@ -210,7 +211,8 @@ function usePendingNames() {
|
|||
return useQuery({
|
||||
queryKey: ['names', 'pending'],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.get('/api/v1/ditto/names?approved=false');
|
||||
const response = await api.get('/api/v1/ditto/names?approved=false');
|
||||
const data = await response.json();
|
||||
return adminAccountSchema.array().parse(data);
|
||||
},
|
||||
placeholderData: [],
|
||||
|
|
|
@ -53,8 +53,8 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
: intl.formatMessage(messages.joinSuccess),
|
||||
);
|
||||
},
|
||||
onError(error) {
|
||||
const message = (error.response?.data as any).error;
|
||||
async onError(error) {
|
||||
const message = (await error.response.json() as any).error;
|
||||
if (message) {
|
||||
toast.error(message);
|
||||
}
|
||||
|
|
|
@ -67,10 +67,10 @@ const EditGroup: React.FC<IEditGroup> = ({ params: { groupId } }) => {
|
|||
invalidate();
|
||||
toast.success(intl.formatMessage(messages.groupSaved));
|
||||
},
|
||||
onError(error) {
|
||||
const message = (error.response?.data as any)?.error;
|
||||
async onError(error) {
|
||||
const message = (await error.response.json() as any)?.error;
|
||||
|
||||
if (error.response?.status === 422 && typeof message !== 'undefined') {
|
||||
if (error.response.status === 422 && message) {
|
||||
toast.error(message);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { AxiosError } from 'axios';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||
import { useCreateGroup, useGroupValidation, type CreateGroupParams } from 'soapbox/api/hooks';
|
||||
import { Modal, Stack } from 'soapbox/components/ui';
|
||||
import { useDebounce } from 'soapbox/hooks';
|
||||
|
@ -70,11 +70,15 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
|
|||
setCurrentStep(Steps.THREE);
|
||||
setGroup(group);
|
||||
},
|
||||
onError(error) {
|
||||
if (error instanceof AxiosError) {
|
||||
const msg = z.object({ error: z.string() }).safeParse(error.response?.data);
|
||||
if (msg.success) {
|
||||
toast.error(msg.data.error);
|
||||
async onError(error) {
|
||||
if (error instanceof HTTPError) {
|
||||
try {
|
||||
const data = await error.response.json();
|
||||
const msg = z.object({ error: z.string() }).parse(data);
|
||||
toast.error(msg.error);
|
||||
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import api from 'soapbox/api';
|
||||
import { MastodonClient } from 'soapbox/api/MastodonClient';
|
||||
|
||||
import { useGetState } from './useGetState';
|
||||
import { useAppSelector } from './useAppSelector';
|
||||
import { useOwnAccount } from './useOwnAccount';
|
||||
|
||||
/** Use stateful Axios client with auth from Redux. */
|
||||
export const useApi = () => {
|
||||
const getState = useGetState();
|
||||
return api(getState);
|
||||
};
|
||||
export function useApi(): MastodonClient {
|
||||
const { account } = useOwnAccount();
|
||||
const accessToken = useAppSelector((state) => account ? state.auth.users.get(account.url)?.access_token : undefined);
|
||||
const baseUrl = account ? new URL(account.url).origin : location.origin;
|
||||
|
||||
return new MastodonClient(baseUrl, accessToken);
|
||||
}
|
|
@ -47,8 +47,8 @@ const useUpdateCredentials = () => {
|
|||
|
||||
return { cachedAccount };
|
||||
},
|
||||
onSuccess(response) {
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
async onSuccess(response) {
|
||||
dispatch(patchMeSuccess(await response.json()));
|
||||
toast.success('Chat Settings updated successfully');
|
||||
},
|
||||
onError(_error, _variables, context: any) {
|
||||
|
|
|
@ -2,7 +2,6 @@ import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery
|
|||
import sumBy from 'lodash/sumBy';
|
||||
|
||||
import { importFetchedAccount, importFetchedAccounts } from 'soapbox/actions/importer';
|
||||
import { getNextLink } from 'soapbox/api';
|
||||
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
|
||||
import { useStatContext } from 'soapbox/contexts/stat-context';
|
||||
import { useApi, useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
|
@ -10,6 +9,7 @@ import { normalizeChatMessage } from 'soapbox/normalizers';
|
|||
import toast from 'soapbox/toast';
|
||||
import { ChatMessage } from 'soapbox/types/entities';
|
||||
import { reOrderChatListItems, updateChatMessage } from 'soapbox/utils/chats';
|
||||
import { getPagination } from 'soapbox/utils/pagination';
|
||||
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries';
|
||||
|
||||
import { queryClient } from './client';
|
||||
|
@ -84,16 +84,16 @@ const useChatMessages = (chat: IChat) => {
|
|||
const getChatMessages = async (chatId: string, pageParam?: any): Promise<PaginatedResult<ChatMessage>> => {
|
||||
const nextPageLink = pageParam?.link;
|
||||
const uri = nextPageLink || `/api/v1/pleroma/chats/${chatId}/messages`;
|
||||
const response = await api.get<any[]>(uri);
|
||||
const { data } = response;
|
||||
const response = await api.get(uri);
|
||||
const data = await response.json();
|
||||
|
||||
const link = getNextLink(response);
|
||||
const hasMore = !!link;
|
||||
const { next } = getPagination(response);
|
||||
const hasMore = !!next;
|
||||
const result = data.map(normalizeChatMessage);
|
||||
|
||||
return {
|
||||
result,
|
||||
link,
|
||||
link: next,
|
||||
hasMore,
|
||||
};
|
||||
};
|
||||
|
@ -133,17 +133,17 @@ const useChats = (search?: string) => {
|
|||
const endpoint = features.chatsV2 ? '/api/v2/pleroma/chats' : '/api/v1/pleroma/chats';
|
||||
const nextPageLink = pageParam?.link;
|
||||
const uri = nextPageLink || endpoint;
|
||||
const response = await api.get<IChat[]>(uri, {
|
||||
params: search ? {
|
||||
const response = await api.get(uri, {
|
||||
searchParams: search ? {
|
||||
search,
|
||||
} : undefined,
|
||||
});
|
||||
const { data } = response;
|
||||
const data: IChat[] = await response.json();
|
||||
|
||||
const link = getNextLink(response);
|
||||
const hasMore = !!link;
|
||||
const { next } = getPagination(response);
|
||||
const hasMore = !!next;
|
||||
|
||||
setUnreadChatsCount(Number(response.headers['x-unread-messages-count']) || sumBy(data, (chat) => chat.unread));
|
||||
setUnreadChatsCount(Number(response.headers.get('x-unread-messages-count')) || sumBy(data, (chat) => chat.unread));
|
||||
|
||||
// Set the relationships to these users in the redux store.
|
||||
fetchRelationships.mutate({ accountIds: data.map((item) => item.account.id) });
|
||||
|
@ -152,7 +152,7 @@ const useChats = (search?: string) => {
|
|||
return {
|
||||
result: data,
|
||||
hasMore,
|
||||
link,
|
||||
link: next,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -178,7 +178,7 @@ const useChats = (search?: string) => {
|
|||
data,
|
||||
};
|
||||
|
||||
const getOrCreateChatByAccountId = (accountId: string) => api.post<IChat>(`/api/v1/pleroma/chats/by-account-id/${accountId}`);
|
||||
const getOrCreateChatByAccountId = (accountId: string) => api.post(`/api/v1/pleroma/chats/by-account-id/${accountId}`);
|
||||
|
||||
return { chatsQuery, getOrCreateChatByAccountId };
|
||||
};
|
||||
|
@ -190,7 +190,8 @@ const useChat = (chatId?: string) => {
|
|||
|
||||
const getChat = async () => {
|
||||
if (chatId) {
|
||||
const { data } = await api.get<IChat>(`/api/v1/pleroma/chats/${chatId}`);
|
||||
const response = await api.get(`/api/v1/pleroma/chats/${chatId}`);
|
||||
const data: IChat = await response.json();
|
||||
|
||||
fetchRelationships.mutate({ accountIds: [data.account.id] });
|
||||
dispatch(importFetchedAccount(data.account));
|
||||
|
@ -217,8 +218,9 @@ const useChatActions = (chatId: string) => {
|
|||
const { chat, changeScreen } = useChatContext();
|
||||
|
||||
const markChatAsRead = async (lastReadId: string) => {
|
||||
return api.post<IChat>(`/api/v1/pleroma/chats/${chatId}/read`, { last_read_id: lastReadId })
|
||||
.then(({ data }) => {
|
||||
return api.post(`/api/v1/pleroma/chats/${chatId}/read`, { last_read_id: lastReadId })
|
||||
.then(async (response) => {
|
||||
const data = await response.json();
|
||||
updatePageItem(ChatKeys.chatSearch(), data, (o, n) => o.id === n.id);
|
||||
const queryData = queryClient.getQueryData<InfiniteData<PaginatedResult<unknown>>>(ChatKeys.chatSearch());
|
||||
|
||||
|
@ -239,12 +241,13 @@ const useChatActions = (chatId: string) => {
|
|||
};
|
||||
|
||||
const createChatMessage = useMutation({
|
||||
mutationFn: ({ chatId, content, mediaIds }: { chatId: string; content: string; mediaIds?: string[] }) => {
|
||||
return api.post<ChatMessage>(`/api/v1/pleroma/chats/${chatId}/messages`, {
|
||||
mutationFn: async ({ chatId, content, mediaIds }: { chatId: string; content: string; mediaIds?: string[] }) => {
|
||||
const response = await api.post(`/api/v1/pleroma/chats/${chatId}/messages`, {
|
||||
content,
|
||||
media_id: (mediaIds && mediaIds.length === 1) ? mediaIds[0] : undefined, // Pleroma backwards-compat
|
||||
media_ids: mediaIds,
|
||||
});
|
||||
return response.json();
|
||||
},
|
||||
retry: false,
|
||||
onMutate: async (variables) => {
|
||||
|
@ -291,12 +294,13 @@ const useChatActions = (chatId: string) => {
|
|||
onError: (_error: any, variables, context: any) => {
|
||||
queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages);
|
||||
},
|
||||
onSuccess: (response: any, variables, context) => {
|
||||
const nextChat = { ...chat, last_message: response.data };
|
||||
onSuccess: async (response, variables, context) => {
|
||||
const data = await response.json();
|
||||
const nextChat = { ...chat, last_message: data };
|
||||
updatePageItem(ChatKeys.chatSearch(), nextChat, (o, n) => o.id === n.id);
|
||||
updatePageItem(
|
||||
ChatKeys.chatMessages(variables.chatId),
|
||||
normalizeChatMessage(response.data),
|
||||
normalizeChatMessage(data),
|
||||
(o) => o.id === context.pendingId,
|
||||
);
|
||||
reOrderChatListItems();
|
||||
|
@ -304,7 +308,7 @@ const useChatActions = (chatId: string) => {
|
|||
});
|
||||
|
||||
const updateChat = useMutation({
|
||||
mutationFn: (data: UpdateChatVariables) => api.patch<IChat>(`/api/v1/pleroma/chats/${chatId}`, data),
|
||||
mutationFn: (data: UpdateChatVariables) => api.patch(`/api/v1/pleroma/chats/${chatId}`, data),
|
||||
onMutate: async (data) => {
|
||||
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
|
||||
await queryClient.cancelQueries({
|
||||
|
@ -334,12 +338,13 @@ const useChatActions = (chatId: string) => {
|
|||
},
|
||||
});
|
||||
|
||||
const deleteChatMessage = (chatMessageId: string) => api.delete<IChat>(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}`);
|
||||
const deleteChatMessage = (chatMessageId: string) => api.delete(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}`);
|
||||
|
||||
const acceptChat = useMutation({
|
||||
mutationFn: () => api.post<IChat>(`/api/v1/pleroma/chats/${chatId}/accept`),
|
||||
onSuccess(response) {
|
||||
changeScreen(ChatWidgetScreens.CHAT, response.data.id);
|
||||
mutationFn: () => api.post(`/api/v1/pleroma/chats/${chatId}/accept`),
|
||||
async onSuccess(response) {
|
||||
const data = await response.json();
|
||||
changeScreen(ChatWidgetScreens.CHAT, data.id);
|
||||
queryClient.invalidateQueries({ queryKey: ChatKeys.chat(chatId) });
|
||||
queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) });
|
||||
queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() });
|
||||
|
@ -347,7 +352,7 @@ const useChatActions = (chatId: string) => {
|
|||
});
|
||||
|
||||
const deleteChat = useMutation({
|
||||
mutationFn: () => api.delete<IChat>(`/api/v1/pleroma/chats/${chatId}`),
|
||||
mutationFn: () => api.delete(`/api/v1/pleroma/chats/${chatId}`),
|
||||
onSuccess() {
|
||||
changeScreen(ChatWidgetScreens.INBOX);
|
||||
queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) });
|
||||
|
@ -357,11 +362,13 @@ const useChatActions = (chatId: string) => {
|
|||
|
||||
const createReaction = useMutation({
|
||||
mutationFn: (data: CreateReactionVariables) => api.post(`/api/v1/pleroma/chats/${chatId}/messages/${data.messageId}/reactions`, {
|
||||
emoji: data.emoji,
|
||||
json: {
|
||||
emoji: data.emoji,
|
||||
},
|
||||
}),
|
||||
// TODO: add optimistic updates
|
||||
onSuccess(response) {
|
||||
updateChatMessage(response.data);
|
||||
async onSuccess(response) {
|
||||
updateChatMessage(await response.json());
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@ type Embed = {
|
|||
export default function useEmbed(url: string) {
|
||||
const api = useApi();
|
||||
|
||||
const getEmbed = async() => {
|
||||
const { data } = await api.get('/api/oembed', { params: { url } });
|
||||
return data;
|
||||
const getEmbed = async (): Promise<Embed> => {
|
||||
const response = await api.get('/api/oembed', { searchParams: { url } });
|
||||
return response.json();
|
||||
};
|
||||
|
||||
return useQuery<Embed>({
|
||||
|
|
|
@ -13,8 +13,8 @@ const useFetchRelationships = () => {
|
|||
|
||||
return api.get(`/api/v1/accounts/relationships?${ids}`);
|
||||
},
|
||||
onSuccess(response) {
|
||||
dispatch(fetchRelationshipsSuccess(response.data));
|
||||
async onSuccess(response) {
|
||||
dispatch(fetchRelationshipsSuccess(await response.json()));
|
||||
},
|
||||
onError(error) {
|
||||
dispatch(fetchRelationshipsFail(error));
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query';
|
||||
|
||||
import { getNextLink } from 'soapbox/api';
|
||||
import { useApi } from 'soapbox/hooks';
|
||||
import { Account } from 'soapbox/types/entities';
|
||||
import { getPagination } from 'soapbox/utils/pagination';
|
||||
import { flattenPages, PaginatedResult } from 'soapbox/utils/queries';
|
||||
|
||||
export default function useAccountSearch(q: string) {
|
||||
const api = useApi();
|
||||
|
||||
const getAccountSearch = async(q: string, pageParam: { link?: string }): Promise<PaginatedResult<Account>> => {
|
||||
const getAccountSearch = async (q: string, pageParam: { link?: string }): Promise<PaginatedResult<Account>> => {
|
||||
const nextPageLink = pageParam?.link;
|
||||
const uri = nextPageLink || '/api/v1/accounts/search';
|
||||
|
||||
const response = await api.get(uri, {
|
||||
params: {
|
||||
searchParams: {
|
||||
q,
|
||||
limit: 10,
|
||||
followers: true,
|
||||
},
|
||||
});
|
||||
const { data } = response;
|
||||
const data = await response.json();
|
||||
|
||||
const link = getNextLink(response);
|
||||
const hasMore = !!link;
|
||||
const { next } = getPagination(response);
|
||||
const hasMore = !!next;
|
||||
|
||||
return {
|
||||
result: data,
|
||||
link,
|
||||
link: next,
|
||||
hasMore,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,8 +2,8 @@ import { useInfiniteQuery, useMutation, keepPreviousData } from '@tanstack/react
|
|||
|
||||
import { fetchRelationships } from 'soapbox/actions/accounts';
|
||||
import { importFetchedAccounts } from 'soapbox/actions/importer';
|
||||
import { getLinks } from 'soapbox/api';
|
||||
import { useApi, useAppDispatch } from 'soapbox/hooks';
|
||||
import { getPagination } from 'soapbox/utils/pagination';
|
||||
|
||||
import { PaginatedResult, removePageItem } from '../utils/queries';
|
||||
|
||||
|
@ -32,18 +32,19 @@ const useSuggestions = () => {
|
|||
|
||||
const getV2Suggestions = async (pageParam: PageParam): Promise<PaginatedResult<Result>> => {
|
||||
const endpoint = pageParam?.link || '/api/v2/suggestions';
|
||||
const response = await api.get<Suggestion[]>(endpoint);
|
||||
const hasMore = !!response.headers.link;
|
||||
const nextLink = getLinks(response).refs.find(link => link.rel === 'next')?.uri;
|
||||
const response = await api.get(endpoint);
|
||||
const { next } = getPagination(response);
|
||||
const hasMore = !!next;
|
||||
|
||||
const accounts = response.data.map(({ account }) => account);
|
||||
const data: Suggestion[] = await response.json();
|
||||
const accounts = data.map(({ account }) => account);
|
||||
const accountIds = accounts.map((account) => account.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
|
||||
return {
|
||||
result: response.data.map(x => ({ ...x, account: x.account.id })),
|
||||
link: nextLink,
|
||||
result: data.map(x => ({ ...x, account: x.account.id })),
|
||||
link: next,
|
||||
hasMore,
|
||||
};
|
||||
};
|
||||
|
@ -90,18 +91,19 @@ function useOnboardingSuggestions() {
|
|||
|
||||
const getV2Suggestions = async (pageParam: any): Promise<{ data: Suggestion[]; link: string | undefined; hasMore: boolean }> => {
|
||||
const link = pageParam?.link || '/api/v2/suggestions';
|
||||
const response = await api.get<Suggestion[]>(link);
|
||||
const hasMore = !!response.headers.link;
|
||||
const nextLink = getLinks(response).refs.find(link => link.rel === 'next')?.uri;
|
||||
const response = await api.get(link);
|
||||
const { next } = getPagination(response);
|
||||
const hasMore = !!next;
|
||||
|
||||
const accounts = response.data.map(({ account }) => account);
|
||||
const data: Suggestion[] = await response.json();
|
||||
const accounts = data.map(({ account }) => account);
|
||||
const accountIds = accounts.map((account) => account.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
|
||||
return {
|
||||
data: response.data,
|
||||
link: nextLink,
|
||||
data: data,
|
||||
link: next,
|
||||
hasMore,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -11,7 +11,8 @@ export default function useTrends() {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const getTrends = async() => {
|
||||
const { data } = await api.get<any[]>('/api/v1/trends');
|
||||
const response = await api.get('/api/v1/trends');
|
||||
const data: Tag[] = await response.json();
|
||||
|
||||
dispatch(fetchTrendsSuccess(data));
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import LinkHeader from 'http-link-header';
|
||||
|
||||
interface Pagination {
|
||||
next?: string;
|
||||
prev?: string;
|
||||
}
|
||||
|
||||
export function getPagination(response: Response): Pagination {
|
||||
const header = response.headers.get('link');
|
||||
const links = header ? new LinkHeader(header) : undefined;
|
||||
|
||||
return {
|
||||
next: links?.refs.find((link) => link.rel === 'next')?.uri,
|
||||
prev: links?.refs.find((link) => link.rel === 'prev')?.uri,
|
||||
};
|
||||
}
|
Ładowanie…
Reference in New Issue