* create Apps view

* create more app components, generate types and messages

* apps refactor, update snapshots

* show error message in tooltip  when app installation fail

* update apps components and view, add apps list to storybook

* update defaultMessages

* create app details view

* update AppListPage with Skeleton component

* create app activate/deactivate dialogs, create app details stories

* add AppHeader to AppDetailsPage

* update defaultMessages

* update AppDetails view and components after review

* create custom app details view

* refactor webhooks

* update webhooks fixtures

* update WebhookDetailsPage story

* update strings

* create CustomAppCreate view and components

* update AppListPage story

* create AppInstall view and page

* handle errors in AppInstall view

* update defaultMessages

* add AppInstallPage to storybook

* add status prop to MessageManager

* update defaultMessages

* remove service account section

* remove service account routes

* remove as operator from notify status

* add notifications for app installations

* update styles for deactivated app

* update app installations with local storage

* update defaultMessages

* AppInstall update

* dd delete button to ongoin installations table

* fix active installations condition

* fix error messages in AppsList

* update defaultMessages

* add iframe to AppDetailsPage

* create AppDetailsSettingsPage

* install macaw-ui

* apps styles clean up

* update schema, fixtures

* few apps updates

* WebhookCreate - fix onBack button name

* WebhookCreatePage story update

* rename apps table from external to thirdparty

* update defaultMessages

* fix test, update snapshots

* AppDetailsSettings - add token to headers

* fix first number in local apps query

* app details settings - use shop domain host

* add onSettingsRowClick to InstalledApps

* resolve conflicts

* update changelog and messages

* add noopener noreferrer do app privacy link

* update snapshots

* update snapshots

* updates after review

* update defaultMessages

* CustomAppDetails - add missing notify status
This commit is contained in:
AlicjaSzu 2020-07-22 12:54:15 +02:00 committed by GitHub
parent df85de6779
commit 211b0b892d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
205 changed files with 14651 additions and 17540 deletions

View file

@ -12,6 +12,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Move fragments to separate directory to avoid circular imports - #592 by @dominik-zeglen
- Add order invoices management - #570 by @orzechdev
- Add Cypress e2e runner - #584 by @krzysztofwolski
- create Apps - #599 by @AlicjaSzu
## 2.10.1

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 22.5C17.799 22.5 22.5 17.799 22.5 12C22.5 6.20101 17.799 1.5 12 1.5C6.20101 1.5 1.5 6.20101 1.5 12C1.5 17.799 6.20101 22.5 12 22.5ZM12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM9.75 5.53473C7.07559 6.46535 5.15625 9.00854 5.15625 12C5.15625 15.7797 8.2203 18.8438 12 18.8438C15.7797 18.8438 18.8438 15.7797 18.8438 12C18.8438 8.9737 16.8794 6.40617 14.1562 5.50287V7.10915C16.0335 7.938 17.3438 9.816 17.3438 12C17.3438 14.9513 14.9513 17.3438 12 17.3438C9.04873 17.3438 6.65625 14.9513 6.65625 12C6.65625 9.85254 7.92298 8.00093 9.75 7.15163V5.53473ZM11.25 4.125V11.1562H12.75V4.125H11.25Z" fill="#06847B"/>
</svg>

After

Width:  |  Height:  |  Size: 864 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1,5 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F5FAFB"/>
<rect x="10.2726" y="9.00024" width="17.9987" height="1.79987" transform="rotate(45 10.2726 9.00024)" fill="#FE6D76"/>
<rect x="23" y="10.2727" width="17.9987" height="1.79987" transform="rotate(135 23 10.2727)" fill="#FE6D76"/>
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="16" fill="#F5FAFB"/>
<path fill="#FE6D76" d="M10.273 9L23 21.728 21.727 23 9 10.273z"/>
<path fill="#FE6D76" d="M23 10.272L10.273 23 9 21.727 21.727 9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 268 B

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.375 1.625H1.625V12.375H12.375V1.625ZM0 0V14H14V0H0ZM12.375 19.625H1.625V30.375H12.375V19.625ZM0 18V32H14V18H0ZM19.625 19.625H30.375V30.375H19.625V19.625ZM18 32V18H32V32H18ZM24.25 2H25.875V6.125H30V7.75H25.875V11.875H24.25V7.75H20.125V6.125H24.25V2Z" fill="#3D3D3D"/>
</svg>

After

Width:  |  Height:  |  Size: 423 B

View file

@ -0,0 +1,4 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="18" cy="18" r="17.5" fill="white" stroke="#EAEAEA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 10H17V17H10V19H17V26H19V19H26V17H19V10Z" fill="#06847B"/>
</svg>

After

Width:  |  Height:  |  Size: 279 B

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2149 2L21 5.78505L18.6131 8.17192L14.8281 4.38686L17.2149 2ZM16.8054 4.38686L18.6131 6.19457L19.0227 5.78505L17.2149 3.97734L16.8054 4.38686ZM14.4186 4.79638L18.2036 8.58144L6.78505 20H3V16.2149L14.4186 4.79638ZM4.39819 16.7941V17.6131L5.38686 18.6018H6.2059L16.2263 8.58144L14.4186 6.77372L4.39819 16.7941Z" fill="#06847B"/>
</svg>

After

Width:  |  Height:  |  Size: 513 B

View file

@ -0,0 +1,3 @@
<svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 7.58853L1.5 3.83853V10.5H16.5V3.83853L9 7.58853ZM18 12H0V0H18V12ZM16.5 1.5V2.16147L9 5.91147L1.5 2.16147V1.5H16.5Z" fill="#06847B"/>
</svg>

After

Width:  |  Height:  |  Size: 288 B

View file

@ -298,6 +298,357 @@
"context": "button",
"string": "Accept"
},
"src_dot_apps": {
"context": "apps section name",
"string": "Apps"
},
"src_dot_apps_dot_components_dot_AppActivateDialog_dot_1499521723": {
"context": "dialog header",
"string": "Activate App"
},
"src_dot_apps_dot_components_dot_AppActivateDialog_dot_3356885734": {
"context": "activate app",
"string": "Are you sure you want to activate {name}? Activating will start gathering events."
},
"src_dot_apps_dot_components_dot_AppActivateDialog_dot_3761045983": {
"context": "activate app",
"string": "Are you sure you want to activate this app? Activating will start gathering events"
},
"src_dot_apps_dot_components_dot_AppActivateDialog_dot_3865193889": {
"context": "button label",
"string": "Activate"
},
"src_dot_apps_dot_components_dot_AppDeactivateDialog_dot_1782042241": {
"context": "button label",
"string": "Deactivate"
},
"src_dot_apps_dot_components_dot_AppDeactivateDialog_dot_2031792590": {
"context": "dialog header",
"string": "Dectivate App"
},
"src_dot_apps_dot_components_dot_AppDeactivateDialog_dot_2955925498": {
"context": "deactivate app",
"string": "Are you sure you want to disable this app? Your data will be kept until you reactivate the app. You will be still billed for the app."
},
"src_dot_apps_dot_components_dot_AppDeactivateDialog_dot_329373780": {
"context": "deactivate app",
"string": "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app. You will be still billed for the app."
},
"src_dot_apps_dot_components_dot_AppDeleteDialog_dot_1347203024": {
"context": "delete custom app",
"string": "Deleting {name}, you will delete all the data and webhooks regarding this app. Are you sure you want to do that?"
},
"src_dot_apps_dot_components_dot_AppDeleteDialog_dot_1462011531": {
"context": "dialog header",
"string": "Delete App"
},
"src_dot_apps_dot_components_dot_AppDeleteDialog_dot_330001535": {
"context": "delete app",
"string": "Are you sure you want to delete this app?"
},
"src_dot_apps_dot_components_dot_AppDeleteDialog_dot_3817179919": {
"context": "delete app",
"string": "Deleting {name}, you will remove installation of the app. If you are paying for app subscription, remember to unsubscribe from the app in Saleor Marketplace. Are you sure you want to delete the app?"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_1782042241": {
"context": "link",
"string": "Deactivate"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_2432433023": {
"context": "app privacy policy link",
"string": "View this apps privacy policy"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_2973480575": {
"context": "link",
"string": "Edit settings"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_3744068622": {
"context": "section header",
"string": "Data privacy"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_3768793197": {
"context": "link",
"string": "Get Support"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_3817397882": {
"context": "button",
"string": "Open App"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_3865193889": {
"context": "link",
"string": "Activate"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_3916538164": {
"context": "section header",
"string": "App permissions"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_846582072": {
"context": "apps about permissions",
"string": "This app has permissions to:"
},
"src_dot_apps_dot_components_dot_AppDetailsPage_dot_85811128": {
"context": "section header",
"string": "About this app"
},
"src_dot_apps_dot_components_dot_AppDetailsSettingsPage_dot_198187074": {
"context": "button",
"string": "Support/FAQ"
},
"src_dot_apps_dot_components_dot_AppDetailsSettingsPage_dot_3302125406": {
"context": "button",
"string": "My App"
},
"src_dot_apps_dot_components_dot_AppDetailsSettingsPage_dot_3662390044": {
"context": "button",
"string": "Dashboard"
},
"src_dot_apps_dot_components_dot_AppInProgressDeleteDialog_dot_1462011531": {
"context": "dialog header",
"string": "Delete App"
},
"src_dot_apps_dot_components_dot_AppInProgressDeleteDialog_dot_330001535": {
"context": "delete app",
"string": "Are you sure you want to delete this app?"
},
"src_dot_apps_dot_components_dot_AppInProgressDeleteDialog_dot_3817179919": {
"context": "delete app",
"string": "Deleting {name}, you will remove installation of the app. If you are paying for app subscription, remember to unsubscribe from the app in Saleor Marketplace. Are you sure you want to delete the app?"
},
"src_dot_apps_dot_components_dot_AppInstallErrorPage_dot_1152717819": {
"context": "title",
"string": "Theres a problem with app."
},
"src_dot_apps_dot_components_dot_AppInstallErrorPage_dot_1261716527": {
"context": "content",
"string": "Saleor couldnt fetch crucial information regarding installation. Without those System cant install the app in your Saleor. Please use the button below to get back to systems dashboard."
},
"src_dot_apps_dot_components_dot_AppInstallErrorPage_dot_3375237564": {
"context": "button",
"string": "Back to homepage"
},
"src_dot_apps_dot_components_dot_AppInstallPage_dot_127674832": {
"context": "app data privacy link",
"string": "Learn more about data privacy"
},
"src_dot_apps_dot_components_dot_AppInstallPage_dot_2538706256": {
"context": "install button",
"string": "Install App"
},
"src_dot_apps_dot_components_dot_AppInstallPage_dot_2936990163": {
"context": "install app permissions",
"string": "Installing this app will give it following permissions:"
},
"src_dot_apps_dot_components_dot_AppInstallPage_dot_3436300370": {
"context": "install app privacy",
"string": "Uninstalling the app will remove all your customers personal data stored by {name}. "
},
"src_dot_apps_dot_components_dot_AppInstallPage_dot_3688014441": {
"context": "section header",
"string": "You are about to install {name}"
},
"src_dot_apps_dot_components_dot_AppInstallPage_dot_3916538164": {
"context": "section header",
"string": "App permissions"
},
"src_dot_apps_dot_components_dot_AppsInProgress_dot_1675189051": {
"context": "section header",
"string": "Ongoing Installations"
},
"src_dot_apps_dot_components_dot_AppsInProgress_dot_2398081356": {
"context": "retry installation",
"string": "Retry"
},
"src_dot_apps_dot_components_dot_AppsInProgress_dot_2868351060": {
"context": "app installation error",
"string": "There was a problem during installation"
},
"src_dot_apps_dot_components_dot_AppsInProgress_dot_3733091754": {
"context": "app installation",
"string": "Installing app..."
},
"src_dot_apps_dot_components_dot_CustomAppCreatePage_dot_1414896555": {
"context": "checkbox label",
"string": "Grant this app full access to the store"
},
"src_dot_apps_dot_components_dot_CustomAppCreatePage_dot_3076071936": {
"context": "card description",
"string": "Expand or restrict app permissions to access certain part of Saleor system."
},
"src_dot_apps_dot_components_dot_CustomAppCreatePage_dot_3189779214": {
"context": "header",
"string": "Create New App"
},
"src_dot_apps_dot_components_dot_CustomAppCreatePage_dot_570656367": {
"context": "checkbox label",
"string": "App is active"
},
"src_dot_apps_dot_components_dot_CustomAppDefaultToken_dot_1336855942": {
"string": "Generated Token"
},
"src_dot_apps_dot_components_dot_CustomAppDefaultToken_dot_2080322626": {
"string": "This token gives you access to your shop's API, which you'll find here: {url}"
},
"src_dot_apps_dot_components_dot_CustomAppDefaultToken_dot_2864204643": {
"context": "button",
"string": "Copied"
},
"src_dot_apps_dot_components_dot_CustomAppDefaultToken_dot_3406541221": {
"context": "button",
"string": "Copy token"
},
"src_dot_apps_dot_components_dot_CustomAppDefaultToken_dot_4189999598": {
"string": "Weve created your default token. Make sure to copy your new personal access token now. You wont be able to see it again."
},
"src_dot_apps_dot_components_dot_CustomAppDetailsPage_dot_1414896555": {
"context": "checkbox label",
"string": "Grant this app full access to the store"
},
"src_dot_apps_dot_components_dot_CustomAppDetailsPage_dot_3076071936": {
"context": "card description",
"string": "Expand or restrict app permissions to access certain part of Saleor system."
},
"src_dot_apps_dot_components_dot_CustomAppDetailsPage_dot_570656367": {
"context": "checkbox label",
"string": "App is active"
},
"src_dot_apps_dot_components_dot_CustomAppInformation_dot_2860466085": {
"context": "header",
"string": "App Information"
},
"src_dot_apps_dot_components_dot_CustomAppInformation_dot_3001692357": {
"context": "custom app name",
"string": "App Name"
},
"src_dot_apps_dot_components_dot_CustomAppTokens_dot_2446088470": {
"context": "custom app token key",
"string": "Key"
},
"src_dot_apps_dot_components_dot_CustomAppTokens_dot_2639089057": {
"string": "No tokens found"
},
"src_dot_apps_dot_components_dot_CustomAppTokens_dot_3209034045": {
"context": "header",
"string": "Tokens"
},
"src_dot_apps_dot_components_dot_CustomAppTokens_dot_3875340101": {
"context": "button",
"string": "Create Token"
},
"src_dot_apps_dot_components_dot_CustomAppTokens_dot_4017491013": {
"string": "Token Note"
},
"src_dot_apps_dot_components_dot_CustomAppTokens_dot_4190792473": {
"context": "table actions",
"string": "Actions"
},
"src_dot_apps_dot_components_dot_CustomApps_dot_3152347892": {
"context": "custom apps content",
"string": "Your custom created apps will be shown here."
},
"src_dot_apps_dot_components_dot_CustomApps_dot_3230241731": {
"context": "create app button",
"string": "Create App"
},
"src_dot_apps_dot_components_dot_DeactivatedText_dot_3064552173": {
"context": "app deactivated",
"string": "Deactivated"
},
"src_dot_apps_dot_components_dot_InstalledApps_dot_1636202177": {
"context": "about app",
"string": "About"
},
"src_dot_apps_dot_components_dot_InstalledApps_dot_186286611": {
"context": "apps content",
"string": "You dont have any installed apps in your dashboard"
},
"src_dot_apps_dot_components_dot_InstalledApps_dot_2008086393": {
"context": "section header",
"string": "Thirdparty Apps"
},
"src_dot_apps_dot_components_dot_Marketplace_dot_2932910073": {
"context": "section header",
"string": "Saleor Marketplace"
},
"src_dot_apps_dot_components_dot_Marketplace_dot_3259214524": {
"context": "marketplace content",
"string": "Marketplace is coming soon"
},
"src_dot_apps_dot_components_dot_Marketplace_dot_3910026447": {
"context": "marketplace button",
"string": "Visit Marketplace"
},
"src_dot_apps_dot_components_dot_Marketplace_dot_52985091": {
"context": "marketplace content",
"string": "Discover great free and paid apps in our Saleor Marketplace."
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_1336855942": {
"string": "Generated Token"
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_2530792168": {
"string": "Access token is used to authenticate service accounts"
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_3009959880": {
"string": "Weve created your token. Make sure to copy your new personal access token now. You wont be able to see it again."
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_3406541221": {
"context": "button",
"string": "Copy token"
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_3875340101": {
"context": "header",
"string": "Create Token"
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_4017491013": {
"string": "Token Note"
},
"src_dot_apps_dot_components_dot_TokenCreateDialog_dot_4120989039": {
"context": "create service token, button",
"string": "Create"
},
"src_dot_apps_dot_components_dot_TokenDeleteDialog_dot_2164262076": {
"context": "dialog title",
"string": "Delete Token"
},
"src_dot_apps_dot_components_dot_TokenDeleteDialog_dot_981802752": {
"context": "delete token",
"string": "Are you sure you want to delete token {token}?"
},
"src_dot_apps_dot_views_dot_AppDetailsSettings_dot_2140018198": {
"context": "app settings error",
"string": "Failed to fetch app settings"
},
"src_dot_apps_dot_views_dot_AppDetails_dot_2021043385": {
"context": "snackbar text",
"string": "App activated"
},
"src_dot_apps_dot_views_dot_AppDetails_dot_3791371350": {
"context": "snackbar text",
"string": "App deactivated"
},
"src_dot_apps_dot_views_dot_AppInstall_dot_2538706256": {
"context": "window title",
"string": "Install App"
},
"src_dot_apps_dot_views_dot_AppsList_dot_1180774955": {
"context": "app has been installed",
"string": "{name} is ready to be used"
},
"src_dot_apps_dot_views_dot_AppsList_dot_3076215778": {
"context": "app has been removed",
"string": "App successfully removed"
},
"src_dot_apps_dot_views_dot_AppsList_dot_539268521": {
"context": "message title",
"string": "Couldnt Install {name}"
},
"src_dot_apps_dot_views_dot_AppsList_dot_966773578": {
"context": "message title",
"string": "App installed"
},
"src_dot_apps_dot_views_dot_CustomAppCreate_dot_3230241731": {
"context": "window title",
"string": "Create App"
},
"src_dot_attributes": {
"context": "attributes section name",
"string": "Attributes"
@ -931,13 +1282,6 @@
"context": "permission list item description",
"string": "This group is last source of that permission"
},
"src_dot_components_dot_AccountStatus_dot_2183517419": {
"context": "section header",
"string": "Account Status"
},
"src_dot_components_dot_AccountStatus_dot_2683780981": {
"string": "If you want to disable this account uncheck the box below"
},
"src_dot_components_dot_AddressEdit_dot_1139500589": {
"string": "Country"
},
@ -970,6 +1314,13 @@
"context": "button",
"string": "Account Settings"
},
"src_dot_components_dot_AppStatus_dot_1624959454": {
"string": "If you want to disable this App please uncheck the box below."
},
"src_dot_components_dot_AppStatus_dot_1750277107": {
"context": "section header",
"string": "App Status"
},
"src_dot_components_dot_AssignCategoryDialog_dot_1305061437": {
"string": "Search Categories"
},
@ -1442,15 +1793,9 @@
"src_dot_configuration_dot_1440737903": {
"string": "Shipping Settings"
},
"src_dot_configuration_dot_1639245766": {
"string": "View and update your webhook and their settings"
},
"src_dot_configuration_dot_1742602794": {
"string": "Attributes and Product Types"
},
"src_dot_configuration_dot_2387898569": {
"string": "Manage external integrations accounts"
},
"src_dot_configuration_dot_3140151600": {
"string": "Staff Settings"
},
@ -1476,6 +1821,9 @@
"context": "button",
"string": "Create"
},
"src_dot_customApps": {
"string": "Local Apps"
},
"src_dot_customers": {
"context": "customers section name",
"string": "Customers"
@ -4090,159 +4438,6 @@
"context": "service accounts section name",
"string": "Service Accounts"
},
"src_dot_services_dot_components_dot_ServiceCreatePage_dot_1848599267": {
"context": "checkbox label",
"string": "User has full access to the store"
},
"src_dot_services_dot_components_dot_ServiceCreatePage_dot_248507553": {
"context": "header",
"string": "Create New Account"
},
"src_dot_services_dot_components_dot_ServiceCreatePage_dot_27827485": {
"context": "checkbox label",
"string": "Service account is active"
},
"src_dot_services_dot_components_dot_ServiceCreatePage_dot_3639008725": {
"context": "card description",
"string": "Expand or restrict user's permissions to access certain part of saleor system."
},
"src_dot_services_dot_components_dot_ServiceDefaultToken_dot_1336855942": {
"string": "Generated Token"
},
"src_dot_services_dot_components_dot_ServiceDefaultToken_dot_2080322626": {
"string": "This token gives you access to your shop's API, which you'll find here: {url}"
},
"src_dot_services_dot_components_dot_ServiceDefaultToken_dot_2864204643": {
"context": "button",
"string": "Copied"
},
"src_dot_services_dot_components_dot_ServiceDefaultToken_dot_3406541221": {
"context": "button",
"string": "Copy token"
},
"src_dot_services_dot_components_dot_ServiceDefaultToken_dot_4189999598": {
"string": "Weve created your default token. Make sure to copy your new personal access token now. You wont be able to see it again."
},
"src_dot_services_dot_components_dot_ServiceDeleteDialog_dot_1534767622": {
"context": "dialog header",
"string": "Delete Service Account"
},
"src_dot_services_dot_components_dot_ServiceDeleteDialog_dot_2297471173": {
"context": "delete service account",
"string": "Are you sure you want to delete {name}?"
},
"src_dot_services_dot_components_dot_ServiceDetailsPage_dot_1848599267": {
"context": "checkbox label",
"string": "User has full access to the store"
},
"src_dot_services_dot_components_dot_ServiceDetailsPage_dot_27827485": {
"context": "checkbox label",
"string": "Service account is active"
},
"src_dot_services_dot_components_dot_ServiceDetailsPage_dot_3639008725": {
"context": "card description",
"string": "Expand or restrict user's permissions to access certain part of saleor system."
},
"src_dot_services_dot_components_dot_ServiceInfo_dot_3789449123": {
"context": "service account",
"string": "Account Name"
},
"src_dot_services_dot_components_dot_ServiceInfo_dot_426959482": {
"context": "header",
"string": "Service Account Information"
},
"src_dot_services_dot_components_dot_ServiceListPage_dot_1895355592": {
"string": "Search Service Accounts"
},
"src_dot_services_dot_components_dot_ServiceListPage_dot_4040605455": {
"context": "tab name",
"string": "All Service Accounts"
},
"src_dot_services_dot_components_dot_ServiceListPage_dot_624280156": {
"context": "button",
"string": "Create account"
},
"src_dot_services_dot_components_dot_ServiceListPage_dot_active": {
"context": "service account",
"string": "Active"
},
"src_dot_services_dot_components_dot_ServiceListPage_dot_deactivated": {
"context": "service account",
"string": "Inactive"
},
"src_dot_services_dot_components_dot_ServiceList_dot_1342308051": {
"string": "No service accounts found"
},
"src_dot_services_dot_components_dot_ServiceList_dot_31853942": {
"context": "account status",
"string": "active"
},
"src_dot_services_dot_components_dot_ServiceList_dot_3239722049": {
"context": "account status",
"string": "inactive"
},
"src_dot_services_dot_components_dot_ServiceList_dot_636461959": {
"context": "service name",
"string": "Name"
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_1336855942": {
"string": "Generated Token"
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_2530792168": {
"string": "Access token is used to authenticate service accounts"
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_3009959880": {
"string": "Weve created your token. Make sure to copy your new personal access token now. You wont be able to see it again."
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_3406541221": {
"context": "button",
"string": "Copy token"
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_3875340101": {
"context": "header",
"string": "Create Token"
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_4017491013": {
"string": "Token Note"
},
"src_dot_services_dot_components_dot_ServiceTokenCreateDialog_dot_4120989039": {
"context": "create service token, button",
"string": "Create"
},
"src_dot_services_dot_components_dot_ServiceTokenDeleteDialog_dot_1534767622": {
"context": "dialog title",
"string": "Delete Service Account"
},
"src_dot_services_dot_components_dot_ServiceTokenDeleteDialog_dot_981802752": {
"context": "delete token",
"string": "Are you sure you want to delete token {token}?"
},
"src_dot_services_dot_components_dot_ServiceTokens_dot_2446088470": {
"context": "service account key",
"string": "Key"
},
"src_dot_services_dot_components_dot_ServiceTokens_dot_2639089057": {
"string": "No tokens found"
},
"src_dot_services_dot_components_dot_ServiceTokens_dot_3875340101": {
"context": "button",
"string": "Create Token"
},
"src_dot_services_dot_components_dot_ServiceTokens_dot_4017491013": {
"string": "Token Note"
},
"src_dot_services_dot_components_dot_ServiceTokens_dot_4190792473": {
"context": "table actions",
"string": "Actions"
},
"src_dot_services_dot_components_dot_ServiceTokens_dot_426959482": {
"context": "header",
"string": "Service Account Information"
},
"src_dot_services_dot_views_dot_ServiceCreate_dot_3167211165": {
"context": "window title",
"string": "Create Service Account"
},
"src_dot_sessionExpired": {
"string": "Your session has expired. Please log in again to continue."
},
@ -5189,12 +5384,21 @@
"src_dot_utils_dot_errors_dot_invalid": {
"string": "Invalid value"
},
"src_dot_utils_dot_errors_dot_invalidManifestFormat": {
"string": "Invalid manifest format"
},
"src_dot_utils_dot_errors_dot_invalidPassword": {
"string": "Invalid password"
},
"src_dot_utils_dot_errors_dot_invalidPermission": {
"string": "Permission is invalid"
},
"src_dot_utils_dot_errors_dot_invalidStatus": {
"context": "error message",
"string": "Cannot request an invoice for draft order"
"string": "Status is invalid"
},
"src_dot_utils_dot_errors_dot_invalidUrlFormat": {
"string": "Url has invalid format"
},
"src_dot_utils_dot_errors_dot_noShippingAddress": {
"context": "error message",
@ -5216,9 +5420,15 @@
"context": "error message",
"string": "Number not set for an invoice"
},
"src_dot_utils_dot_errors_dot_outOfScopeApp": {
"string": "App is out of your permissions scope"
},
"src_dot_utils_dot_errors_dot_outOfScopeGroup": {
"string": "Group is out of your permission scope"
},
"src_dot_utils_dot_errors_dot_outOfScopePermission": {
"string": "Permission is out of your scope"
},
"src_dot_utils_dot_errors_dot_outOfScopeUser": {
"string": "User is out of your permissions scope"
},
@ -5448,9 +5658,6 @@
"context": "webhook specific information",
"string": "Webhook specific information"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_819389696": {
"string": "Assign to Service Account"
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_2772025990": {
"context": "webhooks active",
"string": "Webhook is active"
@ -5471,31 +5678,16 @@
"context": "header",
"string": "{webhookName} Details"
},
"src_dot_webhooks_dot_components_dot_WebhooksListPage_dot_1432828311": {
"string": "Search Webhooks"
},
"src_dot_webhooks_dot_components_dot_WebhooksListPage_dot_2295583901": {
"context": "button",
"string": "Create webhook"
},
"src_dot_webhooks_dot_components_dot_WebhooksListPage_dot_3300314452": {
"context": "tab name",
"string": "All Webhooks"
},
"src_dot_webhooks_dot_components_dot_WebhooksListPage_dot_active": {
"context": "webhook",
"string": "Active"
},
"src_dot_webhooks_dot_components_dot_WebhooksListPage_dot_inactive": {
"context": "webhook",
"string": "Inactive"
},
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_1153324159": {
"string": "No webhooks found"
},
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_2487865635": {
"context": "webhook service account",
"string": "Service Account"
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_3493926696": {
"context": "button",
"string": "Create Webhook"
},
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_3861572549": {
"context": "header",
"string": "Webhooks"
},
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_4120604650": {
"context": "user action bar",

