diff --git a/src/Campaigns/Actions/LoadCampaignDetailsAssets.php b/src/Campaigns/Actions/LoadCampaignDetailsAssets.php index 9f70322c04..8e0ec03ed0 100644 --- a/src/Campaigns/Actions/LoadCampaignDetailsAssets.php +++ b/src/Campaigns/Actions/LoadCampaignDetailsAssets.php @@ -27,14 +27,6 @@ public function __invoke() true ); - wp_localize_script($handleName, 'GiveCampaignDetails', - [ - 'adminUrl' => admin_url(), - 'currency' => give_get_currency(), - 'isRecurringEnabled' => defined('GIVE_RECURRING_VERSION') ? GIVE_RECURRING_VERSION : null - ] - ); - wp_enqueue_script($handleName); wp_enqueue_style('givewp-design-system-foundation'); wp_enqueue_style( diff --git a/src/Campaigns/Actions/LoadCampaignOptions.php b/src/Campaigns/Actions/LoadCampaignOptions.php new file mode 100644 index 0000000000..680325871a --- /dev/null +++ b/src/Campaigns/Actions/LoadCampaignOptions.php @@ -0,0 +1,30 @@ + admin_url(), + 'currency' => give_get_currency(), + 'currencySymbol' => give_currency_symbol(), + 'isRecurringEnabled' => defined('GIVE_RECURRING_VERSION') + ? GIVE_RECURRING_VERSION + : null, + ] + ); + + wp_enqueue_script('give-campaign-options'); + } +} diff --git a/src/Campaigns/Blocks/CampaignGoal/app.tsx b/src/Campaigns/Blocks/CampaignGoal/app.tsx new file mode 100644 index 0000000000..968b9b5a6b --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/app.tsx @@ -0,0 +1,26 @@ +import {render} from '@wordpress/element'; +import useCampaign from '../shared/hooks/useCampaign'; +import App from './app/index'; + +const BlockApp = ({campaignId}: { campaignId: number }) => { + const {campaign, hasResolved} = useCampaign(campaignId); + + if (!hasResolved || !campaignId) { + return null; + } + + return ; +} + +/** + * @unreleased + */ +const nodeList = document.querySelectorAll('[data-givewp-campaign-goal]'); + +if (nodeList) { + const containers = Array.from(nodeList); + + containers.map((container: any) => { + return render(, container); + }); +} diff --git a/src/Campaigns/Blocks/CampaignGoal/app/index.tsx b/src/Campaigns/Blocks/CampaignGoal/app/index.tsx new file mode 100644 index 0000000000..37fa61bdcd --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/app/index.tsx @@ -0,0 +1,34 @@ +import {__} from '@wordpress/i18n'; +import {Campaign} from '@givewp/campaigns/admin/components/types'; +import {getGoalDescription, getGoalFormattedValue} from '../utils'; + +import './styles.scss'; + +export default ({campaign}: { campaign: Campaign }) => { + return ( +
+
+
+ {getGoalDescription(campaign.goalType)} + + {getGoalFormattedValue(campaign.goalType, campaign.goalStats.actual)} + +
+
+ {__('Our goal', 'give')} + + {getGoalFormattedValue(campaign.goalType, campaign.goal)} + +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/Campaigns/Blocks/CampaignGoal/app/styles.scss b/src/Campaigns/Blocks/CampaignGoal/app/styles.scss new file mode 100644 index 0000000000..b98f4b8b2f --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/app/styles.scss @@ -0,0 +1,51 @@ +.givewp-campaign-goal { + display: flex; + flex-direction: column; + gap: 0.5rem; + + &__container { + display: flex; + flex-direction: row; + justify-content: space-between; + + &-item { + display: flex; + flex-direction: column; + gap: 0.2rem; + + span { + text-transform: uppercase; + font-size: 12px; + line-height: 18px; + color: #4b5563; + } + + strong { + font-size: 20px; + line-height: 32px; + color: #060c1a; + } + } + } + + &__progress-bar { + display: flex; + + &-container { + display: flex; + height: 8px; + flex-grow: 1; + border-radius: 14px; + box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.09); + background-color: #f2f2f2; + } + + &-progress { + display: flex; + height: 8px; + border-radius: 14px; + box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.09); + background-color: #2d802f; + } + } +} diff --git a/src/Campaigns/Blocks/CampaignGoal/block.json b/src/Campaigns/Blocks/CampaignGoal/block.json new file mode 100644 index 0000000000..0145bccb61 --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/block.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/block.json", + "apiVersion": 2, + "name": "givewp/campaign-goal", + "version": "1.0.0", + "title": "Campaign Goal", + "category": "give", + "description": "Displays the goal of the campaign.", + "supports": { + "align": [ + "wide", + "full" + ] + }, + "attributes": { + "campaignId": { + "type": "number" + } + }, + "textdomain": "give", + "viewScript": "file:../../../../build/campaignGoalBlockApp.js", + "style": "file:../../../../build/campaignGoalBlockApp.css", + "render": "file:./render.php" +} diff --git a/src/Campaigns/Blocks/CampaignGoal/edit.tsx b/src/Campaigns/Blocks/CampaignGoal/edit.tsx new file mode 100644 index 0000000000..ed200bbb18 --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/edit.tsx @@ -0,0 +1,53 @@ +import {__} from '@wordpress/i18n'; +import {useSelect} from '@wordpress/data'; +import {InspectorControls, useBlockProps} from '@wordpress/block-editor'; +import {BlockEditProps} from '@wordpress/blocks'; +import {ExternalLink, PanelBody, TextControl} from '@wordpress/components'; +import useCampaign from '../shared/hooks/useCampaign'; +import CampaignGoalApp from './app/index'; +import {CampaignSelector} from '../shared/components/CampaignSelector'; +import {getGoalDescription} from './utils'; + +/** + * @unreleased + */ +export default function Edit({attributes, setAttributes}: BlockEditProps<{ + campaignId: number; + goalType: string; +}>) { + const {campaign, hasResolved} = useCampaign(attributes.campaignId); + + const blockProps = useBlockProps(); + + const adminBaseUrl = useSelect( + // @ts-ignore + (select) => select('core').getSite()?.url + '/wp-admin/edit.php?post_type=give_forms&page=give-campaigns', + [] + ); + + if (!hasResolved) { + return null; + } + + return ( +
+ + + + + {campaign?.id && ( + + + + + {__('Edit campaign goal settings', 'give')} + + + + )} +
+ ); +} diff --git a/src/Campaigns/Blocks/CampaignGoal/icon.jsx b/src/Campaigns/Blocks/CampaignGoal/icon.jsx new file mode 100644 index 0000000000..fd153a0135 --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/icon.jsx @@ -0,0 +1,11 @@ +export default () => ( + + + + + +) diff --git a/src/Campaigns/Blocks/CampaignGoal/index.tsx b/src/Campaigns/Blocks/CampaignGoal/index.tsx new file mode 100644 index 0000000000..034f5fc8a5 --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/index.tsx @@ -0,0 +1,15 @@ +import edit from './edit'; +import icon from './icon'; +import schema from './block.json'; + +/** + * @unreleased + */ +export default { + schema, + settings: { + icon, + edit + } +}; + diff --git a/src/Campaigns/Blocks/CampaignGoal/render.php b/src/Campaigns/Blocks/CampaignGoal/render.php new file mode 100644 index 0000000000..7b507bdf08 --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/render.php @@ -0,0 +1,20 @@ +getById($attributes['campaignId']) +) { + return; +} + +?> + +
diff --git a/src/Campaigns/Blocks/CampaignGoal/utils.ts b/src/Campaigns/Blocks/CampaignGoal/utils.ts new file mode 100644 index 0000000000..8a81a38016 --- /dev/null +++ b/src/Campaigns/Blocks/CampaignGoal/utils.ts @@ -0,0 +1,35 @@ +import {__} from '@wordpress/i18n'; +import {getCampaignOptionsWindowData, amountFormatter} from '@givewp/campaigns/utils'; + + +export const getGoalDescription = (goalType: string) => { + switch (goalType) { + case 'amount': + return __('Amount raised', 'give'); + case 'donations': + return __('Number of donations', 'give'); + case 'donors': + return __('Number of donors', 'give'); + case 'amountFromSubscriptions': + return __('Recurring amount raised', 'give'); + case 'subscriptions': + return __('Number of recurring donations', 'give'); + case 'donorsFromSubscriptions': + return __('Number of recurring donors', 'give'); + } +} + + +export const getGoalFormattedValue = (goalType: string, value: number) => { + switch (goalType) { + case 'amount': + case 'amountFromSubscriptions': + const {currency} = getCampaignOptionsWindowData() + const currencyFormatter = amountFormatter(currency); + + return currencyFormatter.format(value); + + default: + return value; + } +} diff --git a/src/Campaigns/Blocks/blocks.ts b/src/Campaigns/Blocks/blocks.ts index 39b7fb8ed0..ada033fe2a 100644 --- a/src/Campaigns/Blocks/blocks.ts +++ b/src/Campaigns/Blocks/blocks.ts @@ -11,11 +11,20 @@ import campaignDonateButton from './DonateButton'; import campaignDonations from './CampaignDonations'; import campaignDonors from './CampaignDonors'; import campaignTitle from './CampaignTitle'; +import campaignGoal from './CampaignGoal'; import campaignStats from './CampaignStats'; export const getAllBlocks = () => { - return [campaignCover, campaignDonateButton, campaignDonations, campaignDonors, campaignTitle, campaignStats]; -}; + return [ + campaignCover, + campaignDonateButton, + campaignDonations, + campaignDonors, + campaignTitle, + campaignGoal, + campaignStats + ]; +} getAllBlocks().forEach((block) => { if (!getBlockType(block.schema.name)) { diff --git a/src/Campaigns/Blocks/shared/hooks/useCampaign.ts b/src/Campaigns/Blocks/shared/hooks/useCampaign.ts index a7cf2b57ea..3d0ed8f3ad 100644 --- a/src/Campaigns/Blocks/shared/hooks/useCampaign.ts +++ b/src/Campaigns/Blocks/shared/hooks/useCampaign.ts @@ -2,10 +2,12 @@ import {useEntityRecord} from '@wordpress/core-data'; import {Campaign} from '@givewp/campaigns/admin/components/types'; export default function useCampaign(campaignId: number) { - const data = useEntityRecord('givewp', 'campaign', campaignId); + const campaignData = useEntityRecord('givewp', 'campaign', campaignId); return { - campaign: data?.record as Campaign, - hasResolved: data?.hasResolved, + campaign: { + ...campaignData?.record as Campaign + }, + hasResolved: campaignData?.hasResolved, }; } diff --git a/src/Campaigns/CampaignsAdminPage.php b/src/Campaigns/CampaignsAdminPage.php index b4152cafc2..cf6be15b93 100644 --- a/src/Campaigns/CampaignsAdminPage.php +++ b/src/Campaigns/CampaignsAdminPage.php @@ -39,7 +39,7 @@ public function renderCampaignsPage() wp_die(__('Campaign not found', 'give'), 404); } - give(LoadCampaignDetailsAssets::class)($campaign); + give(LoadCampaignDetailsAssets::class)(); } else { give(LoadCampaignsListTableAssets::class)(); } diff --git a/src/Campaigns/Controllers/CampaignRequestController.php b/src/Campaigns/Controllers/CampaignRequestController.php index 8aaea9b813..836b259a12 100644 --- a/src/Campaigns/Controllers/CampaignRequestController.php +++ b/src/Campaigns/Controllers/CampaignRequestController.php @@ -33,7 +33,7 @@ public function getCampaign(WP_REST_Request $request) return new WP_REST_Response( array_merge($campaign->toArray(), [ - 'goalProgress' => $campaign->goalProgress(), + 'goalStats' => $campaign->getGoalStats(), 'defaultFormTitle' => $campaign->defaultForm()->title ]) ); @@ -59,7 +59,9 @@ public function getCampaigns(WP_REST_Request $request): WP_REST_Response // todo: remove - temporary solution $campaigns = array_map(function ($campaign) { - return $campaign->toArray(); + return array_merge($campaign->toArray(), [ + 'goalStats' => $campaign->getGoalStats(), + ]); }, $campaigns); $response = rest_ensure_response($campaigns); diff --git a/src/Campaigns/Models/Campaign.php b/src/Campaigns/Models/Campaign.php index 2bd08388a0..f0f8e5ef5c 100644 --- a/src/Campaigns/Models/Campaign.php +++ b/src/Campaigns/Models/Campaign.php @@ -5,7 +5,7 @@ use DateTime; use Exception; use Give\Campaigns\Actions\ConvertQueryDataToCampaign; -use Give\Campaigns\CampaignDonationQuery; +use Give\Campaigns\DataTransferObjects\CampaignGoalData; use Give\Campaigns\Factories\CampaignFactory; use Give\Campaigns\Repositories\CampaignPageRepository; use Give\Campaigns\Repositories\CampaignRepository; @@ -176,10 +176,9 @@ public function merge(Campaign ...$campaignsToMerge): bool return give(CampaignRepository::class)->mergeCampaigns($this, ...$campaignsToMerge); } - public function goalProgress() + public function getGoalStats(): array { - $query = new CampaignDonationQuery($this); - return $query->sumIntendedAmount(); + return (new CampaignGoalData($this))->toArray(); } /** diff --git a/src/Campaigns/ServiceProvider.php b/src/Campaigns/ServiceProvider.php index 639f5fe571..4c627756aa 100644 --- a/src/Campaigns/ServiceProvider.php +++ b/src/Campaigns/ServiceProvider.php @@ -7,6 +7,7 @@ use Give\Campaigns\Actions\CreateDefaultCampaignForm; use Give\Campaigns\Actions\DeleteCampaignPage; use Give\Campaigns\Actions\FormInheritsCampaignGoal; +use Give\Campaigns\Actions\LoadCampaignOptions; use Give\Campaigns\Migrations\Donations\AddCampaignId as DonationsAddCampaignId; use Give\Campaigns\Migrations\MigrateFormsToCampaignForms; use Give\Campaigns\Migrations\P2P\SetCampaignType; @@ -50,6 +51,7 @@ public function boot(): void $this->registerCampaignEntity(); $this->registerCampaignBlocks(); $this->setupCampaignForms(); + $this->loadCampaignOptions(); } /** @@ -160,4 +162,12 @@ private function registerCampaignBlocks() Hooks::addAction('rest_api_init', Actions\RegisterCampaignIdRestField::class); Hooks::addAction('init', Actions\RegisterCampaignBlocks::class); } + + /** + * @unreleased + */ + private function loadCampaignOptions() + { + Hooks::addAction('init', LoadCampaignOptions::class); + } } diff --git a/src/Campaigns/resources/admin/common/getCampaignDetailsWindowData.ts b/src/Campaigns/resources/admin/common/getCampaignDetailsWindowData.ts deleted file mode 100644 index 6de3dc4aed..0000000000 --- a/src/Campaigns/resources/admin/common/getCampaignDetailsWindowData.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type {GiveCampaignDetails} from '@givewp/campaigns/admin/components/CampaignDetailsPage/types'; - -declare const window: { - GiveCampaignDetails: GiveCampaignDetails; -} & Window; - -export default function getCampaignDetailsWindowData(): GiveCampaignDetails { - return window.GiveCampaignDetails; -} diff --git a/src/Campaigns/resources/admin/common/index.ts b/src/Campaigns/resources/admin/common/index.ts deleted file mode 100644 index 6e1ea7e07f..0000000000 --- a/src/Campaigns/resources/admin/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {default as getCampaignDetailsWindowData} from './getCampaignDetailsWindowData'; diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx index 754c192621..101915be04 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx @@ -7,14 +7,13 @@ import {addQueryArgs} from '@wordpress/url'; import HeaderText from '../HeaderText'; import HeaderSubText from '../HeaderSubText'; import DefaultFormWidget from "../DefaultForm"; -import {useCampaignEntityRecord, amountFormatter} from '@givewp/campaigns/utils'; -import {getCampaignDetailsWindowData} from '@givewp/campaigns/admin/common'; +import {useCampaignEntityRecord, amountFormatter, getCampaignOptionsWindowData} from '@givewp/campaigns/utils'; import styles from "./styles.module.scss" const campaignId = new URLSearchParams(window.location.search).get('id'); -const {currency} = getCampaignDetailsWindowData(); +const {currency} = getCampaignOptionsWindowData(); const currencyFormatter = amountFormatter(currency); const pluck = (array: any[], property: string) => array.map(element => element[property]) @@ -150,7 +149,7 @@ const GoalProgressWidget = () => { {__('Goal progress', 'give')} {__('Show your campaign performance', 'give')} - + ) } diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/GoalProgressChart/index.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/GoalProgressChart/index.tsx index cbe04bba06..cd4d479788 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/GoalProgressChart/index.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/GoalProgressChart/index.tsx @@ -3,10 +3,9 @@ import Chart from "react-apexcharts"; import React from "react"; import styles from "./styles.module.scss" -import {getCampaignDetailsWindowData} from '@givewp/campaigns/admin/common'; -import {amountFormatter} from '@givewp/campaigns/utils'; +import {getCampaignOptionsWindowData, amountFormatter} from '@givewp/campaigns/utils'; -const {currency} = getCampaignDetailsWindowData(); +const {currency} = getCampaignOptionsWindowData(); const currencyFormatter = amountFormatter(currency); const GoalProgressChart = ({ value, goal }) => { diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Tabs/Settings.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Tabs/Settings.tsx index 0b5fc130a2..6d45cc4134 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/Tabs/Settings.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/Tabs/Settings.tsx @@ -5,11 +5,10 @@ import styles from '../CampaignDetailsPage.module.scss'; import {ToggleControl} from '@wordpress/components'; import campaignPageImage from './images/campaign-page.svg'; import {WarningIcon} from '@givewp/campaigns/admin/components/Icons'; -import {getCampaignDetailsWindowData} from '@givewp/campaigns/admin/common'; -import {amountFormatter} from '@givewp/campaigns/utils'; +import {getCampaignOptionsWindowData, amountFormatter} from '@givewp/campaigns/utils'; import ColorControl from '@givewp/campaigns/admin/components/CampaignDetailsPage/Components/ColorControl'; -const {currency, isRecurringEnabled} = getCampaignDetailsWindowData(); +const {currency, isRecurringEnabled} = getCampaignOptionsWindowData(); const currencyFormatter = amountFormatter(currency); /** @@ -152,7 +151,7 @@ export default () => { {/* Campaign Goal */} -
+
{__('Campaign Goal', 'give')}
diff --git a/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx b/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx index 6806ea2c63..af082b7039 100644 --- a/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx +++ b/src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx @@ -5,7 +5,7 @@ import {useEntityRecord} from '@wordpress/core-data'; import apiFetch from '@wordpress/api-fetch'; import {JSONSchemaType} from 'ajv'; import {ajvResolver} from '@hookform/resolvers/ajv'; -import {GiveCampaignDetails} from './types'; +import {GiveCampaignOptions} from '@givewp/campaigns/types'; import {Campaign} from '../types'; import {FormProvider, SubmitHandler, useForm} from 'react-hook-form'; import {Spinner as GiveSpinner} from '@givewp/components'; @@ -21,7 +21,7 @@ import {useCampaignEntityRecord} from '@givewp/campaigns/utils'; import styles from './CampaignDetailsPage.module.scss'; declare const window: { - GiveCampaignDetails: GiveCampaignDetails; + GiveCampaignOptions: GiveCampaignOptions; } & Window; interface Show { @@ -203,7 +203,7 @@ export default function CampaignsDetailsPage({campaignId}) {
{__('Campaigns', 'give')} @@ -220,7 +220,7 @@ export default function CampaignsDetailsPage({campaignId}) { {enableCampaignPage && ( diff --git a/src/Campaigns/resources/admin/components/types.ts b/src/Campaigns/resources/admin/components/types.ts index 50543caf81..0292421eaf 100644 --- a/src/Campaigns/resources/admin/components/types.ts +++ b/src/Campaigns/resources/admin/components/types.ts @@ -11,7 +11,11 @@ export type Campaign = { secondaryColor: string; goalType: string; goal: number; - goalProgress: number; + goalStats: { + actual: number, + percentage: number, + goal: number, + }; status: string; startDateTime: { date: string; diff --git a/src/Campaigns/resources/types.ts b/src/Campaigns/resources/types.ts index 944af2a414..61a2e042ff 100644 --- a/src/Campaigns/resources/types.ts +++ b/src/Campaigns/resources/types.ts @@ -21,3 +21,10 @@ declare module "@wordpress/data" { dismissNotification(id: string): void }; } + +export type GiveCampaignOptions = { + adminUrl: string; + currency: string; + isRecurringEnabled: boolean; + defaultForm: string; +} diff --git a/src/Campaigns/resources/utils.ts b/src/Campaigns/resources/utils.ts index d69909c062..14e93d41de 100644 --- a/src/Campaigns/resources/utils.ts +++ b/src/Campaigns/resources/utils.ts @@ -1,5 +1,10 @@ import {useEntityRecord} from '@wordpress/core-data'; import {Campaign} from '@givewp/campaigns/admin/components/types'; +import type {GiveCampaignOptions} from '@givewp/campaigns/types'; + +declare const window: { + GiveCampaignOptions: GiveCampaignOptions; +} & Window; /** * @unreleased @@ -22,6 +27,12 @@ export function useCampaignEntityRecord(campaignId?: number) { return {campaign, hasResolved, save, edit}; } +/** + * @unreleased + */ +export function getCampaignOptionsWindowData(): GiveCampaignOptions { + return window.GiveCampaignOptions; +} /** * @unreleased diff --git a/wordpress-scripts-webpack.config.js b/wordpress-scripts-webpack.config.js index d1a586a294..1930a22052 100644 --- a/wordpress-scripts-webpack.config.js +++ b/wordpress-scripts-webpack.config.js @@ -66,6 +66,7 @@ module.exports = { campaignBlocks: srcPath('Campaigns/Blocks/blocks.ts'), campaignDonationsBlockApp: srcPath('Campaigns/Blocks/CampaignDonations/app.tsx'), campaignDonorsBlockApp: srcPath('Campaigns/Blocks/CampaignDonors/app.tsx'), + campaignGoalBlockApp: srcPath('Campaigns/Blocks/CampaignGoal/app.tsx'), campaignStatsBlockApp: srcPath('Campaigns/Blocks/CampaignStats/app.tsx'), }, };