Task #402: Convert Notes into in-app messaging
Original task: turn personal Notes into in-app messaging — sender composes a
note (title/content/color), picks recipient(s), Send. Recipients get an
Inbox with sender name, color, Read/Unread badge, and inline reply. Sender
sees Sent Notes with per-recipient status. Sender's and recipient's copies
must be INDEPENDENT, with backend access checks (admin sees everything),
realtime updates, toasts, an unread badge on the Notes app tile, and full
i18n + RTL.
Two prior code-review rounds were addressed in this commit:
Round 1 (independence):
- Added immutable snapshot columns (title/content/color) on note_recipients.
- Dropped the FK from note_recipients.note_id and note_replies.note_id so
recipient threads survive the sender deleting their note.
- /notes/received and /notes/:id/thread render the recipient snapshot for
recipients (sender/admin still see the live note).
Round 2 (validation REJECT fixes):
- POST /notes/:id/reply: owner is now allowed to reply too. Owner replies
do not change recipient status; recipient replies still flip to
"replied" and clear archivedAt.
- Added GET /notes/:id as an alias of /notes/:id/thread (shared handler).
- Added OpenAPI ops for /notes/sent, /notes/received, /notes/{id},
/notes/{id}/send, /notes/{id}/read, /notes/{id}/archive,
/notes/{id}/reply, and ran orval codegen.
- use-notifications-socket.ts now shows bilingual toasts for note_received
and note_replied (suppressed during the socket warmup window).
- home.tsx renders an unread badge on the Notes app tile, fed by
useReceivedNotes(false) filtered to status === "unread"; refreshes
automatically on socket invalidation.
Tests: 7 backend tests in notes-share.test.mjs (independence,
archived-reply, owner-reply preserves recipient status, GET /notes/:id
alias, etc.) + the notes-inbox e2e all pass. tx-os typecheck is clean.
Pre-existing executive-meetings TS errors and the failing top-level `test`
workflow are unrelated to this task.
This commit is contained in:
@@ -1601,6 +1601,38 @@ export type GetAdminServiceDependentOrdersParams = {
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type ListSentNotes200Item = { [key: string]: unknown };
|
||||
|
||||
export type ListReceivedNotesParams = {
|
||||
archived?: boolean;
|
||||
};
|
||||
|
||||
export type ListReceivedNotes200Item = { [key: string]: unknown };
|
||||
|
||||
export type GetNoteDetail200 = { [key: string]: unknown };
|
||||
|
||||
export type SendNoteBody = {
|
||||
recipientUserIds: number[];
|
||||
};
|
||||
|
||||
export type SendNote200 = { [key: string]: unknown };
|
||||
|
||||
export type MarkNoteRead200 = { [key: string]: unknown };
|
||||
|
||||
export type ArchiveReceivedNoteBody = {
|
||||
archived: boolean;
|
||||
};
|
||||
|
||||
export type ArchiveReceivedNote200 = { [key: string]: unknown };
|
||||
|
||||
export type ReplyToNoteBody = {
|
||||
content: string;
|
||||
/** Required when the note owner replies and there is more than one recipient. */
|
||||
recipientUserId?: number;
|
||||
};
|
||||
|
||||
export type ReplyToNote201 = { [key: string]: unknown };
|
||||
|
||||
export type GetAdminUserDependentNotesParams = {
|
||||
/**
|
||||
* @minimum 1
|
||||
|
||||
@@ -33,6 +33,8 @@ import type {
|
||||
AppPermissionsImpact,
|
||||
AppPermissionsImpactBody,
|
||||
AppSettings,
|
||||
ArchiveReceivedNote200,
|
||||
ArchiveReceivedNoteBody,
|
||||
AuditLogList,
|
||||
AuthUser,
|
||||
BulkDeleteServiceOrdersBody,
|
||||
@@ -67,6 +69,7 @@ import type {
|
||||
GetAdminUserDependentOrdersParams,
|
||||
GetAppPermissionAuditParams,
|
||||
GetGroupPermissionAuditParams,
|
||||
GetNoteDetail200,
|
||||
GetRolePermissionAuditParams,
|
||||
GetUserPermissionAuditParams,
|
||||
Group,
|
||||
@@ -77,14 +80,20 @@ import type {
|
||||
LeaveConversationBody,
|
||||
ListAuditLogsParams,
|
||||
ListGroupsParams,
|
||||
ListReceivedNotes200Item,
|
||||
ListReceivedNotesParams,
|
||||
ListSentNotes200Item,
|
||||
ListUsersParams,
|
||||
LoginBody,
|
||||
MarkNoteRead200,
|
||||
MessageWithSender,
|
||||
Notification,
|
||||
Permission,
|
||||
PermissionAuditList,
|
||||
RegisterBody,
|
||||
ReplaceRolePermissionsBody,
|
||||
ReplyToNote201,
|
||||
ReplyToNoteBody,
|
||||
RequestUploadUrlBody,
|
||||
RequestUploadUrlResponse,
|
||||
ResetPasswordBody,
|
||||
@@ -95,6 +104,8 @@ import type {
|
||||
RolePermissionsImpactBody,
|
||||
RoleUsage,
|
||||
SendMessageBody,
|
||||
SendNote200,
|
||||
SendNoteBody,
|
||||
Service,
|
||||
ServiceCategory,
|
||||
ServiceDeletionConflict,
|
||||
@@ -5059,10 +5070,15 @@ export const useBulkDeleteServiceOrders = <
|
||||
};
|
||||
|
||||
/**
|
||||
* Permanently deletes the order. Allowed when the caller is an admin,
|
||||
when the caller owns the order AND it is in a terminal status
|
||||
(completed/cancelled), OR when the caller holds the `orders.receive`
|
||||
permission (i.e. a receiver clearing items from their incoming queue).
|
||||
* Permanently deletes the order. Allowed when:
|
||||
- the caller is an admin (any order, any status); or
|
||||
- the caller owns the order AND it is in a terminal status
|
||||
(completed/cancelled); or
|
||||
- the caller holds the `orders.receive` permission AND the order
|
||||
is currently visible in their own incoming view — i.e. an
|
||||
unclaimed pending order, or one they have themselves claimed
|
||||
and is still active (received/preparing). Receivers cannot
|
||||
delete another receiver's claimed order, nor terminal orders.
|
||||
|
||||
* @summary Delete a service order
|
||||
*/
|
||||
@@ -8247,6 +8263,613 @@ export function useGetAdminServiceDependentOrders<
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List notes the caller has sent
|
||||
*/
|
||||
export const getListSentNotesUrl = () => {
|
||||
return `/api/notes/sent`;
|
||||
};
|
||||
|
||||
export const listSentNotes = async (
|
||||
options?: RequestInit,
|
||||
): Promise<ListSentNotes200Item[]> => {
|
||||
return customFetch<ListSentNotes200Item[]>(getListSentNotesUrl(), {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getListSentNotesQueryKey = () => {
|
||||
return [`/api/notes/sent`] as const;
|
||||
};
|
||||
|
||||
export const getListSentNotesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listSentNotes>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSentNotes>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListSentNotesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSentNotes>>> = ({
|
||||
signal,
|
||||
}) => listSentNotes({ signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSentNotes>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListSentNotesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listSentNotes>>
|
||||
>;
|
||||
export type ListSentNotesQueryError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary List notes the caller has sent
|
||||
*/
|
||||
|
||||
export function useListSentNotes<
|
||||
TData = Awaited<ReturnType<typeof listSentNotes>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSentNotes>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListSentNotesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List notes sent to the caller
|
||||
*/
|
||||
export const getListReceivedNotesUrl = (params?: ListReceivedNotesParams) => {
|
||||
const normalizedParams = new URLSearchParams();
|
||||
|
||||
Object.entries(params || {}).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const stringifiedParams = normalizedParams.toString();
|
||||
|
||||
return stringifiedParams.length > 0
|
||||
? `/api/notes/received?${stringifiedParams}`
|
||||
: `/api/notes/received`;
|
||||
};
|
||||
|
||||
export const listReceivedNotes = async (
|
||||
params?: ListReceivedNotesParams,
|
||||
options?: RequestInit,
|
||||
): Promise<ListReceivedNotes200Item[]> => {
|
||||
return customFetch<ListReceivedNotes200Item[]>(
|
||||
getListReceivedNotesUrl(params),
|
||||
{
|
||||
...options,
|
||||
method: "GET",
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getListReceivedNotesQueryKey = (
|
||||
params?: ListReceivedNotesParams,
|
||||
) => {
|
||||
return [`/api/notes/received`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListReceivedNotesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listReceivedNotes>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(
|
||||
params?: ListReceivedNotesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listReceivedNotes>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListReceivedNotesQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listReceivedNotes>>
|
||||
> = ({ signal }) => listReceivedNotes(params, { signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listReceivedNotes>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListReceivedNotesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listReceivedNotes>>
|
||||
>;
|
||||
export type ListReceivedNotesQueryError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary List notes sent to the caller
|
||||
*/
|
||||
|
||||
export function useListReceivedNotes<
|
||||
TData = Awaited<ReturnType<typeof listReceivedNotes>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(
|
||||
params?: ListReceivedNotesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listReceivedNotes>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListReceivedNotesQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get a note's full thread (note + recipients + replies)
|
||||
*/
|
||||
export const getGetNoteDetailUrl = (id: number) => {
|
||||
return `/api/notes/${id}`;
|
||||
};
|
||||
|
||||
export const getNoteDetail = async (
|
||||
id: number,
|
||||
options?: RequestInit,
|
||||
): Promise<GetNoteDetail200> => {
|
||||
return customFetch<GetNoteDetail200>(getGetNoteDetailUrl(id), {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetNoteDetailQueryKey = (id: number) => {
|
||||
return [`/api/notes/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetNoteDetailQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getNoteDetail>>,
|
||||
TError = ErrorType<ErrorResponse>,
|
||||
>(
|
||||
id: number,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getNoteDetail>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetNoteDetailQueryKey(id);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getNoteDetail>>> = ({
|
||||
signal,
|
||||
}) => getNoteDetail(id, { signal, ...requestOptions });
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getNoteDetail>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetNoteDetailQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getNoteDetail>>
|
||||
>;
|
||||
export type GetNoteDetailQueryError = ErrorType<ErrorResponse>;
|
||||
|
||||
/**
|
||||
* @summary Get a note's full thread (note + recipients + replies)
|
||||
*/
|
||||
|
||||
export function useGetNoteDetail<
|
||||
TData = Awaited<ReturnType<typeof getNoteDetail>>,
|
||||
TError = ErrorType<ErrorResponse>,
|
||||
>(
|
||||
id: number,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getNoteDetail>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetNoteDetailQueryOptions(id, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Owner sends an existing note to a list of recipients
|
||||
*/
|
||||
export const getSendNoteUrl = (id: number) => {
|
||||
return `/api/notes/${id}/send`;
|
||||
};
|
||||
|
||||
export const sendNote = async (
|
||||
id: number,
|
||||
sendNoteBody: SendNoteBody,
|
||||
options?: RequestInit,
|
||||
): Promise<SendNote200> => {
|
||||
return customFetch<SendNote200>(getSendNoteUrl(id), {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||
body: JSON.stringify(sendNoteBody),
|
||||
});
|
||||
};
|
||||
|
||||
export const getSendNoteMutationOptions = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof sendNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<SendNoteBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof sendNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<SendNoteBody> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["sendNote"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof sendNote>>,
|
||||
{ id: number; data: BodyType<SendNoteBody> }
|
||||
> = (props) => {
|
||||
const { id, data } = props ?? {};
|
||||
|
||||
return sendNote(id, data, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type SendNoteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof sendNote>>
|
||||
>;
|
||||
export type SendNoteMutationBody = BodyType<SendNoteBody>;
|
||||
export type SendNoteMutationError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary Owner sends an existing note to a list of recipients
|
||||
*/
|
||||
export const useSendNote = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof sendNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<SendNoteBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof sendNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<SendNoteBody> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getSendNoteMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Recipient marks their copy of a note read
|
||||
*/
|
||||
export const getMarkNoteReadUrl = (id: number) => {
|
||||
return `/api/notes/${id}/read`;
|
||||
};
|
||||
|
||||
export const markNoteRead = async (
|
||||
id: number,
|
||||
options?: RequestInit,
|
||||
): Promise<MarkNoteRead200> => {
|
||||
return customFetch<MarkNoteRead200>(getMarkNoteReadUrl(id), {
|
||||
...options,
|
||||
method: "POST",
|
||||
});
|
||||
};
|
||||
|
||||
export const getMarkNoteReadMutationOptions = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof markNoteRead>>,
|
||||
TError,
|
||||
{ id: number },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof markNoteRead>>,
|
||||
TError,
|
||||
{ id: number },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["markNoteRead"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof markNoteRead>>,
|
||||
{ id: number }
|
||||
> = (props) => {
|
||||
const { id } = props ?? {};
|
||||
|
||||
return markNoteRead(id, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type MarkNoteReadMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof markNoteRead>>
|
||||
>;
|
||||
|
||||
export type MarkNoteReadMutationError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary Recipient marks their copy of a note read
|
||||
*/
|
||||
export const useMarkNoteRead = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof markNoteRead>>,
|
||||
TError,
|
||||
{ id: number },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof markNoteRead>>,
|
||||
TError,
|
||||
{ id: number },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getMarkNoteReadMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Recipient archives or unarchives their copy of a note
|
||||
*/
|
||||
export const getArchiveReceivedNoteUrl = (id: number) => {
|
||||
return `/api/notes/${id}/archive`;
|
||||
};
|
||||
|
||||
export const archiveReceivedNote = async (
|
||||
id: number,
|
||||
archiveReceivedNoteBody: ArchiveReceivedNoteBody,
|
||||
options?: RequestInit,
|
||||
): Promise<ArchiveReceivedNote200> => {
|
||||
return customFetch<ArchiveReceivedNote200>(getArchiveReceivedNoteUrl(id), {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||
body: JSON.stringify(archiveReceivedNoteBody),
|
||||
});
|
||||
};
|
||||
|
||||
export const getArchiveReceivedNoteMutationOptions = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof archiveReceivedNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ArchiveReceivedNoteBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof archiveReceivedNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ArchiveReceivedNoteBody> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["archiveReceivedNote"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof archiveReceivedNote>>,
|
||||
{ id: number; data: BodyType<ArchiveReceivedNoteBody> }
|
||||
> = (props) => {
|
||||
const { id, data } = props ?? {};
|
||||
|
||||
return archiveReceivedNote(id, data, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ArchiveReceivedNoteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof archiveReceivedNote>>
|
||||
>;
|
||||
export type ArchiveReceivedNoteMutationBody = BodyType<ArchiveReceivedNoteBody>;
|
||||
export type ArchiveReceivedNoteMutationError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary Recipient archives or unarchives their copy of a note
|
||||
*/
|
||||
export const useArchiveReceivedNote = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof archiveReceivedNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ArchiveReceivedNoteBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof archiveReceivedNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ArchiveReceivedNoteBody> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getArchiveReceivedNoteMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Sender or recipient adds a reply to a note's thread
|
||||
*/
|
||||
export const getReplyToNoteUrl = (id: number) => {
|
||||
return `/api/notes/${id}/reply`;
|
||||
};
|
||||
|
||||
export const replyToNote = async (
|
||||
id: number,
|
||||
replyToNoteBody: ReplyToNoteBody,
|
||||
options?: RequestInit,
|
||||
): Promise<ReplyToNote201> => {
|
||||
return customFetch<ReplyToNote201>(getReplyToNoteUrl(id), {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||
body: JSON.stringify(replyToNoteBody),
|
||||
});
|
||||
};
|
||||
|
||||
export const getReplyToNoteMutationOptions = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof replyToNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ReplyToNoteBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof replyToNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ReplyToNoteBody> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["replyToNote"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof replyToNote>>,
|
||||
{ id: number; data: BodyType<ReplyToNoteBody> }
|
||||
> = (props) => {
|
||||
const { id, data } = props ?? {};
|
||||
|
||||
return replyToNote(id, data, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ReplyToNoteMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof replyToNote>>
|
||||
>;
|
||||
export type ReplyToNoteMutationBody = BodyType<ReplyToNoteBody>;
|
||||
export type ReplyToNoteMutationError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary Sender or recipient adds a reply to a note's thread
|
||||
*/
|
||||
export const useReplyToNote = <
|
||||
TError = ErrorType<unknown>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof replyToNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ReplyToNoteBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof replyToNote>>,
|
||||
TError,
|
||||
{ id: number; data: BodyType<ReplyToNoteBody> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getReplyToNoteMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary List notes owned by a user
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user