Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Campaign Goal Block #7702

Merged
merged 32 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f7b29b7
initial commit
alaca Jan 22, 2025
9c6d894
feature: render block
alaca Jan 23, 2025
088e26e
feature: add icon and btn
alaca Jan 23, 2025
673c59d
feature: add description and hide select component if default form is…
alaca Jan 23, 2025
76ead3f
chore: add unreleased tags
alaca Jan 23, 2025
572a377
refactor: move campaign forms fetching logic outside the useCampaign …
alaca Jan 29, 2025
f66d535
initial commit
alaca Jan 30, 2025
298b35b
refactor: remove unused attributes
alaca Jan 30, 2025
9385381
feature: add helper functions for displaying goal values and description
alaca Jan 30, 2025
7f1a040
refactor: add goalStats prop
alaca Jan 30, 2025
6859f65
refactor: use CampaignGoalData instead of CampaignDonationQuery for g…
alaca Jan 30, 2025
86ee2a8
refactor: use the new goalStats prop
alaca Jan 30, 2025
5034344
feature: add goalStats props
alaca Jan 30, 2025
069c39d
feature: render block
alaca Jan 31, 2025
15f0721
fix: missing campaign goal stats
alaca Feb 3, 2025
92e51e3
Merge branch 'refs/heads/epic/campaigns' into feature/campaign-goal-b…
alaca Feb 3, 2025
ca7d6c9
feature: add text control with goal description
alaca Feb 3, 2025
0c163b7
refactor: fix undefined campaign goalStats; use campaign method to ge…
alaca Feb 3, 2025
3a49194
Merge branch 'refs/heads/epic/campaigns' into feature/campaign-goal-b…
alaca Feb 3, 2025
e2cd894
feature: set language using navigator object
alaca Feb 3, 2025
0926bad
refactor: render block using ssr
alaca Feb 3, 2025
9b59101
Merge branch 'refs/heads/epic/campaigns' into feature/campaign-goal-b…
alaca Feb 7, 2025
04cbdd1
refactor: block settings
alaca Feb 7, 2025
4754280
refactor: everything
alaca Feb 10, 2025
0c409e5
refactor: remove leftover
alaca Feb 11, 2025
c88ad97
feature: format currency
alaca Feb 11, 2025
b1b67c0
refactor: load campaign options on both admin and frontend
alaca Feb 11, 2025
455de2a
Merge branch 'epic/campaigns' into feature/campaign-goal-block-GIVE-1510
jonwaldstein Feb 13, 2025
da65f7d
refactor: getCampaignDetailsWindowData
alaca Feb 14, 2025
eca6853
refactor: remove leftover
alaca Feb 14, 2025
b85d99b
refactor: use data attribute instead of class
alaca Feb 14, 2025
376eba2
refactor: use givewp instead of give
alaca Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/Campaigns/Actions/LoadCampaignDetailsAssets.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
30 changes: 30 additions & 0 deletions src/Campaigns/Actions/LoadCampaignOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Give\Campaigns\Actions;

/**
* The purpose of this action is to have a centralized place for localizing options used on many different places
* by campaign scripts (list tables, blocks, etc.)
*
* @unreleased
*/
class LoadCampaignOptions
{
public function __invoke()
{
wp_register_script('give-campaign-options', false);

wp_localize_script('give-campaign-options', 'GiveCampaignOptions',
[
'adminUrl' => 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');
}
}
26 changes: 26 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/app.tsx
Original file line number Diff line number Diff line change
@@ -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 <App campaign={campaign} />;
}

/**
* @unreleased
*/
const nodeList = document.querySelectorAll('[data-givewp-campaign-goal]');

if (nodeList) {
const containers = Array.from(nodeList);

containers.map((container: any) => {
return render(<BlockApp campaignId={container.dataset?.id} />, container);
});
}
34 changes: 34 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/app/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="givewp-campaign-goal">
<div className="givewp-campaign-goal__container">
<div className="givewp-campaign-goal__container-item">
<span>{getGoalDescription(campaign.goalType)}</span>
<strong>
{getGoalFormattedValue(campaign.goalType, campaign.goalStats.actual)}
</strong>
</div>
<div className="givewp-campaign-goal__container-item">
<span>{__('Our goal', 'give')}</span>
<strong>
{getGoalFormattedValue(campaign.goalType, campaign.goal)}
</strong>
</div>
</div>
<div className="givewp-campaign-goal__progress-bar">
<div className="givewp-campaign-goal__progress-bar-container">
<div
className="givewp-campaign-goal__progress-bar-progress"
style={{width: `${campaign.goalStats.percentage}%`}}>
</div>
</div>
</div>
</div>
);
}
51 changes: 51 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/app/styles.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
24 changes: 24 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/block.json
Original file line number Diff line number Diff line change
@@ -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"
}
53 changes: 53 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/edit.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div {...blockProps}>
<CampaignSelector attributes={attributes} setAttributes={setAttributes}>
<CampaignGoalApp campaign={campaign} />
</CampaignSelector>

{campaign?.id && (
<InspectorControls>
<PanelBody title={__('Settings', 'give')} initialOpen={true}>
<TextControl value={getGoalDescription(campaign.goalType)} onChange={null} disabled={true} />
<ExternalLink
href={`${adminBaseUrl}&id=${attributes.campaignId}&tab=settings#campaign-goal`}
title={__('Edit campaign goal settings', 'give')}
>
{__('Edit campaign goal settings', 'give')}
</ExternalLink>
</PanelBody>
</InspectorControls>
)}
</div>
);
}
11 changes: 11 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd"
d="M19.16 1.013a1 1 0 0 1 .734.54l.851 1.702 1.702.85a1 1 0 0 1 .26 1.602l-3 3A1 1 0 0 1 19 9h-2.586l-3.707 3.707a1 1 0 0 1-1.414-1.414L15 7.586V5a1 1 0 0 1 .293-.707l3-3a1 1 0 0 1 .867-.28z"
fill="#000" />
<path
d="M3 12a9 9 0 0 1 9-9 1 1 0 1 0 0-2C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11a1 1 0 1 0-2 0 9 9 0 1 1-18 0z"
fill="#000" />
<path d="M8 12a4 4 0 0 1 4-4 1 1 0 1 0 0-2 6 6 0 1 0 6 6 1 1 0 1 0-2 0 4 4 0 0 1-8 0z" fill="#000" />
</svg>
)
15 changes: 15 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import edit from './edit';
import icon from './icon';
import schema from './block.json';

/**
* @unreleased
*/
export default {
schema,
settings: {
icon,
edit
}
};

20 changes: 20 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/render.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

use Give\Campaigns\Models\Campaign;
use Give\Campaigns\Repositories\CampaignRepository;

/**
* @var array $attributes
* @var Campaign $campaign
*/

if (
! isset($attributes['campaignId'])
|| ! $campaign = give(CampaignRepository::class)->getById($attributes['campaignId'])
) {
return;
}

?>

<div data-givewp-campaign-goal data-id="<?= $campaign->id; ?>"></div>
35 changes: 35 additions & 0 deletions src/Campaigns/Blocks/CampaignGoal/utils.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 11 additions & 2 deletions src/Campaigns/Blocks/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
8 changes: 5 additions & 3 deletions src/Campaigns/Blocks/shared/hooks/useCampaign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
2 changes: 1 addition & 1 deletion src/Campaigns/CampaignsAdminPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)();
}
Expand Down
6 changes: 4 additions & 2 deletions src/Campaigns/Controllers/CampaignRequestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
])
);
Expand All @@ -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);
Expand Down
Loading
Loading