387
package-lock.json generated
View file

@ -2735,6 +2735,31 @@
"warning": "^3.0.0"
}
},
"@saleor/macaw-ui": {
"version": "0.1.1-9",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.1.1-9.tgz",
"integrity": "sha512-ryftb0jBs1ghIm4ILwJZ7vfQt90Kab9XlNcEUh4JFDV4+HEABrGpwstLKyCtIjFDzxQf/z9L4vbW25o1m6klmg==",
"requires": {
"@types/react-inlinesvg": "^0.8.1",
"classnames": "^2.2.6",
"downshift": "^1.31.16",
"lodash-es": "^4.17.15",
"react-helmet": "^5.2.1",
"react-inlinesvg": "^1.1.2",
"string-similarity": "^1.2.2"
},
"dependencies": {
"react-inlinesvg": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-inlinesvg/-/react-inlinesvg-1.2.0.tgz",
"integrity": "sha512-IsznU+UzpUwDGzBWbf0bfSRA5Jbqz87xeoqLM/nSIDPkoHksInF1wCGybTSn4sIui+30TqboRQP1wAelNTkdog==",
"requires": {
"exenv": "^1.2.2",
"react-from-dom": "^0.3.0"
}
}
}
},
"@samverschueren/stream-to-observable": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
@ -3793,6 +3818,14 @@
"@types/react": "*"
}
},
"@types/react-inlinesvg": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@types/react-inlinesvg/-/react-inlinesvg-0.8.1.tgz",
"integrity": "sha512-pONmeCNqot4diXLhxb+jajDWX57rH/JmMu9H4NQWNMl5WUHAHmcrOfDfB05IgYBfRUX1Cvyjl9EZzKHxSxZpAA==",
"requires": {
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.2.tgz",
@ -6533,6 +6566,11 @@
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
"bail": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
"integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ=="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -7147,20 +7185,17 @@
"character-entities": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz",
"integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==",
"dev": true
"integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w=="
},
"character-entities-legacy": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz",
"integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==",
"dev": true
"integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww=="
},
"character-reference-invalid": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz",
"integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==",
"dev": true
"integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg=="
},
"chardet": {
"version": "0.7.0",
@ -7529,6 +7564,11 @@
"urlgrey": "0.4.4"
}
},
"collapse-white-space": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
"integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ=="
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -8791,7 +8831,6 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
"integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
"dev": true,
"requires": {
"domelementtype": "^2.0.1",
"entities": "^2.0.0"
@ -8800,14 +8839,12 @@
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
"dev": true
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
"dev": true
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}
}
},
@ -10098,6 +10135,11 @@
}
}
},
"exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
},
"exit": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@ -10321,8 +10363,7 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": {
"version": "3.0.2",
@ -12270,6 +12311,63 @@
"uglify-js": "^3.5.1"
}
},
"html-to-react": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.3.tgz",
"integrity": "sha512-txe09A3vxW8yEZGJXJ1is5gGDfBEVACmZDSgwDyH5EsfRdOubBwBCg63ZThZP0xBn0UE4FyvMXZXmohusCxDcg==",
"requires": {
"domhandler": "^3.0",
"htmlparser2": "^4.1.0",
"lodash.camelcase": "^4.3.0",
"ramda": "^0.27"
},
"dependencies": {
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"domhandler": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
"integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
"requires": {
"domelementtype": "^2.0.1"
}
},
"domutils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz",
"integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==",
"requires": {
"dom-serializer": "^0.2.1",
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0"
}
},
"entities": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz",
"integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw=="
},
"htmlparser2": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
"integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"entities": "^2.0.0"
}
},
"ramda": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz",
"integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA=="
}
}
},
"html-webpack-plugin": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
@ -12896,14 +12994,12 @@
"is-alphabetical": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz",
"integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==",
"dev": true
"integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA=="
},
"is-alphanumerical": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz",
"integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==",
"dev": true,
"requires": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
@ -12938,8 +13034,7 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-callable": {
"version": "1.1.4",
@ -12983,8 +13078,7 @@
"is-decimal": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz",
"integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==",
"dev": true
"integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ=="
},
"is-descriptor": {
"version": "0.1.6",
@ -13075,8 +13169,7 @@
"is-hexadecimal": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz",
"integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==",
"dev": true
"integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA=="
},
"is-in-browser": {
"version": "1.1.3",
@ -13156,8 +13249,7 @@
"is-plain-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
"dev": true
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
},
"is-plain-object": {
"version": "2.0.4",
@ -13260,12 +13352,22 @@
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
"is-whitespace-character": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
"integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w=="
},
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
"dev": true
},
"is-word-character": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz",
"integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA=="
},
"is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
@ -14861,6 +14963,11 @@
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@ -14872,11 +14979,20 @@
"integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=",
"dev": true
},
"lodash.every": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.every/-/lodash.every-4.6.0.tgz",
"integrity": "sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc="
},
"lodash.flattendeep": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI="
},
"lodash.foreach": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
},
"lodash.get": {
"version": "4.4.2",
@ -14893,6 +15009,16 @@
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"lodash.map": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM="
},
"lodash.maxby": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.maxby/-/lodash.maxby-4.6.0.tgz",
"integrity": "sha1-CCJABo88eiJ6oAqDgOTzjPB4bj0="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -15131,6 +15257,11 @@
"object-visit": "^1.0.0"
}
},
"markdown-escapes": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
},
"markdown-to-jsx": {
"version": "6.11.4",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz",
@ -15158,6 +15289,14 @@
"safe-buffer": "^5.1.2"
}
},
"mdast-add-list-metadata": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz",
"integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==",
"requires": {
"unist-util-visit-parents": "1.1.2"
}
},
"mdn-data": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
@ -16192,7 +16331,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz",
"integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==",
"dev": true,
"requires": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
@ -17356,6 +17494,11 @@
"react-clientside-effect": "^1.2.0"
}
},
"react-from-dom": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/react-from-dom/-/react-from-dom-0.3.1.tgz",
"integrity": "sha512-PeNBa8iuzoD7qHA9O7YpGnXFvC+XFFwStmFh2/r2zJAvEIaRg6EwOj+EPcDIFwyYBhqPIItxIx/dGdeWiFivjQ=="
},
"react-gtm-module": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.8.tgz",
@ -17495,6 +17638,28 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-markdown": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz",
"integrity": "sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==",
"requires": {
"html-to-react": "^1.3.4",
"mdast-add-list-metadata": "1.0.1",
"prop-types": "^15.7.2",
"react-is": "^16.8.6",
"remark-parse": "^5.0.0",
"unified": "^6.1.5",
"unist-util-visit": "^1.3.0",
"xtend": "^4.0.1"
},
"dependencies": {
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}
}
},
"react-moment": {
"version": "0.9.7",
"resolved": "https://registry.npmjs.org/react-moment/-/react-moment-0.9.7.tgz",
@ -18043,6 +18208,35 @@
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
"dev": true
},
"remark-parse": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz",
"integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==",
"requires": {
"collapse-white-space": "^1.0.2",
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-whitespace-character": "^1.0.0",
"is-word-character": "^1.0.0",
"markdown-escapes": "^1.0.0",
"parse-entities": "^1.1.0",
"repeat-string": "^1.5.4",
"state-toggle": "^1.0.0",
"trim": "0.0.1",
"trim-trailing-lines": "^1.0.0",
"unherit": "^1.0.4",
"unist-util-remove-position": "^1.0.0",
"vfile-location": "^2.0.0",
"xtend": "^4.0.1"
},
"dependencies": {
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}
}
},
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@ -18088,8 +18282,7 @@
"repeat-string": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
"dev": true
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
"repeating": {
"version": "2.0.1",
@ -18100,6 +18293,11 @@
"is-finite": "^1.0.0"
}
},
"replace-ext": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
"integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs="
},
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
@ -19308,6 +19506,11 @@
}
}
},
"state-toggle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz",
"integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ=="
},
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@ -19451,6 +19654,18 @@
}
}
},
"string-similarity": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.2.2.tgz",
"integrity": "sha512-IoHUjcw3Srl8nsPlW04U3qwWPk3oG2ffLM0tN853d/E/JlIvcmZmDY2Kz5HzKp4lEi2T7QD7Zuvjq/1rDw+XcQ==",
"requires": {
"lodash.every": "^4.6.0",
"lodash.flattendeep": "^4.4.0",
"lodash.foreach": "^4.5.0",
"lodash.map": "^4.6.0",
"lodash.maxby": "^4.6.0"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -20163,12 +20378,27 @@
"resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
"integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A=="
},
"trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0="
},
"trim-right": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true
},
"trim-trailing-lines": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz",
"integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA=="
},
"trough": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA=="
},
"ts-dedent": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-1.1.0.tgz",
@ -20379,6 +20609,22 @@
"integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==",
"dev": true
},
"unherit": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
"integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==",
"requires": {
"inherits": "^2.0.0",
"xtend": "^4.0.0"
},
"dependencies": {
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}
}
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
@ -20407,6 +20653,19 @@
"integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==",
"dev": true
},
"unified": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz",
"integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==",
"requires": {
"bail": "^1.0.0",
"extend": "^3.0.0",
"is-plain-obj": "^1.1.0",
"trough": "^1.0.0",
"vfile": "^2.0.0",
"x-is-string": "^0.1.0"
}
},
"union-class-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/union-class-names/-/union-class-names-1.0.0.tgz",
@ -20448,6 +20707,47 @@
"imurmurhash": "^0.1.4"
}
},
"unist-util-is": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz",
"integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A=="
},
"unist-util-remove-position": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz",
"integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==",
"requires": {
"unist-util-visit": "^1.1.0"
}
},
"unist-util-stringify-position": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz",
"integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ=="
},
"unist-util-visit": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz",
"integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==",
"requires": {
"unist-util-visit-parents": "^2.0.0"
},
"dependencies": {
"unist-util-visit-parents": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz",
"integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==",
"requires": {
"unist-util-is": "^3.0.0"
}
}
}
},
"unist-util-visit-parents": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz",
"integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q=="
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@ -20740,6 +21040,30 @@
"extsprintf": "^1.2.0"
}
},
"vfile": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz",
"integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==",
"requires": {
"is-buffer": "^1.1.4",
"replace-ext": "1.0.0",
"unist-util-stringify-position": "^1.0.0",
"vfile-message": "^1.0.0"
}
},
"vfile-location": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz",
"integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA=="
},
"vfile-message": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz",
"integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==",
"requires": {
"unist-util-stringify-position": "^1.1.1"
}
},
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@ -21544,6 +21868,11 @@
"async-limiter": "~1.0.0"
}
},
"x-is-string": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz",
"integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI="
},
"xml-name-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",

