@fleetbase/registry-bridge-engine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.php-cs-fixer.php +29 -0
  2. package/LICENSE.md +651 -0
  3. package/README.md +122 -0
  4. package/addon/adapters/registry-bridge.js +5 -0
  5. package/addon/adapters/registry-extension-bundle.js +1 -0
  6. package/addon/adapters/registry-extension.js +1 -0
  7. package/addon/components/extension-card.hbs +12 -0
  8. package/addon/components/extension-card.js +235 -0
  9. package/addon/components/extension-form.hbs +237 -0
  10. package/addon/components/extension-form.js +123 -0
  11. package/addon/components/extension-modal-title.hbs +14 -0
  12. package/addon/components/extension-modal-title.js +20 -0
  13. package/addon/components/extension-monetize-form.hbs +56 -0
  14. package/addon/components/extension-monetize-form.js +7 -0
  15. package/addon/components/extension-pending-publish-viewer.hbs +52 -0
  16. package/addon/components/extension-pending-publish-viewer.js +37 -0
  17. package/addon/components/extension-reviewer-control.hbs +68 -0
  18. package/addon/components/extension-reviewer-control.js +68 -0
  19. package/addon/components/modals/confirm-extension-purchase.hbs +5 -0
  20. package/addon/components/modals/confirm-extension-purchase.js +3 -0
  21. package/addon/components/modals/extension-details.hbs +69 -0
  22. package/addon/components/modals/extension-details.js +33 -0
  23. package/addon/components/modals/extension-purchase-form.hbs +5 -0
  24. package/addon/components/modals/extension-purchase-form.js +3 -0
  25. package/addon/components/modals/extension-uninstall.hbs +25 -0
  26. package/addon/components/modals/extension-uninstall.js +11 -0
  27. package/addon/components/modals/select-extension-bundle.hbs +43 -0
  28. package/addon/components/modals/select-extension-bundle.js +31 -0
  29. package/addon/components/progress-bar.hbs +12 -0
  30. package/addon/components/progress-bar.js +8 -0
  31. package/addon/controllers/application.js +6 -0
  32. package/addon/controllers/developers/analytics.js +26 -0
  33. package/addon/controllers/developers/extensions/edit/bundles.js +70 -0
  34. package/addon/controllers/developers/extensions/edit/index.js +3 -0
  35. package/addon/controllers/developers/extensions/edit/monetize.js +7 -0
  36. package/addon/controllers/developers/extensions/edit.js +107 -0
  37. package/addon/controllers/developers/extensions/index.js +3 -0
  38. package/addon/controllers/developers/extensions/new.js +32 -0
  39. package/addon/controllers/developers/payments/index.js +39 -0
  40. package/addon/controllers/developers/payments/onboard.js +67 -0
  41. package/addon/controllers/explore/category.js +22 -0
  42. package/addon/controllers/explore/index.js +15 -0
  43. package/addon/controllers/installed.js +86 -0
  44. package/addon/controllers/purchased.js +18 -0
  45. package/addon/engine.js +44 -0
  46. package/addon/models/registry-extension-bundle.js +62 -0
  47. package/addon/models/registry-extension.js +215 -0
  48. package/addon/routes/application.js +12 -0
  49. package/addon/routes/developers/analytics.js +10 -0
  50. package/addon/routes/developers/credentials.js +3 -0
  51. package/addon/routes/developers/extensions/edit/bundles.js +21 -0
  52. package/addon/routes/developers/extensions/edit/details.js +3 -0
  53. package/addon/routes/developers/extensions/edit/index.js +3 -0
  54. package/addon/routes/developers/extensions/edit/monetize.js +3 -0
  55. package/addon/routes/developers/extensions/edit.js +18 -0
  56. package/addon/routes/developers/extensions/index.js +10 -0
  57. package/addon/routes/developers/extensions/new.js +3 -0
  58. package/addon/routes/developers/extensions.js +3 -0
  59. package/addon/routes/developers/payments/index.js +26 -0
  60. package/addon/routes/developers/payments/onboard.js +21 -0
  61. package/addon/routes/developers/payments.js +3 -0
  62. package/addon/routes/developers.js +3 -0
  63. package/addon/routes/explore/category.js +27 -0
  64. package/addon/routes/explore/index.js +17 -0
  65. package/addon/routes/explore.js +3 -0
  66. package/addon/routes/installed.js +10 -0
  67. package/addon/routes/purchased.js +10 -0
  68. package/addon/routes.js +28 -0
  69. package/addon/serializers/registry-extension-bundle.js +15 -0
  70. package/addon/serializers/registry-extension.js +21 -0
  71. package/addon/services/stripe.js +83 -0
  72. package/addon/styles/registry-bridge-engine.css +142 -0
  73. package/addon/templates/application.hbs +26 -0
  74. package/addon/templates/developers/analytics.hbs +83 -0
  75. package/addon/templates/developers/credentials.hbs +1 -0
  76. package/addon/templates/developers/extensions/edit/bundles.hbs +71 -0
  77. package/addon/templates/developers/extensions/edit/details.hbs +16 -0
  78. package/addon/templates/developers/extensions/edit/index.hbs +1 -0
  79. package/addon/templates/developers/extensions/edit/monetize.hbs +3 -0
  80. package/addon/templates/developers/extensions/edit.hbs +48 -0
  81. package/addon/templates/developers/extensions/index.hbs +27 -0
  82. package/addon/templates/developers/extensions/new.hbs +39 -0
  83. package/addon/templates/developers/extensions.hbs +1 -0
  84. package/addon/templates/developers/payments/index.hbs +33 -0
  85. package/addon/templates/developers/payments/onboard.hbs +48 -0
  86. package/addon/templates/developers/payments.hbs +1 -0
  87. package/addon/templates/developers.hbs +1 -0
  88. package/addon/templates/explore/category.hbs +12 -0
  89. package/addon/templates/explore/index.hbs +12 -0
  90. package/addon/templates/explore.hbs +1 -0
  91. package/addon/templates/installed.hbs +32 -0
  92. package/addon/templates/purchased.hbs +34 -0
  93. package/app/adapters/registry-bridge.js +1 -0
  94. package/app/adapters/registry-extension-bundle.js +1 -0
  95. package/app/adapters/registry-extension.js +1 -0
  96. package/app/components/extension-card.js +1 -0
  97. package/app/components/extension-form.js +1 -0
  98. package/app/components/extension-modal-title.js +1 -0
  99. package/app/components/extension-monetize-form.js +1 -0
  100. package/app/components/extension-pending-publish-viewer.js +1 -0
  101. package/app/components/extension-reviewer-control.js +1 -0
  102. package/app/components/modals/confirm-extension-purchase.js +1 -0
  103. package/app/components/modals/extension-details.js +1 -0
  104. package/app/components/modals/extension-purchase-form.js +1 -0
  105. package/app/components/modals/extension-uninstall.js +1 -0
  106. package/app/components/modals/select-extension-bundle.js +1 -0
  107. package/app/components/progress-bar.js +1 -0
  108. package/app/controllers/application.js +1 -0
  109. package/app/controllers/developers/analytics.js +1 -0
  110. package/app/controllers/developers/extensions/edit/bundles.js +1 -0
  111. package/app/controllers/developers/extensions/edit/index.js +1 -0
  112. package/app/controllers/developers/extensions/edit/monetize.js +1 -0
  113. package/app/controllers/developers/extensions/edit.js +1 -0
  114. package/app/controllers/developers/extensions/index.js +1 -0
  115. package/app/controllers/developers/extensions/new.js +1 -0
  116. package/app/controllers/developers/payments/index.js +1 -0
  117. package/app/controllers/developers/payments/onboard.js +1 -0
  118. package/app/controllers/explore/category.js +1 -0
  119. package/app/controllers/explore/index.js +1 -0
  120. package/app/controllers/installed.js +1 -0
  121. package/app/controllers/purchased.js +1 -0
  122. package/app/models/registry-extension-bundle.js +1 -0
  123. package/app/models/registry-extension.js +1 -0
  124. package/app/routes/developers/analytics.js +1 -0
  125. package/app/routes/developers/credentials.js +1 -0
  126. package/app/routes/developers/extensions/edit/bundles.js +1 -0
  127. package/app/routes/developers/extensions/edit/details.js +1 -0
  128. package/app/routes/developers/extensions/edit/index.js +1 -0
  129. package/app/routes/developers/extensions/edit/monetize.js +1 -0
  130. package/app/routes/developers/extensions/edit.js +1 -0
  131. package/app/routes/developers/extensions/index.js +1 -0
  132. package/app/routes/developers/extensions/new.js +1 -0
  133. package/app/routes/developers/extensions.js +1 -0
  134. package/app/routes/developers/payments/index.js +1 -0
  135. package/app/routes/developers/payments/onboard.js +1 -0
  136. package/app/routes/developers/payments.js +1 -0
  137. package/app/routes/developers.js +1 -0
  138. package/app/routes/explore/category.js +1 -0
  139. package/app/routes/explore/index.js +1 -0
  140. package/app/routes/explore.js +1 -0
  141. package/app/routes/installed.js +1 -0
  142. package/app/routes/purchased.js +1 -0
  143. package/app/serializers/registry-extension-bundle.js +1 -0
  144. package/app/serializers/registry-extension.js +1 -0
  145. package/app/services/stripe.js +1 -0
  146. package/app/templates/developers/analytics.js +1 -0
  147. package/app/templates/developers/credentials.js +1 -0
  148. package/app/templates/developers/extensions/edit/bundles.js +1 -0
  149. package/app/templates/developers/extensions/edit/details.js +1 -0
  150. package/app/templates/developers/extensions/edit/index.js +1 -0
  151. package/app/templates/developers/extensions/edit/monetize.js +1 -0
  152. package/app/templates/developers/extensions/edit.js +1 -0
  153. package/app/templates/developers/extensions/index.js +1 -0
  154. package/app/templates/developers/extensions/new.js +1 -0
  155. package/app/templates/developers/extensions.js +1 -0
  156. package/app/templates/developers/payments/index.js +1 -0
  157. package/app/templates/developers/payments/onboard.js +1 -0
  158. package/app/templates/developers/payments.js +1 -0
  159. package/app/templates/developers.js +1 -0
  160. package/app/templates/explore/category.js +1 -0
  161. package/app/templates/explore/index.js +1 -0
  162. package/app/templates/explore.js +1 -0
  163. package/app/templates/installed.js +1 -0
  164. package/app/templates/purchased.js +1 -0
  165. package/composer.json +95 -0
  166. package/config/environment.js +28 -0
  167. package/extension.json +10 -0
  168. package/index.js +26 -0
  169. package/package.json +129 -0
  170. package/phpstan.neon.dist +8 -0
  171. package/phpunit.xml.dist +16 -0
  172. package/server/.gitattributes +14 -0
  173. package/server/config/registry-bridge.php +32 -0
  174. package/server/migrations/2024_03_19_060627_create_registry_users_table.php +42 -0
  175. package/server/migrations/2024_03_21_051614_create_registry_extensions_table.php +76 -0
  176. package/server/migrations/2024_03_25_044537_create_registry_extension_bundles_table.php +54 -0
  177. package/server/migrations/2024_03_29_072101_registry_extension_installs.php +35 -0
  178. package/server/migrations/2024_07_16_155000_create_registry_extension_purchases.php +41 -0
  179. package/server/seeders/ExtensionsCategorySeeder.php +359 -0
  180. package/server/src/Console/Commands/Initialize.php +35 -0
  181. package/server/src/Console/Commands/PostInstallExtension.php +84 -0
  182. package/server/src/Exceptions/InstallFailedException.php +21 -0
  183. package/server/src/Expansions/CategoryExpansion.php +30 -0
  184. package/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php +153 -0
  185. package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +230 -0
  186. package/server/src/Http/Controllers/Internal/v1/RegistryController.php +54 -0
  187. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionBundleController.php +112 -0
  188. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +257 -0
  189. package/server/src/Http/Controllers/Internal/v1/RegistryPaymentsController.php +227 -0
  190. package/server/src/Http/Controllers/RegistryBridgeController.php +13 -0
  191. package/server/src/Http/Filter/RegistryExtensionFilter.php +80 -0
  192. package/server/src/Http/Requests/AddRegistryUserRequest.php +47 -0
  193. package/server/src/Http/Requests/AuthenticateRegistryUserRequest.php +47 -0
  194. package/server/src/Http/Requests/CreateRegistryExtensionBundleRequest.php +42 -0
  195. package/server/src/Http/Requests/CreateRegistryExtensionRequest.php +31 -0
  196. package/server/src/Http/Requests/InstallExtensionRequest.php +30 -0
  197. package/server/src/Http/Requests/RegistryAuthRequest.php +46 -0
  198. package/server/src/Http/Requests/RegistryExtensionActionRequest.php +30 -0
  199. package/server/src/Http/Resources/RegistryUser.php +40 -0
  200. package/server/src/Models/RegistryExtension.php +656 -0
  201. package/server/src/Models/RegistryExtensionBundle.php +1015 -0
  202. package/server/src/Models/RegistryExtensionInstall.php +76 -0
  203. package/server/src/Models/RegistryExtensionPurchase.php +87 -0
  204. package/server/src/Models/RegistryUser.php +140 -0
  205. package/server/src/Providers/RegistryBridgeServiceProvider.php +117 -0
  206. package/server/src/Support/Bridge.php +53 -0
  207. package/server/src/Support/Utils.php +19 -0
  208. package/server/src/routes.php +58 -0
  209. package/server/tests/Feature.php +5 -0
  210. package/translations/en-us.yaml +119 -0
  211. package/tsconfig.declarations.json +10 -0
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ <p align="center">
2
+ <p align="center">
3
+ <img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" width="280" height="80" style="height: 80px;" />
4
+ </p>
5
+ <p align="center">
6
+ Internal Bridge between Fleetbase API and Extensions Registry
7
+ </p>
8
+ </p>
9
+
10
+ ---
11
+
12
+ ## Overview
13
+
14
+ This monorepo contains both the frontend and backend components of the Internal Registry Bridge for Fleetbase. The frontend is built using Ember.js and the backend is implemented in PHP.
15
+
16
+ * PHP 7.3.0 or above
17
+ * Ember.js v4.8 or above
18
+ * Ember CLI v4.8 or above
19
+ * Node.js v18 or above
20
+
21
+ ## Structure
22
+
23
+ ```
24
+ ├── addon
25
+ ├── app
26
+ ├── assets
27
+ ├── translations
28
+ ├── config
29
+ ├── node_modules
30
+ ├── server
31
+ │ ├── config
32
+ │ ├── data
33
+ │ ├── migrations
34
+ │ ├── resources
35
+ │ ├── src
36
+ │ ├── tests
37
+ │ └── vendor
38
+ ├── tests
39
+ ├── testem.js
40
+ ├── index.js
41
+ ├── package.json
42
+ ├── phpstan.neon.dist
43
+ ├── phpunit.xml.dist
44
+ ├── pnpm-lock.yaml
45
+ ├── ember-cli-build.js
46
+ ├── composer.json
47
+ ├── CONTRIBUTING.md
48
+ ├── LICENSE.md
49
+ ├── README.md
50
+ ```
51
+
52
+ ## Installation
53
+
54
+ ### Backend
55
+
56
+ Install the PHP packages using Composer:
57
+
58
+ ```bash
59
+ composer require fleetbase/core-api
60
+ composer require fleetbase/registry-bridge
61
+ ```
62
+ ### Frontend
63
+
64
+ Install the Ember.js Engine/Addon:
65
+
66
+ ```bash
67
+ pnpm install @fleetbase/registry-bridge-engine
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ ### Backend
73
+
74
+ 🧹 Keep a modern codebase with **PHP CS Fixer**:
75
+ ```bash
76
+ composer lint
77
+ ```
78
+
79
+ ⚗️ Run static analysis using **PHPStan**:
80
+ ```bash
81
+ composer test:types
82
+ ```
83
+
84
+ ✅ Run unit tests using **PEST**
85
+ ```bash
86
+ composer test:unit
87
+ ```
88
+
89
+ 🚀 Run the entire test suite:
90
+ ```bash
91
+ composer test
92
+ ```
93
+
94
+ ### Frontend
95
+
96
+ 🧹 Keep a modern codebase with **ESLint**:
97
+ ```bash
98
+ pnpm lint
99
+ ```
100
+
101
+ ✅ Run unit tests using **Ember/QUnit**
102
+ ```bash
103
+ pnpm test
104
+ pnpm test:ember
105
+ pnpm test:ember-compatibility
106
+ ```
107
+
108
+ 🚀 Start the Ember Addon/Engine
109
+ ```bash
110
+ pnpm start
111
+ ```
112
+
113
+ 🔨 Build the Ember Addon/Engine
114
+ ```bash
115
+ pnpm build
116
+ ```
117
+
118
+ ## Contributing
119
+ See the Contributing Guide for details on how to contribute to this project.
120
+
121
+ ## License
122
+ This project is licensed under the MIT License.
@@ -0,0 +1,5 @@
1
+ import ApplicationAdapter from '@fleetbase/ember-core/adapters/application';
2
+
3
+ export default class RegistryBridgeAdapter extends ApplicationAdapter {
4
+ namespace = '~registry/v1';
5
+ }
@@ -0,0 +1 @@
1
+ export { default } from './registry-bridge';
@@ -0,0 +1 @@
1
+ export { default } from './registry-bridge';
@@ -0,0 +1,12 @@
1
+ <button type="button" class="extension-card-container hover:opacity-50 {{@buttonClass}}" ...attributes {{on "click" this.onClick}} {{did-update this.onExtensionUpdated @extension}}>
2
+ <div class="extension-card-icon-container {{@iconWrapperClass}}">
3
+ <Image src={{@extension.icon_url}} class={{@iconClass}} alt={{@extension.name}} @fallbackSrc={{config "defaultValues.extensionIcon"}} />
4
+ </div>
5
+ <div class="extension-card-body-container {{@detailsWrapperClass}}">
6
+ <div class="flex flex-col">
7
+ <div class="font-semibold text-sm block {{@nameTextClass}}">{{@extension.name}}</div>
8
+ <div class="text-xs {{@descriptionTextClass}}">{{n-a @extension.description}}</div>
9
+ </div>
10
+ {{yield @extension}}
11
+ </div>
12
+ </button>
@@ -0,0 +1,235 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+ import { later } from '@ember/runloop';
6
+ import formatCurrency from '@fleetbase/ember-ui/utils/format-currency';
7
+ import isModel from '@fleetbase/ember-core/utils/is-model';
8
+
9
+ function removeParamFromCurrentUrl(paramToRemove) {
10
+ const url = new URL(window.location.href);
11
+ url.searchParams.delete(paramToRemove);
12
+ window.history.pushState({ path: url.href }, '', url.href);
13
+ }
14
+
15
+ function addParamToCurrentUrl(paramName, paramValue) {
16
+ const url = new URL(window.location.href);
17
+ url.searchParams.set(paramName, paramValue);
18
+ window.history.pushState({ path: url.href }, '', url.href);
19
+ }
20
+
21
+ export default class ExtensionCardComponent extends Component {
22
+ @service modalsManager;
23
+ @service notifications;
24
+ @service currentUser;
25
+ @service socket;
26
+ @service fetch;
27
+ @service stripe;
28
+ @service urlSearchParams;
29
+ @tracked extension;
30
+
31
+ constructor(owner, { extension }) {
32
+ super(...arguments);
33
+ this.extension = extension;
34
+ this.checkForCheckoutSession();
35
+ }
36
+
37
+ @action onExtenstionUpdated(el, [extension]) {
38
+ this.extension = extension;
39
+ }
40
+
41
+ @action onClick(options = {}) {
42
+ const installChannel = `install.${this.currentUser.companyId}.${this.extension.id}`;
43
+ const isAlreadyPurchased = this.extension.is_purchased === true;
44
+ const isAlreadyInstalled = this.extension.is_installed === true;
45
+ const isPaymentRequired = this.extension.payment_required === true && isAlreadyPurchased === false;
46
+
47
+ if (typeof this.args.onClick === 'function') {
48
+ this.args.onClick(this.extension);
49
+ }
50
+
51
+ addParamToCurrentUrl('extension_id', this.extension.id);
52
+ this.modalsManager.show('modals/extension-details', {
53
+ titleComponent: 'extension-modal-title',
54
+ modalClass: 'flb--extension-modal modal-lg',
55
+ modalHeaderClass: 'flb--extension-modal-header',
56
+ acceptButtonText: isPaymentRequired ? `Purchase for ${formatCurrency(this.extension.price, this.extension.currency)}` : isAlreadyInstalled ? 'Installed' : 'Install',
57
+ acceptButtonIcon: isPaymentRequired ? 'credit-card' : isAlreadyInstalled ? 'check' : 'download',
58
+ acceptButtonDisabled: isAlreadyInstalled,
59
+ acceptButtonScheme: isPaymentRequired ? 'success' : 'primary',
60
+ declineButtonText: 'Done',
61
+ process: null,
62
+ step: null,
63
+ stepDescription: 'Awaiting install to begin...',
64
+ progress: 0,
65
+ extension: this.extension,
66
+ confirm: async (modal) => {
67
+ modal.startLoading();
68
+
69
+ // Handle purchase flow
70
+ if (isPaymentRequired) {
71
+ return this.startCheckoutSession();
72
+ }
73
+
74
+ // Listen for install progress
75
+ this.socket.listen(installChannel, ({ process, step, progress }) => {
76
+ let stepDescription;
77
+ switch (step) {
78
+ case 'server.install':
79
+ stepDescription = '(1/3) Installing extension...';
80
+ break;
81
+
82
+ case 'engine.install':
83
+ stepDescription = '(2/3) Installing extension...';
84
+ break;
85
+
86
+ case 'console.build':
87
+ stepDescription = '(3/3) Completing install...';
88
+ break;
89
+
90
+ default:
91
+ break;
92
+ }
93
+ modal.setOptions({ process, step, progress, stepDescription });
94
+ });
95
+
96
+ // Start install progress
97
+ modal.setOption('progress', 5);
98
+
99
+ // Run install
100
+ try {
101
+ await this.extension.install();
102
+ this.notifications.info(`${this.extension.name} is now Installed.`);
103
+ later(
104
+ this,
105
+ () => {
106
+ window.location.reload(true);
107
+ },
108
+ 600
109
+ );
110
+ removeParamFromCurrentUrl('extension_id');
111
+ modal.done();
112
+ } catch (error) {
113
+ this.notifications.serverError(error);
114
+ }
115
+ },
116
+ decline: (modal) => {
117
+ modal.done();
118
+ removeParamFromCurrentUrl('extension_id');
119
+ },
120
+ ...options,
121
+ });
122
+ }
123
+
124
+ async startCheckoutSession() {
125
+ const checkout = await this.stripe.initEmbeddedCheckout({
126
+ fetchClientSecret: this.fetchClientSecret.bind(this),
127
+ });
128
+
129
+ await this.modalsManager.done();
130
+ later(
131
+ this,
132
+ () => {
133
+ this.modalsManager.show('modals/extension-purchase-form', {
134
+ title: `Purchase the '${this.extension.name}' Extension`,
135
+ modalClass: 'stripe-extension-purchase',
136
+ modalFooterClass: 'hidden-i',
137
+ extension: this.extension,
138
+ checkoutElementInserted: (el) => {
139
+ checkout.mount(el);
140
+ },
141
+ decline: async (modal) => {
142
+ checkout.destroy();
143
+ await modal.done();
144
+ later(
145
+ this,
146
+ () => {
147
+ this.onClick();
148
+ },
149
+ 100
150
+ );
151
+ },
152
+ });
153
+ },
154
+ 100
155
+ );
156
+ }
157
+
158
+ async fetchClientSecret() {
159
+ try {
160
+ const { clientSecret } = await this.fetch.post(
161
+ 'payments/create-checkout-session',
162
+ { extension: this.extension.id, uri: window.location.pathname },
163
+ { namespace: '~registry/v1' }
164
+ );
165
+
166
+ return clientSecret;
167
+ } catch (error) {
168
+ this.notifications.serverError(error);
169
+ }
170
+ }
171
+
172
+ async checkForCheckoutSession() {
173
+ later(
174
+ this,
175
+ async () => {
176
+ const checkoutSessionId = this.urlSearchParams.get('checkout_session_id');
177
+ const extensionId = this.urlSearchParams.get('extension_id');
178
+
179
+ if (!checkoutSessionId && this.extension.id === extensionId) {
180
+ return this.onClick();
181
+ }
182
+
183
+ if (checkoutSessionId && this.extension.id === extensionId) {
184
+ this.modalsManager.show('modals/confirm-extension-purchase', {
185
+ title: 'Finalizing Purchase',
186
+ modalClass: 'finalize-extension-purchase',
187
+ loadingMessage: 'Completing purchase do not refresh or exit window...',
188
+ modalFooterClass: 'hidden-i',
189
+ backdropClose: false,
190
+ });
191
+
192
+ try {
193
+ const { status, extension } = await this.fetch.post(
194
+ 'payments/get-checkout-session',
195
+ { checkout_session_id: checkoutSessionId, extension: this.extension.id },
196
+ { namespace: '~registry/v1' }
197
+ );
198
+
199
+ // Update this extension
200
+ const extensionModel = this.fetch.jsonToModel(extension, 'registry-extension');
201
+ if (isModel(extensionModel)) {
202
+ this.extension = extensionModel;
203
+ }
204
+
205
+ // Fire a callback
206
+ if (typeof this.args.onCheckoutCompleted === 'function') {
207
+ this.args.onCheckoutCompleted(this.extension, status);
208
+ }
209
+
210
+ if (status === 'complete' || status === 'purchase_complete') {
211
+ // remove checkout session id
212
+ removeParamFromCurrentUrl('checkout_session_id');
213
+
214
+ // close confirmation dialog and notify payment completed
215
+ await this.modalsManager.done();
216
+ if (status === 'complete') {
217
+ this.notifications.success('Payment Completed.');
218
+ }
219
+ later(
220
+ this,
221
+ () => {
222
+ this.onClick();
223
+ },
224
+ 100
225
+ );
226
+ }
227
+ } catch (error) {
228
+ this.notifications.serverError(error);
229
+ }
230
+ }
231
+ },
232
+ 300
233
+ );
234
+ }
235
+ }
@@ -0,0 +1,237 @@
1
+ <div class="flex flex-row items-start">
2
+ <div class="w-32 flex flex-col justify-center">
3
+ <div class="mb-2">
4
+ <Image src={{@extension.icon_url}} @fallbackSrc={{config "defaultValues.extensionIcon"}} alt={{@extension.name}} class="w-32 h-32 rounded-lg border border-black shadow-sm" />
5
+ </div>
6
+ <FileUpload
7
+ @name={{t "registry-bridge.developers.extensions.extension-form.upload-extension-icon"}}
8
+ @accept={{join "," this.acceptedImageTypes}}
9
+ @onFileAdded={{perform this.uploadIcon}}
10
+ @labelClass="flex flex-row items-center justify-center"
11
+ as |queue|
12
+ >
13
+ <a tabindex={{0}} class="flex items-center px-0 mt-2 text-xs no-underline truncate btn btn-sm btn-default" disabled={{not queue.files.length}}>
14
+ {{#if queue.files.length}}
15
+ <div class="mr-1.5">
16
+ <Spinner />
17
+ </div>
18
+ <span>
19
+ {{t "common.uploading"}}
20
+ </span>
21
+ {{else}}
22
+ <FaIcon @icon="icons" class="mr-1.5" />
23
+ <span>
24
+ {{t "registry-bridge.developers.extensions.extension-form.upload-extension-icon"}}
25
+ </span>
26
+ {{/if}}
27
+ </a>
28
+ </FileUpload>
29
+ </div>
30
+ <div class="flex-1 px-6 space-y-6">
31
+ <ContentPanel @title={{t "registry-bridge.developers.extensions.extension-form.details-content-block"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
32
+ <InputGroup
33
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-name"}}
34
+ @value={{@extension.name}}
35
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-name-help-text"}}
36
+ />
37
+ <InputGroup
38
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-description"}}
39
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-description-help-text"}}
40
+ >
41
+ <Textarea
42
+ @value={{@extension.description}}
43
+ placeholder={{t "registry-bridge.developers.extensions.extension-form.extension-description"}}
44
+ class="form-input w-full"
45
+ rows="3"
46
+ />
47
+ </InputGroup>
48
+ <InputGroup
49
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-category"}}
50
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-category-help-text"}}
51
+ >
52
+ <ModelSelect
53
+ @modelName="category"
54
+ @query={{hash for="extension_category" core_category=1}}
55
+ @selectedModel={{@extension.category}}
56
+ @placeholder={{t "registry-bridge.developers.extensions.extension-form.extension-select-category"}}
57
+ @triggerClass="form-select form-input"
58
+ @infiniteScroll={{false}}
59
+ @renderInPlace={{true}}
60
+ @onChange={{fn (mut @extension.category)}}
61
+ as |category|
62
+ >
63
+ <div class="flex items-center">
64
+ <FaIcon @icon={{category.icon}} @size="sm" class="mr-2" />
65
+ <span>{{category.name}}</span>
66
+ </div>
67
+ </ModelSelect>
68
+ </InputGroup>
69
+
70
+ <InputGroup
71
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-tags"}}
72
+ @wrapperClass="mb-0i"
73
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-tags-help-text"}}
74
+ >
75
+ <TagInput
76
+ class="form-input"
77
+ @placeholder={{t "registry-bridge.developers.extensions.extension-form.extension-add-tags"}}
78
+ @allowSpacesInTags={{true}}
79
+ @tags={{@extension.tags}}
80
+ @addTag={{@extension.addTag}}
81
+ @removeTagAtIndex={{@extension.removeTag}}
82
+ @disabled={{this.core_extension}}
83
+ as |tag|
84
+ >
85
+ {{tag}}
86
+ </TagInput>
87
+ </InputGroup>
88
+ </ContentPanel>
89
+ <ContentPanel @title={{t "registry-bridge.developers.extensions.extension-form.extension-bundle"}} @open={{true}} @pad={{false}} @panelBodyClass="bg-white dark:bg-gray-800">
90
+ <div class="px-4 pb-4 pt-3 flex flex-col flex-grow-0">
91
+ <Button @type="magic" @icon="box-archive" @text="Select Bundle" @onClick={{this.selectBundle}} />
92
+ {{#if @extension.next_bundle_filename}}
93
+ <div class="mt-4 bg-blue-200 border border-blue-400 text-blue-900 rounded-lg shadow-sm px-4 py-2 flex flex-row items-center text-xs">
94
+ <FaIcon @icon="box-archive" @size="xs" />
95
+ <div class="ml-2 flex flex-row items-center space-x-2">
96
+ <span>{{@extension.next_bundle_filename}}</span>
97
+ <span>-</span>
98
+ <span>{{@extension.next_bundle_id}}</span>
99
+ </div>
100
+ </div>
101
+ {{/if}}
102
+ </div>
103
+ </ContentPanel>
104
+ <ContentPanel @title={{t "registry-bridge.developers.extensions.extension-form.extension-listing-details"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
105
+ <InputGroup
106
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-promotional-text"}}
107
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-promotional-text-help-text"}}
108
+ >
109
+ <Textarea
110
+ @value={{@extension.promotional_text}}
111
+ placeholder={{t "registry-bridge.developers.extensions.extension-form.extension-promotional-text"}}
112
+ class="form-input w-full"
113
+ rows="3"
114
+ />
115
+ </InputGroup>
116
+ <InputGroup
117
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-subtitle"}}
118
+ @value={{@extension.subtitle}}
119
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-subtitle-help-text"}}
120
+ />
121
+ <InputGroup
122
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-copyright"}}
123
+ @value={{@extension.copyright}}
124
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-copyright-help-text"}}
125
+ />
126
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
127
+ <InputGroup
128
+ @type="url"
129
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-website-url"}}
130
+ @value={{@extension.website_url}}
131
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-website-url-help-text"}}
132
+ @wrapperClass="mb-0i"
133
+ />
134
+ <InputGroup
135
+ @type="url"
136
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-repo-url"}}
137
+ @value={{@extension.repo_url}}
138
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-repo-url-help-text"}}
139
+ @wrapperClass="mb-0i"
140
+ />
141
+ <InputGroup
142
+ @type="url"
143
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-support-url"}}
144
+ @value={{@extension.support_url}}
145
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-support-url-help-text"}}
146
+ @wrapperClass="mb-0i"
147
+ />
148
+ <InputGroup
149
+ @type="url"
150
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-privacy-policy-url"}}
151
+ @value={{@extension.privacy_policy_url}}
152
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-privacy-policy-url-help-text"}}
153
+ @wrapperClass="mb-0i"
154
+ />
155
+ <InputGroup
156
+ @type="url"
157
+ @name={{t "registry-bridge.developers.extensions.extension-form.extension-tos-url"}}
158
+ @value={{@extension.tos_url}}
159
+ @helpText={{t "registry-bridge.developers.extensions.extension-form.extension-tos-url-help-text"}}
160
+ @wrapperClass="mb-0i"
161
+ />
162
+ </div>
163
+ </ContentPanel>
164
+ <ContentPanel @title={{t "registry-bridge.developers.extensions.extension-form.extension-screenshots"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
165
+ <div class="space-y-4">
166
+ {{#if this.isUploading}}
167
+ <div
168
+ class="dropzone w-full rounded-lg px-4 py-8 bg-gray-50 dark:bg-gray-900 bg-opacity-25 text-gray-900 dark:text-white text-center flex flex-col items-center justify-center border-2 border-dashed border-gray-200 dark:border-indigo-500"
169
+ >
170
+ <div class="flex items-center justify-center py-5">
171
+ <Spinner class="text-sm dar:text-gray-100" @loadingMessage={{t "fleet-ops.common.uploading"}} />
172
+ </div>
173
+ </div>
174
+ {{else}}
175
+ {{#let (file-queue name="files" onFileAdded=this.queueFile accept=(join "," this.acceptedImageTypes)) as |queue|}}
176
+ <FileDropzone @queue={{queue}} class="dropzone file-dropzone" as |dropzone|>
177
+ {{#if dropzone.active}}
178
+ {{#if dropzone.valid}}
179
+ {{t "component.dropzone.drop-to-upload"}}
180
+ {{else}}
181
+ {{t "component.dropzone.invalid"}}
182
+ {{/if}}
183
+ {{else if queue.files.length}}
184
+ <div class="my-2">
185
+ <FaIcon @icon="folder-open" class="text-indigo-500 mr-2" />
186
+ {{t "component.dropzone.files-ready-for-upload" numOfFiles=(pluralize queue.files.length (t "component.dropzone.file"))}}
187
+ </div>
188
+ <div class="my-2">({{queue.progress}}%)</div>
189
+ {{else}}
190
+ <h4 class="font-semibold">
191
+ <FaIcon @icon="folder-open" @size="lg" class="text-indigo-500 mr-2" />
192
+ {{t "registry-bridge.developers.extensions.extension-form.upload-screenshots"}}
193
+ </h4>
194
+ <div>
195
+ {{#if dropzone.supported}}
196
+ <p class="text-sm my-5">{{t "component.dropzone.dropzone-supported-files"}}</p>
197
+ {{/if}}
198
+ <FileUpload @name="files" @for="files" @accept={{join "," this.acceptedFileTypes}} @multiple={{true}} @onFileAdded={{this.queueFile}}>
199
+ <a tabindex={{0}} class="btn btn-magic cursor-pointer ml-1">{{t "component.dropzone.or-select-button-text"}}</a>
200
+ </FileUpload>
201
+ </div>
202
+ {{/if}}
203
+ </FileDropzone>
204
+ {{/let}}
205
+ {{#if this.uploadQueue}}
206
+ <div class="mx-4">
207
+ <div class="flex items-center justify-between mb-4">
208
+ <span class="leading-6 dark:text-gray-100">{{t "component.dropzone.upload-queue"}}</span>
209
+ </div>
210
+ <div class="space-y-2 mb-4">
211
+ {{#each this.uploadQueue as |file|}}
212
+ <div class="flex items-center justify-between bg-blue-100 border border-blue-800 dark:border-blue-800 py-1.5 shadow-sm rounded-lg px-4">
213
+ <div class="text-xs text-blue-900 truncate">{{truncate-filename file.name 50}}</div>
214
+ <div class="flex items-center text-sm">
215
+ <Spinner class="text-blue-900 mr-2" />
216
+ <span class="font-bold text-blue-900">{{round file.progress}}%</span>
217
+ </div>
218
+ </div>
219
+ {{/each}}
220
+ </div>
221
+ </div>
222
+ {{/if}}
223
+ <div>
224
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-2 md:gap-4">
225
+ {{#each @extension.screenshots as |file|}}
226
+ <File @file={{file}} @onDelete={{this.removeFile}} />
227
+ {{/each}}
228
+ </div>
229
+ </div>
230
+ {{/if}}
231
+ </div>
232
+ </ContentPanel>
233
+ {{#if @withMonetizeForm}}
234
+ <ExtensionMonetizeForm @extension={{@extension}} />
235
+ {{/if}}
236
+ </div>
237
+ </div>