Nostr Zaps

This commit is contained in:
Alex Gleason 2024-01-15 18:53:49 -06:00
parent e281fbade9
commit 9cad54c97c
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
5 changed files with 67 additions and 2 deletions

View file

@ -78,6 +78,10 @@ const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_FAIL';
const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
const ZAP_REQUEST = 'ZAP_REQUEST';
const ZAP_SUCCESS = 'ZAP_SUCCESS';
const ZAP_FAIL = 'ZAP_FAIL';
const messages = defineMessages({
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
@ -306,6 +310,38 @@ const undislikeFail = (status: StatusEntity, error: unknown) => ({
skipLoading: true,
});
const zap = (status: StatusEntity, amount: number) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
dispatch(zapRequest(status));
api(getState).post(`/api/v1/statuses/${status.id}/zap`, { amount }).then(function(response) {
dispatch(zapSuccess(status));
}).catch(function(error) {
dispatch(zapFail(status, error));
});
};
const zapRequest = (status: StatusEntity) => ({
type: ZAP_REQUEST,
status: status,
skipLoading: true,
});
const zapSuccess = (status: StatusEntity) => ({
type: ZAP_SUCCESS,
status: status,
skipLoading: true,
});
const zapFail = (status: StatusEntity, error: unknown) => ({
type: ZAP_FAIL,
status: status,
error: error,
skipLoading: true,
});
const bookmark = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(bookmarkRequest(status));
@ -801,4 +837,5 @@ export {
remoteInteractionRequest,
remoteInteractionSuccess,
remoteInteractionFail,
zap,
};

View file

@ -6,7 +6,7 @@ import { blockAccount } from 'soapbox/actions/accounts';
import { launchChat } from 'soapbox/actions/chats';
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
import { editEvent } from 'soapbox/actions/events';
import { pinToGroup, toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog, unpinFromGroup } from 'soapbox/actions/interactions';
import { pinToGroup, toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog, unpinFromGroup, zap } from 'soapbox/actions/interactions';
import { openModal } from 'soapbox/actions/modals';
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
import { initMuteModal } from 'soapbox/actions/mutes';
@ -103,6 +103,7 @@ const messages = defineMessages({
unmuteSuccess: { id: 'group.unmute.success', defaultMessage: 'Unmuted the group' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
unpinFromGroup: { id: 'status.unpin_to_group', defaultMessage: 'Unpin from Group' },
zap: { id: 'status.zap', defaultMessage: 'Zap' },
});
interface IStatusActionBar {
@ -188,6 +189,14 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
}
};
const handleZapClick: React.EventHandler<React.MouseEvent> = (e) => {
if (me) {
dispatch(zap(status, 1337));
} else {
onOpenUnauthorizedModal('ZAP');
}
};
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
dispatch(toggleBookmark(status));
};
@ -694,6 +703,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
}
const canShare = ('share' in navigator) && (status.visibility === 'public' || status.visibility === 'group');
const acceptsZaps = status.account.ditto.accepts_zaps === true;
const spacing: {
[key: string]: React.ComponentProps<typeof HStack>['space'];
@ -781,6 +791,19 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
/>
)}
{acceptsZaps && (
<StatusActionButton
title={intl.formatMessage(messages.zap)}
icon={require('@tabler/icons/bolt.svg')}
color='accent'
filled
onClick={handleZapClick}
active={status.zapped}
text={withLabels ? intl.formatMessage(messages.zap) : undefined}
theme={statusActionButtonTheme}
/>
)}
{canShare && (
<StatusActionButton
title={intl.formatMessage(messages.share)}

View file

@ -82,6 +82,7 @@ export const StatusRecord = ImmutableRecord({
uri: '',
url: '',
visibility: 'public' as StatusVisibility,
zapped: false,
event: null as ReturnType<typeof EventRecord> | null,
// Internal fields

View file

@ -6,7 +6,7 @@ import { unescapeHTML } from 'soapbox/utils/html';
import { customEmojiSchema } from './custom-emoji';
import { relationshipSchema } from './relationship';
import { contentSchema, filteredArray, makeCustomEmojiMap } from './utils';
import { coerceObject, contentSchema, filteredArray, makeCustomEmojiMap } from './utils';
import type { Resolve } from 'soapbox/utils/types';
@ -29,6 +29,9 @@ const baseAccountSchema = z.object({
created_at: z.string().datetime().catch(new Date().toUTCString()),
discoverable: z.boolean().catch(false),
display_name: z.string().catch(''),
ditto: coerceObject({
accepts_zaps: z.boolean().catch(false),
}),
emojis: filteredArray(customEmojiSchema),
fields: filteredArray(fieldSchema),
followers_count: z.number().catch(0),

View file

@ -67,6 +67,7 @@ const baseStatusSchema = z.object({
uri: z.string().url().catch(''),
url: z.string().url().catch(''),
visibility: z.string().catch('public'),
zapped: z.coerce.boolean(),
});
type BaseStatus = z.infer<typeof baseStatusSchema>;