View file

@ -20,6 +20,7 @@
"@material-ui/core": "^4.5.1",
"@material-ui/icons": "^4.5.1",
"@material-ui/styles": "^4.5.2",
"@saleor/macaw-ui": "^0.1.1-9",
"apollo": "^2.21.2",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
@ -58,6 +59,7 @@
"react-inlinesvg": "^0.8.4",
"react-intl": "^3.1.8",
"react-jss": "^10.0.0",
"react-markdown": "^4.3.1",
"react-moment": "^0.9.7",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
@ -166,7 +168,7 @@
"moduleNameMapper": {
"@assets(.*)$": "<rootDir>/assets/$1",
"@locale(.*)$": "<rootDir>/locale/$1",
"@saleor(.*)$": "<rootDir>/src/$1",
"@saleor(?!.*macaw)(.*)$": "<rootDir>/src/$1",
"@test/(.*)$": "<rootDir>/testUtils/$1",
"^lodash-es(.*)$": "lodash/$1"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import AppActivateDialog, { AppActivateDialogProps } from "./AppActivateDialog";
const props: AppActivateDialogProps = {
confirmButtonState: "default",
name: "App",
onClose: () => undefined,
onConfirm: () => undefined,
open: true
};
storiesOf("Views / Apps / Activate app", module)
.addDecorator(Decorator)
.add("default", () => <AppActivateDialog {...props} />)
.add("unnamed app", () => <AppActivateDialog {...props} name={null} />);

View file

@ -0,0 +1,61 @@
import DialogContentText from "@material-ui/core/DialogContentText";
import ActionDialog from "@saleor/components/ActionDialog";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { getStringOrPlaceholder } from "@saleor/misc";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface AppActivateDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
open: boolean;
name: string;
onClose: () => void;
onConfirm: () => void;
}
const AppActivateDialog: React.FC<AppActivateDialogProps> = ({
confirmButtonState,
open,
name,
onClose,
onConfirm
}) => {
const intl = useIntl();
return (
<ActionDialog
confirmButtonLabel={intl.formatMessage({
defaultMessage: "Activate",
description: "button label"
})}
confirmButtonState={confirmButtonState}
open={open}
onClose={onClose}
onConfirm={onConfirm}
title={intl.formatMessage({
defaultMessage: "Activate App",
description: "dialog header"
})}
variant="default"
>
<DialogContentText>
{["", null].includes(name) ? (
<FormattedMessage
defaultMessage="Are you sure you want to activate this app? Activating will start gathering events"
description="activate app"
/>
) : (
<FormattedMessage
defaultMessage="Are you sure you want to activate {name}? Activating will start gathering events."
description="activate app"
values={{
name: <strong>{getStringOrPlaceholder(name)}</strong>
}}
/>
)}
</DialogContentText>
</ActionDialog>
);
};
AppActivateDialog.displayName = "AppActivateDialog";
export default AppActivateDialog;

View file

@ -0,0 +1,2 @@
export * from "./AppActivateDialog";
export { default } from "./AppActivateDialog";

View file

@ -0,0 +1,20 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import AppDeactivateDialog, {
AppDeactivateDialogProps
} from "./AppDeactivateDialog";
const props: AppDeactivateDialogProps = {
confirmButtonState: "default",
name: "App",
onClose: () => undefined,
onConfirm: () => undefined,
open: true
};
storiesOf("Views / Apps / Deactivate app", module)
.addDecorator(Decorator)
.add("default", () => <AppDeactivateDialog {...props} />)
.add("unnamed app", () => <AppDeactivateDialog {...props} name={null} />);

View file

@ -0,0 +1,61 @@
import DialogContentText from "@material-ui/core/DialogContentText";
import ActionDialog from "@saleor/components/ActionDialog";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { getStringOrPlaceholder } from "@saleor/misc";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface AppDeactivateDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
open: boolean;
name: string;
onClose: () => void;
onConfirm: () => void;
}
const AppDeactivateDialog: React.FC<AppDeactivateDialogProps> = ({
confirmButtonState,
open,
name,
onClose,
onConfirm
}) => {
const intl = useIntl();
return (
<ActionDialog
confirmButtonLabel={intl.formatMessage({
defaultMessage: "Deactivate",
description: "button label"
})}
confirmButtonState={confirmButtonState}
open={open}
onClose={onClose}
onConfirm={onConfirm}
title={intl.formatMessage({
defaultMessage: "Dectivate App",
description: "dialog header"
})}
variant="delete"
>
<DialogContentText>
{["", null].includes(name) ? (
<FormattedMessage
defaultMessage="Are you sure you want to disable this app? Your data will be kept until you reactivate the app. You will be still billed for the app."
description="deactivate app"
/>
) : (
<FormattedMessage
defaultMessage="Are you sure you want to disable {name}? Your data will be kept until you reactivate the app. You will be still billed for the app."
description="deactivate app"
values={{
name: <strong>{getStringOrPlaceholder(name)}</strong>
}}
/>
)}
</DialogContentText>
</ActionDialog>
);
};
AppDeactivateDialog.displayName = "AppDeactivateDialog";
export default AppDeactivateDialog;

View file

@ -0,0 +1,2 @@
export * from "./AppDeactivateDialog";
export { default } from "./AppDeactivateDialog";

View file

@ -0,0 +1,19 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import AppDeleteDialog, { AppDeleteDialogProps } from "./AppDeleteDialog";
const props: AppDeleteDialogProps = {
confirmButtonState: "default",
name: "App",
onClose: () => undefined,
onConfirm: () => undefined,
open: true,
type: "EXTERNAL"
};
storiesOf("Views / Apps / Delete app", module)
.addDecorator(Decorator)
.add("default", () => <AppDeleteDialog {...props} />)
.add("unnamed app", () => <AppDeleteDialog {...props} name={null} />);

View file

@ -0,0 +1,67 @@
import DialogContentText from "@material-ui/core/DialogContentText";
import ActionDialog from "@saleor/components/ActionDialog";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { getStringOrPlaceholder } from "@saleor/misc";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface AppDeleteDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
open: boolean;
name: string;
onClose: () => void;
onConfirm: () => void;
type: "CUSTOM" | "EXTERNAL";
}
const AppDeleteDialog: React.FC<AppDeleteDialogProps> = ({
confirmButtonState,
open,
name,
onClose,
onConfirm,
type
}) => {
const intl = useIntl();
return (
<ActionDialog
confirmButtonState={confirmButtonState}
open={open}
onClose={onClose}
onConfirm={onConfirm}
title={intl.formatMessage({
defaultMessage: "Delete App",
description: "dialog header"
})}
variant="delete"
>
<DialogContentText>
{["", null].includes(name) ? (
<FormattedMessage
defaultMessage="Are you sure you want to delete this app?"
description="delete app"
/>
) : type === "EXTERNAL" ? (
<FormattedMessage
defaultMessage="Deleting {name}, you will remove installation of the app. If you are paying for app subscription, remember to unsubscribe from the app in Saleor Marketplace. Are you sure you want to delete the app?"
description="delete app"
values={{
name: <strong>{getStringOrPlaceholder(name)}</strong>
}}
/>
) : (
<FormattedMessage
defaultMessage="Deleting {name}, you will delete all the data and webhooks regarding this app. Are you sure you want to do that?"
description="delete custom app"
values={{
name: <strong>{getStringOrPlaceholder(name)}</strong>
}}
/>
)}
</DialogContentText>
</ActionDialog>
);
};
AppDeleteDialog.displayName = "AppDeleteDialog";
export default AppDeleteDialog;

View file

@ -0,0 +1,2 @@
export * from "./AppDeleteDialog";
export { default } from "./AppDeleteDialog";

View file

@ -0,0 +1,20 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import { appDetails } from "../../fixtures";
import AppDetailsPage, { AppDetailsPageProps } from "./AppDetailsPage";
const props: AppDetailsPageProps = {
data: appDetails,
loading: false,
navigateToAppSettings: () => undefined,
onAppActivateOpen: () => undefined,
onAppDeactivateOpen: () => undefined,
onBack: () => undefined
};
storiesOf("Views / Apps / App details", module)
.addDecorator(Decorator)
.add("default", () => <AppDetailsPage {...props} />)
.add("loading", () => <AppDetailsPage {...props} loading={true} />);

View file

@ -0,0 +1,195 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import AppHeader from "@saleor/components/AppHeader";
import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import Container from "@saleor/components/Container";
import ExternalLink from "@saleor/components/ExternalLink";
import PageHeader from "@saleor/components/PageHeader";
import Skeleton from "@saleor/components/Skeleton";
import { sectionNames } from "@saleor/intl";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ReactMarkdown from "react-markdown";
import activateIcon from "../../../../assets/images/activate-icon.svg";
import settingsIcon from "../../../../assets/images/settings-icon.svg";
import supportIcon from "../../../../assets/images/support-icon.svg";
import { useStyles } from "../../styles";
import { App_app } from "../../types/App";
import DeactivatedText from "../DeactivatedText";
export interface AppDetailsPageProps {
loading: boolean;
data: App_app;
navigateToAppSettings: () => void;
onAppActivateOpen: () => void;
onAppDeactivateOpen: () => void;
onBack: () => void;
}
export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
data,
loading,
navigateToAppSettings,
onAppActivateOpen,
onAppDeactivateOpen,
onBack
}) => {
const intl = useIntl();
const classes = useStyles({});
return (
<Container>
<AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.apps)}
</AppHeader>
<PageHeader
title={
<>
{data?.name} {!data?.isActive && <DeactivatedText />}
</>
}
>
<Button
href={data?.homepageUrl}
color="primary"
variant="contained"
data-tc="open-app"
target="_blank"
>
<FormattedMessage defaultMessage="Open App" description="button" />
</Button>
</PageHeader>
<div className={classes.appHeader}>
{data ? (
<div className={classes.appHeaderLinks}>
<ExternalLink
className={classes.headerLinkContainer}
href={data.supportUrl}
target="_blank"
>
<img src={supportIcon} alt="" />
<FormattedMessage
defaultMessage="Get Support"
description="link"
/>
</ExternalLink>
<Button
color="primary"
className={classes.headerLinkContainer}
onClick={navigateToAppSettings}
>
<img src={settingsIcon} alt="" />
<FormattedMessage
defaultMessage="Edit settings"
description="link"
/>
</Button>
<Button
variant="text"
color="primary"
className={classes.headerLinkContainer}
disableFocusRipple
onClick={data.isActive ? onAppDeactivateOpen : onAppActivateOpen}
>
<img src={activateIcon} alt="" />
{data?.isActive ? (
<FormattedMessage
defaultMessage="Deactivate"
description="link"
/>
) : (
<FormattedMessage
defaultMessage="Activate"
description="link"
/>
)}
</Button>
</div>
) : (
<Skeleton />
)}
<div className={classes.hr} />
</div>
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "About this app",
description: "section header"
})}
/>
<CardContent>
{!loading ? <ReactMarkdown source={data?.aboutApp} /> : <Skeleton />}
</CardContent>
</Card>
<CardSpacer />
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "App permissions",
description: "section header"
})}
/>
<CardContent>
{!loading ? (
<>
<Typography>
<FormattedMessage
defaultMessage="This app has permissions to:"
description="apps about permissions"
/>
</Typography>
{!!data?.permissions?.length && (
<ul className={classes.permissionsContainer}>
{data?.permissions?.map(perm => (
<li key={perm.code}>{perm.name}</li>
))}
</ul>
)}
</>
) : (
<Skeleton />
)}
</CardContent>
</Card>
<CardSpacer />
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Data privacy",
description: "section header"
})}
/>
<CardContent>
{!loading ? (
<>
<Typography>{data?.dataPrivacy}</Typography>
<ExternalLink
className={classes.linkContainer}
href={data?.dataPrivacyUrl}
target="_blank"
>
<FormattedMessage
defaultMessage="View this apps privacy policy"
description="app privacy policy link"
/>
</ExternalLink>
</>
) : (
<Skeleton />
)}
</CardContent>
</Card>
<CardSpacer />
</Container>
);
};
AppDetailsPage.displayName = "AppDetailsPage";
export default AppDetailsPage;

View file

@ -0,0 +1,2 @@
export * from "./AppDetailsPage";
export { default } from "./AppDetailsPage";

View file

@ -0,0 +1,20 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import { appDetails } from "../../fixtures";
import AppDetailsSettingsPage, {
AppDetailsSettingsPageProps
} from "./AppDetailsSettingsPage";
const props: AppDetailsSettingsPageProps = {
backendHost: "host",
data: appDetails,
navigateToDashboard: () => undefined,
onBack: () => undefined,
onError: () => undefined
};
storiesOf("Views / Apps / App details settings", module)
.addDecorator(Decorator)
.add("default", () => <AppDetailsSettingsPage {...props} />);

View file

