UNPKG

fastcomments-react-native-sdk

Version:

React Native FastComments Components. Add live commenting to any React Native application.

202 lines (201 loc) 11.5 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { View, Text, Image, TextInput, ActivityIndicator, Button, Linking } from 'react-native'; import { FastCommentsImageAsset } from "../types"; import { useHookstate } from "@hookstate/core"; import { getActionTenantId } from "../services/tenants"; import { createURLQueryString, makeRequest } from "../services/http"; import { newBroadcastId } from '../services/broadcast-id'; import { Pressable } from 'react-native'; import { incChangeCounter } from "../services/comment-render-determination"; const ErrorCodesToMessageIds = { 'username-taken': 'USERNAME_TAKEN_DIFF_EMAIL', // this shouldn't normally show, should only show during weird network related race conditions. 'already-voted': 'ALREADY_VOTED', }; async function doVote({ state, comment, onVoteSuccess }, voteState) { const currentUser = state.currentUser.get(); if (!currentUser) { if (voteState.authUserName.get() && (state.config.allowAnon.get() || voteState.authEmail.get())) { // has authentication info } else { const sso = state.config.sso.get(); if (sso && !state.config.allowAnon.get()) { // go to url or call sso login callback if (sso.loginURL) { return await Linking.openURL(sso.loginURL); } if (sso.loginCallback) { return sso.loginCallback(''); } } else { // show auth form return voteState.isAuthenticating.set(true); } } } voteState.isLoading.set(true); try { const tenantIdToUse = getActionTenantId({ state: state, tenantId: comment.tenantId }); if (comment.isVotedUp || comment.isVotedDown) { // delete vote const response = await makeRequest({ apiHost: state.apiHost.get(), method: 'DELETE', url: `/comments/${tenantIdToUse}/${comment._id}/vote/${comment.myVoteId}${createURLQueryString({ voteId: comment.myVoteId, editKey: comment.editKey, commentId: comment._id, sso: state.ssoConfigString.get(), broadcastId: newBroadcastId(), urlId: state.config.urlId.get(), })}` }); voteState.voteResponse.set(response); if (response.status === 'success') { onVoteSuccess && onVoteSuccess(comment, comment.myVoteId, 'deleted', response.status); if (comment.isVotedUp) { comment.isVotedUp = false; if (!response.wasPendingVote) { comment.votes = (comment.votes || 0) - 1; comment.votesUp = (comment.votesUp || 0) - 1; } } else { comment.isVotedDown = false; if (!response.wasPendingVote) { comment.votes = (comment.votes || 0) + 1; comment.votesDown = (comment.votesDown || 0) - 1; } } } } else { // add vote const response = await makeRequest({ apiHost: state.apiHost.get(), method: 'POST', url: `/comments/${tenantIdToUse}/${comment._id}/vote${createURLQueryString({ urlId: state.config.urlId.get(), sso: state.ssoConfigString.get(), broadcastId: newBroadcastId(), sessionId: currentUser && 'sessionId' in currentUser ? currentUser.sessionId : undefined })}`, body: { voteDir: voteState.voteDir.get(), commentId: comment._id, urlId: state.config.urlId.get(), url: state.config.url.get(), commenterName: currentUser ? currentUser.username : voteState.authUserName.get(), commenterEmail: currentUser && 'email' in currentUser ? currentUser.email : voteState.authEmail.get() } }); voteState.voteResponse.set(response); if (response.status === 'success') { if (voteState.voteDir.get() === 'up') { comment.isVotedUp = true; comment.votes = (comment.votes || 0) + 1; comment.votesUp = (comment.votesUp || 0) + 1; } else { comment.isVotedDown = true; comment.votes = (comment.votes || 0) - 1; comment.votesDown = (comment.votesDown || 0) + 1; } comment.myVoteId = response.voteId; onVoteSuccess && onVoteSuccess(comment, response.voteId, voteState.voteDir.get(), response.status); if (response.editKey) { comment.voteEditKey = response.editKey; } else { comment.voteEditKey = undefined; } voteState.isAwaitingVerification.set(false); incChangeCounter(comment); } else if (response.status === 'pending-verification') { if (voteState.voteDir.get() === 'up') { comment.isVotedUp = true; } else { comment.isVotedDown = true; } if (!response.isVerified) { voteState.isAwaitingVerification.set(true); } comment.myVoteId = response.voteId; onVoteSuccess && onVoteSuccess(comment, response.voteId, voteState.voteDir.get(), response.status); comment.voteEditKey = response.editKey; voteState.isAwaitingVerification.set(false); incChangeCounter(comment); } else { if (response.code === 'already-voted') { // we'll show a message based on this response code. voteState.isAwaitingVerification.set(false); } else if (response.code === 'username-taken') { // we'll show a message based on this response code. // keep auth input open so they can change username input value } else if (response.code === 'banned') { // we'll show a message based on this response code. } } } } catch (e) { console.error('Failed to vote', e); } voteState.isLoading.set(false); } export function CommentVote(props) { const { comment, config, styles, translations, imageAssets } = props; const state = useHookstate(props.state); // OPTIMIZATION: creating scoped state const voteState = useHookstate({}); if (config.disableVoting) { return null; } let voteOptions; let pendingVoteMessage; let auth; let error; const showDownVoting = !config.disableDownVoting; // TODO TouchableOpacity throws weird callback exceeded errors voteOptions = _jsxs(View, { style: styles.commentVote?.commentVoteOptions, children: [comment.votesUp ? _jsx(Text, { style: styles.commentVote?.votesUpText, children: Number(comment.votesUp).toLocaleString() }) : null, _jsx(Pressable, { style: styles.commentVote?.voteButton, onPress: () => { voteState.voteDir.set('up'); // noinspection JSIgnoredPromiseFromCall doVote({ state, comment }, voteState); }, children: _jsx(Image, { source: imageAssets[comment.isVotedUp ? (config.hasDarkBackground ? FastCommentsImageAsset.ICON_UP_ACTIVE_WHITE : FastCommentsImageAsset.ICON_UP_ACTIVE) : FastCommentsImageAsset.ICON_UP], style: styles.commentVote?.voteButtonIcon }) }), showDownVoting && _jsx(View, { style: styles.commentVote?.voteDivider }), showDownVoting && _jsx(Pressable, { style: styles.commentVote?.voteButton, onPress: () => { voteState.voteDir.set('down'); // noinspection JSIgnoredPromiseFromCall doVote({ state, comment }, voteState); }, children: _jsx(Image, { source: imageAssets[comment.isVotedDown ? (config.hasDarkBackground ? FastCommentsImageAsset.ICON_DOWN_ACTIVE_WHITE : FastCommentsImageAsset.ICON_DOWN_ACTIVE) : FastCommentsImageAsset.ICON_DOWN], style: styles.commentVote?.voteButtonIcon }) }), showDownVoting && comment.votesDown ? _jsx(Text, { style: styles.commentVote?.votesDownText, children: Number(comment.votesDown).toLocaleString() }) : null] }); if (voteState.isAuthenticating.get()) { auth = _jsxs(View, { style: styles.commentVote?.commentVoteAuth, children: [!config.disableEmailInputs && _jsxs(View, { children: [_jsx(Text, { children: translations.ENTER_EMAIL_VOTE }), _jsx(TextInput, { style: styles.commentVote?.authInput, textContentType: 'emailAddress', value: voteState.authEmail.get(), placeholder: translations.ENTER_EMAIL_VERIFICATION, onChangeText: (newValue) => voteState.authEmail.set(newValue) })] }), _jsx(TextInput, { style: styles.commentVote?.authInput, textContentType: 'username', value: voteState.authUserName.get(), placeholder: translations.PUBLICLY_DISPLAYED_USERNAME, onChangeText: (newValue) => voteState.authUserName.set(newValue) }), _jsxs(View, { style: styles.commentVote?.voteAuthButtons, children: [_jsx(Button, { title: translations.CANCEL, onPress: () => voteState.isAuthenticating.set(false) }), _jsx(Button, { title: translations.SAVE_N_VOTE, onPress: () => doVote({ state, comment }, voteState) })] })] }); } if (voteState.isAwaitingVerification.get()) { pendingVoteMessage = _jsx(Text, { style: styles.commentVote?.voteAwaitingVerificationMessage, children: translations.VOTE_APPLIES_AFTER_VERIFICATION }); } // This is here instead of above so that when it gets added in and then removed we don't cause the user to accidentally vote down if they double click. const lastVoteResponse = voteState.voteResponse.get(); if (lastVoteResponse && lastVoteResponse.status === 'failed') { if (lastVoteResponse.code === 'banned') { let bannedText = translations.BANNED_VOTING; if (lastVoteResponse.bannedUntil) { bannedText += ' ' + translations.BAN_ENDS.replace('[endsText]', new Date(lastVoteResponse.bannedUntil).toLocaleString()); } error = _jsx(Text, { style: styles.commentVote?.voteError, children: bannedText }); } else if (lastVoteResponse.code && lastVoteResponse.code in ErrorCodesToMessageIds) { error = _jsx(Text, { style: styles.commentVote?.voteError, children: translations[ErrorCodesToMessageIds[lastVoteResponse.code]] }); } else { error = _jsx(Text, { style: styles.commentVote?.voteError, children: translations.ERROR_MESSAGE }); } } return _jsxs(View, { style: styles.commentVote?.root, children: [voteOptions, pendingVoteMessage, auth, error, voteState.isLoading.get() && _jsx(View, { style: styles.commentVote?.loadingView, children: _jsx(ActivityIndicator, { size: "small" }) })] }); }