| 1 |
/** |
| 2 |
* Addons tabs class. |
| 3 |
* |
| 4 |
*/ |
| 5 |
class AllexAddonsTabs extends React.Component { |
| 6 |
/** |
| 7 |
* Constructor. |
| 8 |
* |
| 9 |
* @param props |
| 10 |
*/ |
| 11 |
constructor (props) { |
| 12 |
super(props); |
| 13 |
|
| 14 |
// State |
| 15 |
this.state = { |
| 16 |
currentTab: this.props.currentTab |
| 17 |
}; |
| 18 |
|
| 19 |
// Events |
| 20 |
this.handleClick = this.handleClick.bind(this); |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Handles the click event on tabs. |
| 25 |
* |
| 26 |
* @param e |
| 27 |
*/ |
| 28 |
handleClick (e) { |
| 29 |
e.preventDefault(); |
| 30 |
|
| 31 |
this.setState({currentTab: e.target.dataset.tab}); |
| 32 |
this.props.parentTabClickHandler(e.target.dataset.tab); |
| 33 |
} |
| 34 |
|
| 35 |
/** |
| 36 |
* Renders the element. |
| 37 |
* |
| 38 |
* @returns {*} |
| 39 |
*/ |
| 40 |
render () { |
| 41 |
|
| 42 |
return ( |
| 43 |
<div className="nav-tab-wrapper"> |
| 44 |
{this.props.addonsCount > 0 ? ( |
| 45 |
<div> |
| 46 |
<a href="#" |
| 47 |
className={this.state.currentTab == 'installed' ? 'nav-tab nav-tab-active' : 'nav-tab'} |
| 48 |
data-tab="installed" |
| 49 |
onClick={this.handleClick}> |
| 50 |
|
| 51 |
{allexContext.labels.installed} |
| 52 |
</a> |
| 53 |
|
| 54 |
<a href="#" |
| 55 |
className={this.state.currentTab == 'missed' ? 'nav-tab nav-tab-active' : 'nav-tab'} |
| 56 |
data-tab="missed" |
| 57 |
onClick={this.handleClick}> |
| 58 |
|
| 59 |
{allexContext.labels.browse_more} |
| 60 |
</a> |
| 61 |
</div> |
| 62 |
) : (null)} |
| 63 |
</div> |
| 64 |
); |
| 65 |
} |
| 66 |
} |
| 67 |
|
| 68 |
/** |
| 69 |
* Table of addons. |
| 70 |
* |
| 71 |
* @param {Array} addons |
| 72 |
* @param {String} currentTab |
| 73 |
* @param {String} nonce |
| 74 |
* @param {String} pluginName |
| 75 |
* @param {Int} addonsCount |
| 76 |
* @param {Int} addonsCountTotal |
| 77 |
*/ |
| 78 |
class AllexAddonsTable extends React.Component { |
| 79 |
/** |
| 80 |
* The constructor. |
| 81 |
* |
| 82 |
* @param props |
| 83 |
*/ |
| 84 |
constructor (props) { |
| 85 |
super(props); |
| 86 |
} |
| 87 |
|
| 88 |
/** |
| 89 |
* Renders the element. |
| 90 |
* |
| 91 |
* @returns {*} |
| 92 |
*/ |
| 93 |
render () { |
| 94 |
const rows = this.props.addons.map((addon, index) => { |
| 95 |
// Check if the current addon belongs to the current tab, based on the state. |
| 96 |
if ( |
| 97 |
(addon.isInstalled && this.props.currentTab === 'installed') |
| 98 |
|| (!addon.isInstalled && this.props.currentTab === 'missed') |
| 99 |
) { |
| 100 |
return (<AllexAddonsRow key={'allex_addon_' + addon.slug} addon={addon} |
| 101 |
currentTab={this.props.currentTab} nonce={this.props.nonce} |
| 102 |
pluginName={this.props.pluginName}/>); |
| 103 |
} else { |
| 104 |
return null; |
| 105 |
} |
| 106 |
}); |
| 107 |
|
| 108 |
return ( |
| 109 |
<section id={'allex-addons-' + this.props.currentTab} className="allex-addons-container"> |
| 110 |
{this.props.addonsCount == this.props.addonsCountTotal ? ( |
| 111 |
<p className="notice allex-addons-all-installed">{allexContext.labels.all_plugins_installed}</p> |
| 112 |
) : (null)} |
| 113 |
|
| 114 |
{rows} |
| 115 |
</section> |
| 116 |
); |
| 117 |
} |
| 118 |
} |
| 119 |
|
| 120 |
/** |
| 121 |
* Addons row class. |
| 122 |
* |
| 123 |
* @param {Int} index |
| 124 |
* @param {Object} addon |
| 125 |
* @param {String} currentTab |
| 126 |
* @param {String} nonce |
| 127 |
* @param {String} pluginName |
| 128 |
*/ |
| 129 |
class AllexAddonsRow extends React.Component { |
| 130 |
/** |
| 131 |
* The constructor. |
| 132 |
* |
| 133 |
* @param props |
| 134 |
*/ |
| 135 |
constructor (props) { |
| 136 |
super(props); |
| 137 |
|
| 138 |
// State |
| 139 |
this.state = { |
| 140 |
licenseKey: this.props.addon.licenseKey, |
| 141 |
licenseStatus: this.props.addon.licenseStatus, |
| 142 |
messages: [], |
| 143 |
showForm: this.props.addon.licenseKey === '', |
| 144 |
processing: false |
| 145 |
}; |
| 146 |
|
| 147 |
// Events |
| 148 |
this.onChangeLicenseKey = this.onChangeLicenseKey.bind(this); |
| 149 |
this.localizeLicenseStatus = this.localizeLicenseStatus.bind(this); |
| 150 |
this.showForm = this.showForm.bind(this); |
| 151 |
this.hideForm = this.hideForm.bind(this); |
| 152 |
this.activateLicenseKey = this.activateLicenseKey.bind(this); |
| 153 |
this.addMessage = this.addMessage.bind(this); |
| 154 |
this.focusInput = this.focusInput.bind(this); |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* |
| 159 |
* @param status |
| 160 |
* |
| 161 |
* @returns {String} |
| 162 |
*/ |
| 163 |
localizeLicenseStatus (status) { |
| 164 |
if (status !== null && status !== '' && typeof status === 'string') { |
| 165 |
if (allexContext.labels['license_status_' + status]) { |
| 166 |
return allexContext.labels['license_status_' + status]; |
| 167 |
} |
| 168 |
|
| 169 |
return status; |
| 170 |
} |
| 171 |
|
| 172 |
return ''; |
| 173 |
} |
| 174 |
|
| 175 |
/** |
| 176 |
* Show the license key form |
| 177 |
*/ |
| 178 |
showForm () { |
| 179 |
this.setState({ |
| 180 |
showForm: true, |
| 181 |
messages: [] |
| 182 |
}); |
| 183 |
} |
| 184 |
|
| 185 |
/** |
| 186 |
* Hide the license key form |
| 187 |
*/ |
| 188 |
hideForm () { |
| 189 |
this.setState({ |
| 190 |
showForm: false, |
| 191 |
messages: [] |
| 192 |
}); |
| 193 |
} |
| 194 |
|
| 195 |
/** |
| 196 |
* Add a message to the state. |
| 197 |
* |
| 198 |
* @param messageText |
| 199 |
*/ |
| 200 |
addMessage (messageText) { |
| 201 |
let messages = this.state.messages; |
| 202 |
|
| 203 |
messages.push(messageText); |
| 204 |
|
| 205 |
this.setState({messages: messages}); |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* Set focus on the input. |
| 210 |
*/ |
| 211 |
focusInput () { |
| 212 |
jQuery('article[data-slug="' + this.props.addon.slug + '"] input').focus().select(); |
| 213 |
} |
| 214 |
|
| 215 |
/** |
| 216 |
* Make a request to the system to activate the license key. |
| 217 |
*/ |
| 218 |
activateLicenseKey () { |
| 219 |
let component = this; |
| 220 |
|
| 221 |
jQuery.ajax({ |
| 222 |
type: 'POST', |
| 223 |
url: ajaxurl, |
| 224 |
data: { |
| 225 |
action: 'allex_addon_license_validate', |
| 226 |
key: this.state.licenseKey, |
| 227 |
plugin_name: this.props.pluginName, |
| 228 |
addon_name: this.props.addon.slug, |
| 229 |
addon_edd_id: this.props.addon.eddId, |
| 230 |
nonce: this.props.nonce |
| 231 |
}, |
| 232 |
beforeSend: function (jqXHR, settings) { |
| 233 |
component.setState({processing: true, messages: []}); |
| 234 |
}, |
| 235 |
success: function (response, textStatus, jqXHR) { |
| 236 |
component.setState({ |
| 237 |
processing: false, |
| 238 |
licenseStatus: response.license_status |
| 239 |
}); |
| 240 |
|
| 241 |
// Error. |
| 242 |
if (!response.success) { |
| 243 |
component.focusInput(); |
| 244 |
component.addMessage(response.message); |
| 245 |
|
| 246 |
return; |
| 247 |
} |
| 248 |
|
| 249 |
// Invalid licenses. |
| 250 |
if (response.license_status !== 'valid') { |
| 251 |
component.focusInput(); |
| 252 |
component.addMessage(component.localizeLicenseStatus(response.license_status)); |
| 253 |
|
| 254 |
return; |
| 255 |
} |
| 256 |
|
| 257 |
// Valid licenses. |
| 258 |
if (response.license_status === 'valid') { |
| 259 |
component.setState({showForm: false}); |
| 260 |
|
| 261 |
return; |
| 262 |
} |
| 263 |
}, |
| 264 |
error: function (jqXHR, textStatus, errorThrown) { |
| 265 |
component.setState({processing: false}); |
| 266 |
|
| 267 |
// Error. |
| 268 |
component.addMessage(textStatus + '. ' + allexContext.labels.contact_support); |
| 269 |
} |
| 270 |
}); |
| 271 |
} |
| 272 |
|
| 273 |
/** |
| 274 |
* Event triggered when a license key is added in the input field. |
| 275 |
* |
| 276 |
* @param e |
| 277 |
*/ |
| 278 |
onChangeLicenseKey (e) { |
| 279 |
this.setState({ |
| 280 |
licenseKey: e.target.value, |
| 281 |
licenseStatus: 'inactive' |
| 282 |
}); |
| 283 |
} |
| 284 |
|
| 285 |
/** |
| 286 |
* Renders the element. |
| 287 |
* |
| 288 |
* @returns {*} |
| 289 |
*/ |
| 290 |
render () { |
| 291 |
let messages = this.state.messages.map((message, index) => { |
| 292 |
let key = this.props.addon.slug + '_' + index; |
| 293 |
|
| 294 |
return (<li key={key}>{message}</li>); |
| 295 |
}); |
| 296 |
|
| 297 |
let showForm = this.state.showForm && this.props.currentTab === 'installed'; |
| 298 |
let showKey = this.props.currentTab === 'installed'; |
| 299 |
|
| 300 |
let form = <div className="allex-addon-license-form"> |
| 301 |
<div> |
| 302 |
<label>{allexContext.labels.license_key}</label> |
| 303 |
|
| 304 |
<input |
| 305 |
type="text" |
| 306 |
size="40" |
| 307 |
maxLength="32" |
| 308 |
autoComplete="false" |
| 309 |
placeholder={allexContext.labels.enter_license_key} |
| 310 |
value={this.state.licenseKey} |
| 311 |
onChange={this.onChangeLicenseKey} |
| 312 |
disabled={this.state.processing ? 'disabled' : ''}/> |
| 313 |
|
| 314 |
<button |
| 315 |
type="button" |
| 316 |
className="button activate" |
| 317 |
onClick={this.activateLicenseKey} |
| 318 |
disabled={this.state.processing ? 'disabled' : ''}> |
| 319 |
|
| 320 |
{this.state.processing ? allexContext.labels.please_wait : allexContext.labels.activate} |
| 321 |
</button> |
| 322 |
|
| 323 |
{this.state.processing ? ( |
| 324 |
<div className="lds-dual-ring"></div> |
| 325 |
) : ( |
| 326 |
<i className="dashicons dashicons-no-alt" |
| 327 |
onClick={this.hideForm}></i> |
| 328 |
)} |
| 329 |
</div> |
| 330 |
|
| 331 |
{this.state.processing === false ? ( |
| 332 |
<ul className="allex-addon-messages"> |
| 333 |
{messages} |
| 334 |
</ul> |
| 335 |
) : (null)} |
| 336 |
</div>; |
| 337 |
|
| 338 |
return ( |
| 339 |
<article data-slug={this.props.addon.slug}> |
| 340 |
<i className={this.props.addon.iconClass}></i> |
| 341 |
|
| 342 |
<header> |
| 343 |
<h3>{this.props.addon.title}</h3> |
| 344 |
<p>{this.props.addon.description}</p> |
| 345 |
</header> |
| 346 |
|
| 347 |
{showForm ? (form) : (null)} |
| 348 |
|
| 349 |
{this.props.currentTab === 'installed' && !showForm ? ( |
| 350 |
<div className="allex-addon-license"> |
| 351 |
<label>{allexContext.labels.license_key}</label> |
| 352 |
<code>{this.state.licenseKey}</code> |
| 353 |
|
| 354 |
<a href="#" |
| 355 |
onClick={this.showForm}>{allexContext.labels.change}</a> |
| 356 |
|
| 357 |
|
| 358 |
<div className="allex-addon-license-status"> |
| 359 |
{this.localizeLicenseStatus(this.state.licenseStatus)} |
| 360 |
</div> |
| 361 |
</div> |
| 362 |
) : (null)} |
| 363 |
</article> |
| 364 |
); |
| 365 |
} |
| 366 |
} |
| 367 |
|
| 368 |
/** |
| 369 |
* Addons container class. |
| 370 |
* |
| 371 |
* @param {String} pluginName |
| 372 |
* @param {Int} addonsCount |
| 373 |
* @param {Int} addonsCountTotal |
| 374 |
* @param {String} browseMoreUrl |
| 375 |
* @param {Array} addons |
| 376 |
* @param {String} nonce |
| 377 |
* |
| 378 |
*/ |
| 379 |
class AllexAddonsContainer extends React.Component { |
| 380 |
/** |
| 381 |
* Constructor. |
| 382 |
* |
| 383 |
* @param props |
| 384 |
*/ |
| 385 |
constructor (props) { |
| 386 |
super(props); |
| 387 |
|
| 388 |
// State |
| 389 |
this.state = { |
| 390 |
currentTab: this.props.addonsCount > 0 ? 'installed' : 'missed' |
| 391 |
}; |
| 392 |
|
| 393 |
// Events |
| 394 |
this.handleTabClick = this.handleTabClick.bind(this); |
| 395 |
} |
| 396 |
|
| 397 |
/** |
| 398 |
* Event called by the child tabs' click event. |
| 399 |
* |
| 400 |
* @param currentTab |
| 401 |
*/ |
| 402 |
handleTabClick (currentTab) { |
| 403 |
this.setState({currentTab: currentTab}); |
| 404 |
} |
| 405 |
|
| 406 |
/** |
| 407 |
* Renders the element. |
| 408 |
* |
| 409 |
* @returns {*} |
| 410 |
*/ |
| 411 |
render () { |
| 412 |
return ( |
| 413 |
<div id="allex-addons-table" className={this.props.pluginName}> |
| 414 |
<AllexAddonsTabs |
| 415 |
addonsCount={this.props.addonsCount} |
| 416 |
addonsCountTotal={this.props.addonsCountTotal} |
| 417 |
currentTab={this.state.currentTab} |
| 418 |
parentTabClickHandler={this.handleTabClick}> |
| 419 |
</AllexAddonsTabs> |
| 420 |
|
| 421 |
{this.state.currentTab == 'missed' ? ( |
| 422 |
<div className="allex-addons-table-header"> |
| 423 |
<a href={this.props.browseMoreUrl} className="button button-primary" target="_blank"> |
| 424 |
{allexContext.labels.get_plugins} |
| 425 |
</a> |
| 426 |
</div> |
| 427 |
) : (null)} |
| 428 |
|
| 429 |
<AllexAddonsTable addons={this.props.addons} currentTab={this.state.currentTab} nonce={this.props.nonce} |
| 430 |
pluginName={this.props.pluginName} addonsCount={this.props.addonsCount} |
| 431 |
addonsCountTotal={this.props.addonsCountTotal}/> |
| 432 |
</div> |
| 433 |
); |
| 434 |
} |
| 435 |
} |
| 436 |
|