@ -0,0 +1,146 @@
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import AppHeader from "@saleor/components/AppHeader";
import CardSpacer from "@saleor/components/CardSpacer";
import Container from "@saleor/components/Container";
import Grid from "@saleor/components/Grid";
import Hr from "@saleor/components/Hr";
import useTheme from "@saleor/hooks/useTheme";
import { sectionNames } from "@saleor/intl";
import classNames from "classnames";
import React, { useEffect, useRef } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import urlJoin from "url-join";
import { App_app } from "../../types/App";
import { useStyles } from "./styles";
import useSettingsBreadcrumbs from "./useSettingsBreadcrumbs";
export interface AppDetailsSettingsPageProps {
backendHost: string;
data: App_app;
navigateToDashboard: () => void;
onBack: () => void;
onError: () => void;
}
export const AppDetailsSettingsPage: React.FC<AppDetailsSettingsPageProps> = ({
backendHost,
data,
navigateToDashboard,
onBack,
onError
}) => {
const iframeRef = useRef(null);
const intl = useIntl();
const classes = useStyles({});
const { sendThemeToExtension } = useTheme();
const [breadcrumbs, onBreadcrumbClick] = useSettingsBreadcrumbs();
useEffect(() => {
if (!iframeRef.current?.innerHTML && data?.configurationUrl) {
fetch(data?.configurationUrl, {
headers: {
"x-saleor-domain": backendHost,
"x-saleor-token": data.accessToken
},
method: "GET"
})
.then(async response => {
const url = new URL(response.url);
const text = await response.text();
const content = new DOMParser().parseFromString(text, "text/html");
const iFrame = document.createElement("iframe");
iFrame.src = "about:blank";
iFrame.id = "extension-app";
iframeRef.current.innerHTML = "";
iframeRef.current.appendChild(iFrame);
const iFrameDoc =
iFrame.contentWindow && iFrame.contentWindow.document;
const documentElement = content.documentElement;
const formScript = documentElement.querySelector("script");
const formURL = new URL(documentElement.querySelector("script").src);
formScript.src = `${urlJoin(url.origin, formURL.pathname)}`;
iFrameDoc.write(content.documentElement.innerHTML);
iFrameDoc.close();
iFrame.contentWindow.onload = sendThemeToExtension;
})
.catch(() => onError());
}
}, [data]);
return (
<Container>
<AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.apps)}
</AppHeader>
<Grid variant="uniform">
<div className={classes.breadcrumbContainer}>
<div className={classes.breadcrumbs}>
<Typography
className={classNames(
classes.breadcrumb,
classes.breadcrumbDisabled
)}
variant="h5"
>
{data?.name}
</Typography>
{breadcrumbs.map(b => (
<Typography
className={classes.breadcrumb}
variant="h5"
onClick={() => onBreadcrumbClick(b.value)}
key={b.label}
>
{b.label}
</Typography>
))}
</div>
</div>
<div className={classes.appSettingsHeader}>
<Button
onClick={navigateToDashboard}
variant="contained"
color="primary"
>
<FormattedMessage defaultMessage="Dashboard" description="button" />
</Button>
<Button
href={data?.homepageUrl}
variant="contained"
color="primary"
data-tc="open-app"
target="_blank"
>
<FormattedMessage defaultMessage="My App" description="button" />
</Button>
<Button
href={data?.supportUrl}
variant="contained"
color="primary"
data-tc="open-support"
target="_blank"
>
<FormattedMessage
defaultMessage="Support/FAQ"
description="button"
/>
</Button>
</div>
</Grid>
<CardSpacer />
<Hr />
<CardSpacer />
<div ref={iframeRef} className={classes.iframeContainer} />
<CardSpacer />
</Container>
);
};
AppDetailsSettingsPage.displayName = "AppDetailsSettingsPage";
export default AppDetailsSettingsPage;

View file

@ -0,0 +1,2 @@
export * from "./AppDetailsSettingsPage";
export { default } from "./AppDetailsSettingsPage";

View file

@ -0,0 +1,62 @@
import { makeStyles } from "@material-ui/core/styles";
export const useStyles = makeStyles(
theme => ({
appSettingsHeader: {
"& > button, & > a": {
"&:last-child": {
marginRight: 0
},
marginRight: theme.spacing(2)
},
display: "flex",
justifyContent: "flex-end"
},
breadcrumb: {
"&:not(:last-child)": {
"&:after": {
content: "'/'",
display: "block",
position: "absolute",
right: -theme.spacing(2),
top: 0
},
"&:not(:first-child):hover": {
cursor: "pointer",
textDecoration: "underline"
}
},
marginRight: theme.spacing(3),
position: "relative"
},
breadcrumbContainer: {
alignItems: "center",
display: "flex"
},
breadcrumbDisabled: {
"&:hover": {
textDecoration: "none"
},
color: theme.palette.text.disabled
},
breadcrumbs: {
display: "flex"
},
hr: {
border: "none",
borderTop: `1px solid ${theme.palette.divider}`,
height: 0,
marginBottom: 0,
marginTop: 0,
width: "100%"
},
iframeContainer: {
"& > iframe": {
border: "none",
minHeight: "75vh",
width: "100%"
}
}
}),
{ name: "AppDetailsSettingsPage" }
);

View file

@ -0,0 +1,38 @@
import {
Breadcrumb,
BreadcrumbChangeMessage,
BreadcrumbClickMessage,
ExtensionMessageEvent,
ExtensionMessageType,
sendMessageToExtension,
useExtensionMessage
} from "@saleor/macaw-ui/extensions";
import { useState } from "react";
type UseSettingsBreadcrumbs = [Breadcrumb[], (value: string) => void];
function useSettingsBreadcrumbs(): UseSettingsBreadcrumbs {
const [breadcrumbs, setBreadcrumbs] = useState<Breadcrumb[]>([]);
const handleBreadcrumbSet = (
event: ExtensionMessageEvent<BreadcrumbChangeMessage>
) => {
if (event.data.type === ExtensionMessageType.BREADCRUMB_SET) {
setBreadcrumbs(event.data.breadcrumbs);
}
};
useExtensionMessage(handleBreadcrumbSet);
const handleBreadcrumbClick = (value: string) =>
sendMessageToExtension<BreadcrumbClickMessage>(
{
breadcrumb: value,
type: ExtensionMessageType.BREADCRUMB_CLICK
},
"*"
);
return [breadcrumbs, handleBreadcrumbClick];
}
export default useSettingsBreadcrumbs;

View file

@ -0,0 +1,22 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import AppInProgressDeleteDialog, {
AppInProgressDeleteDialogProps
} from "./AppInProgressDeleteDialog";
const props: AppInProgressDeleteDialogProps = {
confirmButtonState: "default",
name: "App",
onClose: () => undefined,
onConfirm: () => undefined,
open: true
};
storiesOf("Views / Apps / Delete app failed installation", module)
.addDecorator(Decorator)
.add("default", () => <AppInProgressDeleteDialog {...props} />)
.add("unnamed app", () => (
<AppInProgressDeleteDialog {...props} name={null} />
));

View file

@ -0,0 +1,57 @@
import DialogContentText from "@material-ui/core/DialogContentText";
import ActionDialog from "@saleor/components/ActionDialog";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { getStringOrPlaceholder } from "@saleor/misc";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface AppInProgressDeleteDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
open: boolean;
name: string;
onClose: () => void;
onConfirm: () => void;
}
const AppInProgressDeleteDialog: React.FC<AppInProgressDeleteDialogProps> = ({
confirmButtonState,
open,
name,
onClose,
onConfirm
}) => {
const intl = useIntl();
return (
<ActionDialog
confirmButtonState={confirmButtonState}
open={open}
onClose={onClose}
onConfirm={onConfirm}
title={intl.formatMessage({
defaultMessage: "Delete App",
description: "dialog header"
})}
variant="delete"
>
<DialogContentText>
{["", null].includes(name) ? (
<FormattedMessage
defaultMessage="Are you sure you want to delete this app?"
description="delete app"
/>
) : (
<FormattedMessage
defaultMessage="Deleting {name}, you will remove installation of the app. If you are paying for app subscription, remember to unsubscribe from the app in Saleor Marketplace. Are you sure you want to delete the app?"
description="delete app"
values={{
name: <strong>{getStringOrPlaceholder(name)}</strong>
}}
/>
)}
</DialogContentText>
</ActionDialog>
);
};
AppInProgressDeleteDialog.displayName = "AppInProgressDeleteDialog";
export default AppInProgressDeleteDialog;

View file

@ -0,0 +1,2 @@
export * from "./AppInProgressDeleteDialog";
export { default } from "./AppInProgressDeleteDialog";

View file

@ -0,0 +1,56 @@
import errorImg from "@assets/images/app-install-error.svg";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Container from "@saleor/components/Container";
import React from "react";
import { FormattedMessage } from "react-intl";
import { useStyles } from "./styles";
interface AppInstallErrorPageProps {
onBack: () => void;
}
export const AppInstallErrorPage: React.FC<AppInstallErrorPageProps> = ({
onBack
}) => {
const classes = useStyles({});
return (
<Container className={classes.root}>
<Grid spacing={3} alignItems="center" container>
<Grid xs={12} sm={6} item>
<img src={errorImg} alt="" />
</Grid>
<Grid xs={12} sm={6} item>
<Typography variant="h3" component="h3">
<FormattedMessage
defaultMessage="Theres a problem with app."
description="title"
/>
</Typography>
<Typography variant="body2">
<FormattedMessage
defaultMessage="Saleor couldnt fetch crucial information regarding installation. Without those System cant install the app in your Saleor. Please use the button below to get back to systems dashboard."
description="content"
/>
</Typography>
<Button
className={classes.button}
color="primary"
variant="contained"
onClick={onBack}
>
<FormattedMessage
defaultMessage="Back to homepage"
description="button"
/>
</Button>
</Grid>
</Grid>
</Container>
);
};
export default AppInstallErrorPage;

View file

@ -0,0 +1,2 @@
export * from "./AppInstallErrorPage";
export { default } from "./AppInstallErrorPage";

View file

@ -0,0 +1,26 @@
import { makeStyles } from "@material-ui/core/styles";
export const useStyles = makeStyles(
theme => ({
button: {
marginTop: theme.spacing(2),
padding: theme.spacing(1.5, 2)
},
root: {
"& > div": {
minHeight: "80vh"
},
"& h3": {
fontWeight: 600,
marginBottom: theme.spacing(3),
maxWidth: theme.spacing(60)
},
"& img": {
maxWidth: "100%"
}
}
}),
{
name: "AppInstallErrorPage"
}
);

View file

@ -0,0 +1,18 @@
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import { installApp } from "../../fixtures";
import AppInstallPage, { AppInstallPageProps } from "./AppInstallPage";
const props: AppInstallPageProps = {
data: installApp,
loading: false,
navigateToAppsList: () => undefined,
onSubmit: () => undefined
};
storiesOf("Views / Apps / Install App", module)
.addDecorator(Decorator)
.add("default", () => <AppInstallPage {...props} />)
.add("loading", () => <AppInstallPage {...props} loading={true} />);

View file

@ -0,0 +1,160 @@
import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
import plusIcon from "@assets/images/plus-icon.svg";
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import Container from "@saleor/components/Container";
import Hr from "@saleor/components/Hr";
import Skeleton from "@saleor/components/Skeleton";
import { buttonMessages } from "@saleor/intl";
import classNames from "classnames";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useStyles } from "../../styles";
import { AppFetch_appFetchManifest_manifest } from "../../types/AppFetch";
export interface AppInstallPageProps {
data: AppFetch_appFetchManifest_manifest;
loading: boolean;
navigateToAppsList: () => void;
onSubmit: () => void;
}
export const AppInstallPage: React.FC<AppInstallPageProps> = ({
data,
loading,
navigateToAppsList,
onSubmit
}) => {
const intl = useIntl();
const classes = useStyles({});
const name = data?.name || "";
return (
<Container>
<CardSpacer />
<Card>
<CardTitle
title={
loading ? (
<Skeleton />
) : (
intl.formatMessage(
{
defaultMessage: `You are about to install {name}`,
description: "section header"
},
{ name }
)
)
}
/>
<CardContent className={classes.installCard}>
{loading ? (
<Skeleton />
) : (
<div className={classes.installAppContainer}>
<div
className={classNames(
classes.installIcon,
classes.installSaleorIcon
)}
>
<img src={saleorDarkLogoSmall} alt="" />
</div>
<img src={plusIcon} alt="" />
<div className={classes.installIcon}>
<h2>{name?.charAt(0).toUpperCase()}</h2>
</div>
</div>
)}
</CardContent>
</Card>
<CardSpacer />
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "App permissions",
description: "section header"
})}
/>
<CardContent>
{loading ? (
<Skeleton />
) : (
<>
<Typography className={classes.installPermissionTitle}>
<FormattedMessage
defaultMessage="Installing this app will give it following permissions:"
description="install app permissions"
/>
</Typography>
{!!data?.permissions?.length && (
<ul className={classes.permissionsContainer}>
{data?.permissions?.map(perm => (
<li key={perm.code}>{perm.name}</li>
))}
</ul>
)}
<Hr className={classes.installSpacer} />
<Typography
variant="body2"
className={classes.installPrivacyText}
>
<FormattedMessage
defaultMessage="Uninstalling the app will remove all your customers personal data stored by {name}. "
description="install app privacy"
values={{ name }}
/>
<a
href={data?.dataPrivacyUrl}
rel="noopener noreferrer"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more about data privacy"
description="app data privacy link"
/>
</a>
</Typography>
</>
)}
</CardContent>
</Card>
<CardSpacer />
<Grid container justify="space-between">
<Grid xs={6} item>
<Button
variant="outlined"
color="primary"
onClick={navigateToAppsList}
>
<Typography color="primary">
<FormattedMessage {...buttonMessages.cancel} />
</Typography>
</Button>
</Grid>
<Grid xs={6} item className={classes.alignRight}>
<Button variant="contained" color="primary" onClick={onSubmit}>
<Typography className={classes.installText}>
<FormattedMessage
defaultMessage="Install App"
description="install button"
/>
</Typography>
</Button>
</Grid>
</Grid>
</Container>
);
};
AppInstallPage.displayName = "AppInstallPage";
export default AppInstallPage;

View file

@ -0,0 +1,2 @@
export * from "./AppInstallPage";
export { default } from "./AppInstallPage";

View file

@ -0,0 +1,117 @@
import Button from "@material-ui/core/Button";
import Progress from "@material-ui/core/CircularProgress";
import IconButton from "@material-ui/core/IconButton";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import Tooltip from "@material-ui/core/Tooltip";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete";
import ErrorIcon from "@material-ui/icons/Error";
import CardTitle from "@saleor/components/CardTitle";
import { renderCollection, stopPropagation } from "@saleor/misc";
import classNames from "classnames";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { JobStatusEnum } from "../../../types/globalTypes";
import { useStyles } from "../../styles";
import { AppsInstallations_appsInstallations } from "../../types/AppsInstallations";
import CardContainer from "../CardContainer";
export interface AppsInProgressProps {
appsList: AppsInstallations_appsInstallations[];
disabled: boolean;
onAppInstallRetry: (id: string) => void;
onRemove: (id: string) => void;
}
const AppsInProgress: React.FC<AppsInProgressProps> = ({
appsList,
disabled,
onAppInstallRetry,
onRemove,
...props
}) => {
const intl = useIntl();
const classes = useStyles(props);
return (
<CardContainer
header={
<CardTitle
title={intl.formatMessage({
defaultMessage: "Ongoing Installations",
description: "section header"
})}
/>
}
>
<TableBody>
{renderCollection(appsList, ({ status, appName, id, message }) => (
<TableRow key={id} className={classes.tableRow}>
<TableCell className={classes.colName}>
<span data-tc="name">{appName}</span>
</TableCell>
{status === JobStatusEnum.PENDING && (
<TableCell
className={classNames(
classes.colAction,
classes.colInstallAction
)}
>
<Typography variant="body2" className={classes.text}>
<FormattedMessage
defaultMessage="Installing app..."
description="app installation"
/>
</Typography>
<div className={classes.colSpinner}>
<Progress size={20} />
</div>
</TableCell>
)}
{status === JobStatusEnum.FAILED && (
<TableCell
className={classNames(
classes.colAction,
classes.colInstallAction
)}
>
<Typography variant="body2" className={classes.error}>
<FormattedMessage
defaultMessage="There was a problem during installation"
description="app installation error"
/>
<Tooltip
title={<Typography variant="body2">{message}</Typography>}
classes={{
tooltip: classes.customTooltip
}}
>
<ErrorIcon />
</Tooltip>
</Typography>
<Button color="primary" onClick={() => onAppInstallRetry(id)}>
<FormattedMessage
defaultMessage="Retry"
description="retry installation"
/>
</Button>
<IconButton
color="primary"
onClick={stopPropagation(() => onRemove(id))}
>
<DeleteIcon />
</IconButton>
</TableCell>
)}
</TableRow>
))}
</TableBody>
</CardContainer>
);
};
AppsInProgress.displayName = "AppsInProgress";
export default AppsInProgress;

View file

@ -0,0 +1,2 @@
export * from "./AppsInProgress";
export { default } from "./AppsInProgress";

View file

@ -0,0 +1,58 @@
import {
listActionsProps,
pageListProps,
searchPageProps,
sortPageProps,
tabPageProps
} from "@saleor/fixtures";
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import { appsInProgress, appsList, customAppsList } from "../../fixtures";
import AppsListPage, { AppsListPageProps } from "./AppsListPage";
const props: AppsListPageProps = {
...listActionsProps,
...pageListProps.default,
...searchPageProps,
...sortPageProps,
...tabPageProps,
appsInProgressList: { appsInstallations: appsInProgress },
customAppsList,
disabled: false,
installedAppsList: appsList,
loadingAppsInProgress: false,
navigateToCustomApp: () => undefined,
navigateToCustomAppCreate: () => undefined,
onAppInProgressRemove: () => undefined,
onAppInstallRetry: () => undefined,
onCustomAppRemove: () => undefined,
onInstalledAppRemove: () => undefined,
onNextPage: () => undefined,
onPreviousPage: () => undefined,
onRowClick: () => undefined,
onSettingsRowClick: () => undefined
};
storiesOf("Views / Apps / Apps list", module)
.addDecorator(Decorator)
.add("default", () => <AppsListPage {...props} />)
.add("loading", () => (
<AppsListPage
{...props}
appsInProgressList={undefined}
disabled={true}
loadingAppsInProgress={true}
installedAppsList={undefined}
customAppsList={undefined}
/>
))
.add("no data", () => (
<AppsListPage
{...props}
appsInProgressList={undefined}
installedAppsList={[]}
customAppsList={[]}
/>
));

View file

