|
| 1 | +import React, { useState } from 'react'; |
| 2 | +import { Button, Menu } from 'antd'; |
| 3 | +import { $t } from 'services/i18n'; |
| 4 | +import { IWidgetCommonState, useWidget, WidgetModule, WidgetParams } from './common/useWidget'; |
| 5 | +import { WidgetLayout } from './common/WidgetLayout'; |
| 6 | +import FormFactory, { TInputValue } from 'components-react/shared/inputs/FormFactory'; |
| 7 | +import Form from 'components-react/shared/inputs/Form'; |
| 8 | +import { metadata } from '../shared/inputs/metadata'; |
| 9 | +import { WidgetType } from 'services/widgets'; |
| 10 | +import { authorizedHeaders, jfetch } from 'util/requests'; |
| 11 | +import { Services } from 'components-react/service-provider'; |
| 12 | + |
| 13 | +interface IGoalState extends IWidgetCommonState { |
| 14 | + data: { |
| 15 | + goal: { |
| 16 | + title: string; |
| 17 | + goal_amount: number; |
| 18 | + current_amount: number; |
| 19 | + to_go: string; |
| 20 | + } | null; |
| 21 | + settings: { |
| 22 | + background_color: string; |
| 23 | + bar_color: string; |
| 24 | + bar_bg_color: string; |
| 25 | + text_color: string; |
| 26 | + bar_text_color: string; |
| 27 | + font: string; |
| 28 | + bar_thickness: string; |
| 29 | + layout: string; |
| 30 | + custom_enabled: boolean; |
| 31 | + custom_html: string; |
| 32 | + custom_js: string; |
| 33 | + custom_css: string; |
| 34 | + }; |
| 35 | + custom_defaults: { |
| 36 | + html: string; |
| 37 | + js: string; |
| 38 | + css: string; |
| 39 | + }; |
| 40 | + has_goal: boolean; |
| 41 | + show_bar: string; |
| 42 | + }; |
| 43 | +} |
| 44 | + |
| 45 | +export function GenericGoal() { |
| 46 | + const { |
| 47 | + isLoading, |
| 48 | + settings, |
| 49 | + createGoalMeta, |
| 50 | + goalSettings, |
| 51 | + visualMeta, |
| 52 | + updateSetting, |
| 53 | + setSelectedTab, |
| 54 | + selectedTab, |
| 55 | + saveGoal, |
| 56 | + type, |
| 57 | + } = useGenericGoal(); |
| 58 | + |
| 59 | + const isCharity = type === WidgetType.CharityGoal; |
| 60 | + |
| 61 | + const hasGoal = !!goalSettings; |
| 62 | + |
| 63 | + const [goalCreateValues, setGoalCreateValues] = useState<Dictionary<TInputValue>>({ |
| 64 | + title: '', |
| 65 | + goal_amount: 100, |
| 66 | + manual_goal_amount: 0, |
| 67 | + ends_at: '', |
| 68 | + }); |
| 69 | + |
| 70 | + function updateGoalCreate(key: string) { |
| 71 | + return (val: TInputValue) => { |
| 72 | + setGoalCreateValues({ ...goalCreateValues, [key]: val }); |
| 73 | + }; |
| 74 | + } |
| 75 | + |
| 76 | + return ( |
| 77 | + <WidgetLayout> |
| 78 | + <Menu onClick={e => setSelectedTab(e.key)} selectedKeys={[selectedTab]}> |
| 79 | + <Menu.Item key="general">{$t('Visual Settings')}</Menu.Item> |
| 80 | + {!isCharity && <Menu.Item key="goal">{$t('Goal Settings')}</Menu.Item>} |
| 81 | + </Menu> |
| 82 | + <Form> |
| 83 | + {!isLoading && selectedTab === 'goal' && !hasGoal && ( |
| 84 | + <> |
| 85 | + <FormFactory |
| 86 | + metadata={createGoalMeta} |
| 87 | + values={goalCreateValues} |
| 88 | + onChange={updateGoalCreate} |
| 89 | + /> |
| 90 | + <Button className="button button--action" onClick={() => saveGoal(goalCreateValues)}> |
| 91 | + {$t('Save Goal')} |
| 92 | + </Button> |
| 93 | + </> |
| 94 | + )} |
| 95 | + {!isLoading && selectedTab === 'goal' && hasGoal && <DisplayGoal goal={goalSettings} />} |
| 96 | + {!isLoading && selectedTab === 'general' && ( |
| 97 | + <FormFactory |
| 98 | + metadata={visualMeta} |
| 99 | + values={settings} |
| 100 | + onChange={updateSetting} |
| 101 | + name="visualSettingsForm" |
| 102 | + /> |
| 103 | + )} |
| 104 | + </Form> |
| 105 | + </WidgetLayout> |
| 106 | + ); |
| 107 | +} |
| 108 | + |
| 109 | +function DisplayGoal(p: { goal: IGoalState['data']['goal'] }) { |
| 110 | + const { resetGoal } = useGenericGoal(); |
| 111 | + |
| 112 | + if (!p.goal) return <></>; |
| 113 | + return ( |
| 114 | + <div className="section__body"> |
| 115 | + <div className="goal-row"> |
| 116 | + <span>{$t('Title')}</span> |
| 117 | + <span>{p.goal.title}</span> |
| 118 | + </div> |
| 119 | + <div className="goal-row"> |
| 120 | + <span>{$t('Goal Amount')}</span> |
| 121 | + <span>{p.goal.goal_amount}</span> |
| 122 | + </div> |
| 123 | + <div className="goal-row"> |
| 124 | + <span>{$t('Current Amount')}</span> |
| 125 | + <span>{p.goal.current_amount}</span> |
| 126 | + </div> |
| 127 | + <div className="goal-row"> |
| 128 | + <span>{$t('Days Remaining')}</span> |
| 129 | + <span>{p.goal.to_go}</span> |
| 130 | + </div> |
| 131 | + <Button className="button button--soft-warning" onClick={resetGoal}> |
| 132 | + {$t('End Goal')} |
| 133 | + </Button> |
| 134 | + </div> |
| 135 | + ); |
| 136 | +} |
| 137 | + |
| 138 | +export class GenericGoalModule extends WidgetModule<IGoalState> { |
| 139 | + get dateValidator() { |
| 140 | + return { |
| 141 | + // regex from https://stackoverflow.com/questions/2520633/what-is-the-mm-dd-yyyy-regular-expression-and-how-do-i-use-it-in-php |
| 142 | + pattern: /^(0[1-9]|1[012])[/](0[1-9]|[12][0-9]|3[01])[/](19|20)\d\d$/, |
| 143 | + message: $t('Must be in MM/DD/YYYY format.'), |
| 144 | + }; |
| 145 | + } |
| 146 | + |
| 147 | + get createGoalMeta() { |
| 148 | + return { |
| 149 | + title: metadata.text({ |
| 150 | + label: $t('Title'), |
| 151 | + required: true, |
| 152 | + max: 60, |
| 153 | + }), |
| 154 | + goal_amount: metadata.number({ |
| 155 | + label: $t('Goal Amount'), |
| 156 | + required: true, |
| 157 | + min: 1, |
| 158 | + }), |
| 159 | + manual_goal_amount: metadata.number({ |
| 160 | + label: $t('Starting Amount'), |
| 161 | + min: 0, |
| 162 | + }), |
| 163 | + ends_at: metadata.text({ |
| 164 | + label: $t('End After'), |
| 165 | + required: true, |
| 166 | + placeholder: 'MM/DD/YYYY', |
| 167 | + rules: [this.dateValidator], |
| 168 | + }), |
| 169 | + }; |
| 170 | + } |
| 171 | + |
| 172 | + get goalSettings() { |
| 173 | + return this.widgetData.goal; |
| 174 | + } |
| 175 | + |
| 176 | + get visualMeta() { |
| 177 | + const meta = { |
| 178 | + layout: metadata.list({ |
| 179 | + label: $t('Layout'), |
| 180 | + options: [ |
| 181 | + { label: $t('Standard'), value: 'standard' }, |
| 182 | + { label: $t('Condensed'), value: 'condensed' }, |
| 183 | + ], |
| 184 | + }), |
| 185 | + background_color: metadata.color({ label: $t('Background Color') }), |
| 186 | + bar_color: metadata.color({ label: $t('Bar Color') }), |
| 187 | + bar_bg_color: metadata.color({ label: $t('Bar Background Color') }), |
| 188 | + text_color: metadata.color({ |
| 189 | + label: $t('Text Color'), |
| 190 | + tooltip: $t('A hex code for the base text color.'), |
| 191 | + }), |
| 192 | + bar_text_color: metadata.color({ label: $t('Bar Text Color') }), |
| 193 | + bar_thickness: metadata.slider({ label: $t('Bar Thickness'), min: 32, max: 128, step: 4 }), |
| 194 | + font: { type: 'fontFamily', label: $t('Font Family') }, |
| 195 | + }; |
| 196 | + |
| 197 | + if (this.state.type === WidgetType.SubGoal) { |
| 198 | + return { include_resubs: metadata.switch({ label: $t('Include Resubs') }), ...meta }; |
| 199 | + } |
| 200 | + return meta; |
| 201 | + } |
| 202 | + |
| 203 | + get headers() { |
| 204 | + return authorizedHeaders( |
| 205 | + Services.UserService.apiToken, |
| 206 | + new Headers({ 'Content-Type': 'application/json' }), |
| 207 | + ); |
| 208 | + } |
| 209 | + |
| 210 | + resetGoal() { |
| 211 | + const url = this.config.goalUrl; |
| 212 | + if (!url) return; |
| 213 | + jfetch(new Request(url, { method: 'DELETE', headers: this.headers })); |
| 214 | + } |
| 215 | + |
| 216 | + saveGoal(options: Dictionary<TInputValue>) { |
| 217 | + const url = this.config.goalUrl; |
| 218 | + if (!url) return; |
| 219 | + jfetch( |
| 220 | + new Request(url, { |
| 221 | + method: 'POST', |
| 222 | + headers: this.headers, |
| 223 | + body: JSON.stringify(options), |
| 224 | + }), |
| 225 | + ); |
| 226 | + } |
| 227 | + |
| 228 | + patchAfterFetch(data: IGoalState['data']): IGoalState['data'] { |
| 229 | + // fix a bug when API returning an empty array instead of null |
| 230 | + if (Array.isArray(data.goal)) data.goal = null; |
| 231 | + return data; |
| 232 | + } |
| 233 | +} |
| 234 | + |
| 235 | +function useGenericGoal() { |
| 236 | + return useWidget<GenericGoalModule>(); |
| 237 | +} |
0 commit comments