diff --git a/app/soapbox/components/modal_root.tsx b/app/soapbox/components/modal_root.tsx index eabd450d0..a75c82f9d 100644 --- a/app/soapbox/components/modal_root.tsx +++ b/app/soapbox/components/modal_root.tsx @@ -7,6 +7,8 @@ import { useHistory } from 'react-router-dom'; import { cancelReplyCompose } from 'soapbox/actions/compose'; import { openModal, closeModal } from 'soapbox/actions/modals'; import { useAppDispatch, useAppSelector, usePrevious } from 'soapbox/hooks'; +import { queryClient } from 'soapbox/queries/client'; +import { IPolicy, PolicyKeys } from 'soapbox/queries/policies'; import type { UnregisterCallback } from 'history'; import type { ReducerCompose } from 'soapbox/reducers/compose'; @@ -74,6 +76,15 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) })); } else if (hasComposeContent && type === 'CONFIRM') { dispatch(closeModal('CONFIRM')); + } else if (type === 'POLICY') { + // If the user has not accepted the Policy, prevent them + // from closing the Modal. + const pendingPolicy = queryClient.getQueryData(PolicyKeys.policy) as IPolicy; + if (pendingPolicy?.pending_policy_id) { + return; + } + + onClose(); } else { onClose(); } diff --git a/app/soapbox/features/chats/components/chat-page/components/blankslate.tsx b/app/soapbox/features/chats/components/chat-page/components/blankslate.tsx index 6c2d66e9f..52fb96014 100644 --- a/app/soapbox/features/chats/components/chat-page/components/blankslate.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/blankslate.tsx @@ -33,7 +33,7 @@ const Blankslate: React.FC = () => { - + + + ); +}; + +export { PolicyModal as default, supportedPolicyIds }; diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 56d0e473e..ed3980ebc 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -32,12 +32,14 @@ import HomePage from 'soapbox/pages/home_page'; import ProfilePage from 'soapbox/pages/profile_page'; import RemoteInstancePage from 'soapbox/pages/remote_instance_page'; import StatusPage from 'soapbox/pages/status_page'; +import { usePendingPolicy } from 'soapbox/queries/policies'; import { getAccessToken, getVapidKey } from 'soapbox/utils/auth'; import { isStandalone } from 'soapbox/utils/state'; import { StatProvider } from '../../contexts/stat-context'; import BackgroundShapes from './components/background_shapes'; +import { supportedPolicyIds } from './components/modals/policy-modal'; import Navbar from './components/navbar'; import BundleContainer from './containers/bundle_container'; import { @@ -311,6 +313,7 @@ const UI: React.FC = ({ children }) => { const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); + const { data: pendingPolicy } = usePendingPolicy(); const [draggingOver, setDraggingOver] = useState(false); const [mobile, setMobile] = useState(isMobile(window.innerWidth)); @@ -500,6 +503,12 @@ const UI: React.FC = ({ children }) => { dispatch(registerPushNotifications()); }, [vapidKey]); + useEffect(() => { + if (pendingPolicy && supportedPolicyIds.includes(pendingPolicy.pending_policy_id)) { + dispatch(openModal('POLICY')); + } + }, [pendingPolicy]); + const handleHotkeyNew = (e?: KeyboardEvent) => { e?.preventDefault(); if (!node.current) return; diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 9537ba7e4..5f83d0ed2 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -110,6 +110,10 @@ export function AccountModerationModal() { return import(/* webpackChunkName: "modals/account-moderation-modal" */'../components/modals/account-moderation-modal/account-moderation-modal'); } +export function PolicyModal() { + return import(/* webpackChunkName: "modals/policy-modal" */'../components/modals/policy-modal'); +} + export function MediaGallery() { return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery'); } diff --git a/app/soapbox/queries/policies.ts b/app/soapbox/queries/policies.ts new file mode 100644 index 000000000..908064e7e --- /dev/null +++ b/app/soapbox/queries/policies.ts @@ -0,0 +1,46 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; + +import { useApi, useOwnAccount } from 'soapbox/hooks'; + +import { queryClient } from './client'; + +export interface IPolicy { + pending_policy_id: string +} + +const PolicyKeys = { + policy: ['policy'] as const, +}; + +function usePendingPolicy() { + const api = useApi(); + const account = useOwnAccount(); + + const getPolicy = async() => { + const { data } = await api.get('/api/v1/truth/policies/pending'); + + return data; + }; + + return useQuery(PolicyKeys.policy, getPolicy, { + retry: 3, + refetchOnWindowFocus: true, + staleTime: 60000, // 1 minute + cacheTime: Infinity, + enabled: !!account, + }); +} + +function useAcceptPolicy() { + const api = useApi(); + + return useMutation(( + { policy_id }: { policy_id: string }, + ) => api.patch(`/api/v1/truth/policies/${policy_id}/accept`), { + onSuccess() { + queryClient.setQueryData(PolicyKeys.policy, {}); + }, + }); +} + +export { usePendingPolicy, useAcceptPolicy, PolicyKeys }; \ No newline at end of file