@ -0,0 +1,76 @@
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl";
import { ListProps } from "@saleor/types";
import React from "react";
import { useIntl } from "react-intl";
import { AppsInstallations } from "../../types/AppsInstallations";
import { AppsList_apps_edges } from "../../types/AppsList";
import AppsInProgress from "../AppsInProgress/AppsInProgress";
import CustomApps from "../CustomApps/CustomApps";
import InstalledApps from "../InstalledApps/InstalledApps";
import Marketplace from "../Marketplace";
export interface AppsListPageProps extends ListProps {
installedAppsList: AppsList_apps_edges[];
customAppsList: AppsList_apps_edges[];
appsInProgressList?: AppsInstallations;
loadingAppsInProgress: boolean;
navigateToCustomApp: (id: string) => () => void;
navigateToCustomAppCreate: () => void;
onInstalledAppRemove: (id: string) => void;
onCustomAppRemove: (id: string) => void;
onAppInProgressRemove: (id: string) => void;
onAppInstallRetry: (id: string) => void;
onSettingsRowClick: (id: string) => () => void;
}
const AppsListPage: React.FC<AppsListPageProps> = ({
appsInProgressList,
customAppsList,
installedAppsList,
loadingAppsInProgress,
navigateToCustomApp,
navigateToCustomAppCreate,
onInstalledAppRemove,
onCustomAppRemove,
onAppInProgressRemove,
onAppInstallRetry,
onSettingsRowClick,
...listProps
}) => {
const intl = useIntl();
const appsInProgress = appsInProgressList?.appsInstallations;
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.apps)} />
{!!appsInProgress?.length && (
<AppsInProgress
appsList={appsInProgress}
disabled={loadingAppsInProgress}
onAppInstallRetry={onAppInstallRetry}
onRemove={onAppInProgressRemove}
/>
)}
<InstalledApps
appsList={installedAppsList}
onRemove={onInstalledAppRemove}
onSettingsRowClick={onSettingsRowClick}
{...listProps}
/>
<CustomApps
appsList={customAppsList}
navigateToCustomApp={navigateToCustomApp}
navigateToCustomAppCreate={navigateToCustomAppCreate}
onRemove={onCustomAppRemove}
/>
<Marketplace />
</Container>
);
};
AppsListPage.displayName = "AppsListPage";
export default AppsListPage;

View file

@ -0,0 +1,2 @@
export * from "./AppsListPage";
export { default } from "./AppsListPage";

View file

@ -0,0 +1,21 @@
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import Skeleton from "@saleor/components/Skeleton";
import React from "react";
import { useStyles } from "../../styles";
export const AppsSkeleton = () => {
const classes = useStyles({});
return (
<TableRow className={classes.tableRow}>
<TableCell className={classes.colName}>
<Skeleton />
</TableCell>
</TableRow>
);
};
AppsSkeleton.displayName = "AppsSkeleton";
export default AppsSkeleton;

View file

@ -0,0 +1,2 @@
export * from "./AppsSkeleton";
export { default } from "./AppsSkeleton";

View file

@ -0,0 +1,29 @@
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import React from "react";
import { useStyles } from "../../styles";
export interface CardContainerProps {
children: React.ReactNode;
header: React.ReactNode;
}
const CardContainer: React.FC<CardContainerProps> = ({ children, header }) => {
const classes = useStyles({});
return (
<div className={classes.appContainer}>
<Card>
{header}
<CardContent className={classes.appContent}>
<ResponsiveTable>{children}</ResponsiveTable>
</CardContent>
</Card>
</div>
);
};
CardContainer.displayName = "CardContainer";
export default CardContainer;

View file

@ -0,0 +1,2 @@
export * from "./CardContainer";
export { default } from "./CardContainer";

View file

@ -1,6 +1,7 @@
import { AppErrorFragment } from "@saleor/apps/types/AppErrorFragment";
import AccountPermissions from "@saleor/components/AccountPermissions";
import AccountStatus from "@saleor/components/AccountStatus";
import AppHeader from "@saleor/components/AppHeader";
import AppStatus from "@saleor/components/AppStatus";
import CardSpacer from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
@ -9,32 +10,31 @@ import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo";
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
import { sectionNames } from "@saleor/intl";
import { PermissionEnum } from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors";
import getAccountErrorMessage from "@saleor/utils/errors/account";
import getAppErrorMessage from "@saleor/utils/errors/app";
import React from "react";
import { useIntl } from "react-intl";
import ServiceInfo from "../ServiceInfo";
import CustomAppInformation from "../CustomAppInformation";
export interface ServiceCreatePageFormData {
export interface CustomAppCreatePageFormData {
hasFullAccess: boolean;
isActive: boolean;
name: string;
permissions: PermissionEnum[];
}
export interface ServiceCreatePageProps {
export interface CustomAppCreatePageProps {
disabled: boolean;
errors: AccountErrorFragment[];
errors: AppErrorFragment[];
permissions: ShopInfo_shop_permissions[];
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
onSubmit: (data: ServiceCreatePageFormData) => void;
onSubmit: (data: CustomAppCreatePageFormData) => void;
}
const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
const CustomAppCreatePage: React.FC<CustomAppCreatePageProps> = props => {
const {
disabled,
errors,
@ -45,7 +45,7 @@ const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
} = props;
const intl = useIntl();
const initialForm: ServiceCreatePageFormData = {
const initialForm: CustomAppCreatePageFormData = {
hasFullAccess: false,
isActive: false,
name: "",
@ -53,24 +53,24 @@ const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
};
const formErrors = getFormErrors(["permissions"], errors || []);
const permissionsError = getAccountErrorMessage(formErrors.permissions, intl);
const permissionsError = getAppErrorMessage(formErrors.permissions, intl);
return (
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
{({ data, change, hasChanged, submit }) => (
<Container>
<AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.serviceAccounts)}
{intl.formatMessage(sectionNames.apps)}
</AppHeader>
<PageHeader
title={intl.formatMessage({
defaultMessage: "Create New Account",
defaultMessage: "Create New App",
description: "header"
})}
/>
<Grid>
<div>
<ServiceInfo
<CustomAppInformation
data={data}
disabled={disabled}
errors={errors}
@ -85,21 +85,21 @@ const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
permissionsExceeded={false}
onChange={change}
fullAccessLabel={intl.formatMessage({
defaultMessage: "User has full access to the store",
defaultMessage: "Grant this app full access to the store",
description: "checkbox label"
})}
description={intl.formatMessage({
defaultMessage:
"Expand or restrict user's permissions to access certain part of saleor system.",
"Expand or restrict app permissions to access certain part of Saleor system.",
description: "card description"
})}
/>
<CardSpacer />
<AccountStatus
<AppStatus
data={data}
disabled={disabled}
label={intl.formatMessage({
defaultMessage: "Service account is active",
defaultMessage: "App is active",
description: "checkbox label"
})}
onChange={change}
@ -117,5 +117,5 @@ const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
);
};
ServiceCreatePage.displayName = "ServiceCreatePage";
export default ServiceCreatePage;
CustomAppCreatePage.displayName = "CustomAppCreatePage";
export default CustomAppCreatePage;

View file

@ -0,0 +1,2 @@
export * from "./CustomAppCreatePage";
export { default } from "./CustomAppCreatePage";

View file

@ -3,8 +3,6 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import IconButton from "@material-ui/core/IconButton";
import Paper from "@material-ui/core/Paper";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography";
import CloseIcon from "@material-ui/icons/Close";
import Link from "@saleor/components/Link";
@ -12,50 +10,16 @@ import useClipboard from "@saleor/hooks/useClipboard";
import React from "react";
import { FormattedMessage } from "react-intl";
export interface ServiceDefaultTokenProps {
import { useStyles } from "./styles";
export interface CustomAppDefaultTokenProps {
apiUri: string;
token: string;
onApiUriClick: () => void;
onTokenClose: () => void;
}
const useStyles = makeStyles(
theme => ({
cancel: {
marginRight: theme.spacing(1)
},
closeContainer: {
display: "flex",
justifyContent: "flex-end",
position: "relative",
right: -theme.spacing(),
top: -theme.spacing(1)
},
content: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "1fr 60px",
marginBottom: theme.spacing(3)
},
copy: {
marginTop: theme.spacing(),
position: "relative",
right: theme.spacing(1)
},
paper: {
background: fade(theme.palette.primary.main, 0.05),
padding: theme.spacing(2, 3)
},
root: {
boxShadow: "0px 5px 10px rgba(0, 0, 0, 0.05)"
}
}),
{
name: "ServiceTokenCreateDialog"
}
);
const ServiceDefaultToken: React.FC<ServiceDefaultTokenProps> = props => {
const CustomAppDefaultToken: React.FC<CustomAppDefaultTokenProps> = props => {
const { apiUri, token, onApiUriClick, onTokenClose } = props;
const classes = useStyles(props);
const [copied, copy] = useClipboard();
@ -112,5 +76,5 @@ const ServiceDefaultToken: React.FC<ServiceDefaultTokenProps> = props => {
);
};
ServiceDefaultToken.displayName = "ServiceDefaultToken";
export default ServiceDefaultToken;
CustomAppDefaultToken.displayName = "CustomAppDefaultToken";
export default CustomAppDefaultToken;

View file

@ -0,0 +1,2 @@
export * from "./CustomAppDefaultToken";
export { default } from "./CustomAppDefaultToken";

View file

@ -0,0 +1,38 @@
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
export const useStyles = makeStyles(
theme => ({
cancel: {
marginRight: theme.spacing(1)
},
closeContainer: {
display: "flex",
justifyContent: "flex-end",
position: "relative",
right: -theme.spacing(),
top: -theme.spacing(1)
},
content: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "1fr 60px",
marginBottom: theme.spacing(3)
},
copy: {
marginTop: theme.spacing(),
position: "relative",
right: theme.spacing(1)
},
paper: {
background: fade(theme.palette.primary.main, 0.05),
padding: theme.spacing(2, 3)
},
root: {
boxShadow: "0px 5px 10px rgba(0, 0, 0, 0.05)"
}
}),
{
name: "CustomAppTokenCreateDialog"
}
);

View file

@ -1,6 +1,7 @@
import { AppErrorFragment } from "@saleor/apps/types/AppErrorFragment";
import AccountPermissions from "@saleor/components/AccountPermissions";
import AccountStatus from "@saleor/components/AccountStatus";
import AppHeader from "@saleor/components/AppHeader";
import AppStatus from "@saleor/components/AppStatus";
import CardSpacer from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
@ -9,93 +10,95 @@ import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo";
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { ServiceDetails_serviceAccount } from "@saleor/services/types/ServiceDetails";
import { PermissionEnum } from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors";
import getAccountErrorMessage from "@saleor/utils/errors/account";
import getAppErrorMessage from "@saleor/utils/errors/app";
import WebhooksList from "@saleor/webhooks/components/WebhooksList";
import React from "react";
import { useIntl } from "react-intl";
import ServiceDefaultToken from "../ServiceDefaultToken";
import ServiceInfo from "../ServiceInfo";
import ServiceTokens from "../ServiceTokens";
import { AppUpdate_appUpdate_app } from "../../types/AppUpdate";
import CustomAppDefaultToken from "../CustomAppDefaultToken";
import CustomAppInformation from "../CustomAppInformation";
import CustomAppTokens from "../CustomAppTokens";
export interface ServiceDetailsPageFormData {
export interface CustomAppDetailsPageFormData {
hasFullAccess: boolean;
isActive: boolean;
name: string;
permissions: PermissionEnum[];
}
export interface ServiceDetailsPageProps {
export interface CustomAppDetailsPageProps {
apiUri: string;
disabled: boolean;
errors: AccountErrorFragment[];
errors: AppErrorFragment[];
permissions: ShopInfo_shop_permissions[];
saveButtonBarState: ConfirmButtonTransitionState;
service: ServiceDetails_serviceAccount;
app: AppUpdate_appUpdate_app;
token: string;
onApiUriClick: () => void;
onBack: () => void;
onTokenDelete: (id: string) => void;
onDelete: () => void;
onTokenClose: () => void;
onTokenCreate: () => void;
onSubmit: (data: ServiceDetailsPageFormData) => void;
onSubmit: (data: CustomAppDetailsPageFormData) => void;
onWebhookCreate: () => void;
onWebhookRemove: (id: string) => void;
navigateToWebhookDetails: (id: string) => () => void;
}
const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => {
const {
apiUri,
disabled,
errors,
permissions,
saveButtonBarState,
service,
app,
navigateToWebhookDetails,
token,
onApiUriClick,
onBack,
onDelete,
onTokenClose,
onTokenCreate,
onTokenDelete,
onSubmit
onSubmit,
onWebhookCreate,
onWebhookRemove
} = props;
const intl = useIntl();
const formErrors = getFormErrors(["permissions"], errors || []);
const permissionsError = getAccountErrorMessage(formErrors.permissions, intl);
const webhooks = app?.webhooks;
const initialForm: ServiceDetailsPageFormData = {
hasFullAccess: maybe(
() =>
permissions.filter(
const formErrors = getFormErrors(["permissions"], errors || []);
const permissionsError = getAppErrorMessage(formErrors.permissions, intl);
const initialForm: CustomAppDetailsPageFormData = {
hasFullAccess:
permissions?.filter(
perm =>
maybe(() => service.permissions, []).filter(
userPerm => userPerm.code === perm.code
).length === 0
).length === 0,
false
),
isActive: maybe(() => service.isActive, false),
name: maybe(() => service.name, ""),
permissions: maybe(() => service.permissions, []).map(perm => perm.code)
app?.permissions?.filter(userPerm => userPerm.code === perm.code)
.length === 0
).length === 0 || false,
isActive: !!app?.isActive,
name: app?.name || "",
permissions: app?.permissions?.map(perm => perm.code) || []
};
return (
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
{({ data, change, hasChanged, submit }) => (
<Container>
<AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.serviceAccounts)}
{intl.formatMessage(sectionNames.apps)}
</AppHeader>
<PageHeader title={maybe(() => service.name)} />
<PageHeader title={app?.name} />
<Grid>
<div>
{token && (
<>
<ServiceDefaultToken
<CustomAppDefaultToken
apiUri={apiUri}
token={token}
onApiUriClick={onApiUriClick}
@ -104,18 +107,25 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
<CardSpacer />
</>
)}
<ServiceInfo
<CustomAppInformation
data={data}
disabled={disabled}
errors={errors}
onChange={change}
/>
<CardSpacer />
<ServiceTokens
tokens={service?.tokens}
<CustomAppTokens
tokens={app?.tokens}
onCreate={onTokenCreate}
onDelete={onTokenDelete}
/>
<CardSpacer />
<WebhooksList
webhooks={webhooks}
onRemove={onWebhookRemove}
onRowClick={navigateToWebhookDetails}
onCreate={app?.isActive && onWebhookCreate}
/>
</div>
<div>
<AccountPermissions
@ -126,21 +136,21 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
permissionsExceeded={false}
onChange={change}
fullAccessLabel={intl.formatMessage({
defaultMessage: "User has full access to the store",
defaultMessage: "Grant this app full access to the store",
description: "checkbox label"
})}
description={intl.formatMessage({
defaultMessage:
"Expand or restrict user's permissions to access certain part of saleor system.",
"Expand or restrict app permissions to access certain part of Saleor system.",
description: "card description"
})}
/>
<CardSpacer />
<AccountStatus
<AppStatus
data={data}
disabled={disabled}
label={intl.formatMessage({
defaultMessage: "Service account is active",
defaultMessage: "App is active",
description: "checkbox label"
})}
onChange={change}
@ -152,7 +162,6 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
state={saveButtonBarState}
onCancel={onBack}
onSave={submit}
onDelete={onDelete}
/>
</Container>
)}
@ -160,5 +169,5 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
);
};
ServiceDetailsPage.displayName = "ServiceDetailsPage";
export default ServiceDetailsPage;
CustomAppDetailsPage.displayName = "CustomAppDetailsPage";
export default CustomAppDetailsPage;

View file

@ -0,0 +1,2 @@
export * from "./CustomAppDetailsPage";
export { default } from "./CustomAppDetailsPage";

View file

@ -1,24 +1,24 @@
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField";
import { AppErrorFragment } from "@saleor/apps/types/AppErrorFragment";
import CardTitle from "@saleor/components/CardTitle";
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
import { FormChange } from "@saleor/hooks/useForm";
import { getFormErrors } from "@saleor/utils/errors";
import getAccountErrorMessage from "@saleor/utils/errors/account";
import getAppErrorMessage from "@saleor/utils/errors/app";
import React from "react";
import { useIntl } from "react-intl";
export interface ServiceInfoProps {
export interface CustomAppInfoProps {
data: {
name: string;
};
disabled: boolean;
errors: AccountErrorFragment[];
errors: AppErrorFragment[];
onChange: FormChange;
}
const ServiceInfo: React.FC<ServiceInfoProps> = ({
const CustomAppInformation: React.FC<CustomAppInfoProps> = ({
data,
disabled,
errors,
@ -32,7 +32,7 @@ const ServiceInfo: React.FC<ServiceInfoProps> = ({
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Service Account Information",
defaultMessage: "App Information",
description: "header"
})}
/>
@ -41,10 +41,10 @@ const ServiceInfo: React.FC<ServiceInfoProps> = ({
disabled={disabled}
error={!!formErrors.name}
label={intl.formatMessage({
defaultMessage: "Account Name",
description: "service account"
defaultMessage: "App Name",
description: "custom app name"
})}
helperText={getAccountErrorMessage(formErrors.name, intl)}
helperText={getAppErrorMessage(formErrors.name, intl)}
fullWidth
name="name"
value={data.name}
@ -55,5 +55,5 @@ const ServiceInfo: React.FC<ServiceInfoProps> = ({
);
};
ServiceInfo.displayName = "ServiceInfo";
export default ServiceInfo;
CustomAppInformation.displayName = "CustomAppInformation";
export default CustomAppInformation;

View file

@ -0,0 +1,2 @@
export * from "./CustomAppInformation";
export { default } from "./CustomAppInformation";

View file

@ -1,7 +1,6 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import IconButton from "@material-ui/core/IconButton";
import makeStyles from "@material-ui/core/styles/makeStyles";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
@ -10,42 +9,22 @@ import DeleteIcon from "@material-ui/icons/Delete";
import CardTitle from "@saleor/components/CardTitle";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import { ServiceDetailsFragment_tokens } from "@saleor/fragments/types/ServiceDetailsFragment";
import { maybe, renderCollection } from "@saleor/misc";
import { renderCollection } from "@saleor/misc";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface ServiceTokensProps {
tokens: ServiceDetailsFragment_tokens[];
import { AppUpdate_appUpdate_app_tokens } from "../../types/AppUpdate";
import { useStyles } from "./styles";
export interface CustomAppTokensProps {
tokens: Array<AppUpdate_appUpdate_app_tokens | null> | null;
onCreate: () => void;
onDelete: (id: string) => void;
}
const useStyles = makeStyles(
theme => ({
[theme.breakpoints.down("md")]: {
colNote: {
width: 200
}
},
colActions: {
textAlign: "right",
width: 100
},
colKey: {
width: 200
},
colNote: {},
table: {
tableLayout: "fixed"
}
}),
{ name: "ServiceTokens" }
);
const numberOfColumns = 3;
const ServiceTokens: React.FC<ServiceTokensProps> = props => {
const CustomAppTokens: React.FC<CustomAppTokensProps> = props => {
const { tokens, onCreate, onDelete } = props;
const classes = useStyles(props);
const intl = useIntl();
@ -54,7 +33,7 @@ const ServiceTokens: React.FC<ServiceTokensProps> = props => {
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Service Account Information",
defaultMessage: "Tokens",
description: "header"
})}
toolbar={
@ -75,7 +54,7 @@ const ServiceTokens: React.FC<ServiceTokensProps> = props => {
<TableCell className={classes.colKey}>
<FormattedMessage
defaultMessage="Key"
description="service account key"
description="custom app token key"
/>
</TableCell>
<TableCell className={classes.colActions}>
@ -92,13 +71,10 @@ const ServiceTokens: React.FC<ServiceTokensProps> = props => {
token => (
<TableRow key={token ? token.id : "skeleton"}>
<TableCell className={classes.colNote}>
{maybe<React.ReactNode>(() => token.name, <Skeleton />)}
{token?.name || <Skeleton />}
</TableCell>
<TableCell className={classes.colKey}>
{maybe<React.ReactNode>(
() => `**** ${token.authToken}`,
<Skeleton />
)}
{token?.authToken ? `**** ${token.authToken}` : <Skeleton />}
</TableCell>
<TableCell className={classes.colActions}>
<IconButton
@ -124,5 +100,5 @@ const ServiceTokens: React.FC<ServiceTokensProps> = props => {
);
};
ServiceTokens.displayName = "ServiceTokens";
export default ServiceTokens;
CustomAppTokens.displayName = "CustomAppTokens";
export default CustomAppTokens;

View file

@ -0,0 +1,2 @@
export * from "./CustomAppTokens";
export { default } from "./CustomAppTokens";

View file

@ -0,0 +1,23 @@
import makeStyles from "@material-ui/core/styles/makeStyles";
export const useStyles = makeStyles(
theme => ({
[theme.breakpoints.down("md")]: {
colNote: {
width: 200
}
},
colActions: {
textAlign: "right",
width: 100
},
colKey: {
width: 200
},
colNote: {},
table: {
tableLayout: "fixed"
}
}),
{ name: "CustomAppTokens" }
);

View file

@ -0,0 +1,116 @@
import Button from "@material-ui/core/Button";
import CardHeader from "@material-ui/core/CardHeader";
import IconButton from "@material-ui/core/IconButton";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete";
import { commonMessages } from "@saleor/intl";
import { renderCollection, stopPropagation } from "@saleor/misc";
import React from "react";
import { FormattedMessage } from "react-intl";
import { useStyles } from "../../styles";
import { AppsList_apps_edges } from "../../types/AppsList";
import AppsSkeleton from "../AppsSkeleton";
import CardContainer from "../CardContainer";
import DeactivatedText from "../DeactivatedText";
export interface CustomAppsProps {
appsList: AppsList_apps_edges[];
navigateToCustomApp: (id: string) => () => void;
navigateToCustomAppCreate?: () => void;
onRemove: (id: string) => void;
}
const CustomApps: React.FC<CustomAppsProps> = ({
appsList,
navigateToCustomAppCreate,
onRemove,
navigateToCustomApp
}) => {
const classes = useStyles({});
return (
<CardContainer
header={
<>
<CardHeader
className={classes.title}
action={
!!navigateToCustomAppCreate && (
<Button color="primary" onClick={navigateToCustomAppCreate}>
<FormattedMessage
defaultMessage="Create App"
description="create app button"
/>
</Button>
)
}
title={
<Typography
className={classes.title}
variant="h5"
component="span"
>
<FormattedMessage {...commonMessages.customApps} />
</Typography>
}
/>
<hr className={classes.hr} />
</>
}
>
<TableBody>
{renderCollection(
appsList,
(app, index) =>
app ? (
<TableRow
key={app.node.id}
className={classes.tableRow}
onClick={navigateToCustomApp(app.node.id)}
>
<TableCell className={classes.colName}>
<span data-tc="name" className={classes.appName}>
{app.node.name}
</span>
{!app.node.isActive && (
<div className={classes.statusWrapper}>
<DeactivatedText />
</div>
)}
</TableCell>
<TableCell className={classes.colAction}>
<IconButton
color="primary"
onClick={stopPropagation(() => onRemove(app.node.id))}
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
) : (
<AppsSkeleton key={index} />
),
() => (
<TableRow className={classes.tableRow}>
<TableCell className={classes.colName}>
<Typography className={classes.text} variant="body2">
<FormattedMessage
defaultMessage="Your custom created apps will be shown here."
description="custom apps content"
/>
</Typography>
</TableCell>
</TableRow>
)
)}
</TableBody>
</CardContainer>
);
};
CustomApps.displayName = "CustomApps";
export default CustomApps;

View file

@ -0,0 +1,2 @@
export * from "./CustomApps";
export { default } from "./CustomApps";

View file

@ -0,0 +1,19 @@
import Typography from "@material-ui/core/Typography";
import React from "react";
import { FormattedMessage } from "react-intl";
import { useStyles } from "./styles";
export const DeactivatedText: React.FC<{}> = () => {
const classes = useStyles({});
return (
<Typography className={classes.root}>
<FormattedMessage
defaultMessage="Deactivated"
description="app deactivated"
/>
</Typography>
);
};
export default DeactivatedText;

View file

@ -0,0 +1,2 @@
export * from "./DeactivatedText";
export { default } from "./DeactivatedText";

View file

@ -0,0 +1,26 @@
import { makeStyles } from "@material-ui/core/styles";
export const useStyles = makeStyles(
theme => ({
root: {
"&:before": {
backgroundColor: theme.palette.error.main,
borderRadius: "50%",
content: "''",
display: "block",
height: 8,
left: 0,
position: "absolute",
top: "50%",
transform: "translateY(-50%)",
width: 8
},
color: theme.palette.error.main,
display: "inline-block",
marginLeft: theme.spacing(1.5),
paddingLeft: theme.spacing(2),
position: "relative"
}
}),
{ name: "DeactivatedText" }
);

View file

@ -0,0 +1,133 @@
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete";
import CardTitle from "@saleor/components/CardTitle";
import TablePagination from "@saleor/components/TablePagination";
import { renderCollection, stopPropagation } from "@saleor/misc";
import { ListProps } from "@saleor/types";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useStyles } from "../../styles";
import { AppsList_apps_edges } from "../../types/AppsList";
import AppsSkeleton from "../AppsSkeleton";
import CardContainer from "../CardContainer";
import DeactivatedText from "../DeactivatedText";
export interface InstalledAppsProps extends ListProps {
appsList: AppsList_apps_edges[];
onRemove: (id: string) => void;
onSettingsRowClick: (id: string) => () => void;
}
const numberOfColumns = 2;
const InstalledApps: React.FC<InstalledAppsProps> = ({
appsList,
onRemove,
settings,
disabled,
onNextPage,
onPreviousPage,
onRowClick,
onUpdateListSettings,
onSettingsRowClick,
pageInfo,
...props
}) => {
const intl = useIntl();
const classes = useStyles(props);
return (
<CardContainer
header={
<CardTitle
title={intl.formatMessage({
defaultMessage: "Thirdparty Apps",
description: "section header"
})}
/>
}
>
<>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
settings={settings}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
appsList,
(app, index) =>
app ? (
<TableRow
key={app.node.id}
className={classes.tableRow}
onClick={onSettingsRowClick(app.node.id)}
>
<TableCell className={classes.colName}>
<span data-tc="name" className={classes.appName}>
{app.node.name}
</span>
{!app.node.isActive && (
<div className={classes.statusWrapper}>
<DeactivatedText />
</div>
)}
</TableCell>
<TableCell className={classes.colAction}>
<Button
color="primary"
onClick={stopPropagation(onRowClick(app.node.id))}
>
<FormattedMessage
defaultMessage="About"
description="about app"
/>
</Button>
<IconButton
color="primary"
onClick={stopPropagation(() => onRemove(app.node.id))}
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
) : (
<AppsSkeleton key={index} />
),
() => (
<TableRow className={classes.tableRow}>
<TableCell className={classes.colName}>
<Typography className={classes.text} variant="body2">
<FormattedMessage
defaultMessage="You dont have any installed apps in your dashboard"
description="apps content"
/>
</Typography>
</TableCell>
</TableRow>
)
)}
</TableBody>
</>
</CardContainer>
);
};
InstalledApps.displayName = "InstalledApps";
export default InstalledApps;

View file

@ -0,0 +1,2 @@
export * from "./InstalledApps";
export { default } from "./InstalledApps";

View file

@ -0,0 +1,59 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import CardTitle from "@saleor/components/CardTitle";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useStyles } from "../../styles";
interface MarketplaceProps {
link?: () => void;
}
const Marketplace: React.FC<MarketplaceProps> = ({ link }) => {
const intl = useIntl();
const classes = useStyles({});
return (
<div className={classes.appContainer}>
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Saleor Marketplace",
description: "section header"
})}
/>
<CardContent className={classes.marketplaceContent}>
{!!link ? (
<>
<Typography variant="body2">
<FormattedMessage
defaultMessage="Discover great free and paid apps in our Saleor Marketplace."
description="marketplace content"
/>
</Typography>
<Button color="primary" onClick={link}>
<FormattedMessage
defaultMessage="Visit Marketplace"
description="marketplace button"
/>
</Button>
</>
) : (
<Typography variant="body2">
<FormattedMessage
defaultMessage="Marketplace is coming soon"
description="marketplace content"
/>
</Typography>
)}
</CardContent>
</Card>
</div>
);
};
Marketplace.displayName = "Marketplace";
export default Marketplace;

View file

@ -0,0 +1,2 @@
export * from "./Marketplace";
export { default } from "./Marketplace";

View file

@ -4,8 +4,6 @@ import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import Paper from "@material-ui/core/Paper";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import CardSpacer from "@saleor/components/CardSpacer";
@ -19,7 +17,9 @@ import { buttonMessages } from "@saleor/intl";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface ServiceTokenCreateDialogProps {
import { useStyles } from "./styles";
export interface TokenCreateDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
open: boolean;
token: string | undefined;
@ -27,35 +27,15 @@ export interface ServiceTokenCreateDialogProps {
onCreate: (name: string) => void;
}
type ServiceTokenCreateStep = "form" | "summary";
const useStyles = makeStyles(
theme => ({
cancel: {
marginRight: theme.spacing(1)
},
copy: {
marginTop: theme.spacing(),
position: "relative",
right: theme.spacing(1)
},
paper: {
background: fade(theme.palette.primary.main, 0.05),
padding: theme.spacing(2, 3)
}
}),
{
name: "ServiceTokenCreateDialog"
}
);
type TokenCreateStep = "form" | "summary";
function handleCopy(token: string) {
navigator.clipboard.writeText(token);
}
const ServiceTokenCreateDialog: React.FC<ServiceTokenCreateDialogProps> = props => {
const TokenCreateDialog: React.FC<TokenCreateDialogProps> = props => {
const { confirmButtonState, open, token, onClose, onCreate } = props;
const [step, setStep] = React.useState<ServiceTokenCreateStep>("form");
const [step, setStep] = React.useState<TokenCreateStep>("form");
const intl = useIntl();
const classes = useStyles(props);
@ -155,5 +135,5 @@ const ServiceTokenCreateDialog: React.FC<ServiceTokenCreateDialogProps> = props
);
};
ServiceTokenCreateDialog.displayName = "ServiceTokenCreateDialog";
export default ServiceTokenCreateDialog;
TokenCreateDialog.displayName = "TokenCreateDialog";
export default TokenCreateDialog;

View file

@ -0,0 +1,2 @@
export { default } from "./TokenCreateDialog";
export * from "./TokenCreateDialog";

View file

@ -0,0 +1,22 @@
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
export const useStyles = makeStyles(
theme => ({
cancel: {
marginRight: theme.spacing(1)
},
copy: {
marginTop: theme.spacing(),
position: "relative",
right: theme.spacing(1)
},
paper: {
background: fade(theme.palette.primary.main, 0.05),
padding: theme.spacing(2, 3)
}
}),
{
name: "TokenCreateDialog"
}
);

View file

@ -2,11 +2,9 @@ import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import ServiceTokenDeleteDialog, {
ServiceTokenDeleteDialogProps
} from "./ServiceTokenDeleteDialog";
import TokenDeleteDialog, { TokenDeleteDialogProps } from "./TokenDeleteDialog";
const props: ServiceTokenDeleteDialogProps = {
const props: TokenDeleteDialogProps = {
confirmButtonState: "default",
name: "Slack",
onClose: () => undefined,
@ -14,6 +12,6 @@ const props: ServiceTokenDeleteDialogProps = {
open: true
};
storiesOf("Views / Services / Token delete", module)
storiesOf("Views / Apps / Custom app details / Token delete", module)
.addDecorator(Decorator)
.add("default", () => <ServiceTokenDeleteDialog {...props} />);
.add("default", () => <TokenDeleteDialog {...props} />);

View file

@ -4,7 +4,7 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
export interface ServiceTokenDeleteDialogProps {
export interface TokenDeleteDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
open: boolean;
onConfirm: () => void;
@ -12,7 +12,7 @@ export interface ServiceTokenDeleteDialogProps {
name: string;
}
const ServiceTokenDeleteDialog: React.FC<ServiceTokenDeleteDialogProps> = ({
const TokenDeleteDialog: React.FC<TokenDeleteDialogProps> = ({
name,
confirmButtonState,
onClose,
@ -29,7 +29,7 @@ const ServiceTokenDeleteDialog: React.FC<ServiceTokenDeleteDialogProps> = ({
onConfirm={onConfirm}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Service Account",
defaultMessage: "Delete Token",
description: "dialog title"
})}
>
@ -46,5 +46,5 @@ const ServiceTokenDeleteDialog: React.FC<ServiceTokenDeleteDialogProps> = ({
);
};
ServiceTokenDeleteDialog.displayName = "ServiceTokenDeleteDialog";
export default ServiceTokenDeleteDialog;
TokenDeleteDialog.displayName = "TokenDeleteDialog";
export default TokenDeleteDialog;

View file

@ -0,0 +1,2 @@
export * from "./TokenDeleteDialog";
export { default } from "./TokenDeleteDialog";

134
src/apps/fixtures.ts Normal file
View file

@ -0,0 +1,134 @@
import {
AppTypeEnum,
JobStatusEnum,
PermissionEnum
} from "../types/globalTypes";
import { App_app } from "./types/App";
import { AppFetch_appFetchManifest_manifest } from "./types/AppFetch";
import { AppsInstallations_appsInstallations } from "./types/AppsInstallations";
import { AppsList_apps_edges } from "./types/AppsList";
export const appsList: AppsList_apps_edges[] = [
{
__typename: "AppCountableEdge",
node: {
__typename: "App",
id: "QXBwOjE3Ng==",
isActive: true,
name: "app",
type: AppTypeEnum.THIRDPARTY
}
},
{
__typename: "AppCountableEdge",
node: {
__typename: "App",
id: "QXBwOjE3Ng==",
isActive: false,
name: "app1",
type: AppTypeEnum.THIRDPARTY
}
}
];
export const customAppsList: AppsList_apps_edges[] = [
{
__typename: "AppCountableEdge",
node: {
__typename: "App",
id: "QXBwOjE3Ng==",
isActive: true,
name: "app custom",
type: AppTypeEnum.LOCAL
}
}
];
export const appsInProgress: AppsInstallations_appsInstallations[] = [
{
__typename: "AppInstallation",
appName: "app",
id: "QXBwSW5zdGFsbGF0aW9uOjk2",
manifestUrl: "http://localhost:3000/manifest",
message: "Failed to connect to app. Try later or contact with app support.",
status: JobStatusEnum.FAILED
},
{
__typename: "AppInstallation",
appName: "app pending",
id: "QXBwSW5zdGFsbGF0aW9uOjk2",
manifestUrl: "http://localhost:3000/manifest",
message: "Pending.",
status: JobStatusEnum.PENDING
},
{
__typename: "AppInstallation",
appName: "app success",
id: "QXBwSW5zdGFsbGF0aW9uOjk2",
manifestUrl: "http://localhost:3000/manifest",
message: "Success.",
status: JobStatusEnum.SUCCESS
}
];
export const appDetails: App_app = {
__typename: "App",
aboutApp: "Lorem ipsum",
accessToken: "token",
appUrl: "http://localhost:8888/app",
configurationUrl: "htpp://localhost:8888/configuration",
created: "2020-06-02T12:24:26.818138+00:00",
dataPrivacy: "Lorem ipsum",
dataPrivacyUrl: "http://localhost:8888/app-data-privacy",
homepageUrl: "http://localhost:8888/homepage",
id: "QXBwOjE4MQ==",
isActive: true,
metadata: [],
name: "app1",
permissions: [
{
__typename: "Permission",
code: PermissionEnum.MANAGE_ORDERS,
name: "Manage orders."
},
{
__typename: "Permission",
code: PermissionEnum.MANAGE_USERS,
name: "Manage customers."
}
],
privateMetadata: [],
supportUrl: "http://localhost:8888/support",
tokens: [],
type: AppTypeEnum.THIRDPARTY,
version: "1.0.0",
webhooks: []
};
export const installApp: AppFetch_appFetchManifest_manifest = {
__typename: "Manifest",
about: "Lorem ipsum",
appUrl: null,
configurationUrl: null,
dataPrivacy: null,
dataPrivacyUrl: null,
homepageUrl: null,
identifier: "app",
name: "app",
permissions: [
{
__typename: "Permission",
code: PermissionEnum.MANAGE_USERS,
name: "Manage users"
},
{
__typename: "Permission",
code: PermissionEnum.MANAGE_ORDERS,
name: "Manage orders"
}
],
supportUrl: null,
tokenTargetUrl: null,
version: "1.0"
};

118
src/apps/index.tsx Normal file
View file

@ -0,0 +1,118 @@
import { sectionNames } from "@saleor/intl";
import WebhooksRoutes from "@saleor/webhooks";
import { parse as parseQs } from "qs";
import React from "react";
import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { WindowTitle } from "../components/WindowTitle";
import {
AppDetailsUrlQueryParams,
appInstallPath,
AppInstallUrlQueryParams,
AppListUrlQueryParams,
appPath,
appSettingsPath,
appsListPath,
customAppAddPath,
customAppPath,
CustomAppUrlQueryParams
} from "./urls";
import AppDetailsView from "./views/AppDetails";
import AppDetailsSettingsView from "./views/AppDetailsSettings";
import AppInstallView from "./views/AppInstall";
import AppsListView from "./views/AppsList";
import CustomAppCreateView from "./views/CustomAppCreate";
import CustomAppDetailsView from "./views/CustomAppDetails";
const AppDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
match
}) => {
const qs = parseQs(location.search.substr(1));
const params: AppDetailsUrlQueryParams = qs;
return (
<AppDetailsView id={decodeURIComponent(match.params.id)} params={params} />
);
};
const AppDetailsSettings: React.FC<RouteComponentProps<{ id: string }>> = ({
match
}) => <AppDetailsSettingsView id={decodeURIComponent(match.params.id)} />;
const AppInstall: React.FC<RouteComponentProps> = props => {
const qs = parseQs(location.search.substr(1));
const params: AppInstallUrlQueryParams = qs;
return <AppInstallView params={params} {...props} />;
};
interface CustomAppDetailsProps extends RouteComponentProps<{ id: string }> {
token: string;
onTokenClose: () => void;
}
const CustomAppDetails: React.FC<CustomAppDetailsProps> = ({
match,
token,
onTokenClose
}) => {
const qs = parseQs(location.search.substr(1));
const params: CustomAppUrlQueryParams = qs;
return (
<CustomAppDetailsView
id={decodeURIComponent(match.params.id)}
params={params}
token={token}
onTokenClose={onTokenClose}
/>
);
};
const AppsList: React.FC<RouteComponentProps> = () => {
const qs = parseQs(location.search.substr(1));
const params: AppListUrlQueryParams = qs;
return <AppsListView params={params} />;
};
const Component = () => {
const intl = useIntl();
const [token, setToken] = React.useState<string>(null);
return (
<>
<WindowTitle title={intl.formatMessage(sectionNames.apps)} />
<Switch>
<Route exact path={appsListPath} component={AppsList} />
<Route
exact
path={customAppAddPath}
render={() => <CustomAppCreateView setToken={setToken} />}
/>
<Route exact path={appInstallPath} component={AppInstall} />
<Route exact path={appPath(":id")} component={AppDetails} />
<Route
exact
path={appSettingsPath(":id")}
component={AppDetailsSettings}
/>
<Route
exact
path={customAppPath(":id")}
render={props => (
<CustomAppDetails
{...props}
token={token}
onTokenClose={() => setToken(null)}
/>
)}
/>
<WebhooksRoutes />
</Switch>
</>
);
};
export default Component;

269
src/apps/mutations.ts Normal file
View file

@ -0,0 +1,269 @@
import { appFragment } from "@saleor/fragments/apps";
import { appErrorFragment } from "@saleor/fragments/errors";
import { webhooksFragment } from "@saleor/fragments/webhooks";
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import { AppActivate, AppActivateVariables } from "./types/AppActivate";
import { AppCreate, AppCreateVariables } from "./types/AppCreate";
import { AppDeactivate, AppDeactivateVariables } from "./types/AppDeactivate";
import { AppDelete, AppDeleteVariables } from "./types/AppDelete";
import {
AppDeleteFailedInstallation,
AppDeleteFailedInstallationVariables
} from "./types/AppDeleteFailedInstallation";
import { AppFetch, AppFetchVariables } from "./types/AppFetch";
import { AppInstall, AppInstallVariables } from "./types/AppInstall";
import {
AppRetryInstall,
AppRetryInstallVariables
} from "./types/AppRetryInstall";
import {
AppTokenCreate,
AppTokenCreateVariables
} from "./types/AppTokenCreate";
import {
AppTokenDelete,
AppTokenDeleteVariables
} from "./types/AppTokenDelete";
import { AppUpdate, AppUpdateVariables } from "./types/AppUpdate";
export const appCreateMutation = gql`
${appFragment}
${webhooksFragment}
${appErrorFragment}
mutation AppCreate($input: AppInput!) {
appCreate(input: $input) {
authToken
app {
...AppFragment
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appDeleteMutation = gql`
${appFragment}
${webhooksFragment}
${appErrorFragment}
mutation AppDelete($id: ID!) {
appDelete(id: $id) {
app {
...AppFragment
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appDeleteFailedInstallationMutation = gql`
${appErrorFragment}
mutation AppDeleteFailedInstallation($id: ID!) {
appDeleteFailedInstallation(id: $id) {
appInstallation {
id
status
appName
message
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appFetchMutation = gql`
mutation AppFetch($manifestUrl: String!) {
appFetchManifest(manifestUrl: $manifestUrl) {
manifest {
identifier
version
about
name
appUrl
configurationUrl
tokenTargetUrl
dataPrivacy
dataPrivacyUrl
homepageUrl
supportUrl
permissions {
code
name
}
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appInstallMutation = gql`
${appErrorFragment}
mutation AppInstall($input: AppInstallInput!) {
appInstall(input: $input) {
appInstallation {
id
status
appName
manifestUrl
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appRetryInstallMutation = gql`
${appErrorFragment}
mutation AppRetryInstall($id: ID!) {
appRetryInstall(id: $id) {
appInstallation {
id
status
appName
manifestUrl
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appActivateMutation = gql`
${appErrorFragment}
mutation AppActivate($id: ID!) {
appActivate(id: $id) {
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appDeactivateMutation = gql`
${appErrorFragment}
mutation AppDeactivate($id: ID!) {
appDeactivate(id: $id) {
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appUpdateMutation = gql`
${appErrorFragment}
${appFragment}
${webhooksFragment}
mutation AppUpdate($id: ID!, $input: AppInput!) {
appUpdate(id: $id, input: $input) {
app {
...AppFragment
permissions {
code
name
}
}
errors: appErrors {
...AppErrorFragment
message
permissions
}
}
}
`;
export const appTokenCreateMutation = gql`
${appErrorFragment}
mutation AppTokenCreate($input: AppTokenInput!) {
appTokenCreate(input: $input) {
appToken {
name
authToken
id
}
authToken
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const appTokenDeleteMutation = gql`
${appErrorFragment}
mutation AppTokenDelete($id: ID!) {
appTokenDelete(id: $id) {
appToken {
name
authToken
id
}
errors: appErrors {
...AppErrorFragment
}
}
}
`;
export const useAppCreateMutation = makeMutation<AppCreate, AppCreateVariables>(
appCreateMutation
);
export const useAppDeleteMutation = makeMutation<AppDelete, AppDeleteVariables>(
appDeleteMutation
);
export const useAppDeleteFailedInstallationMutation = makeMutation<
AppDeleteFailedInstallation,
AppDeleteFailedInstallationVariables
>(appDeleteFailedInstallationMutation);
export const useAppInstallMutation = makeMutation<
AppInstall,
AppInstallVariables
>(appInstallMutation);
export const useAppRetryInstallMutation = makeMutation<
AppRetryInstall,
AppRetryInstallVariables
>(appRetryInstallMutation);
export const useAppManifestFetchMutation = makeMutation<
AppFetch,
AppFetchVariables
>(appFetchMutation);
export const useAppActivateMutation = makeMutation<
AppActivate,
AppActivateVariables
>(appActivateMutation);
export const useAppDeactivateMutation = makeMutation<
AppDeactivate,
AppDeactivateVariables
>(appDeactivateMutation);
export const useAppUpdateMutation = makeMutation<AppUpdate, AppUpdateVariables>(
appUpdateMutation
);
export const useAppTokenCreateMutation = makeMutation<
AppTokenCreate,
AppTokenCreateVariables
>(appTokenCreateMutation);
export const useAppTokenDeleteMutation = makeMutation<
AppTokenDelete,
AppTokenDeleteVariables
>(appTokenDeleteMutation);

83
src/apps/queries.ts Normal file
View file

@ -0,0 +1,83 @@
import { appFragment } from "@saleor/fragments/apps";
import { webhooksFragment } from "@saleor/fragments/webhooks";
import makeQuery from "@saleor/hooks/makeQuery";
import gql from "graphql-tag";
import { App, AppVariables } from "./types/App";
import { AppsInstallations } from "./types/AppsInstallations";
import { AppsList, AppsListVariables } from "./types/AppsList";
const appsList = gql`
query AppsList(
$before: String
$after: String
$first: Int
$last: Int
$sort: AppSortingInput
$filter: AppFilterInput
) {
apps(
before: $before
after: $after
first: $first
last: $last
sortBy: $sort
filter: $filter
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
name
isActive
type
}
}
}
}
`;
const appsInProgressList = gql`
query AppsInstallations {
appsInstallations {
status
message
appName
manifestUrl
id
}
}
`;
const appDetails = gql`
${appFragment}
${webhooksFragment}
query App($id: ID!) {
app(id: $id) {
...AppFragment
aboutApp
permissions {
code
name
}
dataPrivacy
dataPrivacyUrl
}
}
`;
export const useAppsListQuery = makeQuery<AppsList, AppsListVariables>(
appsList
);
export const useAppsInProgressListQuery = makeQuery<AppsInstallations, {}>(
appsInProgressList
);
export const useAppDetails = makeQuery<App, AppVariables>(appDetails);

204
src/apps/styles.ts Normal file
View file

@ -0,0 +1,204 @@
import { makeStyles } from "@material-ui/core/styles";
export const useStyles = makeStyles(
theme => ({
[theme.breakpoints.up("lg")]: {
colName: {
"&&": {
width: "auto"
}
}
},
alignRight: {
textAlign: "right"
},
appContainer: {
marginBottom: theme.spacing(3)
},
appContent: {
"&:last-child": {
padding: "0!important"
},
padding: 0
},
appHeader: {
marginBottom: theme.spacing(3)
},
appHeaderLinks: {
"& img": {
marginRight: theme.spacing(1)
},
alignItems: "center",
display: "flex",
padding: theme.spacing(2, 0)
},
appName: {
color: theme.palette.primary.main
},
colAction: {
"&&": {
paddingRight: theme.spacing(1),
textAlign: "right"
},
textAlign: "right"
},
colInstallAction: {
"& > *": {
display: "inline-flex"
}
},
colName: {
paddingLeft: 0,
width: theme.spacing(30)
},
colSpinner: {
"& svg": {
textAlign: "right"
},
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(2)
},
customTooltip: {
"& > div": {
backgroundColor: theme.palette.error.main,
borderRadius: theme.spacing(1),
color: theme.palette.primary.contrastText,
padding: theme.spacing(2)
},
padding: "0!important"
},
error: {
"& svg": {
bottom: theme.spacing(0.2),
marginLeft: theme.spacing(0.6),
position: "relative"
},
color: theme.palette.error.main,
margin: theme.spacing(0, 1, 0.7, 0)
},
headerLinkContainer: {
"& span": {
fontWeight: 500
},
alignItems: "center",
display: "flex",
fontSize: theme.spacing(2),
fontWeight: 500,
lineHeight: 1.2,
marginRight: theme.spacing(3),
padding: 0,
textTransform: "none"
},
hr: {
border: "none",
borderTop: `1px solid ${theme.palette.divider}`,
height: 0,
marginBottom: 0,
marginTop: 0,
width: "100%"
},
installAppContainer: {
"& > div": {
position: "relative"
},
"& img": {
position: "relative"
},
display: "flex",
justifyContent: "space-between",
padding: theme.spacing(2, 0),
position: "relative",
width: theme.spacing(35)
},
installCard: {
"&:before": {
backgroundColor: theme.palette.divider,
content: "''",
height: 2,
position: "absolute",
top: "50%",
transform: "translateY(-50%)",
width: theme.spacing(30)
},
display: "flex",
justifyContent: "center",
position: "relative"
},
installIcon: {
alignItems: "center",
backgroundColor: theme.palette.divider,
border: `1px solid ${theme.palette.divider}`,
borderRadius: "50%",
display: "flex",
height: theme.spacing(9),
justifyContent: "center",
overflow: "hidden",
width: theme.spacing(9)
},
installPermissionTitle: {
fontWeight: 500
},
installPrivacyText: {
"& a": {
color: theme.palette.primary.main,
textDecoration: "none"
},
color: theme.palette.text.hint
},
installSaleorIcon: {
backgroundColor: theme.palette.secondary.main,
border: "none"
},
installSpacer: {
margin: theme.spacing(2, 0)
},
installText: {
color: theme.palette.primary.contrastText
},
linkContainer: {
fontWeight: 500,
marginTop: theme.spacing(1.5)
},
marketplaceContent: {
"& button": {
marginTop: theme.spacing(1)
},
"&:last-child": {
padding: theme.spacing(2, 3, 2, 3)
},
padding: theme.spacing(1)
},
permissionsContainer: {
"& li": {
"&:last-child": {
marginBottom: 0
},
marginBottom: theme.spacing(1)
},
paddingLeft: theme.spacing(2)
},
retryBtnCol: {
paddingRight: theme.spacing(1),
width: theme.spacing(14)
},
statusWrapper: {
display: "inline-block",
marginLeft: theme.spacing(2.5)
},
table: {
tableLayout: "fixed"
},
tableRow: {
cursor: "pointer"
},
text: {
color: theme.palette.text.secondary
},
title: {
flex: 1,
fontWeight: 500,
lineHeight: 1
}
}),
{ name: "AppList" }
);

79
src/apps/types/App.ts Normal file
View file

@ -0,0 +1,79 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppTypeEnum, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: App
// ====================================================
export interface App_app_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface App_app_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface App_app_tokens {
__typename: "AppToken";
authToken: string | null;
id: string;
name: string | null;
}
export interface App_app_webhooks_app {
__typename: "App";
id: string;
name: string | null;
}
export interface App_app_webhooks {
__typename: "Webhook";
id: string;
name: string;
isActive: boolean;
app: App_app_webhooks_app;
}
export interface App_app_permissions {
__typename: "Permission";
code: PermissionEnum;
name: string;
}
export interface App_app {
__typename: "App";
id: string;
name: string | null;
created: any | null;
isActive: boolean | null;
type: AppTypeEnum | null;
homepageUrl: string | null;
appUrl: string | null;
configurationUrl: string | null;
supportUrl: string | null;
version: string | null;
accessToken: string | null;
privateMetadata: (App_app_privateMetadata | null)[];
metadata: (App_app_metadata | null)[];
tokens: (App_app_tokens | null)[] | null;
webhooks: (App_app_webhooks | null)[] | null;
aboutApp: string | null;
permissions: (App_app_permissions | null)[] | null;
dataPrivacy: string | null;
dataPrivacyUrl: string | null;
}
export interface App {
app: App_app | null;
}
export interface AppVariables {
id: string;
}

View file

@ -0,0 +1,30 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppActivate
// ====================================================
export interface AppActivate_appActivate_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppActivate_appActivate {
__typename: "AppActivate";
errors: AppActivate_appActivate_errors[];
}
export interface AppActivate {
appActivate: AppActivate_appActivate | null;
}
export interface AppActivateVariables {
id: string;
}

View file

@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppInput, AppTypeEnum, AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppCreate
// ====================================================
export interface AppCreate_appCreate_app_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppCreate_appCreate_app_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppCreate_appCreate_app_tokens {
__typename: "AppToken";
authToken: string | null;
id: string;
name: string | null;
}
export interface AppCreate_appCreate_app_webhooks_app {
__typename: "App";
id: string;
name: string | null;
}
export interface AppCreate_appCreate_app_webhooks {
__typename: "Webhook";
id: string;
name: string;
isActive: boolean;
app: AppCreate_appCreate_app_webhooks_app;
}
export interface AppCreate_appCreate_app {
__typename: "App";
id: string;
name: string | null;
created: any | null;
isActive: boolean | null;
type: AppTypeEnum | null;
homepageUrl: string | null;
appUrl: string | null;
configurationUrl: string | null;
supportUrl: string | null;
version: string | null;
accessToken: string | null;
privateMetadata: (AppCreate_appCreate_app_privateMetadata | null)[];
metadata: (AppCreate_appCreate_app_metadata | null)[];
tokens: (AppCreate_appCreate_app_tokens | null)[] | null;
webhooks: (AppCreate_appCreate_app_webhooks | null)[] | null;
}
export interface AppCreate_appCreate_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppCreate_appCreate {
__typename: "AppCreate";
authToken: string | null;
app: AppCreate_appCreate_app | null;
errors: AppCreate_appCreate_errors[];
}
export interface AppCreate {
appCreate: AppCreate_appCreate | null;
}
export interface AppCreateVariables {
input: AppInput;
}

View file

@ -0,0 +1,30 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppDeactivate
// ====================================================
export interface AppDeactivate_appDeactivate_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppDeactivate_appDeactivate {
__typename: "AppDeactivate";
errors: AppDeactivate_appDeactivate_errors[];
}
export interface AppDeactivate {
appDeactivate: AppDeactivate_appDeactivate | null;
}
export interface AppDeactivateVariables {
id: string;
}

View file

@ -0,0 +1,83 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppTypeEnum, AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppDelete
// ====================================================
export interface AppDelete_appDelete_app_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppDelete_appDelete_app_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppDelete_appDelete_app_tokens {
__typename: "AppToken";
authToken: string | null;
id: string;
name: string | null;
}
export interface AppDelete_appDelete_app_webhooks_app {
__typename: "App";
id: string;
name: string | null;
}
export interface AppDelete_appDelete_app_webhooks {
__typename: "Webhook";
id: string;
name: string;
isActive: boolean;
app: AppDelete_appDelete_app_webhooks_app;
}
export interface AppDelete_appDelete_app {
__typename: "App";
id: string;
name: string | null;
created: any | null;
isActive: boolean | null;
type: AppTypeEnum | null;
homepageUrl: string | null;
appUrl: string | null;
configurationUrl: string | null;
supportUrl: string | null;
version: string | null;
accessToken: string | null;
privateMetadata: (AppDelete_appDelete_app_privateMetadata | null)[];
metadata: (AppDelete_appDelete_app_metadata | null)[];
tokens: (AppDelete_appDelete_app_tokens | null)[] | null;
webhooks: (AppDelete_appDelete_app_webhooks | null)[] | null;
}
export interface AppDelete_appDelete_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppDelete_appDelete {
__typename: "AppDelete";
app: AppDelete_appDelete_app | null;
errors: AppDelete_appDelete_errors[];
}
export interface AppDelete {
appDelete: AppDelete_appDelete | null;
}
export interface AppDeleteVariables {
id: string;
}

View file

@ -0,0 +1,39 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { JobStatusEnum, AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppDeleteFailedInstallation
// ====================================================
export interface AppDeleteFailedInstallation_appDeleteFailedInstallation_appInstallation {
__typename: "AppInstallation";
id: string;
status: JobStatusEnum;
appName: string;
message: string | null;
}
export interface AppDeleteFailedInstallation_appDeleteFailedInstallation_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppDeleteFailedInstallation_appDeleteFailedInstallation {
__typename: "AppDeleteFailedInstallation";
appInstallation: AppDeleteFailedInstallation_appDeleteFailedInstallation_appInstallation | null;
errors: AppDeleteFailedInstallation_appDeleteFailedInstallation_errors[];
}
export interface AppDeleteFailedInstallation {
appDeleteFailedInstallation: AppDeleteFailedInstallation_appDeleteFailedInstallation | null;
}
export interface AppDeleteFailedInstallationVariables {
id: string;
}

View file

@ -0,0 +1,17 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: AppErrorFragment
// ====================================================
export interface AppErrorFragment {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}

View file

@ -0,0 +1,53 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { PermissionEnum, AppErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppFetch
// ====================================================
export interface AppFetch_appFetchManifest_manifest_permissions {
__typename: "Permission";
code: PermissionEnum;
name: string;
}
export interface AppFetch_appFetchManifest_manifest {
__typename: "Manifest";
identifier: string;
version: string;
about: string | null;
name: string;
appUrl: string | null;
configurationUrl: string | null;
tokenTargetUrl: string | null;
dataPrivacy: string | null;
dataPrivacyUrl: string | null;
homepageUrl: string | null;
supportUrl: string | null;
permissions: (AppFetch_appFetchManifest_manifest_permissions | null)[] | null;
}
export interface AppFetch_appFetchManifest_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppFetch_appFetchManifest {
__typename: "AppFetchManifest";
manifest: AppFetch_appFetchManifest_manifest | null;
errors: AppFetch_appFetchManifest_errors[];
}
export interface AppFetch {
appFetchManifest: AppFetch_appFetchManifest | null;
}
export interface AppFetchVariables {
manifestUrl: string;
}

View file

@ -0,0 +1,61 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppTypeEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: AppFragment
// ====================================================
export interface AppFragment_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppFragment_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppFragment_tokens {
__typename: "AppToken";
authToken: string | null;
id: string;
name: string | null;
}
export interface AppFragment_webhooks_app {
__typename: "App";
id: string;
name: string | null;
}
export interface AppFragment_webhooks {
__typename: "Webhook";
id: string;
name: string;
isActive: boolean;
app: AppFragment_webhooks_app;
}
export interface AppFragment {
__typename: "App";
id: string;
name: string | null;
created: any | null;
isActive: boolean | null;
type: AppTypeEnum | null;
homepageUrl: string | null;
appUrl: string | null;
configurationUrl: string | null;
supportUrl: string | null;
version: string | null;
accessToken: string | null;
privateMetadata: (AppFragment_privateMetadata | null)[];
metadata: (AppFragment_metadata | null)[];
tokens: (AppFragment_tokens | null)[] | null;
webhooks: (AppFragment_webhooks | null)[] | null;
}

View file

@ -0,0 +1,39 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppInstallInput, JobStatusEnum, AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppInstall
// ====================================================
export interface AppInstall_appInstall_appInstallation {
__typename: "AppInstallation";
id: string;
status: JobStatusEnum;
appName: string;
manifestUrl: string;
}
export interface AppInstall_appInstall_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppInstall_appInstall {
__typename: "AppInstall";
appInstallation: AppInstall_appInstall_appInstallation | null;
errors: AppInstall_appInstall_errors[];
}
export interface AppInstall {
appInstall: AppInstall_appInstall | null;
}
export interface AppInstallVariables {
input: AppInstallInput;
}

View file

@ -0,0 +1,39 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { JobStatusEnum, AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppRetryInstall
// ====================================================
export interface AppRetryInstall_appRetryInstall_appInstallation {
__typename: "AppInstallation";
id: string;
status: JobStatusEnum;
appName: string;
manifestUrl: string;
}
export interface AppRetryInstall_appRetryInstall_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppRetryInstall_appRetryInstall {
__typename: "AppRetryInstall";
appInstallation: AppRetryInstall_appRetryInstall_appInstallation | null;
errors: AppRetryInstall_appRetryInstall_errors[];
}
export interface AppRetryInstall {
appRetryInstall: AppRetryInstall_appRetryInstall | null;
}
export interface AppRetryInstallVariables {
id: string;
}

View file

@ -0,0 +1,39 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppTokenInput, AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppTokenCreate
// ====================================================
export interface AppTokenCreate_appTokenCreate_appToken {
__typename: "AppToken";
name: string | null;
authToken: string | null;
id: string;
}
export interface AppTokenCreate_appTokenCreate_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppTokenCreate_appTokenCreate {
__typename: "AppTokenCreate";
appToken: AppTokenCreate_appTokenCreate_appToken | null;
authToken: string | null;
errors: AppTokenCreate_appTokenCreate_errors[];
}
export interface AppTokenCreate {
appTokenCreate: AppTokenCreate_appTokenCreate | null;
}
export interface AppTokenCreateVariables {
input: AppTokenInput;
}

View file

@ -0,0 +1,38 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppErrorCode, PermissionEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppTokenDelete
// ====================================================
export interface AppTokenDelete_appTokenDelete_appToken {
__typename: "AppToken";
name: string | null;
authToken: string | null;
id: string;
}
export interface AppTokenDelete_appTokenDelete_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppTokenDelete_appTokenDelete {
__typename: "AppTokenDelete";
appToken: AppTokenDelete_appTokenDelete_appToken | null;
errors: AppTokenDelete_appTokenDelete_errors[];
}
export interface AppTokenDelete {
appTokenDelete: AppTokenDelete_appTokenDelete | null;
}
export interface AppTokenDeleteVariables {
id: string;
}

View file

@ -0,0 +1,91 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppInput, AppTypeEnum, PermissionEnum, AppErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AppUpdate
// ====================================================
export interface AppUpdate_appUpdate_app_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppUpdate_appUpdate_app_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface AppUpdate_appUpdate_app_tokens {
__typename: "AppToken";
authToken: string | null;
id: string;
name: string | null;
}
export interface AppUpdate_appUpdate_app_webhooks_app {
__typename: "App";
id: string;
name: string | null;
}
export interface AppUpdate_appUpdate_app_webhooks {
__typename: "Webhook";
id: string;
name: string;
isActive: boolean;
app: AppUpdate_appUpdate_app_webhooks_app;
}
export interface AppUpdate_appUpdate_app_permissions {
__typename: "Permission";
code: PermissionEnum;
name: string;
}
export interface AppUpdate_appUpdate_app {
__typename: "App";
id: string;
name: string | null;
created: any | null;
isActive: boolean | null;
type: AppTypeEnum | null;
homepageUrl: string | null;
appUrl: string | null;
configurationUrl: string | null;
supportUrl: string | null;
version: string | null;
accessToken: string | null;
privateMetadata: (AppUpdate_appUpdate_app_privateMetadata | null)[];
metadata: (AppUpdate_appUpdate_app_metadata | null)[];
tokens: (AppUpdate_appUpdate_app_tokens | null)[] | null;
webhooks: (AppUpdate_appUpdate_app_webhooks | null)[] | null;
permissions: (AppUpdate_appUpdate_app_permissions | null)[] | null;
}
export interface AppUpdate_appUpdate_errors {
__typename: "AppError";
field: string | null;
message: string | null;
code: AppErrorCode;
permissions: PermissionEnum[] | null;
}
export interface AppUpdate_appUpdate {
__typename: "AppUpdate";
app: AppUpdate_appUpdate_app | null;
errors: AppUpdate_appUpdate_errors[];
}
export interface AppUpdate {
appUpdate: AppUpdate_appUpdate | null;
}
export interface AppUpdateVariables {
id: string;
input: AppInput;
}

View file

@ -0,0 +1,22 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { JobStatusEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: AppsInstallations
// ====================================================
export interface AppsInstallations_appsInstallations {
__typename: "AppInstallation";
status: JobStatusEnum;
message: string | null;
appName: string;
manifestUrl: string;
id: string;
}
export interface AppsInstallations {
appsInstallations: AppsInstallations_appsInstallations[];
}

View file

@ -0,0 +1,50 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { AppSortingInput, AppFilterInput, AppTypeEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL query operation: AppsList
// ====================================================
export interface AppsList_apps_pageInfo {
__typename: "PageInfo";
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string | null;
endCursor: string | null;
}
export interface AppsList_apps_edges_node {
__typename: "App";
id: string;
name: string | null;
isActive: boolean | null;
type: AppTypeEnum | null;
}
export interface AppsList_apps_edges {
__typename: "AppCountableEdge";
node: AppsList_apps_edges_node;
}
export interface AppsList_apps {
__typename: "AppCountableConnection";
pageInfo: AppsList_apps_pageInfo;
totalCount: number | null;
edges: AppsList_apps_edges[];
}
export interface AppsList {
apps: AppsList_apps | null;
}
export interface AppsListVariables {
before?: string | null;
after?: string | null;
first?: number | null;
last?: number | null;
sort?: AppSortingInput | null;
filter?: AppFilterInput | null;
}

59
src/apps/urls.ts Normal file
View file

@ -0,0 +1,59 @@
import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join";
import { ActiveTab, Dialog, Pagination, SingleAction } from "../types";
export const MANIFEST_ATTR = "manifestUrl";
export type AppListUrlDialog = "remove" | "remove-app" | "remove-custom-app";
export type AppDetailsUrlDialog = "app-activate" | "app-deactivate";
export type AppListUrlQueryParams = ActiveTab &
Dialog<AppListUrlDialog> &
SingleAction &
Pagination;
export type AppDetailsUrlQueryParams = Dialog<AppDetailsUrlDialog> &
SingleAction;
export type AppInstallUrlQueryParams = Partial<{ [MANIFEST_ATTR]: string }>;
export enum AppListUrlSortField {
name = "name",
active = "active"
}
export type CustomAppUrlDialog =
| "create-token"
| "remove-webhook"
| "remove-token";
export type CustomAppUrlQueryParams = Dialog<CustomAppUrlDialog> & SingleAction;
export const appsSection = "/apps/";
export const appsListPath = appsSection;
export const customAppListPath = "/apps/custom/";
export const appPath = (id: string) => urlJoin(appsSection, id);
export const appSettingsPath = (id: string) =>
urlJoin(appsSection, id, "settings");
export const customAppPath = (id: string) => urlJoin(customAppListPath, id);
export const appInstallPath = urlJoin(appsSection, "install");
export const appInstallUrl = appInstallPath;
export const appUrl = (id: string, params?: AppDetailsUrlQueryParams) =>
appPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
export const appSettingsUrl = (id: string, params?: AppDetailsUrlQueryParams) =>
urlJoin(appPath(encodeURIComponent(id)), "settings") +
"?" +
stringifyQs(params);
export const customAppUrl = (id: string, params?: CustomAppUrlQueryParams) =>
customAppPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
export const customAppAddPath = urlJoin(customAppListPath, "add");
export const customAppAddUrl = customAppAddPath;
export const appsListUrl = (params?: AppListUrlQueryParams) =>
appsListPath + "?" + stringifyQs(params);

View file

@ -0,0 +1,125 @@
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import getAppErrorMessage from "@saleor/utils/errors/app";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React from "react";
import { useIntl } from "react-intl";
import AppActivateDialog from "../../components/AppActivateDialog";
import AppDeactivateDialog from "../../components/AppDeactivateDialog";
import AppDetailsPage from "../../components/AppDetailsPage";
import {
useAppActivateMutation,
useAppDeactivateMutation
} from "../../mutations";
import { useAppDetails } from "../../queries";
import {
AppDetailsUrlDialog,
AppDetailsUrlQueryParams,
appSettingsUrl,
appsListPath,
appUrl
} from "../../urls";
interface AppDetailsProps {
id: string;
params: AppDetailsUrlQueryParams;
}
export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
const { data, loading, refetch } = useAppDetails({
displayLoader: true,
variables: { id }
});
const navigate = useNavigator();
const notify = useNotifier();
const intl = useIntl();
const mutationOpts = { variables: { id } };
const [activateApp, activateAppResult] = useAppActivateMutation({
onCompleted: data => {
const errors = data?.appActivate?.errors;
if (errors?.length === 0) {
notify({
status: "success",
text: intl.formatMessage({
defaultMessage: "App activated",
description: "snackbar text"
})
});
refetch();
closeModal();
} else {
errors.forEach(error =>
notify({
status: "error",
text: getAppErrorMessage(error, intl)
})
);
}
}
});
const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({
onCompleted: data => {
const errors = data?.appDeactivate?.errors;
if (errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage({
defaultMessage: "App deactivated",
description: "snackbar text"
})
});
refetch();
closeModal();
} else {
errors.forEach(error =>
notify({
status: "error",
text: getAppErrorMessage(error, intl)
})
);
}
}
});
const [openModal, closeModal] = createDialogActionHandlers<
AppDetailsUrlDialog,
AppDetailsUrlQueryParams
>(navigate, params => appUrl(id, params), params);
const handleActivateConfirm = () => {
activateApp(mutationOpts);
};
const handleDeactivateConfirm = () => {
deactivateApp(mutationOpts);
};
return (
<>
<AppActivateDialog
confirmButtonState={activateAppResult.status}
name={data?.app.name}
onClose={closeModal}
onConfirm={handleActivateConfirm}
open={params.action === "app-activate"}
/>
<AppDeactivateDialog
confirmButtonState={deactivateAppResult.status}
name={data?.app.name}
onClose={closeModal}
onConfirm={handleDeactivateConfirm}
open={params.action === "app-deactivate"}
/>
<AppDetailsPage
data={data?.app}
loading={loading}
navigateToAppSettings={() => navigate(appSettingsUrl(id))}
onAppActivateOpen={() => openModal("app-activate")}
onAppDeactivateOpen={() => openModal("app-deactivate")}
onBack={() => navigate(appsListPath)}
/>
</>
);
};
export default AppDetails;

View file

@ -0,0 +1,2 @@
export * from "./AppDetails";
export { default } from "./AppDetails";

View file

@ -0,0 +1,46 @@
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop";
import React from "react";
import { useIntl } from "react-intl";
import AppDetailsSettingsPage from "../../components/AppDetailsSettingsPage";
import { useAppDetails } from "../../queries";
import { appsListPath, appUrl } from "../../urls";
interface AppDetailsSetttingsProps {
id: string;
}
export const AppDetailsSettings: React.FC<AppDetailsSetttingsProps> = ({
id
}) => {
const shop = useShop();
const { data } = useAppDetails({
displayLoader: true,
variables: { id }
});
const navigate = useNavigator();
const notify = useNotifier();
const intl = useIntl();
return (
<AppDetailsSettingsPage
backendHost={shop?.domain.host}
data={data?.app}
navigateToDashboard={() => navigate(appUrl(id))}
onBack={() => navigate(appsListPath)}
onError={() =>
notify({
status: "error",
text: intl.formatMessage({
defaultMessage: "Failed to fetch app settings",
description: "app settings error"
})
})
}
/>
);
};
export default AppDetailsSettings;

View file

@ -0,0 +1,2 @@
export * from "./AppDetailsSettings";
export { default } from "./AppDetailsSettings";

Some files were not shown because too many files have changed in this diff Show more