commit
6a10582c6c
333 changed files with 21241 additions and 8778 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -19,8 +19,8 @@
|
|||
!.tx
|
||||
*.css
|
||||
*.log
|
||||
*.pot
|
||||
*.pyc
|
||||
*.mo
|
||||
local_settings.py
|
||||
__pycache__/
|
||||
build/
|
||||
|
|
|
@ -9,3 +9,4 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Fix configure menu section - #109 by @benekex2
|
||||
- Add switch to make attribute available in product list as a column - #99 by @dominik-zeglen
|
||||
- Add tc tags for E2E testing - #134 by @dominik-zeglen
|
||||
- Use react-intl - #105 by @dominik-zeglen
|
||||
|
|
48
babel.config.js
Normal file
48
babel.config.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
module.exports = api => {
|
||||
const isTest = api.env("test");
|
||||
const isStorybook = api.env("storybook");
|
||||
|
||||
const ignore =
|
||||
isTest || isStorybook
|
||||
? []
|
||||
: ["**/*.test.ts", "**/*.test.tsx", "src/storybook"];
|
||||
|
||||
const presets = [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
corejs: "3.2.1",
|
||||
modules: isTest ? "auto" : false,
|
||||
useBuiltIns: "usage"
|
||||
}
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"
|
||||
];
|
||||
|
||||
const plugins = [
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
decoratorsBeforeExport: true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"react-intl-auto",
|
||||
[
|
||||
"react-intl",
|
||||
{
|
||||
extractFromFormatMessageCall: true,
|
||||
messagesDir: "build/locale/"
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
return {
|
||||
presets,
|
||||
plugins,
|
||||
ignore
|
||||
};
|
||||
};
|
8316
locale/messages.pot
Normal file
8316
locale/messages.pot
Normal file
File diff suppressed because it is too large
Load diff
308
package-lock.json
generated
308
package-lock.json
generated
|
@ -97,6 +97,24 @@
|
|||
"@apollographql/graphql-language-service-types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/cli": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.5.5.tgz",
|
||||
"integrity": "sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": "^2.0.4",
|
||||
"commander": "^2.8.1",
|
||||
"convert-source-map": "^1.1.0",
|
||||
"fs-readdir-recursive": "^1.1.0",
|
||||
"glob": "^7.0.0",
|
||||
"lodash": "^4.17.13",
|
||||
"mkdirp": "^0.5.1",
|
||||
"output-file-sync": "^2.0.0",
|
||||
"slash": "^2.0.0",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
|
@ -435,6 +453,16 @@
|
|||
"@babel/plugin-syntax-json-strings": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-numeric-separator": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.2.0.tgz",
|
||||
"integrity": "sha512-DohMOGDrZiMKS7LthjUZNNcWl8TAf5BZDwZAH4wpm55FuJTHgfqPGdibg7rZDmont/8Yg0zA03IgT6XLeP+4sg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/plugin-syntax-numeric-separator": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-object-rest-spread": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz",
|
||||
|
@ -520,6 +548,15 @@
|
|||
"@babel/helper-plugin-utils": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-numeric-separator": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.2.0.tgz",
|
||||
"integrity": "sha512-DroeVNkO/BnGpL2R7+ZNZqW+E24aR/4YWxP3Qb15d6lPU8KDzF8HlIUIRCOJRn4X77/oyW4mJY+7FHfY82NLtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-object-rest-spread": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
|
||||
|
@ -1225,6 +1262,11 @@
|
|||
"tslib": "^1"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-relativetimeformat": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-2.6.3.tgz",
|
||||
"integrity": "sha512-sb3PcbTSNQfnL4HM2XKedt8Oopf2EwaoxyjeMbgvje6x1zQuf1oTAidLHT65Dkox7hfSql8ZjpAqWqnHJsLN9w=="
|
||||
},
|
||||
"@heroku-cli/color": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@heroku-cli/color/-/color-1.1.14.tgz",
|
||||
|
@ -2820,11 +2862,10 @@
|
|||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/i18next": {
|
||||
"version": "8.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/i18next/-/i18next-8.4.6.tgz",
|
||||
"integrity": "sha512-ZiSCqW8j9/gQCYixz1nMhyCprSGh3rwdyX+FHAzEN+bMCmc7yCYjNutl6jvMYSxSIGeL0CLEXPM8Nlk2lE0t5w==",
|
||||
"dev": true
|
||||
"@types/invariant": {
|
||||
"version": "2.2.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.30.tgz",
|
||||
"integrity": "sha512-98fB+yo7imSD2F7PF7GIpELNgtLNgo5wjivu0W5V4jx+KVVJxo6p/qN4zdzSTBWy4/sN3pPyXwnhRSD28QX+ag=="
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.1",
|
||||
|
@ -4827,6 +4868,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"babel-plugin-react-intl": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-4.1.12.tgz",
|
||||
"integrity": "sha512-dCw7LjfDCS03ugW0Oz1XG8J/OgAnEDsKqcyfzlDb4JL6aLiYz87XvM9sIdbMAdPZXaHLhj9GyK8CueyMIm66NQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@types/babel__core": "^7.1.2",
|
||||
"fs-extra": "^8.0.1",
|
||||
"intl-messageformat-parser": "^3.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-plugin-react-intl-auto": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-react-intl-auto/-/babel-plugin-react-intl-auto-2.2.0.tgz",
|
||||
"integrity": "sha512-L6IS4NQCr+uGw8yOJ+tBfm5R0UhrM2mZyhN+X7jCsnEhTcWopkWe7geLm7AzJC2SWFXnr7phwXlJbN4erwKRrA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.5.5",
|
||||
"murmurhash3js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"babel-plugin-syntax-async-functions": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
|
||||
|
@ -7126,9 +7203,10 @@
|
|||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
|
||||
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
|
||||
"dev": true
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.2.0",
|
||||
|
@ -8893,6 +8971,13 @@
|
|||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"figgy-pudding": {
|
||||
|
@ -9284,6 +9369,12 @@
|
|||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
"integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
|
||||
"dev": true
|
||||
},
|
||||
"fs-write-stream-atomic": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
|
||||
|
@ -9899,6 +9990,15 @@
|
|||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"gettext-parser": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.1.0.tgz",
|
||||
"integrity": "sha1-LFpmONiTk0ubVQN9CtgstwBLJnk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"encoding": "^0.1.11"
|
||||
}
|
||||
},
|
||||
"git-parse": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/git-parse/-/git-parse-1.0.3.tgz",
|
||||
|
@ -10247,6 +10347,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"has-color": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
|
||||
"integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
|
@ -10809,21 +10915,6 @@
|
|||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
|
||||
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
|
||||
},
|
||||
"i18next": {
|
||||
"version": "11.10.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-11.10.2.tgz",
|
||||
"integrity": "sha512-1rowdX8PqrvsdFhYb3v0A/LlIHLQL1HTa4ia29IzhvNAg2fesNV7R1jXibWLmLQdz3FfTB8RuqSqDEjIawXruA=="
|
||||
},
|
||||
"i18next-browser-languagedetector": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.4.tgz",
|
||||
"integrity": "sha512-wPbtH18FdOuB245I8Bhma5/XSDdN/HpYlX+wga1eMy+slhaFQSnrWX6fp+aYSL2eEuj0RlfHeEVz6Fo/lxAj6A=="
|
||||
},
|
||||
"i18next-xhr-backend": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-1.5.1.tgz",
|
||||
"integrity": "sha512-9OLdC/9YxDvTFcgsH5t2BHCODHEotHCa6h7Ly0EUlUC7Y2GS09UeoHOGj3gWKQ3HCqXz8NlH4gOrK3NNc9vPuw=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
@ -11103,6 +11194,30 @@
|
|||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
|
||||
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
|
||||
},
|
||||
"intl-format-cache": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-4.1.10.tgz",
|
||||
"integrity": "sha512-7NqorxPNPuhbtwlXe71Dbjh9NlLvkoUymCI2AS/cyIsG7wYr27x9E/h4P16vftHwsTOjiDIjM/oGukddxgcz3A=="
|
||||
},
|
||||
"intl-locales-supported": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/intl-locales-supported/-/intl-locales-supported-1.4.5.tgz",
|
||||
"integrity": "sha512-D7oriM5x46rd7kNlSW0f9noIBegFr3ReIM6xlMpwH4lfIPD/zvBelPlCjR10IK16boGJG9lKccOvRAM8wzpbrA=="
|
||||
},
|
||||
"intl-messageformat": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-6.1.5.tgz",
|
||||
"integrity": "sha512-Ae/3PwnShCkDtnPvx9FLlBj1xiooa5NeeFZBizOQZZ/iaLt8IvgkPCdadkOF3f++FWDOBVtP5RszhMkJQKqmng==",
|
||||
"requires": {
|
||||
"intl-format-cache": "^4.1.10",
|
||||
"intl-messageformat-parser": "^3.0.7"
|
||||
}
|
||||
},
|
||||
"intl-messageformat-parser": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-3.0.7.tgz",
|
||||
"integrity": "sha512-L16VbbV3NFaiZV65XwOIH9fBe52TS2EkOR0k8Y4ratsgTE7KPEbcUCUrz/iEQwJo7BcWY4ohkZbeYZRgAiPR1Q=="
|
||||
},
|
||||
"into-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
|
||||
|
@ -13800,6 +13915,12 @@
|
|||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
|
||||
"dev": true
|
||||
},
|
||||
"murmurhash3js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/murmurhash3js/-/murmurhash3js-3.0.1.tgz",
|
||||
"integrity": "sha1-Ppg+W0fCoG9DpxMXTn5DXKBEuZg=",
|
||||
"dev": true
|
||||
},
|
||||
"mustache": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz",
|
||||
|
@ -14100,6 +14221,41 @@
|
|||
"integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"nomnom": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz",
|
||||
"integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "~0.4.0",
|
||||
"underscore": "~1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz",
|
||||
"integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
|
||||
"integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "~1.0.0",
|
||||
"has-color": "~0.1.0",
|
||||
"strip-ansi": "~0.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz",
|
||||
"integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
|
@ -14551,6 +14707,17 @@
|
|||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"output-file-sync": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz",
|
||||
"integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.11",
|
||||
"is-plain-obj": "^1.1.0",
|
||||
"mkdirp": "^0.5.1"
|
||||
}
|
||||
},
|
||||
"p-cancelable": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
|
||||
|
@ -15110,6 +15277,16 @@
|
|||
"ts-pnp": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"po2json": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
|
||||
"integrity": "sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"gettext-parser": "1.1.0",
|
||||
"nomnom": "1.8.1"
|
||||
}
|
||||
},
|
||||
"polished": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/polished/-/polished-3.4.1.tgz",
|
||||
|
@ -16077,6 +16254,47 @@
|
|||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"react-intl": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-3.1.8.tgz",
|
||||
"integrity": "sha512-kUQvTfDvMpgvwDldsmTy/XQPPgaSquh3+mL0iEspKkGqtAM9J9p25jNy8+cP0w/Y5LfWQFniUjlEdCDznoTZ/Q==",
|
||||
"requires": {
|
||||
"@formatjs/intl-relativetimeformat": "^2.6.3",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/invariant": "^2.2.30",
|
||||
"@types/react": "^16.0.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"intl-format-cache": "^4.1.10",
|
||||
"intl-locales-supported": "^1.4.5",
|
||||
"intl-messageformat": "^6.1.5",
|
||||
"intl-messageformat-parser": "^3.0.7",
|
||||
"invariant": "^2.1.1",
|
||||
"react": "^16.3.0",
|
||||
"shallow-equal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"react-intl-po": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-intl-po/-/react-intl-po-2.2.2.tgz",
|
||||
"integrity": "sha512-BvW4gyohm4Ac0sSN6dBJB3+PeIjsns+IE9JqT/7NolBRGNc+fCKzWa5e1eZdhu9nY+7W1t0sWqhrt+LXgXb6Iw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.3.2",
|
||||
"commander": "^2.15.1",
|
||||
"glob": "^7.1.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"po2json": "^0.4.5",
|
||||
"ramda": "^0.25.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ramda": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
|
||||
"integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz",
|
||||
|
@ -16848,9 +17066,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.0.tgz",
|
||||
"integrity": "sha512-4Liqw7ccABzsWV5BzeZeGRSq7KWIgQYzOcmRDEwSX4WAawlQpcAFXZ1Kid72XYrjSnK5yxOS6Gez/iGusYE/Pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
|
@ -17243,8 +17461,7 @@
|
|||
"shallow-equal": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.0.tgz",
|
||||
"integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA=="
|
||||
},
|
||||
"shallowequal": {
|
||||
"version": "1.1.0",
|
||||
|
@ -18881,6 +19098,14 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"pify": {
|
||||
|
@ -19022,6 +19247,14 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dedent": {
|
||||
|
@ -19375,19 +19608,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ts-loader": {
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz",
|
||||
"integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.3.0",
|
||||
"enhanced-resolve": "^4.0.0",
|
||||
"loader-utils": "^1.0.2",
|
||||
"micromatch": "^3.1.4",
|
||||
"semver": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz",
|
||||
|
@ -19603,6 +19823,12 @@
|
|||
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
|
||||
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
|
||||
"dev": true
|
||||
},
|
||||
"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",
|
||||
|
|
43
package.json
43
package.json
|
@ -39,9 +39,6 @@
|
|||
"fuzzaldrin": "^2.1.0",
|
||||
"graphql": "^14.4.2",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"i18next": "^11.10.2",
|
||||
"i18next-browser-languagedetector": "^2.2.4",
|
||||
"i18next-xhr-backend": "^1.5.1",
|
||||
"is-url": "^1.2.4",
|
||||
"jss": "^9.8.7",
|
||||
"keycode": "^2.2.0",
|
||||
|
@ -56,6 +53,7 @@
|
|||
"react-helmet": "^5.2.1",
|
||||
"react-infinite-scroller": "^1.2.4",
|
||||
"react-inlinesvg": "^0.8.4",
|
||||
"react-intl": "^3.1.8",
|
||||
"react-jss": "^8.6.1",
|
||||
"react-moment": "^0.7.9",
|
||||
"react-router": "^5.0.1",
|
||||
|
@ -70,12 +68,15 @@
|
|||
"use-react-router": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.5.5",
|
||||
"@babel/core": "^7.5.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.2.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
|
||||
"@babel/preset-env": "^7.5.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@babel/runtime": "^7.5.4",
|
||||
"@storybook/addon-storyshots": "^5.1.9",
|
||||
"@storybook/react": "^5.1.9",
|
||||
|
@ -84,7 +85,6 @@
|
|||
"@types/draft-js": "^0.10.34",
|
||||
"@types/enzyme": "^3.10.2",
|
||||
"@types/fuzzaldrin": "^2.1.2",
|
||||
"@types/i18next": "^8.4.6",
|
||||
"@types/jest": "^23.3.14",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@types/moment-timezone": "^0.5.12",
|
||||
|
@ -104,7 +104,10 @@
|
|||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-react-intl": "^4.1.12",
|
||||
"babel-plugin-react-intl-auto": "^2.2.0",
|
||||
"codecov": "^3.5.0",
|
||||
"core-js": "^3.2.1",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.14.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
|
@ -114,11 +117,12 @@
|
|||
"jest": "^24.8.0",
|
||||
"jest-file": "^1.0.0",
|
||||
"plop": "^2.4.0",
|
||||
"react-intl-po": "^2.2.2",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"regenerator-runtime": "^0.11.1",
|
||||
"rimraf": "^2.7.0",
|
||||
"testcafe": "^1.3.3",
|
||||
"ts-jest": "^23.10.5",
|
||||
"ts-loader": "^5.4.5",
|
||||
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
||||
"tslint": "^5.18.0",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
|
@ -130,30 +134,9 @@
|
|||
"optionalDependencies": {
|
||||
"fsevents": "^1.2.9"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"decoratorsBeforeExport": true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-object-rest-spread"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsConfig": "tsconfig.json"
|
||||
}
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest",
|
||||
"^.+\\.(jsx?|tsx?)$": "babel-jest",
|
||||
"^.+\\.(png|svg|jpe?g)$": "jest-file"
|
||||
},
|
||||
"testRegex": ".*\\.test\\.tsx?$",
|
||||
|
@ -173,11 +156,15 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "webpack -p",
|
||||
"extract-json-messages": "rimraf build/locale && babel src 'src/**/*.{ts,tsx}' -o build/dashboard.bundle.js",
|
||||
"extract-pot-messages": "rip json2pot \"build/locale/**/*.json\" -c description -o locale/messages.pot",
|
||||
"extract-messages": "npm run extract-json-messages && npm run extract-pot-messages",
|
||||
"build-messages": "rip po2json 'locale/**/*.po' -m 'build/locale/**/*.json' -o 'locale' -c 'description'",
|
||||
"build-types": "apollo client:codegen --target=typescript types --globalTypesFile=src/types/globalTypes.ts",
|
||||
"generate-component": "plop --plopfile .plop/plopfile.js",
|
||||
"lint": "tslint 'src/**/*.{ts,tsx}'",
|
||||
"lint-fix": "tslint 'src/**/*.{ts,tsx}' --fix",
|
||||
"start": "webpack-dev-server --open",
|
||||
"start": "webpack-dev-server --open -d",
|
||||
"storybook": "start-storybook -p 3000 -c src/storybook/",
|
||||
"build-storybook": "build-storybook -c src/storybook/ -o build/storybook",
|
||||
"test": "jest src/",
|
||||
|
|
54
react-intl.d.ts
vendored
Normal file
54
react-intl.d.ts
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
declare module "react-intl" {
|
||||
import * as ReactIntl from "node_modules/react-intl";
|
||||
export * from "node_modules/react-intl";
|
||||
|
||||
export interface MessageDescriptor {
|
||||
description?: string;
|
||||
defaultMessage: string;
|
||||
id?: string;
|
||||
}
|
||||
type Messages<Names extends keyof any = string> = Record<
|
||||
Names,
|
||||
MessageDescriptor
|
||||
>;
|
||||
type PrimitiveType = string | number | boolean | null | undefined | Date;
|
||||
type FormatXMLElementFn = (...args: any[]) => string | object;
|
||||
export interface IntlFormatters
|
||||
extends Omit<ReactIntl.IntlFormatters, "formatMessage"> {
|
||||
formatMessage(
|
||||
descriptor: MessageDescriptor,
|
||||
values?: Record<string, PrimitiveType>
|
||||
): string;
|
||||
formatMessage(
|
||||
descriptor: MessageDescriptor,
|
||||
values?: Record<
|
||||
string,
|
||||
PrimitiveType | React.ReactElement | FormatXMLElementFn
|
||||
>
|
||||
): string | React.ReactNodeArray;
|
||||
}
|
||||
export interface FormattedMessageProps<
|
||||
V extends Record<string, any> = Record<string, React.ReactNode>
|
||||
> extends MessageDescriptor {
|
||||
values?: V;
|
||||
tagName?: React.ElementType<any>;
|
||||
children?(...nodes: React.ReactNodeArray): React.ReactNode;
|
||||
}
|
||||
|
||||
export function defineMessages<Names extends keyof any>(
|
||||
messageDescriptors: Messages<Names>
|
||||
): Messages<Names>;
|
||||
|
||||
export interface IntlShape extends ReactIntl.IntlConfig, IntlFormatters {
|
||||
formatters: ReactIntl.Formatters;
|
||||
}
|
||||
|
||||
export class FormattedMessage<
|
||||
TValues extends Record<string, any> = Record<
|
||||
string,
|
||||
PrimitiveType | React.ReactElement | FormatXMLElementFn
|
||||
>
|
||||
> extends React.Component<FormattedMessageProps<TValues>> {}
|
||||
|
||||
export function useIntl(): IntlShape;
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import i18n from "../../../i18n";
|
||||
|
||||
export interface AttributeBulkDeleteDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
quantity: string;
|
||||
quantity: number;
|
||||
open: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
|
@ -15,26 +15,36 @@ export interface AttributeBulkDeleteDialogProps {
|
|||
|
||||
const AttributeBulkDeleteDialog: React.StatelessComponent<
|
||||
AttributeBulkDeleteDialogProps
|
||||
> = ({ confirmButtonState, quantity, onClose, onConfirm, open }) => (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={i18n.t("Remove attributes")}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ quantity }}</strong> attributes?",
|
||||
{
|
||||
quantity
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ActionDialog>
|
||||
);
|
||||
> = ({ confirmButtonState, quantity, onClose, onConfirm, open }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete attributes",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this attribute}
|
||||
other {{displayQuantity} attributes}
|
||||
}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: quantity,
|
||||
displayQuantity: <strong>{quantity}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
AttributeBulkDeleteDialog.displayName = "AttributeBulkDeleteDialog";
|
||||
export default AttributeBulkDeleteDialog;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import i18n from "@saleor/i18n";
|
||||
|
||||
export interface AttributeDeleteDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
|
@ -19,27 +19,33 @@ const AttributeDeleteDialog: React.FC<AttributeDeleteDialogProps> = ({
|
|||
onClose,
|
||||
onConfirm,
|
||||
open
|
||||
}) => (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onConfirm={onConfirm}
|
||||
variant="delete"
|
||||
title={i18n.t("Remove attribute")}
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ name }}</strong>?",
|
||||
{
|
||||
name
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ActionDialog>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onConfirm={onConfirm}
|
||||
variant="delete"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete attribute",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {attributeName}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
attributeName: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
|
||||
AttributeDeleteDialog.displayName = "AttributeDeleteDialog";
|
||||
export default AttributeDeleteDialog;
|
||||
|
|
|
@ -2,13 +2,14 @@ import Card from "@material-ui/core/Card";
|
|||
import CardContent from "@material-ui/core/CardContent";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import slugify from "slugify";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import SingleSelectField from "@saleor/components/SingleSelectField";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { FormErrors } from "@saleor/types";
|
||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { AttributePageFormData } from "../AttributePage";
|
||||
|
@ -21,78 +22,99 @@ export interface AttributeDetailsProps {
|
|||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
const inputTypeChoices = [
|
||||
{
|
||||
label: i18n.t("Dropdown"),
|
||||
value: AttributeInputTypeEnum.DROPDOWN
|
||||
},
|
||||
{
|
||||
label: i18n.t("Multiple Select"),
|
||||
value: AttributeInputTypeEnum.MULTISELECT
|
||||
}
|
||||
];
|
||||
|
||||
const AttributeDetails: React.FC<AttributeDetailsProps> = ({
|
||||
canChangeType,
|
||||
data,
|
||||
disabled,
|
||||
errors,
|
||||
onChange
|
||||
}) => (
|
||||
<Card>
|
||||
<CardTitle title={i18n.t("General Information")} />
|
||||
<CardContent>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.name}
|
||||
label={i18n.t("Default Label")}
|
||||
name={"name" as keyof AttributePageFormData}
|
||||
fullWidth
|
||||
helperText={errors.name}
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const inputTypeChoices = [
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Dropdown",
|
||||
description: "product attribute type"
|
||||
}),
|
||||
value: AttributeInputTypeEnum.DROPDOWN
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Multiple Select",
|
||||
description: "product attribute type"
|
||||
}),
|
||||
value: AttributeInputTypeEnum.MULTISELECT
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(commonMessages.generalInformations)}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.slug}
|
||||
label={i18n.t("Attribute Code")}
|
||||
name={"slug" as keyof AttributePageFormData}
|
||||
placeholder={slugify(data.name).toLowerCase()}
|
||||
fullWidth
|
||||
helperText={
|
||||
errors.slug ||
|
||||
i18n.t("This is used internally. Make sure you don’t use spaces", {
|
||||
context: "slug input"
|
||||
})
|
||||
}
|
||||
value={data.slug}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<SingleSelectField
|
||||
choices={inputTypeChoices}
|
||||
disabled={disabled || !canChangeType}
|
||||
error={!!errors.inputType}
|
||||
hint={errors.inputType}
|
||||
label={i18n.t("Catalog Input type for Store Owner", {
|
||||
context: "attribute input type"
|
||||
})}
|
||||
name="inputType"
|
||||
onChange={onChange}
|
||||
value={data.inputType}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
checked={data.valueRequired}
|
||||
label={i18n.t("Value Required", {
|
||||
context: "attribute must have value"
|
||||
})}
|
||||
name={"valueRequired" as keyof AttributePageFormData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
<CardContent>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.name}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Default Label",
|
||||
description: "attribute's label"
|
||||
})}
|
||||
name={"name" as keyof AttributePageFormData}
|
||||
fullWidth
|
||||
helperText={errors.name}
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.slug}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Attribute Code",
|
||||
description: "attribute's slug short code label"
|
||||
})}
|
||||
name={"slug" as keyof AttributePageFormData}
|
||||
placeholder={slugify(data.name).toLowerCase()}
|
||||
fullWidth
|
||||
helperText={
|
||||
errors.slug ||
|
||||
intl.formatMessage({
|
||||
defaultMessage:
|
||||
"This is used internally. Make sure you don’t use spaces",
|
||||
description: "attribute slug input field helper text"
|
||||
})
|
||||
}
|
||||
value={data.slug}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<SingleSelectField
|
||||
choices={inputTypeChoices}
|
||||
disabled={disabled || !canChangeType}
|
||||
error={!!errors.inputType}
|
||||
hint={errors.inputType}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Catalog Input type for Store Owner",
|
||||
description: "attribute's editor component"
|
||||
})}
|
||||
name="inputType"
|
||||
onChange={onChange}
|
||||
value={data.inputType}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
checked={data.valueRequired}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Value Required",
|
||||
description: "check to require attribute to have value"
|
||||
})}
|
||||
name={"valueRequired" as keyof AttributePageFormData}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
AttributeDetails.displayName = "AttributeDetails";
|
||||
export default AttributeDetails;
|
||||
|
|
|
@ -6,15 +6,15 @@ import TableFooter from "@material-ui/core/TableFooter";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { translateBoolean } from "@saleor/intl";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { translateBoolean } from "@saleor/utils/i18n";
|
||||
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
|
||||
|
||||
export interface AttributeListProps extends ListProps, ListActions {
|
||||
|
@ -71,6 +71,7 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
|
|||
toolbar
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Table>
|
||||
|
@ -83,23 +84,31 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
|
|||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colSlug}>
|
||||
{i18n.t("Attribute Code", { context: "attribute slug" })}
|
||||
<FormattedMessage defaultMessage="Attribute Code" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
{i18n.t("Default Label", { context: "attribute name" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Default Label"
|
||||
description="attribute's label'"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colVisible}>
|
||||
{i18n.t("Visible", { context: "attribute visibility" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Visible"
|
||||
description="attribute is visible"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSearchable}>
|
||||
{i18n.t("Searchable", {
|
||||
context: "attribute can be searched in dashboard"
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Searchable"
|
||||
description="attribute can be searched in dashboard"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colFaceted}>
|
||||
{i18n.t("Use in faceted search", {
|
||||
context: "attribute can be searched in storefront"
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Use in faceted search"
|
||||
description="attribute can be searched in storefront"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
|
@ -145,21 +154,21 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
|
|||
</TableCell>
|
||||
<TableCell className={classes.colVisible}>
|
||||
{attribute ? (
|
||||
translateBoolean(attribute.visibleInStorefront)
|
||||
translateBoolean(attribute.visibleInStorefront, intl)
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSearchable}>
|
||||
{attribute ? (
|
||||
translateBoolean(attribute.filterableInDashboard)
|
||||
translateBoolean(attribute.filterableInDashboard, intl)
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colFaceted}>
|
||||
{attribute ? (
|
||||
translateBoolean(attribute.filterableInStorefront)
|
||||
translateBoolean(attribute.filterableInStorefront, intl)
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
|
@ -170,7 +179,7 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
|
|||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No attributes found")}
|
||||
<FormattedMessage defaultMessage="No attributes found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
|
|
|
@ -2,10 +2,11 @@ import Button from "@material-ui/core/Button";
|
|||
import Card from "@material-ui/core/Card";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import Container from "../../../components/Container";
|
||||
import PageHeader from "../../../components/PageHeader";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
|
||||
import AttributeList from "../AttributeList/AttributeList";
|
||||
|
@ -17,17 +18,25 @@ export interface AttributeListPageProps extends PageListProps, ListActions {
|
|||
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||
onAdd,
|
||||
...listProps
|
||||
}) => (
|
||||
<Container>
|
||||
<PageHeader title={i18n.t("Attributes")}>
|
||||
<Button onClick={onAdd} color="primary" variant="contained">
|
||||
{i18n.t("Add attribute")} <AddIcon />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Card>
|
||||
<AttributeList {...listProps} />
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.attributes)}>
|
||||
<Button onClick={onAdd} color="primary" variant="contained">
|
||||
<FormattedMessage
|
||||
defaultMessage="Add attribute"
|
||||
description="button"
|
||||
/>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Card>
|
||||
<AttributeList {...listProps} />
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
AttributeListPage.displayName = "AttributeListPage";
|
||||
export default AttributeListPage;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import slugify from "slugify";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
|
@ -9,7 +10,7 @@ import Form from "@saleor/components/Form";
|
|||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ReorderAction, UserError } from "@saleor/types";
|
||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
||||
|
@ -62,6 +63,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
onValueReorder,
|
||||
onValueUpdate
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const initialForm: AttributePageFormData =
|
||||
attribute === null
|
||||
? {
|
||||
|
@ -109,12 +111,15 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
<Form errors={errors} initial={initialForm} onSubmit={handleSubmit}>
|
||||
{({ change, errors: formErrors, data, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Attributes")}</AppHeader>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.attributes)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={
|
||||
attribute === null
|
||||
? i18n.t("Create New Attribute", {
|
||||
context: "page title"
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Create New Attribute",
|
||||
description: "page title"
|
||||
})
|
||||
: maybe(() => attribute.name)
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@ import CardContent from "@material-ui/core/CardContent";
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { FormErrors } from "@saleor/types";
|
||||
import { AttributePageFormData } from "../AttributePage";
|
||||
|
||||
|
@ -25,103 +26,130 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
|||
errors,
|
||||
disabled,
|
||||
onChange
|
||||
}) => (
|
||||
<Card>
|
||||
<CardTitle title={i18n.t("Properties")} />
|
||||
<CardContent>
|
||||
{/* <Typography variant="subtitle1">
|
||||
{i18n.t("General Properties")}
|
||||
</Typography>
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
<ControlledSwitch
|
||||
name={"" as keyof AttributePageFormData}
|
||||
checked={false}
|
||||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
<span>{i18n.t("Variant Attribute")}</span>
|
||||
<Typography variant="caption">
|
||||
{i18n.t(
|
||||
"If enabled, you'll be able to use this attribute to create product variants"
|
||||
)}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
onChange={onChange}
|
||||
/> */}
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
<Typography variant="subtitle1">
|
||||
{i18n.t("Storefront Properties")}
|
||||
</Typography>
|
||||
<Hr />
|
||||
<ControlledSwitch
|
||||
name={"filterableInStorefront" as keyof AttributePageFormData}
|
||||
checked={data.filterableInStorefront}
|
||||
disabled={disabled}
|
||||
label={i18n.t("Use in faceted navigation")}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{data.filterableInStorefront && (
|
||||
<TextField
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={intl.formatMessage(commonMessages.properties)} />
|
||||
<CardContent>
|
||||
{/* <Typography variant="subtitle1">
|
||||
<FormattedMessage
|
||||
defaultMessage="General Properties"
|
||||
description="attribute general properties section"
|
||||
|
||||
/>
|
||||
</Typography>
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
<ControlledSwitch
|
||||
name={"" as keyof AttributePageFormData}
|
||||
checked={false}
|
||||
disabled={disabled}
|
||||
error={!!errors.storefrontSearchPosition}
|
||||
fullWidth
|
||||
helperText={errors.storefrontSearchPosition}
|
||||
name={"storefrontSearchPosition" as keyof AttributePageFormData}
|
||||
label={i18n.t("Position in faceted navigation")}
|
||||
value={data.storefrontSearchPosition}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
defaultMessage="Variant Attribute"
|
||||
description="attribute is variant-only"
|
||||
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage
|
||||
defaultMessage="If enabled, you'll be able to use this attribute to create product variants"
|
||||
|
||||
/>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
onChange={onChange}
|
||||
/> */}
|
||||
|
||||
<Typography variant="subtitle1">
|
||||
<FormattedMessage
|
||||
defaultMessage="Storefront Properties"
|
||||
description="attribute properties regarding storefront"
|
||||
/>
|
||||
</Typography>
|
||||
<Hr />
|
||||
<ControlledSwitch
|
||||
name={"filterableInStorefront" as keyof AttributePageFormData}
|
||||
checked={data.filterableInStorefront}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Use in faceted navigation",
|
||||
description: "attribute is filterable in storefront"
|
||||
})}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
name={"visibleInStorefront" as keyof AttributePageFormData}
|
||||
checked={data.visibleInStorefront}
|
||||
disabled={disabled}
|
||||
label={i18n.t("Visible on Product Page in Storefront", {
|
||||
context: "attribute"
|
||||
})}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<CardSpacer />
|
||||
|
||||
<Typography variant="subtitle1">
|
||||
{i18n.t("Dashboard Properties")}
|
||||
</Typography>
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
<ControlledSwitch
|
||||
name={"filterableInDashboard" as keyof AttributePageFormData}
|
||||
checked={data.filterableInDashboard}
|
||||
disabled={disabled}
|
||||
label={i18n.t("Use in Filtering")}
|
||||
secondLabel={
|
||||
<Typography variant="caption">
|
||||
{i18n.t(
|
||||
"If enabled, you’ll be able to use this attribute to filter products in product list."
|
||||
)}
|
||||
</Typography>
|
||||
}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
name={"availableInGrid" as keyof AttributePageFormData}
|
||||
checked={data.availableInGrid}
|
||||
disabled={disabled}
|
||||
label={i18n.t("Add to Column Options")}
|
||||
secondLabel={
|
||||
<Typography variant="caption">
|
||||
{i18n.t(
|
||||
"If enable this attribute can be used as a column in product table."
|
||||
)}
|
||||
</Typography>
|
||||
}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
{data.filterableInStorefront && (
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.storefrontSearchPosition}
|
||||
fullWidth
|
||||
helperText={errors.storefrontSearchPosition}
|
||||
name={"storefrontSearchPosition" as keyof AttributePageFormData}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Position in faceted navigation",
|
||||
description: "attribute position in storefront filters"
|
||||
})}
|
||||
value={data.storefrontSearchPosition}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
name={"visibleInStorefront" as keyof AttributePageFormData}
|
||||
checked={data.visibleInStorefront}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Visible on Product Page in Storefront",
|
||||
description: "attribute"
|
||||
})}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Typography variant="subtitle1">
|
||||
<FormattedMessage
|
||||
defaultMessage="Dashboard Properties"
|
||||
description="attribute properties regarding dashboard"
|
||||
/>
|
||||
</Typography>
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
<ControlledSwitch
|
||||
name={"filterableInDashboard" as keyof AttributePageFormData}
|
||||
checked={data.filterableInDashboard}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Use in Filtering",
|
||||
description: "use attribute in filtering"
|
||||
})}
|
||||
secondLabel={
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage defaultMessage="If enabled, you’ll be able to use this attribute to filter products in product list." />
|
||||
</Typography>
|
||||
}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<ControlledSwitch
|
||||
name={"availableInGrid" as keyof AttributePageFormData}
|
||||
checked={data.availableInGrid}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Add to Column Options",
|
||||
description: "add attribute as column in product list table"
|
||||
})}
|
||||
secondLabel={
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage defaultMessage="If enable this attribute can be used as a column in product table." />
|
||||
</Typography>
|
||||
}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
AttributeProperties.displayName = "AttributeProperties";
|
||||
export default AttributeProperties;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import i18n from "@saleor/i18n";
|
||||
|
||||
export interface AttributeValueDeleteDialogProps {
|
||||
attributeName: string;
|
||||
|
@ -23,30 +23,43 @@ const AttributeValueDeleteDialog: React.FC<AttributeValueDeleteDialogProps> = ({
|
|||
onClose,
|
||||
onConfirm,
|
||||
open
|
||||
}) => (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onConfirm={onConfirm}
|
||||
variant="delete"
|
||||
title={i18n.t("Remove attribute value")}
|
||||
>
|
||||
<DialogContentText>
|
||||
{useName
|
||||
? i18n.t(
|
||||
'Are you sure you want to remove "{{ name }}" value? If you remove it you won’t be able to assign it to any of the products with "{{ attributeName }}" attribute.',
|
||||
{
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onConfirm={onConfirm}
|
||||
variant="delete"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete attribute value",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
{useName ? (
|
||||
<FormattedMessage
|
||||
defaultMessage='Are you sure you want to delete "{ name }" value? If you delete it you won’t be able to assign it to any of the products with "{ attributeName }" attribute.'
|
||||
values={{
|
||||
attributeName,
|
||||
name
|
||||
}
|
||||
)
|
||||
: i18n.t('Are you sure you want to remove "{{ name }}" value?', {
|
||||
name
|
||||
})}
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage='Are you sure you want to delete "{ name }" value?'
|
||||
description="delete attribute value"
|
||||
values={{
|
||||
name
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
|
||||
AttributeValueDeleteDialog.displayName = "AttributeValueDeleteDialog";
|
||||
export default AttributeValueDeleteDialog;
|
||||
|
|
|
@ -5,13 +5,14 @@ import DialogContent from "@material-ui/core/DialogContent";
|
|||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import Form from "@saleor/components/Form";
|
||||
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { UserError } from "@saleor/types";
|
||||
import { AttributeDetails_attribute_values } from "../../types/AttributeDetails";
|
||||
|
@ -40,6 +41,7 @@ const AttributeValueEditDialog: React.StatelessComponent<
|
|||
onSubmit,
|
||||
open
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const initialForm: AttributeValueEditDialogFormData = {
|
||||
name: maybe(() => attributeValue.name, "")
|
||||
};
|
||||
|
@ -48,13 +50,17 @@ const AttributeValueEditDialog: React.StatelessComponent<
|
|||
return (
|
||||
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
|
||||
<DialogTitle>
|
||||
{attributeValue === null
|
||||
? i18n.t("Add Value", {
|
||||
context: "add attribute value"
|
||||
})
|
||||
: i18n.t("Edit Value", {
|
||||
context: "edit attribute value"
|
||||
})}
|
||||
{attributeValue === null ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Add Value"
|
||||
description="add attribute value"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit Value"
|
||||
description="edit attribute value"
|
||||
/>
|
||||
)}
|
||||
</DialogTitle>
|
||||
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ change, data, errors: formErrors, submit }) => (
|
||||
|
@ -67,8 +73,9 @@ const AttributeValueEditDialog: React.StatelessComponent<
|
|||
fullWidth
|
||||
helperText={formErrors.name}
|
||||
name={"name" as keyof AttributeValueEditDialogFormData}
|
||||
label={i18n.t("Name", {
|
||||
context: "attribute name"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Name",
|
||||
description: "attribute name"
|
||||
})}
|
||||
value={data.name}
|
||||
onChange={change}
|
||||
|
@ -76,7 +83,7 @@ const AttributeValueEditDialog: React.StatelessComponent<
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
|
@ -84,7 +91,7 @@ const AttributeValueEditDialog: React.StatelessComponent<
|
|||
variant="contained"
|
||||
onClick={submit}
|
||||
>
|
||||
{i18n.t("Save")}
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
|
|
@ -9,6 +9,7 @@ import TableRow from "@material-ui/core/TableRow";
|
|||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
|
@ -16,7 +17,6 @@ import {
|
|||
SortableTableBody,
|
||||
SortableTableRow
|
||||
} from "@saleor/components/SortableTable";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection, stopPropagation } from "@saleor/misc";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import { AttributeDetailsFragment_values } from "../../types/AttributeDetailsFragment";
|
||||
|
@ -63,14 +63,21 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
|
|||
values
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Attribute Values")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Attribute Values",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onValueAdd}>
|
||||
{i18n.t("Add value", { context: "button" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Add value"
|
||||
description="add attribute value button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -79,10 +86,16 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
|
|||
<TableRow>
|
||||
<TableCell className={classes.columnDrag} />
|
||||
<TableCell className={classes.columnAdmin}>
|
||||
{i18n.t("Admin")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Admin"
|
||||
description="attribute values list: slug column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.columnStore}>
|
||||
{i18n.t("Default Store View")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Default Store View"
|
||||
description="attribute values list: name column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell />
|
||||
</TableRow>
|
||||
|
@ -116,7 +129,12 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
|
|||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{i18n.t("No values found")}</TableCell>
|
||||
<TableCell colSpan={2}>
|
||||
<FormattedMessage
|
||||
defaultMessage="No values found"
|
||||
description="No attribute values found"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -2,8 +2,9 @@ import { parse as parseQs } from "qs";
|
|||
import React from "react";
|
||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { useIntl } from "react-intl";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import i18n from "../i18n";
|
||||
import {
|
||||
attributeAddPath,
|
||||
AttributeAddUrlQueryParams,
|
||||
|
@ -42,14 +43,18 @@ const AttributeDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const AttributeSection: React.FC = () => (
|
||||
<>
|
||||
<WindowTitle title={i18n.t("Attributes")} />
|
||||
<Switch>
|
||||
<Route exact path={attributeListPath} component={AttributeList} />
|
||||
<Route exact path={attributeAddPath} component={AttributeCreate} />
|
||||
<Route path={attributePath(":id")} component={AttributeDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
export const AttributeSection: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.attributes)} />
|
||||
<Switch>
|
||||
<Route exact path={attributeListPath} component={AttributeList} />
|
||||
<Route exact path={attributeAddPath} component={AttributeCreate} />
|
||||
<Route path={attributePath(":id")} component={AttributeDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default AttributeSection;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import slugify from "slugify";
|
||||
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ReorderEvent, UserError } from "@saleor/types";
|
||||
import {
|
||||
|
@ -42,6 +42,7 @@ function areValuesEqual(
|
|||
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const [values, setValues] = React.useState<
|
||||
AttributeValueEditDialogFormData[]
|
||||
|
@ -75,7 +76,11 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
};
|
||||
const handleCreate = (data: AttributeCreate) => {
|
||||
if (data.attributeCreate.errors.length === 0) {
|
||||
notify({ text: i18n.t("Successfully created attribute") });
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Successfully created attribute"
|
||||
})
|
||||
});
|
||||
navigate(attributeUrl(data.attributeCreate.attribute.id));
|
||||
}
|
||||
};
|
||||
|
@ -84,10 +89,15 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
setValueErrors([
|
||||
{
|
||||
field: "name",
|
||||
message: i18n.t("A value named {{ name }} already exists", {
|
||||
context: "value edit error",
|
||||
name: input.name
|
||||
})
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "A value named { name } already exists",
|
||||
description: "attribute value edit error"
|
||||
},
|
||||
{
|
||||
name: input.name
|
||||
}
|
||||
)
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
|
@ -100,10 +110,15 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
setValueErrors([
|
||||
{
|
||||
field: "name",
|
||||
message: i18n.t("A value named {{ name }} already exists", {
|
||||
context: "value edit error",
|
||||
name: input.name
|
||||
})
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "A value named { name } already exists",
|
||||
description: "attribute value edit error"
|
||||
},
|
||||
{
|
||||
name: input.name
|
||||
}
|
||||
)
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ReorderEvent } from "@saleor/types";
|
||||
import { move } from "@saleor/utils/lists";
|
||||
|
@ -40,6 +41,7 @@ interface AttributeDetailsProps {
|
|||
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
|
@ -63,39 +65,51 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
|
||||
const handleDelete = (data: AttributeDelete) => {
|
||||
if (data.attributeDelete.errors.length === 0) {
|
||||
notify({ text: i18n.t("Attribute removed") });
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Attribute deleted"
|
||||
})
|
||||
});
|
||||
navigate(attributeListUrl());
|
||||
}
|
||||
};
|
||||
const handleValueDelete = (data: AttributeValueDelete) => {
|
||||
if (data.attributeValueDelete.errors.length === 0) {
|
||||
notify({ text: i18n.t("Value removed") });
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Value deleted",
|
||||
description: "attribute value deleted"
|
||||
})
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
const handleUpdate = (data: AttributeUpdate) => {
|
||||
if (data.attributeUpdate.errors.length === 0) {
|
||||
notify({ text: i18n.t("Saved changes") });
|
||||
notify({ text: intl.formatMessage(commonMessages.savedChanges) });
|
||||
}
|
||||
};
|
||||
const handleValueUpdate = (data: AttributeValueUpdate) => {
|
||||
if (data.attributeValueUpdate.errors.length === 0) {
|
||||
notify({ text: i18n.t("Saved changes") });
|
||||
notify({ text: intl.formatMessage(commonMessages.savedChanges) });
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
const handleValueCreate = (data: AttributeValueCreate) => {
|
||||
if (data.attributeValueCreate.errors.length === 0) {
|
||||
notify({ text: i18n.t("Added new value") });
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Added new value",
|
||||
description: "added new attribute value"
|
||||
})
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
const handleValueReorderMutation = (data: AttributeValueReorder) => {
|
||||
if (data.attributeReorderValues.errors.length !== 0) {
|
||||
notify({
|
||||
text: i18n.t("Error: {{ errorMessage }}", {
|
||||
errorMessage: data.attributeReorderValues.errors[0].message
|
||||
})
|
||||
text: data.attributeReorderValues.errors[0].message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
|
@ -9,7 +10,6 @@ import usePaginator, {
|
|||
} from "@saleor/hooks/usePaginator";
|
||||
import { PAGINATE_BY } from "../../../config";
|
||||
import useBulkActions from "../../../hooks/useBulkActions";
|
||||
import i18n from "../../../i18n";
|
||||
import { getMutationState, maybe } from "../../../misc";
|
||||
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
||||
import AttributeListPage from "../../components/AttributeListPage";
|
||||
|
@ -35,6 +35,7 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
|
@ -71,7 +72,10 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
if (data.attributeBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: i18n.t("Attributes removed")
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Attributes successfully delete",
|
||||
description: "deleted multiple attributes"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
|
@ -116,12 +120,15 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
/>
|
||||
<AttributeBulkDeleteDialog
|
||||
confirmButtonState={bulkDeleteMutationState}
|
||||
open={params.action === "remove"}
|
||||
open={
|
||||
params.action === "remove" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
onConfirm={() =>
|
||||
attributeBulkDelete({ variables: { ids: params.ids } })
|
||||
}
|
||||
onClose={closeModal}
|
||||
quantity={maybe(() => params.ids.length.toString(), "...")}
|
||||
quantity={maybe(() => params.ids.length)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import TextField from "@material-ui/core/TextField";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import backgroundArt from "@assets/images/login-background.svg";
|
||||
import saleorDarkLogo from "@assets/images/logo-dark.svg";
|
||||
|
@ -17,7 +18,7 @@ import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
|||
import Form from "@saleor/components/Form";
|
||||
import { FormSpacer } from "@saleor/components/FormSpacer";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
|
||||
export interface FormData {
|
||||
email: string;
|
||||
|
@ -117,6 +118,7 @@ export interface LoginCardProps extends WithStyles<typeof styles> {
|
|||
const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
||||
({ classes, error, disableLoginButton, onSubmit }: LoginCardProps) => {
|
||||
const { isDark } = useTheme();
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Form
|
||||
|
@ -136,21 +138,16 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
|||
/>
|
||||
{error && (
|
||||
<div className={classes.panel}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Sorry, your username and/or password are incorrect. <br />Please try again."
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage defaultMessage="Sorry, your username and/or password are incorrect. Please try again." />
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
autoComplete="username"
|
||||
label={i18n.t("Email", { context: "form" })}
|
||||
label={intl.formatMessage(commonMessages.email)}
|
||||
name="email"
|
||||
onChange={handleChange}
|
||||
value={data.email}
|
||||
|
@ -162,7 +159,9 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
|||
<TextField
|
||||
fullWidth
|
||||
autoComplete="current-password"
|
||||
label={i18n.t("Password")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Password"
|
||||
})}
|
||||
name="password"
|
||||
onChange={handleChange}
|
||||
type="password"
|
||||
|
@ -173,7 +172,10 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
|||
<div className={classes.buttonContainer}>
|
||||
<ControlledCheckbox
|
||||
checked={data.rememberMe}
|
||||
label={i18n.t("Remember me")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Remember me",
|
||||
description: "login"
|
||||
})}
|
||||
name="rememberMe"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
@ -187,7 +189,10 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
|
|||
type="submit"
|
||||
data-tc="submit"
|
||||
>
|
||||
{i18n.t("Login")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Login"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
{/* <FormSpacer />
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import { Theme } from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import React from "react";
|
||||
|
||||
import Button from "@material-ui/core/Button";
|
||||
|
@ -15,35 +11,35 @@ import Hr from "@saleor/components/Hr";
|
|||
import ImageTile from "@saleor/components/ImageTile";
|
||||
import ImageUpload from "@saleor/components/ImageUpload";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import i18n from "../../../i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { CategoryDetails_category_backgroundImage } from "../../types/CategoryDetails";
|
||||
import { FormData } from "../CategoryUpdatePage";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
fileField: {
|
||||
display: "none"
|
||||
},
|
||||
image: {
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
userSelect: "none",
|
||||
width: "100%"
|
||||
},
|
||||
imageContainer: {
|
||||
background: "#ffffff",
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: theme.spacing.unit,
|
||||
height: 148,
|
||||
justifySelf: "start",
|
||||
overflow: "hidden",
|
||||
padding: theme.spacing.unit * 2,
|
||||
position: "relative",
|
||||
width: 148
|
||||
}
|
||||
});
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
fileField: {
|
||||
display: "none"
|
||||
},
|
||||
image: {
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
userSelect: "none",
|
||||
width: "100%"
|
||||
},
|
||||
imageContainer: {
|
||||
background: "#ffffff",
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: theme.spacing.unit,
|
||||
height: 148,
|
||||
justifySelf: "start",
|
||||
overflow: "hidden",
|
||||
padding: theme.spacing.unit * 2,
|
||||
position: "relative",
|
||||
width: 148
|
||||
}
|
||||
}));
|
||||
|
||||
export interface CategoryBackgroundProps extends WithStyles<typeof styles> {
|
||||
export interface CategoryBackgroundProps {
|
||||
data: FormData;
|
||||
image: CategoryDetails_category_backgroundImage;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
|
@ -51,83 +47,78 @@ export interface CategoryBackgroundProps extends WithStyles<typeof styles> {
|
|||
onImageUpload: (file: File) => void;
|
||||
}
|
||||
|
||||
export const CategoryBackground = withStyles(styles)(
|
||||
class CategoryBackgroundComponent extends React.Component<
|
||||
CategoryBackgroundProps,
|
||||
{}
|
||||
> {
|
||||
imgInputAnchor = React.createRef<HTMLInputElement>();
|
||||
const CategoryBackground: React.FC<CategoryBackgroundProps> = props => {
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
const anchor = React.useRef<HTMLInputElement>();
|
||||
|
||||
clickImgInput = () => this.imgInputAnchor.current.click();
|
||||
const { data, onImageUpload, image, onChange, onImageDelete } = props;
|
||||
|
||||
render() {
|
||||
const {
|
||||
classes,
|
||||
data,
|
||||
onImageUpload,
|
||||
image,
|
||||
onChange,
|
||||
onImageDelete
|
||||
} = this.props;
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Background image (optional)")}
|
||||
toolbar={
|
||||
<>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
onClick={this.clickImgInput}
|
||||
>
|
||||
{i18n.t("Upload image")}
|
||||
</Button>
|
||||
<input
|
||||
className={classes.fileField}
|
||||
id="fileUpload"
|
||||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
ref={this.imgInputAnchor}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{image === undefined ? (
|
||||
<CardContent>
|
||||
<div>
|
||||
<div className={classes.imageContainer}>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
) : image === null ? (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
) : (
|
||||
<CardContent>
|
||||
<ImageTile image={image} onImageDelete={onImageDelete} />
|
||||
</CardContent>
|
||||
)}
|
||||
const handleImageUploadButtonClick = () => anchor.current.click();
|
||||
|
||||
{image && (
|
||||
<>
|
||||
<Hr />
|
||||
<CardContent>
|
||||
<TextField
|
||||
name="backgroundImageAlt"
|
||||
label={i18n.t("Description")}
|
||||
helperText={i18n.t("Optional")}
|
||||
value={data.backgroundImageAlt}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Background image (optional)",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
onClick={handleImageUploadButtonClick}
|
||||
>
|
||||
<FormattedMessage {...commonMessages.uploadImage} />
|
||||
</Button>
|
||||
<input
|
||||
className={classes.fileField}
|
||||
id="fileUpload"
|
||||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
ref={anchor}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{image === undefined ? (
|
||||
<CardContent>
|
||||
<div>
|
||||
<div className={classes.imageContainer}>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
) : image === null ? (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
) : (
|
||||
<CardContent>
|
||||
<ImageTile image={image} onImageDelete={onImageDelete} />
|
||||
</CardContent>
|
||||
)}
|
||||
|
||||
{image && (
|
||||
<>
|
||||
<Hr />
|
||||
<CardContent>
|
||||
<TextField
|
||||
name="backgroundImageAlt"
|
||||
label={intl.formatMessage(commonMessages.description)}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage: "(Optional)",
|
||||
description: "field is optional"
|
||||
})}
|
||||
value={data.backgroundImageAlt}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
CategoryBackground.displayName = "CategoryBackground";
|
||||
export default CategoryBackground;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
|
@ -9,7 +10,7 @@ import Form from "@saleor/components/Form";
|
|||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import i18n from "../../../i18n";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { UserError } from "../../../types";
|
||||
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
|
||||
|
||||
|
@ -43,50 +44,58 @@ export const CategoryCreatePage: React.StatelessComponent<
|
|||
onBack,
|
||||
errors: userErrors,
|
||||
saveButtonBarState
|
||||
}) => (
|
||||
<Form
|
||||
onSubmit={onSubmit}
|
||||
initial={initialData}
|
||||
errors={userErrors}
|
||||
confirmLeave
|
||||
>
|
||||
{({ data, change, errors, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Categories")}</AppHeader>
|
||||
<PageHeader title={i18n.t("Add Category")} />
|
||||
<div>
|
||||
<CategoryDetailsForm
|
||||
disabled={disabled}
|
||||
data={data}
|
||||
onChange={change}
|
||||
errors={errors}
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<Form
|
||||
onSubmit={onSubmit}
|
||||
initial={initialData}
|
||||
errors={userErrors}
|
||||
confirmLeave
|
||||
>
|
||||
{({ data, change, errors, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create New Category",
|
||||
description: "page header"
|
||||
})}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
helperText={i18n.t(
|
||||
"Add search engine title and description to make this product easier to find"
|
||||
)}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={data.name}
|
||||
loading={disabled}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
labels={{
|
||||
save: i18n.t("Save category")
|
||||
}}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
<div>
|
||||
<CategoryDetailsForm
|
||||
disabled={disabled}
|
||||
data={data}
|
||||
onChange={change}
|
||||
errors={errors}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
})}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={data.name}
|
||||
loading={disabled}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
CategoryCreatePage.displayName = "CategoryCreatePage";
|
||||
export default CategoryCreatePage;
|
||||
|
|
|
@ -11,8 +11,9 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -36,27 +37,33 @@ const CategoryDeleteDialog = withStyles(styles, {
|
|||
name: "CategoryDeleteDialog"
|
||||
})(({ classes, name, open, onConfirm, onClose }: CategoryDeleteDialogProps) => (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>{i18n.t("Delete category", { context: "title" })}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{name}}</strong>?",
|
||||
{ name }
|
||||
)
|
||||
}}
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete category"
|
||||
description="dialog title"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {categoryName}?"
|
||||
description="delete category"
|
||||
values={{
|
||||
categoryName: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.deleteButton}
|
||||
variant="contained"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{i18n.t("Delete category", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
|
@ -4,11 +4,12 @@ import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import i18n from "../../../i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { maybe } from "../../../misc";
|
||||
import { CategoryDetails_category } from "../../types/CategoryDetails";
|
||||
|
||||
|
@ -40,15 +41,21 @@ export const CategoryDetailsForm = withStyles(styles, {
|
|||
onChange,
|
||||
errors
|
||||
}: CategoryDetailsFormProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={i18n.t("General information")} />
|
||||
<CardTitle
|
||||
title={intl.formatMessage(commonMessages.generalInformations)}
|
||||
/>
|
||||
<CardContent>
|
||||
<>
|
||||
<div>
|
||||
<TextField
|
||||
classes={{ root: classes.root }}
|
||||
label={i18n.t("Name")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Category Name"
|
||||
})}
|
||||
name="name"
|
||||
disabled={disabled}
|
||||
value={data && data.name}
|
||||
|
@ -62,7 +69,9 @@ export const CategoryDetailsForm = withStyles(styles, {
|
|||
disabled={disabled}
|
||||
error={!!errors.descriptionJson}
|
||||
helperText={errors.descriptionJson}
|
||||
label={i18n.t("Description")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Category Description"
|
||||
})}
|
||||
initial={maybe(() => JSON.parse(category.descriptionJson))}
|
||||
name="description"
|
||||
onChange={onChange}
|
||||
|
|
|
@ -12,13 +12,13 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
|
||||
|
@ -87,112 +87,132 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
|||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick
|
||||
}: CategoryListProps) => (
|
||||
<Card>
|
||||
{!isRoot && (
|
||||
<CardTitle
|
||||
title={i18n.t("All Subcategories")}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onAdd}>
|
||||
{i18n.t("Add subcategory")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={categories}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
{i18n.t("Category Name", { context: "object" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSubcategories}>
|
||||
{i18n.t("Subcategories", { context: "object" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
{i18n.t("No. Products", { context: "object" }).replace(" ", "\xa0")}
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<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(
|
||||
categories,
|
||||
category => {
|
||||
const isSelected = category ? isChecked(category.id) : false;
|
||||
}: CategoryListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
hover={!!category}
|
||||
onClick={category ? onRowClick(category.id) : undefined}
|
||||
key={category ? category.id : "skeleton"}
|
||||
selected={isSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(category.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
{category && category.name ? category.name : <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSubcategories}>
|
||||
{category &&
|
||||
category.children &&
|
||||
category.children.totalCount !== undefined ? (
|
||||
category.children.totalCount
|
||||
return (
|
||||
<Card>
|
||||
{!isRoot && (
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "All Subcategories",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add subcategory"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={categories}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage defaultMessage="Category Name" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSubcategories}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Subcategories"
|
||||
description="number of subcategories"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
<FormattedMessage
|
||||
defaultMessage="No. of Products"
|
||||
description="number of products"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<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(
|
||||
categories,
|
||||
category => {
|
||||
const isSelected = category ? isChecked(category.id) : false;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
hover={!!category}
|
||||
onClick={category ? onRowClick(category.id) : undefined}
|
||||
key={category ? category.id : "skeleton"}
|
||||
selected={isSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(category.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
{category && category.name ? category.name : <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSubcategories}>
|
||||
{category &&
|
||||
category.children &&
|
||||
category.children.totalCount !== undefined ? (
|
||||
category.children.totalCount
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
{category &&
|
||||
category.products &&
|
||||
category.products.totalCount !== undefined ? (
|
||||
category.products.totalCount
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{isRoot ? (
|
||||
<FormattedMessage defaultMessage="No categories found" />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
{category &&
|
||||
category.products &&
|
||||
category.products.totalCount !== undefined ? (
|
||||
category.products.totalCount
|
||||
) : (
|
||||
<Skeleton />
|
||||
<FormattedMessage defaultMessage="No subcategories found" />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{isRoot
|
||||
? i18n.t("No categories found")
|
||||
: i18n.t("No subcategories found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
CategoryList.displayName = "CategoryList";
|
||||
export default CategoryList;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import CategoryList from "../CategoryList";
|
||||
|
||||
|
@ -36,31 +37,38 @@ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
|
|||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
}) => (
|
||||
<Container>
|
||||
<PageHeader title={i18n.t("Categories")}>
|
||||
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||
{i18n.t("Add category")} <AddIcon />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<CategoryList
|
||||
categories={categories}
|
||||
onAdd={onAdd}
|
||||
onRowClick={onRowClick}
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
isRoot={true}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
isChecked={isChecked}
|
||||
selected={selected}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.categories)}>
|
||||
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add category"
|
||||
description="button"
|
||||
/>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<CategoryList
|
||||
categories={categories}
|
||||
onAdd={onAdd}
|
||||
onRowClick={onRowClick}
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
isRoot={true}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
isChecked={isChecked}
|
||||
selected={selected}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
CategoryListPage.displayName = "CategoryListPage";
|
||||
export default CategoryListPage;
|
||||
|
|
|
@ -13,13 +13,13 @@ import TableFooter from "@material-ui/core/TableFooter";
|
|||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -61,76 +61,96 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick
|
||||
}: ProductListProps) => (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Products")}
|
||||
toolbar={
|
||||
<Button variant="text" color="primary" onClick={onAddProduct}>
|
||||
{i18n.t("Add product")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{(products === undefined || products.length > 0) && <TableCell />}
|
||||
<TableCell className={classes.textLeft}>
|
||||
{i18n.t("Name", { context: "object" })}
|
||||
</TableCell>
|
||||
<TableCell>{i18n.t("Type", { context: "object" })}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
hasNextPage={hasNextPage}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
products,
|
||||
product => (
|
||||
<TableRow key={product ? product.id : "skeleton"}>
|
||||
<TableCellAvatar
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
}: ProductListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Products",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button variant="text" color="primary" onClick={onAddProduct}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add product"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{(products === undefined || products.length > 0) && <TableCell />}
|
||||
<TableCell className={classes.textLeft}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Name"
|
||||
description="product name"
|
||||
/>
|
||||
<TableCell className={classes.textLeft}>
|
||||
{product ? (
|
||||
<span
|
||||
onClick={onRowClick && onRowClick(product.id)}
|
||||
className={classes.link}
|
||||
>
|
||||
{product.name}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>{i18n.t("No products found")}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
)
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage
|
||||
defaultMessage="Type"
|
||||
description="product type"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
hasNextPage={hasNextPage}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
products,
|
||||
product => (
|
||||
<TableRow key={product ? product.id : "skeleton"}>
|
||||
<TableCellAvatar
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
/>
|
||||
<TableCell className={classes.textLeft}>
|
||||
{product ? (
|
||||
<span
|
||||
onClick={onRowClick && onRowClick(product.id)}
|
||||
className={classes.link}
|
||||
>
|
||||
{product.name}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>
|
||||
<FormattedMessage defaultMessage="No products found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
ProductList.displayName = "CategoryProductList";
|
||||
export default ProductList;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ProductList from "@saleor/components/ProductList";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||
|
||||
|
@ -29,35 +29,50 @@ export const CategoryProductsCard: React.StatelessComponent<
|
|||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
}) => (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Products in {{ categoryName }}", { categoryName })}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onAdd}>
|
||||
{i18n.t("Add product")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ProductList
|
||||
settings={{
|
||||
columns: ["isPublished", "price", "productType"],
|
||||
rowNumber: undefined
|
||||
}}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onRowClick}
|
||||
selected={selected}
|
||||
isChecked={isChecked}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Products in {categoryName}",
|
||||
description: "section header"
|
||||
},
|
||||
{
|
||||
categoryName
|
||||
}
|
||||
)}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add product"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ProductList
|
||||
settings={{
|
||||
columns: ["isPublished", "price", "productType"],
|
||||
rowNumber: undefined
|
||||
}}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onRowClick}
|
||||
selected={selected}
|
||||
isChecked={isChecked}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
CategoryProductsCard.displayName = "CategoryProductsCard";
|
||||
export default CategoryProductsCard;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
|
@ -10,7 +11,7 @@ import PageHeader from "@saleor/components/PageHeader";
|
|||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||
import i18n from "../../../i18n";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "../../../misc";
|
||||
import { TabListActions, UserError } from "../../../types";
|
||||
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
|
||||
|
@ -96,6 +97,7 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
|||
toggle,
|
||||
toggleAll
|
||||
}: CategoryUpdatePageProps) => {
|
||||
const intl = useIntl();
|
||||
const initialData: FormData = category
|
||||
? {
|
||||
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
|
||||
|
@ -120,7 +122,9 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
|||
>
|
||||
{({ data, change, errors, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Categories")}</AppHeader>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
</AppHeader>
|
||||
<PageHeader title={category ? category.name : undefined} />
|
||||
<CategoryDetailsForm
|
||||
category={category}
|
||||
|
@ -139,9 +143,10 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
|||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
helperText={i18n.t(
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
)}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
})}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
|
@ -156,13 +161,19 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
|||
isActive={currentTab === CategoryPageTab.categories}
|
||||
changeTab={changeTab}
|
||||
>
|
||||
{i18n.t("Subcategories")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Subcategories"
|
||||
description="number of subcategories in category"
|
||||
/>
|
||||
</CategoriesTab>
|
||||
<ProductsTab
|
||||
isActive={currentTab === CategoryPageTab.products}
|
||||
changeTab={changeTab}
|
||||
>
|
||||
{i18n.t("Products")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Products"
|
||||
description="number of products in category"
|
||||
/>
|
||||
</ProductsTab>
|
||||
</TabContainer>
|
||||
<CardSpacer />
|
||||
|
@ -204,9 +215,6 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
|||
onCancel={onBack}
|
||||
onDelete={onDelete}
|
||||
onSave={submit}
|
||||
labels={{
|
||||
delete: i18n.t("Delete category")
|
||||
}}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { parse as parseQs } from "qs";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import i18n from "../i18n";
|
||||
import {
|
||||
categoryAddPath,
|
||||
categoryListPath,
|
||||
|
@ -56,16 +58,20 @@ const CategoryList: React.StatelessComponent<RouteComponentProps<{}>> = ({
|
|||
return <CategoryListComponent params={params} />;
|
||||
};
|
||||
|
||||
const Component = () => (
|
||||
<>
|
||||
<WindowTitle title={i18n.t("Categories")} />
|
||||
<Switch>
|
||||
<Route exact path={categoryListPath} component={CategoryList} />
|
||||
<Route exact path={categoryAddPath()} component={CategoryCreate} />
|
||||
<Route exact path={categoryAddPath(":id")} component={CategoryCreate} />
|
||||
<Route path={categoryPath(":id")} component={CategoryDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
const Component = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.categories)} />
|
||||
<Switch>
|
||||
<Route exact path={categoryListPath} component={CategoryList} />
|
||||
<Route exact path={categoryAddPath()} component={CategoryCreate} />
|
||||
<Route exact path={categoryAddPath(":id")} component={CategoryCreate} />
|
||||
<Route path={categoryPath(":id")} component={CategoryDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import CategoryCreatePage from "../components/CategoryCreatePage";
|
||||
import { TypedCategoryCreateMutation } from "../mutations";
|
||||
|
@ -19,10 +19,15 @@ export const CategoryCreateView: React.StatelessComponent<
|
|||
> = ({ parentId }) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleSuccess = (data: CategoryCreate) => {
|
||||
if (data.categoryCreate.errors.length === 0) {
|
||||
notify({ text: i18n.t("Category created") });
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Category created"
|
||||
})
|
||||
});
|
||||
navigate(categoryUrl(data.categoryCreate.category.id));
|
||||
}
|
||||
};
|
||||
|
@ -42,7 +47,12 @@ export const CategoryCreateView: React.StatelessComponent<
|
|||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={i18n.t("Create category")} />
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create category",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<CategoryCreatePage
|
||||
saveButtonBarState={formTransitionState}
|
||||
errors={errors}
|
||||
|
|
|
@ -2,6 +2,7 @@ import DialogContentText from "@material-ui/core/DialogContentText";
|
|||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
|
@ -11,8 +12,8 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { PAGINATE_BY } from "../../config";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import { TypedProductBulkDeleteMutation } from "../../products/mutations";
|
||||
import { productBulkDelete } from "../../products/types/productBulkDelete";
|
||||
|
@ -59,12 +60,13 @@ export const CategoryDetails: React.StatelessComponent<
|
|||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const handleCategoryDelete = (data: CategoryDelete) => {
|
||||
if (data.categoryDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Category deleted", {
|
||||
context: "notification"
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Category deleted"
|
||||
})
|
||||
});
|
||||
navigate(categoryListUrl());
|
||||
|
@ -140,7 +142,7 @@ export const CategoryDetails: React.StatelessComponent<
|
|||
if (data.categoryBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: i18n.t("Categories removed")
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
|
@ -151,7 +153,7 @@ export const CategoryDetails: React.StatelessComponent<
|
|||
if (data.productBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: i18n.t("Products removed")
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
|
@ -319,32 +321,36 @@ export const CategoryDetails: React.StatelessComponent<
|
|||
deleteCategory({ variables: { id } })
|
||||
}
|
||||
open={params.action === "delete"}
|
||||
title={i18n.t("Delete category", {
|
||||
context: "modal title"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete category",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ categoryName }}</strong>? <br /> ",
|
||||
{
|
||||
categoryName: maybe(
|
||||
() => data.category.name
|
||||
),
|
||||
context: "modal message"
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
{i18n.t(
|
||||
"Remember that this will also remove all products assigned to this category."
|
||||
)}
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {categoryName}?"
|
||||
values={{
|
||||
categoryName: (
|
||||
<strong>
|
||||
{maybe(
|
||||
() => data.category.name,
|
||||
"..."
|
||||
)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
<DialogContentText>
|
||||
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "delete-categories"}
|
||||
open={
|
||||
params.action === "delete-categories" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
confirmButtonState={
|
||||
categoryBulkDeleteMutationState
|
||||
}
|
||||
|
@ -354,27 +360,32 @@ export const CategoryDetails: React.StatelessComponent<
|
|||
variables: { ids: params.ids }
|
||||
})
|
||||
}
|
||||
title={i18n.t("Remove categories")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete categories",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ number }}</strong> categories?",
|
||||
{
|
||||
number: maybe(
|
||||
() =>
|
||||
params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
{i18n.t(
|
||||
"Remember that this will also remove all products assigned to this category."
|
||||
)}
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this attribute}
|
||||
other {{displayQuantity} categories}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(
|
||||
() => params.ids.length
|
||||
),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
<DialogContentText>
|
||||
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
|
@ -388,23 +399,31 @@ export const CategoryDetails: React.StatelessComponent<
|
|||
variables: { ids: params.ids }
|
||||
})
|
||||
}
|
||||
title={i18n.t("Remove products")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete products",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ number }}</strong> products?",
|
||||
{
|
||||
number: maybe(
|
||||
() =>
|
||||
params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{" "}
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this attribute}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(
|
||||
() => params.ids.length
|
||||
),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@ import DialogContentText from "@material-ui/core/DialogContentText";
|
|||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
|
@ -10,7 +11,6 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
|||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import { CategoryListPage } from "../components/CategoryListPage/CategoryListPage";
|
||||
|
@ -39,6 +39,8 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
|||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.CATEGORY_LIST
|
||||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
return (
|
||||
<TypedRootCategoriesQuery displayLoader variables={paginationState}>
|
||||
|
@ -124,26 +126,26 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
|||
})
|
||||
}
|
||||
open={params.action === "delete"}
|
||||
title={i18n.t("Remove categories")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete categories",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ number }}</strong> categories?",
|
||||
{
|
||||
number: maybe(
|
||||
() => params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this attribute}
|
||||
other {{displayQuantity} categories}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>{maybe(() => params.ids.length)}</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
{i18n.t(
|
||||
"Remember that this will also remove all products assigned to this category."
|
||||
)}
|
||||
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
</>
|
||||
|
|
|
@ -2,6 +2,7 @@ import Card from "@material-ui/core/Card";
|
|||
import CardContent from "@material-ui/core/CardContent";
|
||||
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
|
@ -14,7 +15,7 @@ import PageHeader from "@saleor/components/PageHeader";
|
|||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import VisibilityCard from "@saleor/components/VisibilityCard";
|
||||
import i18n from "../../../i18n";
|
||||
import { commonMessages, sectionNames } from "@saleor/intl";
|
||||
import { UserError } from "../../../types";
|
||||
import CollectionDetails from "../CollectionDetails/CollectionDetails";
|
||||
import { CollectionImage } from "../CollectionImage/CollectionImage";
|
||||
|
@ -63,105 +64,108 @@ const CollectionCreatePage: React.StatelessComponent<
|
|||
saveButtonBarState,
|
||||
onBack,
|
||||
onSubmit
|
||||
}: CollectionCreatePageProps) => (
|
||||
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ change, data, errors: formErrors, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Collections")}</AppHeader>
|
||||
<PageHeader
|
||||
title={i18n.t("Add collection", {
|
||||
context: "page title"
|
||||
})}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<CollectionDetails
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<CollectionImage
|
||||
image={
|
||||
data.backgroundImage.url
|
||||
? {
|
||||
__typename: "Image",
|
||||
alt: data.backgroundImageAlt,
|
||||
url: data.backgroundImage.url
|
||||
}
|
||||
: null
|
||||
}
|
||||
onImageDelete={() =>
|
||||
change({
|
||||
target: {
|
||||
name: "backgroundImage",
|
||||
value: {
|
||||
url: null,
|
||||
value: null
|
||||
}
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
onImageUpload={file =>
|
||||
change({
|
||||
target: {
|
||||
name: "backgroundImage",
|
||||
value: {
|
||||
url: URL.createObjectURL(file),
|
||||
value: file
|
||||
}
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
onChange={change}
|
||||
data={data}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
description={data.seoDescription}
|
||||
disabled={disabled}
|
||||
descriptionPlaceholder=""
|
||||
helperText={i18n.t(
|
||||
"Add search engine title and description to make this collection easier to find",
|
||||
{
|
||||
context: "help text"
|
||||
}
|
||||
)}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
}: CollectionCreatePageProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ change, data, errors: formErrors, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.collections)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Add collection",
|
||||
description: "page header"
|
||||
})}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Availability", {
|
||||
context: "collection status"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<VisibilityCard
|
||||
data={data}
|
||||
errors={formErrors}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<CollectionDetails
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<CollectionImage
|
||||
image={
|
||||
data.backgroundImage.url
|
||||
? {
|
||||
__typename: "Image",
|
||||
alt: data.backgroundImageAlt,
|
||||
url: data.backgroundImage.url
|
||||
}
|
||||
: null
|
||||
}
|
||||
onImageDelete={() =>
|
||||
change({
|
||||
target: {
|
||||
name: "backgroundImage",
|
||||
value: {
|
||||
url: null,
|
||||
value: null
|
||||
}
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
onImageUpload={file =>
|
||||
change({
|
||||
target: {
|
||||
name: "backgroundImage",
|
||||
value: {
|
||||
url: URL.createObjectURL(file),
|
||||
value: file
|
||||
}
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
onChange={change}
|
||||
data={data}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
description={data.seoDescription}
|
||||
disabled={disabled}
|
||||
descriptionPlaceholder=""
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this collection easier to find"
|
||||
})}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
<div>
|
||||
<div>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(commonMessages.availability)}
|
||||
/>
|
||||
<CardContent>
|
||||
<VisibilityCard
|
||||
data={data}
|
||||
errors={formErrors}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
CollectionCreatePage.displayName = "CollectionCreatePage";
|
||||
export default CollectionCreatePage;
|
||||
|
|
|
@ -4,13 +4,14 @@ import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe } from "../../../misc";
|
||||
import { FormErrors } from "../../../types";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { FormErrors } from "@saleor/types";
|
||||
import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
||||
|
||||
const styles = createStyles({
|
||||
|
@ -38,33 +39,42 @@ const CollectionDetails = withStyles(styles, { name: "CollectionDetails" })(
|
|||
data,
|
||||
onChange,
|
||||
errors
|
||||
}: CollectionDetailsProps) => (
|
||||
<Card>
|
||||
<CardTitle title={i18n.t("General information")} />
|
||||
<CardContent>
|
||||
<TextField
|
||||
classes={{ root: classes.name }}
|
||||
label={i18n.t("Name")}
|
||||
name="name"
|
||||
disabled={disabled}
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name}
|
||||
}: CollectionDetailsProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(commonMessages.generalInformations)}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<RichTextEditor
|
||||
error={!!errors.descriptionJson}
|
||||
helperText={errors.descriptionJson}
|
||||
initial={maybe(() => JSON.parse(collection.descriptionJson))}
|
||||
label={i18n.t("Description")}
|
||||
name="description"
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
<CardContent>
|
||||
<TextField
|
||||
classes={{ root: classes.name }}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Name",
|
||||
description: "collection name"
|
||||
})}
|
||||
name="name"
|
||||
disabled={disabled}
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<RichTextEditor
|
||||
error={!!errors.descriptionJson}
|
||||
helperText={errors.descriptionJson}
|
||||
initial={maybe(() => JSON.parse(collection.descriptionJson))}
|
||||
label={intl.formatMessage(commonMessages.description)}
|
||||
name="description"
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
CollectionDetails.displayName = "CollectionDetails";
|
||||
export default CollectionDetails;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
|
@ -12,7 +13,7 @@ import PageHeader from "@saleor/components/PageHeader";
|
|||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import VisibilityCard from "@saleor/components/VisibilityCard";
|
||||
import i18n from "../../../i18n";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "../../../misc";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
||||
|
@ -57,6 +58,8 @@ const CollectionDetailsPage: React.StatelessComponent<
|
|||
onSubmit,
|
||||
...collectionProductsProps
|
||||
}: CollectionDetailsPageProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Form
|
||||
initial={{
|
||||
|
@ -74,7 +77,9 @@ const CollectionDetailsPage: React.StatelessComponent<
|
|||
>
|
||||
{({ change, data, errors: formErrors, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Collections")}</AppHeader>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.collections)}
|
||||
</AppHeader>
|
||||
<PageHeader title={maybe(() => collection.name)} />
|
||||
<Grid>
|
||||
<div>
|
||||
|
@ -104,12 +109,10 @@ const CollectionDetailsPage: React.StatelessComponent<
|
|||
description={data.seoDescription}
|
||||
disabled={disabled}
|
||||
descriptionPlaceholder=""
|
||||
helperText={i18n.t(
|
||||
"Add search engine title and description to make this collection easier to find",
|
||||
{
|
||||
context: "help text"
|
||||
}
|
||||
)}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this collection easier to find"
|
||||
})}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={maybe(() => collection.name)}
|
||||
onChange={change}
|
||||
|
@ -128,8 +131,9 @@ const CollectionDetailsPage: React.StatelessComponent<
|
|||
disabled={disabled}
|
||||
name="isFeatured"
|
||||
onChange={change}
|
||||
label={i18n.t("Feature on Homepage", {
|
||||
context: "button"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Feature on Homepage",
|
||||
description: "switch button"
|
||||
})}
|
||||
/>
|
||||
</VisibilityCard>
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
|
@ -15,7 +16,7 @@ import Hr from "@saleor/components/Hr";
|
|||
import ImageTile from "@saleor/components/ImageTile";
|
||||
import ImageUpload from "@saleor/components/ImageUpload";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import i18n from "../../../i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { CollectionDetails_collection_backgroundImage } from "../../types/CollectionDetails";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -51,7 +52,7 @@ const styles = (theme: Theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
export interface CollectionImageProps extends WithStyles<typeof styles> {
|
||||
export interface CollectionImageProps {
|
||||
data: {
|
||||
backgroundImageAlt: string;
|
||||
};
|
||||
|
@ -62,80 +63,81 @@ export interface CollectionImageProps extends WithStyles<typeof styles> {
|
|||
}
|
||||
|
||||
export const CollectionImage = withStyles(styles)(
|
||||
class CollectionImageComponent extends React.Component<
|
||||
CollectionImageProps,
|
||||
{}
|
||||
> {
|
||||
imgInputAnchor = React.createRef<HTMLInputElement>();
|
||||
({
|
||||
classes,
|
||||
data,
|
||||
onImageUpload,
|
||||
image,
|
||||
onChange,
|
||||
onImageDelete
|
||||
}: CollectionImageProps & WithStyles<typeof styles>) => {
|
||||
const anchor = React.useRef<HTMLInputElement>();
|
||||
const intl = useIntl();
|
||||
|
||||
clickImgInput = () => this.imgInputAnchor.current.click();
|
||||
const handleImageUploadButtonClick = () => anchor.current.click();
|
||||
|
||||
render() {
|
||||
const {
|
||||
classes,
|
||||
data,
|
||||
onImageUpload,
|
||||
image,
|
||||
onChange,
|
||||
onImageDelete
|
||||
} = this.props;
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Background image (optional)")}
|
||||
toolbar={
|
||||
<>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
onClick={this.clickImgInput}
|
||||
>
|
||||
{i18n.t("Upload image")}
|
||||
</Button>
|
||||
<input
|
||||
className={classes.fileField}
|
||||
id="fileUpload"
|
||||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
ref={this.imgInputAnchor}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{image === undefined ? (
|
||||
<CardContent>
|
||||
<div>
|
||||
<div className={classes.imageContainer}>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
) : image === null ? (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
) : (
|
||||
<CardContent>
|
||||
<ImageTile image={image} onImageDelete={onImageDelete} />
|
||||
</CardContent>
|
||||
)}
|
||||
{image && (
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Background image (optional)",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<>
|
||||
<Hr />
|
||||
<CardContent>
|
||||
<TextField
|
||||
name="backgroundImageAlt"
|
||||
label={i18n.t("Description")}
|
||||
helperText={i18n.t("Optional")}
|
||||
value={data.backgroundImageAlt}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
</CardContent>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
onClick={handleImageUploadButtonClick}
|
||||
>
|
||||
<FormattedMessage {...commonMessages.uploadImage} />
|
||||
</Button>
|
||||
<input
|
||||
className={classes.fileField}
|
||||
id="fileUpload"
|
||||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
ref={anchor}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
/>
|
||||
{image === undefined ? (
|
||||
<CardContent>
|
||||
<div>
|
||||
<div className={classes.imageContainer}>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
) : image === null ? (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
) : (
|
||||
<CardContent>
|
||||
<ImageTile image={image} onImageDelete={onImageDelete} />
|
||||
</CardContent>
|
||||
)}
|
||||
{image && (
|
||||
<>
|
||||
<Hr />
|
||||
<CardContent>
|
||||
<TextField
|
||||
name="backgroundImageAlt"
|
||||
label={intl.formatMessage(commonMessages.description)}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage: "(Optional)",
|
||||
description: "field is optional"
|
||||
})}
|
||||
value={data.backgroundImageAlt}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
multiline
|
||||
/>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
CollectionImage.displayName = "CollectionImage";
|
||||
|
|
|
@ -11,13 +11,13 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
|
@ -68,107 +68,124 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
|
|||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
}: CollectionListProps) => (
|
||||
<Card>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={collections}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
{i18n.t("Category Name", { context: "table cell" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
{i18n
|
||||
.t("No. Products", { context: "table cell" })
|
||||
.replace(" ", "\xa0")}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAvailability}>
|
||||
{i18n.t("Availability", { context: "table cell" })}
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<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(
|
||||
collections,
|
||||
collection => {
|
||||
const isSelected = collection ? isChecked(collection.id) : false;
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
hover={!!collection}
|
||||
onClick={collection ? onRowClick(collection.id) : undefined}
|
||||
key={collection ? collection.id : "skeleton"}
|
||||
selected={isSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(collection.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => collection.name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => collection.products.totalCount,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAvailability}>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
status={collection.isPublished ? "success" : "error"}
|
||||
label={
|
||||
collection.isPublished
|
||||
? i18n.t("Published")
|
||||
: i18n.t("Not published")
|
||||
}
|
||||
/>
|
||||
),
|
||||
<Skeleton />
|
||||
)}
|
||||
}: CollectionListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={collections}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage defaultMessage="Category Name" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
<FormattedMessage defaultMessage="No. of Products" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAvailability}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Availability"
|
||||
description="collection availability"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<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(
|
||||
collections,
|
||||
collection => {
|
||||
const isSelected = collection
|
||||
? isChecked(collection.id)
|
||||
: false;
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
hover={!!collection}
|
||||
onClick={collection ? onRowClick(collection.id) : undefined}
|
||||
key={collection ? collection.id : "skeleton"}
|
||||
selected={isSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(collection.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => collection.name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => collection.products.totalCount,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAvailability}>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
status={
|
||||
collection.isPublished ? "success" : "error"
|
||||
}
|
||||
label={
|
||||
collection.isPublished
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "collection is published"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "collection is not published"
|
||||
})
|
||||
}
|
||||
/>
|
||||
),
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No collections found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No collections found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
CollectionList.displayName = "CollectionList";
|
||||
export default CollectionList;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { Container } from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
import CollectionList from "../CollectionList/CollectionList";
|
||||
|
@ -17,21 +18,28 @@ const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({
|
|||
disabled,
|
||||
onAdd,
|
||||
...listProps
|
||||
}) => (
|
||||
<Container>
|
||||
<PageHeader title={i18n.t("Collections", { context: "page title" })}>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
variant="contained"
|
||||
onClick={onAdd}
|
||||
>
|
||||
{i18n.t("Add collection", { context: "button" })}
|
||||
<AddIcon />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<CollectionList disabled={disabled} {...listProps} />
|
||||
</Container>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.collections)}>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
variant="contained"
|
||||
onClick={onAdd}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add collection"
|
||||
description="button"
|
||||
/>
|
||||
<AddIcon />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<CollectionList disabled={disabled} {...listProps} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
CollectionListPage.displayName = "CollectionListPage";
|
||||
export default CollectionListPage;
|
||||
|
|
|
@ -14,6 +14,7 @@ import TableFooter from "@material-ui/core/TableFooter";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
|
@ -24,7 +25,6 @@ import TableCellAvatar, {
|
|||
} from "@saleor/components/TableCellAvatar";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
||||
|
@ -83,137 +83,165 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
|
|||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
}: CollectionProductsProps) => (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={
|
||||
!!collection ? (
|
||||
i18n.t("Products in {{ collectionName }}", {
|
||||
collectionName: collection.name
|
||||
})
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
}
|
||||
toolbar={
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="text"
|
||||
color="primary"
|
||||
onClick={onAdd}
|
||||
>
|
||||
{i18n.t("Assign product", {
|
||||
context: "button"
|
||||
})}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table className={classes.table}>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={maybe(() => collection.products.edges.map(edge => edge.node))}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameLabel}>
|
||||
{i18n.t("Name", { context: "table header" })}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colType}>
|
||||
{i18n.t("Type", { context: "table header" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{i18n.t("Published", { context: "table header" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions} />
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={maybe(() => pageInfo.hasNextPage)}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={maybe(() => pageInfo.hasPreviousPage)}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
maybe(() => collection.products.edges.map(edge => edge.node)),
|
||||
product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
}: CollectionProductsProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
hover={!!product}
|
||||
onClick={!!product ? onRowClick(product.id) : undefined}
|
||||
key={product ? product.id : "skeleton"}
|
||||
selected={isSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={
|
||||
!!collection ? (
|
||||
intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Products in {name}",
|
||||
description: "products in collection"
|
||||
},
|
||||
{
|
||||
name: maybe(() => collection.name, "...")
|
||||
}
|
||||
)
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
}
|
||||
toolbar={
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="text"
|
||||
color="primary"
|
||||
onClick={onAdd}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign product"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table className={classes.table}>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={maybe(() =>
|
||||
collection.products.edges.map(edge => edge.node)
|
||||
)}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameLabel}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Name"
|
||||
description="product name"
|
||||
/>
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colType}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Type"
|
||||
description="product type"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product is published"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions} />
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={maybe(() => pageInfo.hasNextPage)}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={maybe(() => pageInfo.hasPreviousPage)}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
maybe(() => collection.products.edges.map(edge => edge.node)),
|
||||
product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={classes.tableRow}
|
||||
hover={!!product}
|
||||
onClick={!!product ? onRowClick(product.id) : undefined}
|
||||
key={product ? product.id : "skeleton"}
|
||||
selected={isSelected}
|
||||
>
|
||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colType}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => product.productType.name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isPublished
|
||||
? i18n.t("Published")
|
||||
: i18n.t("Not published")
|
||||
}
|
||||
status={product.isPublished ? "success" : "error"}
|
||||
/>
|
||||
),
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions}>
|
||||
<IconButton
|
||||
disabled={!product}
|
||||
onClick={event => onProductUnassign(product.id, event)}
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
>
|
||||
<DeleteIcon color="primary" />
|
||||
</IconButton>
|
||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colType}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => product.productType.name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isPublished
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "product is published"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "product is not published"
|
||||
})
|
||||
}
|
||||
status={product.isPublished ? "success" : "error"}
|
||||
/>
|
||||
),
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions}>
|
||||
<IconButton
|
||||
disabled={!product}
|
||||
onClick={event => onProductUnassign(product.id, event)}
|
||||
>
|
||||
<DeleteIcon color="primary" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No products found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No products found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
CollectionProducts.displayName = "CollectionProducts";
|
||||
export default CollectionProducts;
|
||||
|
|
|
@ -31,30 +31,28 @@ import {
|
|||
} from "../types/UnassignCollectionProduct";
|
||||
|
||||
interface CollectionUpdateOperationsProps {
|
||||
children: (
|
||||
props: {
|
||||
updateCollectionWithHomepage: PartialMutationProviderOutput<
|
||||
CollectionUpdateWithHomepage,
|
||||
CollectionUpdateWithHomepageVariables
|
||||
>;
|
||||
assignProduct: PartialMutationProviderOutput<
|
||||
CollectionAssignProduct,
|
||||
CollectionAssignProductVariables
|
||||
>;
|
||||
unassignProduct: PartialMutationProviderOutput<
|
||||
UnassignCollectionProduct,
|
||||
UnassignCollectionProductVariables
|
||||
>;
|
||||
updateCollection: PartialMutationProviderOutput<
|
||||
CollectionUpdate,
|
||||
CollectionUpdateVariables
|
||||
>;
|
||||
removeCollection: PartialMutationProviderOutput<
|
||||
RemoveCollection,
|
||||
RemoveCollectionVariables
|
||||
>;
|
||||
}
|
||||
) => React.ReactNode;
|
||||
children: (props: {
|
||||
updateCollectionWithHomepage: PartialMutationProviderOutput<
|
||||
CollectionUpdateWithHomepage,
|
||||
CollectionUpdateWithHomepageVariables
|
||||
>;
|
||||
assignProduct: PartialMutationProviderOutput<
|
||||
CollectionAssignProduct,
|
||||
CollectionAssignProductVariables
|
||||
>;
|
||||
unassignProduct: PartialMutationProviderOutput<
|
||||
UnassignCollectionProduct,
|
||||
UnassignCollectionProductVariables
|
||||
>;
|
||||
updateCollection: PartialMutationProviderOutput<
|
||||
CollectionUpdate,
|
||||
CollectionUpdateVariables
|
||||
>;
|
||||
removeCollection: PartialMutationProviderOutput<
|
||||
RemoveCollection,
|
||||
RemoveCollectionVariables
|
||||
>;
|
||||
}) => React.ReactNode;
|
||||
onUpdate: (data: CollectionUpdate) => void;
|
||||
onProductAssign: (data: CollectionAssignProduct) => void;
|
||||
onProductUnassign: (data: UnassignCollectionProduct) => void;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { parse as parseQs } from "qs";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import i18n from "../i18n";
|
||||
import {
|
||||
collectionAddPath,
|
||||
collectionListPath,
|
||||
|
@ -39,14 +40,18 @@ const CollectionDetails: React.StatelessComponent<
|
|||
);
|
||||
};
|
||||
|
||||
const Component = () => (
|
||||
<>
|
||||
<WindowTitle title={i18n.t("Collections")} />
|
||||
<Switch>
|
||||
<Route exact path={collectionListPath} component={CollectionList} />
|
||||
<Route exact path={collectionAddPath} component={CollectionCreate} />
|
||||
<Route path={collectionPath(":id")} component={CollectionDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
const Component = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.collections)} />
|
||||
<Switch>
|
||||
<Route exact path={collectionListPath} component={CollectionList} />
|
||||
<Route exact path={collectionAddPath} component={CollectionCreate} />
|
||||
<Route path={collectionPath(":id")} component={CollectionDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Component;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import { CollectionCreateInput } from "../../types/globalTypes";
|
||||
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
||||
|
@ -11,15 +11,16 @@ import { TypedCollectionCreateMutation } from "../mutations";
|
|||
import { CreateCollection } from "../types/CreateCollection";
|
||||
import { collectionListUrl, collectionUrl } from "../urls";
|
||||
|
||||
export const CollectionCreate: React.StatelessComponent<{}> = () => {
|
||||
export const CollectionCreate: React.FC = () => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleCollectionCreateSuccess = (data: CreateCollection) => {
|
||||
if (data.collectionCreate.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Created collection", {
|
||||
context: "notification"
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Created collection"
|
||||
})
|
||||
});
|
||||
navigate(collectionUrl(data.collectionCreate.collection.id));
|
||||
|
@ -45,7 +46,12 @@ export const CollectionCreate: React.StatelessComponent<{}> = () => {
|
|||
);
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={i18n.t("Create collection")} />
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create collection",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<CollectionCreatePage
|
||||
errors={maybe(() => data.collectionCreate.errors, [])}
|
||||
onBack={() => navigate(collectionListUrl())}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import AssignProductDialog from "@saleor/components/AssignProductDialog";
|
||||
|
@ -11,9 +12,9 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
|
||||
import SearchProducts from "../../containers/SearchProducts";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import { productUrl } from "../../products/urls";
|
||||
import { CollectionInput } from "../../types/globalTypes";
|
||||
|
@ -47,6 +48,7 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
params.ids
|
||||
);
|
||||
const paginate = usePaginator();
|
||||
const intl = useIntl();
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
|
@ -77,9 +79,7 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
const handleCollectionUpdate = (data: CollectionUpdate) => {
|
||||
if (data.collectionUpdate.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Updated collection", {
|
||||
context: "notification"
|
||||
})
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(collectionUrl(id));
|
||||
} else {
|
||||
|
@ -98,8 +98,8 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
const handleProductAssign = (data: CollectionAssignProduct) => {
|
||||
if (data.collectionAddProducts.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Added product to collection", {
|
||||
context: "notification"
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Added product to collection"
|
||||
})
|
||||
});
|
||||
navigate(collectionUrl(id), true);
|
||||
|
@ -109,8 +109,8 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
const handleProductUnassign = (data: UnassignCollectionProduct) => {
|
||||
if (data.collectionRemoveProducts.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Removed product from collection", {
|
||||
context: "notification"
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Deleted product from collection"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
|
@ -121,8 +121,8 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
const handleCollectionRemove = (data: RemoveCollection) => {
|
||||
if (data.collectionDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Removed collection", {
|
||||
context: "notification"
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Deleted collection"
|
||||
})
|
||||
});
|
||||
navigate(collectionListUrl());
|
||||
|
@ -272,7 +272,10 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
)
|
||||
}
|
||||
>
|
||||
{i18n.t("Unassign")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Unassign"
|
||||
description="unassign product from collection, button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
|
@ -308,25 +311,24 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
onClose={closeModal}
|
||||
onConfirm={() => removeCollection.mutate({ id })}
|
||||
open={params.action === "remove"}
|
||||
title={i18n.t("Remove collection", {
|
||||
context: "modal title"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Collection",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ collectionName }}</strong>?",
|
||||
{
|
||||
collectionName: maybe(
|
||||
() => data.collection.name,
|
||||
"..."
|
||||
),
|
||||
context: "modal"
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {collectionName}?"
|
||||
values={{
|
||||
collectionName: (
|
||||
<strong>
|
||||
{maybe(() => data.collection.name, "...")}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
confirmButtonState={unassignTransitionState}
|
||||
|
@ -339,24 +341,25 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
})
|
||||
}
|
||||
open={params.action === "unassign"}
|
||||
title={i18n.t("Unassign products from collection", {
|
||||
context: "modal title"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unassign products from collection",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to unassign <strong>{{ number }}</strong> products?",
|
||||
{
|
||||
context: "modal",
|
||||
number: maybe(
|
||||
() => params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to unassign {counter, plural,
|
||||
one {this product}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>{maybe(() => params.ids.length)}</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
confirmButtonState={imageRemoveTransitionState}
|
||||
|
@ -370,15 +373,14 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
})
|
||||
}
|
||||
open={params.action === "removeImage"}
|
||||
title={i18n.t("Remove image", {
|
||||
context: "modal title"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete image",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
{i18n.t(
|
||||
"Are you sure you want to remove collection's image?"
|
||||
)}
|
||||
<FormattedMessage defaultMessage="Are you sure you want to delete collection's image?" />
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
</>
|
||||
|
|
|
@ -3,6 +3,7 @@ import DialogContentText from "@material-ui/core/DialogContentText";
|
|||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
|
@ -12,7 +13,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import CollectionListPage from "../components/CollectionListPage/CollectionListPage";
|
||||
|
@ -47,6 +48,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.COLLECTION_LIST
|
||||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
|
@ -79,7 +81,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
const handleCollectionBulkDelete = (data: CollectionBulkDelete) => {
|
||||
if (data.collectionBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Removed collections")
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
|
@ -90,7 +92,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
const handleCollectionBulkPublish = (data: CollectionBulkPublish) => {
|
||||
if (data.collectionBulkPublish.errors.length === 0) {
|
||||
notify({
|
||||
text: i18n.t("Changed publication status")
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
|
@ -147,13 +149,19 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
openModal("unpublish", listElements)
|
||||
}
|
||||
>
|
||||
{i18n.t("Unpublish")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Unpublish"
|
||||
description="unpublish collections"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => openModal("publish", listElements)}
|
||||
>
|
||||
{i18n.t("Publish")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Publish"
|
||||
description="publish collections"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
|
@ -169,7 +177,10 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
toggleAll={toggleAll}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "publish"}
|
||||
open={
|
||||
params.action === "publish" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={bulkPublishTransitionState}
|
||||
onConfirm={() =>
|
||||
|
@ -181,24 +192,33 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
})
|
||||
}
|
||||
variant="default"
|
||||
title={i18n.t("Publish collections")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Publish collections",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to publish <strong>{{ number }}</strong> collections?",
|
||||
{
|
||||
number: maybe(
|
||||
() => params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to publish {counter, plural,
|
||||
one {this collection}
|
||||
other {{displayQuantity} collections}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "unpublish"}
|
||||
open={
|
||||
params.action === "unpublish" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={bulkPublishTransitionState}
|
||||
onConfirm={() =>
|
||||
|
@ -210,24 +230,33 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
})
|
||||
}
|
||||
variant="default"
|
||||
title={i18n.t("Unpublish collections")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unpublish collections",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to unpublish <strong>{{ number }}</strong> collections?",
|
||||
{
|
||||
number: maybe(
|
||||
() => params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to unpublish {counter, plural,
|
||||
one {this collection}
|
||||
other {{displayQuantity} collections}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "remove"}
|
||||
open={
|
||||
params.action === "remove" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={bulkDeleteTransitionState}
|
||||
onConfirm={() =>
|
||||
|
@ -238,21 +267,27 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
})
|
||||
}
|
||||
variant="delete"
|
||||
title={i18n.t("Remove collections")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete collections",
|
||||
description: "dialog title"
|
||||
})}
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to remove <strong>{{ number }}</strong> collections?",
|
||||
{
|
||||
number: maybe(
|
||||
() => params.ids.length.toString(),
|
||||
"..."
|
||||
)
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this collection}
|
||||
other {{displayQuantity} collections}
|
||||
}?"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -11,8 +11,9 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "../ConfirmButton/ConfirmButton";
|
||||
|
@ -50,31 +51,35 @@ const ActionDialog = withStyles(styles, { name: "ActionDialog" })(
|
|||
variant,
|
||||
onConfirm,
|
||||
onClose
|
||||
}: ActionDialogProps) => (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
onClick={onConfirm}
|
||||
className={classNames({
|
||||
[classes.deleteButton]: variant === "delete"
|
||||
})}
|
||||
>
|
||||
{confirmButtonLabel ||
|
||||
(variant === "delete"
|
||||
? i18n.t("Delete", { context: "button" })
|
||||
: i18n.t("Confirm", { context: "button" }))}
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}: ActionDialogProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
onClick={onConfirm}
|
||||
className={classNames({
|
||||
[classes.deleteButton]: variant === "delete"
|
||||
})}
|
||||
>
|
||||
{confirmButtonLabel ||
|
||||
(variant === "delete"
|
||||
? intl.formatMessage(buttonMessages.delete)
|
||||
: intl.formatMessage(buttonMessages.confirm))}
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
ActionDialog.displayName = "ActionDialog";
|
||||
export default ActionDialog;
|
||||
|
|
|
@ -6,9 +6,10 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { AddressTypeInput } from "@saleor/customers/types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { FormErrors } from "@saleor/types";
|
||||
import FormSpacer from "../FormSpacer";
|
||||
import SingleAutocompleteSelectField, {
|
||||
|
@ -44,144 +45,164 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
|
|||
errors,
|
||||
onChange,
|
||||
onCountryChange
|
||||
}: AddressEditProps) => (
|
||||
<>
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.firstName}
|
||||
helperText={errors.firstName}
|
||||
label={i18n.t("First name")}
|
||||
name="firstName"
|
||||
onChange={onChange}
|
||||
value={data.firstName}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.lastName}
|
||||
helperText={errors.lastName}
|
||||
label={i18n.t("Last name")}
|
||||
name="lastName"
|
||||
onChange={onChange}
|
||||
value={data.lastName}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.companyName}
|
||||
helperText={errors.companyName}
|
||||
label={i18n.t("Company")}
|
||||
name="companyName"
|
||||
onChange={onChange}
|
||||
value={data.companyName}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.phone}
|
||||
fullWidth
|
||||
helperText={errors.phone}
|
||||
label={i18n.t("Phone")}
|
||||
name="phone"
|
||||
value={data.phone}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.streetAddress1}
|
||||
helperText={errors.streetAddress1}
|
||||
label={i18n.t("Address line 1")}
|
||||
name="streetAddress1"
|
||||
onChange={onChange}
|
||||
value={data.streetAddress1}
|
||||
fullWidth
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.streetAddress2}
|
||||
helperText={errors.streetAddress2}
|
||||
label={i18n.t("Address line 2")}
|
||||
name="streetAddress2"
|
||||
onChange={onChange}
|
||||
value={data.streetAddress2}
|
||||
fullWidth
|
||||
/>
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.city}
|
||||
helperText={errors.city}
|
||||
label={i18n.t("City")}
|
||||
name="city"
|
||||
onChange={onChange}
|
||||
value={data.city}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.postalCode}
|
||||
helperText={errors.postalCode}
|
||||
label={i18n.t("ZIP / Postal code")}
|
||||
name="postalCode"
|
||||
onChange={onChange}
|
||||
value={data.postalCode}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}: AddressEditProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<SingleAutocompleteSelectField
|
||||
disabled={disabled}
|
||||
displayValue={countryDisplayValue}
|
||||
error={!!errors.country}
|
||||
helperText={errors.country}
|
||||
label={i18n.t("Country")}
|
||||
name="country"
|
||||
onChange={onCountryChange}
|
||||
value={data.country}
|
||||
choices={countries}
|
||||
InputProps={{
|
||||
autoComplete: "off"
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.firstName}
|
||||
helperText={errors.firstName}
|
||||
label={intl.formatMessage(commonMessages.firstName)}
|
||||
name="firstName"
|
||||
onChange={onChange}
|
||||
value={data.firstName}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.lastName}
|
||||
helperText={errors.lastName}
|
||||
label={intl.formatMessage(commonMessages.lastName)}
|
||||
name="lastName"
|
||||
onChange={onChange}
|
||||
value={data.lastName}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.countryArea}
|
||||
helperText={errors.countryArea}
|
||||
label={i18n.t("Country area")}
|
||||
name="countryArea"
|
||||
onChange={onChange}
|
||||
value={data.countryArea}
|
||||
fullWidth
|
||||
/>
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.companyName}
|
||||
helperText={errors.companyName}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Company"
|
||||
})}
|
||||
name="companyName"
|
||||
onChange={onChange}
|
||||
value={data.companyName}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.phone}
|
||||
fullWidth
|
||||
helperText={errors.phone}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Phone"
|
||||
})}
|
||||
name="phone"
|
||||
value={data.phone}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.streetAddress1}
|
||||
helperText={errors.streetAddress1}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Address line 1"
|
||||
})}
|
||||
name="streetAddress1"
|
||||
onChange={onChange}
|
||||
value={data.streetAddress1}
|
||||
fullWidth
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.streetAddress2}
|
||||
helperText={errors.streetAddress2}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Address line 2"
|
||||
})}
|
||||
name="streetAddress2"
|
||||
onChange={onChange}
|
||||
value={data.streetAddress2}
|
||||
fullWidth
|
||||
/>
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.city}
|
||||
helperText={errors.city}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "City"
|
||||
})}
|
||||
name="city"
|
||||
onChange={onChange}
|
||||
value={data.city}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.postalCode}
|
||||
helperText={errors.postalCode}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "ZIP / Postal code"
|
||||
})}
|
||||
name="postalCode"
|
||||
onChange={onChange}
|
||||
value={data.postalCode}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<div>
|
||||
<SingleAutocompleteSelectField
|
||||
disabled={disabled}
|
||||
displayValue={countryDisplayValue}
|
||||
error={!!errors.country}
|
||||
helperText={errors.country}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Country"
|
||||
})}
|
||||
name="country"
|
||||
onChange={onCountryChange}
|
||||
value={data.country}
|
||||
choices={countries}
|
||||
InputProps={{
|
||||
autoComplete: "off"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
error={!!errors.countryArea}
|
||||
helperText={errors.countryArea}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Country area"
|
||||
})}
|
||||
name="countryArea"
|
||||
onChange={onChange}
|
||||
value={data.countryArea}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
AddressEdit.displayName = "AddressEdit";
|
||||
export default AddressEdit;
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { RouteComponentProps, withRouter } from "react-router";
|
||||
|
||||
import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
|
||||
|
@ -27,14 +28,13 @@ import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import useUser from "@saleor/hooks/useUser";
|
||||
import i18n from "@saleor/i18n";
|
||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
||||
import Container from "../Container";
|
||||
import AppActionContext from "./AppActionContext";
|
||||
import AppHeaderContext from "./AppHeaderContext";
|
||||
import { appLoaderHeight, drawerWidth, drawerWidthExpanded } from "./consts";
|
||||
import MenuList from "./MenuList";
|
||||
import menuStructure from "./menuStructure";
|
||||
import createMenuStructure from "./menuStructure";
|
||||
import ResponsiveDrawer from "./ResponsiveDrawer";
|
||||
import ThemeSwitch from "./ThemeSwitch";
|
||||
|
||||
|
@ -108,9 +108,7 @@ const styles = (theme: Theme) =>
|
|||
},
|
||||
isMenuSmallDark: {
|
||||
"&:hover": {
|
||||
background: `linear-gradient(0deg, rgba(25, 195, 190, 0.1), rgba(25, 195, 190, 0.1)), ${
|
||||
theme.palette.background.paper
|
||||
}`
|
||||
background: `linear-gradient(0deg, rgba(25, 195, 190, 0.1), rgba(25, 195, 190, 0.1)), ${theme.palette.background.paper}`
|
||||
},
|
||||
border: `solid 1px #252728`,
|
||||
transition: `background ${theme.transitions.duration.shorter}ms`
|
||||
|
@ -277,6 +275,9 @@ const AppLayout = withStyles(styles, {
|
|||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const { logout, user } = useUser();
|
||||
const navigate = useNavigator();
|
||||
const intl = useIntl();
|
||||
|
||||
const menuStructure = createMenuStructure(intl);
|
||||
|
||||
const handleLogout = () => {
|
||||
close();
|
||||
|
@ -430,9 +431,10 @@ const AppLayout = withStyles(styles, {
|
|||
className={classes.userMenuItem}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
{i18n.t("Log out", {
|
||||
context: "button"
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Log out"
|
||||
description="button"
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</ClickAwayListener>
|
||||
|
|
|
@ -8,13 +8,17 @@ import Typography from "@material-ui/core/Typography";
|
|||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { matchPath } from "react-router";
|
||||
|
||||
import configureIcon from "@assets/images/menu-configure-icon.svg";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { User } from "../../auth/types/User";
|
||||
import { configurationMenu, configurationMenuUrl } from "../../configuration";
|
||||
import i18n from "../../i18n";
|
||||
import {
|
||||
configurationMenuUrl,
|
||||
createConfigurationMenu
|
||||
} from "../../configuration";
|
||||
import { createHref } from "../../misc";
|
||||
import { orderDraftListUrl, orderListUrl } from "../../orders/urls";
|
||||
import MenuNested from "./MenuNested";
|
||||
|
@ -167,6 +171,7 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
|
|||
isActive: false,
|
||||
label: null
|
||||
});
|
||||
const intl = useIntl();
|
||||
|
||||
const handleSubMenu = itemLabel => {
|
||||
setActiveSubMenu({
|
||||
|
@ -300,7 +305,7 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
|
|||
);
|
||||
})}
|
||||
{renderConfigure &&
|
||||
configurationMenu.filter(menuItem =>
|
||||
createConfigurationMenu(intl).filter(menuItem =>
|
||||
user.permissions
|
||||
.map(perm => perm.code)
|
||||
.includes(menuItem.permission)
|
||||
|
@ -323,7 +328,7 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
|
|||
[classes.menuListItemTextHide]: !isMenuSmall
|
||||
})}
|
||||
>
|
||||
{i18n.t("Configuration")}
|
||||
<FormattedMessage {...sectionNames.configuration} />
|
||||
</Typography>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -2,7 +2,6 @@ import { categoryListUrl } from "../../categories/urls";
|
|||
import { collectionListUrl } from "../../collections/urls";
|
||||
import { customerListUrl } from "../../customers/urls";
|
||||
import { saleListUrl, voucherListUrl } from "../../discounts/urls";
|
||||
import i18n from "../../i18n";
|
||||
import { orderDraftListUrl, orderListUrl } from "../../orders/urls";
|
||||
import { productListUrl } from "../../products/urls";
|
||||
import { languageListUrl } from "../../translations/urls";
|
||||
|
@ -14,6 +13,8 @@ import discountsIcon from "@assets/images/menu-discounts-icon.svg";
|
|||
import homeIcon from "@assets/images/menu-home-icon.svg";
|
||||
import ordersIcon from "@assets/images/menu-orders-icon.svg";
|
||||
import translationIcon from "@assets/images/menu-translation-icon.svg";
|
||||
import { commonMessages, sectionNames } from "@saleor/intl";
|
||||
import { IntlShape } from "react-intl";
|
||||
|
||||
export interface IMenuItem {
|
||||
ariaLabel: string;
|
||||
|
@ -24,88 +25,91 @@ export interface IMenuItem {
|
|||
url?: string;
|
||||
}
|
||||
|
||||
const menuStructure: IMenuItem[] = [
|
||||
{
|
||||
ariaLabel: "home",
|
||||
icon: homeIcon,
|
||||
label: i18n.t("Home", { context: "Menu label" }),
|
||||
url: "/"
|
||||
},
|
||||
{
|
||||
ariaLabel: "catalogue",
|
||||
children: [
|
||||
{
|
||||
ariaLabel: "products",
|
||||
label: i18n.t("Products", { context: "Menu label" }),
|
||||
url: productListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "categories",
|
||||
label: i18n.t("Categories", { context: "Menu label" }),
|
||||
url: categoryListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "collections",
|
||||
label: i18n.t("Collections", { context: "Menu label" }),
|
||||
url: collectionListUrl()
|
||||
}
|
||||
],
|
||||
icon: catalogIcon,
|
||||
label: i18n.t("Catalog", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS
|
||||
},
|
||||
{
|
||||
ariaLabel: "orders",
|
||||
children: [
|
||||
{
|
||||
ariaLabel: "orders",
|
||||
label: i18n.t("Orders", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_ORDERS,
|
||||
url: orderListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "order drafts",
|
||||
label: i18n.t("Drafts", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_ORDERS,
|
||||
url: orderDraftListUrl()
|
||||
}
|
||||
],
|
||||
icon: ordersIcon,
|
||||
label: i18n.t("Orders", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_ORDERS
|
||||
},
|
||||
{
|
||||
ariaLabel: "customers",
|
||||
icon: customerIcon,
|
||||
label: i18n.t("Customers", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_USERS,
|
||||
url: customerListUrl()
|
||||
},
|
||||
function createMenuStructure(intl: IntlShape): IMenuItem[] {
|
||||
return [
|
||||
{
|
||||
ariaLabel: "home",
|
||||
icon: homeIcon,
|
||||
label: intl.formatMessage(sectionNames.home),
|
||||
url: "/"
|
||||
},
|
||||
{
|
||||
ariaLabel: "catalogue",
|
||||
children: [
|
||||
{
|
||||
ariaLabel: "products",
|
||||
label: intl.formatMessage(sectionNames.products),
|
||||
url: productListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "categories",
|
||||
label: intl.formatMessage(sectionNames.categories),
|
||||
url: categoryListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "collections",
|
||||
label: intl.formatMessage(sectionNames.collections),
|
||||
url: collectionListUrl()
|
||||
}
|
||||
],
|
||||
icon: catalogIcon,
|
||||
label: intl.formatMessage(commonMessages.catalog),
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS
|
||||
},
|
||||
{
|
||||
ariaLabel: "orders",
|
||||
children: [
|
||||
{
|
||||
ariaLabel: "orders",
|
||||
label: intl.formatMessage(sectionNames.orders),
|
||||
permission: PermissionEnum.MANAGE_ORDERS,
|
||||
url: orderListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "order drafts",
|
||||
label: intl.formatMessage(commonMessages.drafts),
|
||||
permission: PermissionEnum.MANAGE_ORDERS,
|
||||
url: orderDraftListUrl()
|
||||
}
|
||||
],
|
||||
icon: ordersIcon,
|
||||
label: intl.formatMessage(sectionNames.orders),
|
||||
permission: PermissionEnum.MANAGE_ORDERS
|
||||
},
|
||||
{
|
||||
ariaLabel: "customers",
|
||||
icon: customerIcon,
|
||||
label: intl.formatMessage(sectionNames.customers),
|
||||
permission: PermissionEnum.MANAGE_USERS,
|
||||
url: customerListUrl()
|
||||
},
|
||||
|
||||
{
|
||||
ariaLabel: "discounts",
|
||||
children: [
|
||||
{
|
||||
ariaLabel: "sales",
|
||||
label: i18n.t("Sales", { context: "Menu label" }),
|
||||
url: saleListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "vouchers",
|
||||
label: i18n.t("Vouchers", { context: "Menu label" }),
|
||||
url: voucherListUrl()
|
||||
}
|
||||
],
|
||||
icon: discountsIcon,
|
||||
label: i18n.t("Discounts", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_DISCOUNTS
|
||||
},
|
||||
{
|
||||
ariaLabel: "translations",
|
||||
icon: translationIcon,
|
||||
label: i18n.t("Translations", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_TRANSLATIONS,
|
||||
url: languageListUrl
|
||||
}
|
||||
];
|
||||
export default menuStructure;
|
||||
{
|
||||
ariaLabel: "discounts",
|
||||
children: [
|
||||
{
|
||||
ariaLabel: "sales",
|
||||
label: intl.formatMessage(sectionNames.sales),
|
||||
url: saleListUrl()
|
||||
},
|
||||
{
|
||||
ariaLabel: "vouchers",
|
||||
label: intl.formatMessage(sectionNames.vouchers),
|
||||
url: voucherListUrl()
|
||||
}
|
||||
],
|
||||
icon: discountsIcon,
|
||||
label: intl.formatMessage(commonMessages.discounts),
|
||||
permission: PermissionEnum.MANAGE_DISCOUNTS
|
||||
},
|
||||
{
|
||||
ariaLabel: "translations",
|
||||
icon: translationIcon,
|
||||
label: intl.formatMessage(sectionNames.translations),
|
||||
permission: PermissionEnum.MANAGE_TRANSLATIONS,
|
||||
url: languageListUrl
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export default createMenuStructure;
|
||||
|
|
|
@ -11,14 +11,15 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { SearchCategories_categories_edges_node } from "../../containers/SearchCategories/types/SearchCategories";
|
||||
import i18n from "../../i18n";
|
||||
import Checkbox from "../Checkbox";
|
||||
|
||||
export interface FormData {
|
||||
|
@ -85,6 +86,7 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
onFetch,
|
||||
onSubmit
|
||||
}: AssignCategoriesDialogProps) => {
|
||||
const intl = useIntl();
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<
|
||||
SearchCategories_categories_edges_node[]
|
||||
|
@ -100,17 +102,22 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>{i18n.t("Assign Categories")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign Categories"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<TextField
|
||||
name="query"
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={i18n.t("Search Categories", {
|
||||
context: "category search input label"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Search Categories"
|
||||
})}
|
||||
placeholder={i18n.t("Search by category name, etc...", {
|
||||
context: "category search input placeholder"
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Search by category name, etc..."
|
||||
})}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
|
@ -156,7 +163,7 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
|
@ -165,7 +172,10 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{i18n.t("Assign categories", { context: "button" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign categories"
|
||||
description="button"
|
||||
/>
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
|
@ -11,9 +11,10 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { SearchCollections_collections_edges_node } from "../../containers/SearchCollections/types/SearchCollections";
|
||||
import Checkbox from "../Checkbox";
|
||||
import ConfirmButton, {
|
||||
|
@ -85,6 +86,7 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
onFetch,
|
||||
onSubmit
|
||||
}: AssignCollectionDialogProps) => {
|
||||
const intl = useIntl();
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const [selectedCollections, setSelectedCollections] = React.useState<
|
||||
SearchCollections_collections_edges_node[]
|
||||
|
@ -100,17 +102,22 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>{i18n.t("Assign Collection")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign Collection"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<TextField
|
||||
name="query"
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={i18n.t("Search Collection", {
|
||||
context: "product search input label"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Search Collection"
|
||||
})}
|
||||
placeholder={i18n.t("Search by collection name, etc...", {
|
||||
context: "product search input placeholder"
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Search by collection name, etc..."
|
||||
})}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
|
@ -157,7 +164,7 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
|
@ -166,7 +173,10 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{i18n.t("Assign collections", { context: "button" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign collections"
|
||||
description="button"
|
||||
/>
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
|
@ -11,6 +11,7 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
|
@ -18,7 +19,7 @@ import ConfirmButton, {
|
|||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { SearchProducts_products_edges_node } from "../../containers/SearchProducts/types/SearchProducts";
|
||||
import Checkbox from "../Checkbox";
|
||||
|
@ -88,6 +89,7 @@ const AssignProductDialog = withStyles(styles, {
|
|||
onFetch,
|
||||
onSubmit
|
||||
}: AssignProductDialogProps & WithStyles<typeof styles>) => {
|
||||
const intl = useIntl();
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const [selectedProducts, setSelectedProducts] = React.useState<
|
||||
SearchProducts_products_edges_node[]
|
||||
|
@ -103,21 +105,24 @@ const AssignProductDialog = withStyles(styles, {
|
|||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>{i18n.t("Assign Product")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign Product"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
name="query"
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={i18n.t("Search Products", {
|
||||
context: "product search input label"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Search Products"
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Search by product name, attribute, product type etc..."
|
||||
})}
|
||||
placeholder={i18n.t(
|
||||
"Search by product name, attribute, product type etc...",
|
||||
{
|
||||
context: "product search input placeholder"
|
||||
}
|
||||
)}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
autoComplete: "off",
|
||||
|
@ -168,7 +173,7 @@ const AssignProductDialog = withStyles(styles, {
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
|
@ -177,7 +182,10 @@ const AssignProductDialog = withStyles(styles, {
|
|||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{i18n.t("Assign products", { context: "button" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign products"
|
||||
description="button"
|
||||
/>
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
|
@ -11,8 +11,9 @@ import TextField from "@material-ui/core/TextField";
|
|||
import ArrowBack from "@material-ui/icons/ArrowBack";
|
||||
import Downshift from "downshift";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import {
|
||||
getMenuItemByPath,
|
||||
IMenu,
|
||||
|
@ -153,7 +154,7 @@ const AutocompleteSelectMenu = withStyles(styles, {
|
|||
}
|
||||
>
|
||||
<ArrowBack className={classes.menuBack} />
|
||||
{i18n.t("Back")}
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
</MenuItem>
|
||||
)}
|
||||
{(menuPath.length
|
||||
|
@ -176,7 +177,7 @@ const AutocompleteSelectMenu = withStyles(styles, {
|
|||
</>
|
||||
) : (
|
||||
<MenuItem disabled component="div">
|
||||
{i18n.t("No results")}
|
||||
<FormattedMessage defaultMessage="No results" />
|
||||
</MenuItem>
|
||||
)}
|
||||
</Paper>
|
||||
|
|
|
@ -5,8 +5,7 @@ import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
|||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "@saleor/i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface ColumnPickerButtonProps {
|
||||
active: boolean;
|
||||
|
@ -51,9 +50,10 @@ const ColumnPickerButton: React.FC<ColumnPickerButtonProps> = props => {
|
|||
onClick={onClick}
|
||||
variant="outlined"
|
||||
>
|
||||
{i18n.t("Columns", {
|
||||
context: "select visible columns button"
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Columns"
|
||||
description="select visible columns button"
|
||||
/>
|
||||
<ArrowDropDownIcon
|
||||
color="primary"
|
||||
className={classNames(classes.icon, {
|
||||
|
|
|
@ -6,9 +6,10 @@ import Typography from "@material-ui/core/Typography";
|
|||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import useElementScroll from "@saleor/hooks/useElementScroll";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import ControlledCheckbox from "../ControlledCheckbox";
|
||||
import Hr from "../Hr";
|
||||
|
@ -74,14 +75,14 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
|||
<Card>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary">
|
||||
{i18n.t(
|
||||
"{{ numberOfSelected }} columns selected out of {{ numberOfTotal }}",
|
||||
{
|
||||
context: "pick columns to display",
|
||||
<FormattedMessage
|
||||
defaultMessage="{numberOfSelected} columns selected out of {numberOfTotal}"
|
||||
description="pick columns to display"
|
||||
values={{
|
||||
numberOfSelected: selectedColumns.length,
|
||||
numberOfTotal: columns.length
|
||||
}
|
||||
)}
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<Hr />
|
||||
|
@ -109,14 +110,14 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
|||
>
|
||||
<div className={classes.actionBar}>
|
||||
<Button color="default" onClick={onReset}>
|
||||
{i18n.t("Reset")}
|
||||
<FormattedMessage defaultMessage="Reset" description="button" />
|
||||
</Button>
|
||||
<div>
|
||||
<Button color="default" onClick={onCancel}>
|
||||
{i18n.t("Cancel")}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<Button color="primary" variant="contained" onClick={onSave}>
|
||||
{i18n.t("Save")}
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,10 +8,10 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import CheckIcon from "@material-ui/icons/Check";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export type ConfirmButtonTransitionState =
|
||||
| "loading"
|
||||
|
@ -170,14 +170,11 @@ const ConfirmButton = withStyles(styles)(
|
|||
displayCompletedActionState
|
||||
})}
|
||||
>
|
||||
{transitionState === "error" && displayCompletedActionState
|
||||
? i18n.t("Error", {
|
||||
context: "button"
|
||||
})
|
||||
: children ||
|
||||
i18n.t("Confirm", {
|
||||
context: "button"
|
||||
})}
|
||||
{transitionState === "error" && displayCompletedActionState ? (
|
||||
<FormattedMessage defaultMessage="Error" description="button" />
|
||||
) : (
|
||||
children || <FormattedMessage {...buttonMessages.confirm} />
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -16,10 +16,10 @@ import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
|||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import i18n from "../../i18n";
|
||||
import { maybe, renderCollection } from "../../misc";
|
||||
import { CountryFragment } from "../../taxes/types/CountryFragment";
|
||||
|
||||
|
@ -99,7 +99,10 @@ const CountryList = withStyles(styles, {
|
|||
title={title}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={onCountryAssign}>
|
||||
{i18n.t("Assign countries")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign countries"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -110,10 +113,13 @@ const CountryList = withStyles(styles, {
|
|||
<TableCell
|
||||
className={classNames(classes.wideColumn, classes.toLeft)}
|
||||
>
|
||||
{i18n.t("{{ number }} Countries", {
|
||||
context: "number of countries",
|
||||
number: maybe(() => countries.length.toString(), "...")
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="{number} Countries"
|
||||
description="number of countries"
|
||||
values={{
|
||||
number: maybe(() => countries.length.toString(), "...")
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={classNames(classes.textRight, classes.iconCell)}
|
||||
|
|
|
@ -18,7 +18,7 @@ export class DateProvider extends React.Component<{}, DateProviderState> {
|
|||
componentDidMount() {
|
||||
this.intervalId = window.setInterval(
|
||||
() => this.setState({ date: Date.now() }),
|
||||
10_000
|
||||
10000
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import ActionDialog from "../ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "../ConfirmButton";
|
||||
|
||||
|
@ -19,28 +19,31 @@ const DeleteFilterTabDialog: React.FC<DeleteFilterTabDialogProps> = ({
|
|||
onSubmit,
|
||||
open,
|
||||
tabName
|
||||
}) => (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onSubmit}
|
||||
title={i18n.t("Delete Search", {
|
||||
context: "modal title custom search delete"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: i18n.t(
|
||||
"Are you sure you want to delete <strong>{{ name }}</strong> search tab?",
|
||||
{
|
||||
name: tabName
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ActionDialog>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onSubmit}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Search",
|
||||
description: "custom search delete, dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {name} search tab?"
|
||||
values={{
|
||||
name: <strong>{tabName}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
DeleteFilterTabDialog.displayName = "DeleteFilterTabDialog";
|
||||
export default DeleteFilterTabDialog;
|
||||
|
|
|
@ -2,9 +2,7 @@ import Card from "@material-ui/core/Card";
|
|||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
|
||||
import { FormattedMessage } from "react-intl";
|
||||
interface ErrorMessageCardProps {
|
||||
message: string;
|
||||
}
|
||||
|
@ -15,7 +13,7 @@ const ErrorMessageCard: React.StatelessComponent<ErrorMessageCardProps> = ({
|
|||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h5" component="h2">
|
||||
{i18n.t("Error", { context: "title" })}
|
||||
<FormattedMessage defaultMessage="Error" description="header" />
|
||||
</Typography>
|
||||
<Typography variant="body2">{message}</Typography>
|
||||
</CardContent>
|
||||
|
|
|
@ -8,9 +8,9 @@ import {
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import notFoundImage from "@assets/images/what.svg";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
export interface ErrorPageProps extends WithStyles<typeof styles> {
|
||||
onBack: () => void;
|
||||
|
@ -68,14 +68,16 @@ const ErrorPage = withStyles(styles, { name: "NotFoundPage" })(
|
|||
<div className={classes.innerContainer}>
|
||||
<div>
|
||||
<Typography className={classes.upperHeader} variant="h4">
|
||||
{i18n.t("Ooops!...")}
|
||||
<FormattedMessage defaultMessage="Ooops!..." />
|
||||
</Typography>
|
||||
<Typography className={classes.bottomHeader} variant="h3">
|
||||
{i18n.t("Error")}
|
||||
<FormattedMessage defaultMessage="Error" />
|
||||
</Typography>
|
||||
<Typography>{i18n.t("We've encountered a problem...")}</Typography>
|
||||
<Typography>
|
||||
{i18n.t("Don't worry, everything is gonna be fine")}
|
||||
<FormattedMessage defaultMessage="We've encountered a problem..." />
|
||||
</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="Don't worry, everything is gonna be fine" />
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -85,7 +87,10 @@ const ErrorPage = withStyles(styles, { name: "NotFoundPage" })(
|
|||
variant="contained"
|
||||
onClick={onBack}
|
||||
>
|
||||
{i18n.t("Back to home", { context: "button" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Back to home"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button";
|
|||
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const styles = createStyles({
|
||||
fileUploadField: {
|
||||
|
@ -42,7 +42,10 @@ const FileUpload = withStyles(styles, { name: "FileUpload" })(
|
|||
value={value}
|
||||
/>
|
||||
<Button disabled={disabled} onClick={() => this.upload.click()}>
|
||||
{i18n.t("Upload")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Upload"
|
||||
description="upload file, button"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -13,9 +13,9 @@ import Typography from "@material-ui/core/Typography";
|
|||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { FilterContent } from ".";
|
||||
import i18n from "../../i18n";
|
||||
import { FilterContentSubmitData } from "./FilterContent";
|
||||
import { IFilter } from "./types";
|
||||
|
||||
|
@ -103,7 +103,10 @@ const Filter = withStyles(styles, { name: "Filter" })(
|
|||
onClick={() => setFilterMenuOpened(!isFilterMenuOpened)}
|
||||
>
|
||||
<Typography className={classes.addFilterText}>
|
||||
{i18n.t("Add Filter")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Add Filter"
|
||||
description="button"
|
||||
/>
|
||||
</Typography>
|
||||
<ArrowDropDownIcon
|
||||
color="primary"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { makeStyles } from "@material-ui/styles";
|
||||
import i18n from "../../i18n";
|
||||
import { getMenuItemByValue, isLeaf, walkToRoot } from "../../utils/menu";
|
||||
import FormSpacer from "../FormSpacer";
|
||||
import SingleSelectField from "../SingleSelectField";
|
||||
|
@ -45,6 +45,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
filters,
|
||||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [menuValue, setMenuValue] = React.useState<string>("");
|
||||
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
|
||||
const classes = useStyles({});
|
||||
|
@ -72,7 +73,9 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
}
|
||||
}}
|
||||
value={menus ? menus[0].value : menuValue}
|
||||
placeholder={i18n.t("Select Filter...")}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Select Filter..."
|
||||
})}
|
||||
/>
|
||||
{menus &&
|
||||
menus.map(
|
||||
|
@ -95,7 +98,9 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
? menuValue
|
||||
: menus[filterItemIndex - 1].label.toString()
|
||||
}
|
||||
placeholder={i18n.t("Select Filter...")}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Select Filter..."
|
||||
})}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
@ -124,7 +129,10 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
})
|
||||
}
|
||||
>
|
||||
{i18n.t("Add filter")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Add filter"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -2,8 +2,8 @@ import TextField from "@material-ui/core/TextField";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles } from "@material-ui/styles";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import Calendar from "../../icons/Calendar";
|
||||
import FormSpacer from "../FormSpacer";
|
||||
import PriceField from "../PriceField";
|
||||
|
@ -41,6 +41,7 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
onChange,
|
||||
value
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
if (filter.data.type === FieldType.date) {
|
||||
|
@ -62,7 +63,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
} else if (filter.data.type === FieldType.rangeDate) {
|
||||
return (
|
||||
<>
|
||||
<Typography>{i18n.t("from")}</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="from" />
|
||||
</Typography>
|
||||
<TextField
|
||||
className={className}
|
||||
fullWidth
|
||||
|
@ -77,7 +80,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
}}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<Typography>{i18n.t("to")}</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="to" />
|
||||
</Typography>
|
||||
<TextField
|
||||
className={className}
|
||||
fullWidth
|
||||
|
@ -96,7 +101,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
} else if (filter.data.type === FieldType.range) {
|
||||
return (
|
||||
<>
|
||||
<Typography>{i18n.t("from")}</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="from" />
|
||||
</Typography>
|
||||
<TextField
|
||||
className={className}
|
||||
fullWidth
|
||||
|
@ -110,7 +117,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
}}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<Typography>{i18n.t("to")}</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="to" />
|
||||
</Typography>
|
||||
<TextField
|
||||
className={className}
|
||||
fullWidth
|
||||
|
@ -128,7 +137,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
} else if (filter.data.type === FieldType.rangePrice) {
|
||||
return (
|
||||
<>
|
||||
<Typography>{i18n.t("from")}</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="from" />
|
||||
</Typography>
|
||||
<PriceField
|
||||
currencySymbol={currencySymbol}
|
||||
className={className}
|
||||
|
@ -141,7 +152,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
}}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<Typography>{i18n.t("to")}</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="to" />
|
||||
</Typography>
|
||||
<PriceField
|
||||
currencySymbol={currencySymbol}
|
||||
className={className}
|
||||
|
@ -169,7 +182,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
}
|
||||
}}
|
||||
value={value as string}
|
||||
placeholder={i18n.t("Select Filter...")}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Select Filter..."
|
||||
})}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FilterProps } from "../../types";
|
||||
import Debounce from "../Debounce";
|
||||
import { IFilter } from "../Filter/types";
|
||||
|
@ -28,6 +28,7 @@ const FilterBar: React.FC<FilterBarProps> = ({
|
|||
onTabChange,
|
||||
onFilterDelete
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [search, setSearch] = React.useState(initialSearch);
|
||||
React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]);
|
||||
|
||||
|
@ -47,7 +48,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
|
|||
{isCustom && (
|
||||
<FilterTab
|
||||
onClick={() => undefined}
|
||||
label={i18n.t("Custom Filter")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Custom Filter"
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</FilterTabs>
|
||||
|
|
|
@ -4,8 +4,7 @@ import CardHeader from "@material-ui/core/CardHeader";
|
|||
import IconButton from "@material-ui/core/IconButton";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
export interface FilterCardProps {
|
||||
handleClear();
|
||||
|
@ -14,20 +13,26 @@ export interface FilterCardProps {
|
|||
const FilterCard: React.StatelessComponent<FilterCardProps> = ({
|
||||
children,
|
||||
handleClear
|
||||
}) => (
|
||||
<Card>
|
||||
<form>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton onClick={handleClear}>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
}
|
||||
title={i18n.t("Filters")}
|
||||
/>
|
||||
<CardContent>{children}</CardContent>
|
||||
</form>
|
||||
</Card>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<form>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton onClick={handleClear}>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Filters"
|
||||
})}
|
||||
/>
|
||||
<CardContent>{children}</CardContent>
|
||||
</form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
FilterCard.displayName = "FilterCard";
|
||||
export default FilterCard;
|
||||
|
|
|
@ -8,8 +8,7 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import ImageIcon from "../../icons/Image";
|
||||
import Dropzone from "../Dropzone";
|
||||
|
||||
|
@ -95,9 +94,10 @@ export const ImageUpload = withStyles(styles, { name: "ImageUpload" })(
|
|||
<input {...getInputProps()} className={classes.fileField} />
|
||||
<ImageIcon className={classes.photosIcon} />
|
||||
<Typography className={classes.uploadText} variant="body1">
|
||||
{i18n.t("Drop here to upload", {
|
||||
context: "image upload"
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Drop here to upload"
|
||||
description="image upload"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,8 +15,8 @@ import Typography from "@material-ui/core/Typography";
|
|||
import ArrowDropDown from "@material-ui/icons/ArrowDropDown";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { LanguageCodeEnum } from "../../types/globalTypes";
|
||||
import { ShopInfo_shop_languages } from "../Shop/types/ShopInfo";
|
||||
|
||||
|
@ -110,11 +110,14 @@ const LanguageSwitch = withStyles(styles, { name: "LanguageSwitch" })(
|
|||
onLanguageChange(lang.code);
|
||||
}}
|
||||
>
|
||||
{i18n.t("{{ languageName }} - {{ languageCode }}", {
|
||||
context: "button",
|
||||
languageCode: lang.code,
|
||||
languageName: lang.language
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="{languageName} - {languageCode}"
|
||||
description="button"
|
||||
values={{
|
||||
languageCode: lang.code,
|
||||
languageName: lang.language
|
||||
}}
|
||||
/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
))}
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TextField, { StandardTextFieldProps } from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import Chip from "../Chip";
|
||||
|
||||
interface ListFieldState {
|
||||
|
@ -105,7 +106,7 @@ const ListField = withStyles(styles)(
|
|||
color="primary"
|
||||
onClick={this.handleValueAdd}
|
||||
>
|
||||
{i18n.t("Add", { context: "button" })}
|
||||
<FormattedMessage defaultMessage="Add" description="button" />
|
||||
</Button>
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -1,11 +1,44 @@
|
|||
import React from "react";
|
||||
import { IntlProvider } from "react-intl";
|
||||
|
||||
export const LocaleContext = React.createContext<string>("en");
|
||||
export type LocaleContextType = string;
|
||||
export const LocaleContext = React.createContext<LocaleContextType>("en");
|
||||
|
||||
const { Consumer: LocaleConsumer, Provider } = LocaleContext;
|
||||
const { Consumer: LocaleConsumer, Provider: RawLocaleProvider } = LocaleContext;
|
||||
|
||||
const LocaleProvider = ({ children }) => {
|
||||
return <Provider value={navigator.language}>{children}</Provider>;
|
||||
enum Locale {
|
||||
EN = "en",
|
||||
EN_GB = "en-gb",
|
||||
EN_US = "en-us"
|
||||
}
|
||||
|
||||
type LocaleMessages = Record<string, string>;
|
||||
const localeData: Record<Locale, LocaleMessages> = {
|
||||
[Locale.EN]: {},
|
||||
[Locale.EN_GB]: {},
|
||||
[Locale.EN_US]: {}
|
||||
};
|
||||
|
||||
export { LocaleConsumer, LocaleProvider };
|
||||
function getMatchingLocale(): Locale {
|
||||
const localeEntries = Object.entries(Locale);
|
||||
|
||||
for (const preferredLocale of navigator.languages) {
|
||||
for (const localeEntry of localeEntries) {
|
||||
if (localeEntry[1].toLowerCase() === preferredLocale.toLowerCase()) {
|
||||
return Locale[localeEntry[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LocaleProvider: React.FC = ({ children }) => {
|
||||
const [locale] = React.useState(getMatchingLocale());
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={localeData[locale]} key={locale}>
|
||||
<RawLocaleProvider value={locale}>{children}</RawLocaleProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export { LocaleConsumer, LocaleProvider, RawLocaleProvider };
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { useIntl } from "react-intl";
|
||||
import { LocaleConsumer } from "../Locale";
|
||||
import IMoney from "../Money";
|
||||
|
||||
|
@ -18,29 +17,48 @@ const formatMoney = (money: IMoney, locale: string) =>
|
|||
export const MoneyRange: React.StatelessComponent<MoneyRangeProps> = ({
|
||||
from,
|
||||
to
|
||||
}) => (
|
||||
<LocaleConsumer>
|
||||
{locale =>
|
||||
from && to
|
||||
? i18n.t("{{ fromMoney }} - {{ toMoney }}", {
|
||||
context: "money",
|
||||
fromMoney: formatMoney(from, locale),
|
||||
toMoney: formatMoney(to, locale)
|
||||
})
|
||||
: from && !to
|
||||
? i18n.t("from {{ money }}", {
|
||||
context: "money",
|
||||
money: formatMoney(from, locale)
|
||||
})
|
||||
: !from && to
|
||||
? i18n.t("to {{ money }}", {
|
||||
context: "money",
|
||||
money: formatMoney(to, locale)
|
||||
})
|
||||
: "-"
|
||||
}
|
||||
</LocaleConsumer>
|
||||
);
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<LocaleConsumer>
|
||||
{locale =>
|
||||
from && to
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{fromMoney} - {toMoney}",
|
||||
description: "money"
|
||||
},
|
||||
{
|
||||
fromMoney: formatMoney(from, locale),
|
||||
toMoney: formatMoney(to, locale)
|
||||
}
|
||||
)
|
||||
: from && !to
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "from {money}",
|
||||
description: "money"
|
||||
},
|
||||
{
|
||||
money: formatMoney(from, locale)
|
||||
}
|
||||
)
|
||||
: !from && to
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "to {money}",
|
||||
description: "money"
|
||||
},
|
||||
{
|
||||
money: formatMoney(to, locale)
|
||||
}
|
||||
)
|
||||
: "-"
|
||||
}
|
||||
</LocaleConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
MoneyRange.displayName = "MoneyRange";
|
||||
export default MoneyRange;
|
||||
|
|
|
@ -13,12 +13,12 @@ import Typography from "@material-ui/core/Typography";
|
|||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import Downshift, { ControllerStateAndHelpers } from "downshift";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { compareTwoStrings } from "string-similarity";
|
||||
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Debounce, { DebounceProps } from "@saleor/components/Debounce";
|
||||
import i18n from "@saleor/i18n";
|
||||
import ArrowDropdownIcon from "@saleor/icons/ArrowDropdown";
|
||||
import Hr from "../Hr";
|
||||
|
||||
|
@ -244,10 +244,13 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
data-tc="multiautocomplete-select-option"
|
||||
>
|
||||
<span className={classes.menuItemLabel}>
|
||||
{i18n.t("Add new value: {{ value }}", {
|
||||
context: "add custom option",
|
||||
value: inputValue
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Add new value: {value}"
|
||||
description="add custom option to select input"
|
||||
values={{
|
||||
value: inputValue
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
@ -259,7 +262,7 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
component="div"
|
||||
data-tc="multiautocomplete-select-no-options"
|
||||
>
|
||||
{i18n.t("No results found")}
|
||||
<FormattedMessage defaultMessage="No results found" />
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -11,8 +11,8 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import Checkbox from "../Checkbox";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -110,7 +110,9 @@ export const MultiSelectField = withStyles(styles, {
|
|||
);
|
||||
})
|
||||
) : (
|
||||
<MenuItem disabled={true}>{i18n.t("No results found")}</MenuItem>
|
||||
<MenuItem disabled={true}>
|
||||
<FormattedMessage defaultMessage="No results found" />
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
{hint && <FormHelperText>{hint}</FormHelperText>}
|
||||
|
|
|
@ -8,9 +8,9 @@ import {
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import notFoundImage from "@assets/images/not-found-404.svg";
|
||||
import i18n from "@saleor/i18n";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -65,12 +65,14 @@ const NotFoundPage = withStyles(styles, { name: "NotFoundPage" })(
|
|||
<div className={classes.innerContainer}>
|
||||
<div>
|
||||
<Typography className={classes.header} variant="h3">
|
||||
{i18n.t("Ooops!...")}
|
||||
<FormattedMessage defaultMessage="Ooops!..." />
|
||||
</Typography>
|
||||
<Typography className={classes.header} variant="h4">
|
||||
{i18n.t("Something's missing")}
|
||||
<FormattedMessage defaultMessage="Something's missing" />
|
||||
</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="Sorry, the page was not found" />
|
||||
</Typography>
|
||||
<Typography>{i18n.t("Sorry, the page was not found")}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
|
@ -79,7 +81,10 @@ const NotFoundPage = withStyles(styles, { name: "NotFoundPage" })(
|
|||
variant="contained"
|
||||
onClick={onBack}
|
||||
>
|
||||
{i18n.t("Go back to dashboard", { context: "button" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Go back to dashboard"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,12 +10,12 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import { ProductListColumns } from "@saleor/config";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
|
@ -97,6 +97,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
onUpdateListSettings,
|
||||
onRowClick
|
||||
}: ProductListProps) => {
|
||||
const intl = useIntl();
|
||||
const displayColumn = React.useCallback(
|
||||
(column: ProductListColumns) =>
|
||||
isSelected(column, settings.columns, (a, b) => a === b),
|
||||
|
@ -124,22 +125,28 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameHeader}>
|
||||
{i18n.t("Name", { context: "object" })}
|
||||
<FormattedMessage defaultMessage="Name" description="product" />
|
||||
</span>
|
||||
</TableCell>
|
||||
{displayColumn("productType") && (
|
||||
<TableCell className={classes.colType}>
|
||||
{i18n.t("Type", { context: "object" })}
|
||||
<FormattedMessage defaultMessage="Type" description="product" />
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("isPublished") && (
|
||||
<TableCell className={classes.colPublished}>
|
||||
{i18n.t("Published", { context: "object" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product status"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("price") && (
|
||||
<TableCell className={classes.colPrice}>
|
||||
{i18n.t("Price", { context: "object" })}
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableHead>
|
||||
|
@ -205,11 +212,13 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<StatusLabel
|
||||
label={
|
||||
product.isAvailable
|
||||
? i18n.t("Published", {
|
||||
context: "product status"
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "product status"
|
||||
})
|
||||
: i18n.t("Not published", {
|
||||
context: "product status"
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "product status"
|
||||
})
|
||||
}
|
||||
status={product.isAvailable ? "success" : "error"}
|
||||
|
@ -237,7 +246,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No products found")}
|
||||
<FormattedMessage defaultMessage="No products found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
|
|
|
@ -8,8 +8,7 @@ import RadioGroup from "@material-ui/core/RadioGroup";
|
|||
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const styles = createStyles({
|
||||
formControl: {
|
||||
|
@ -83,7 +82,9 @@ export const RadioGroupField = withStyles(styles, {
|
|||
/>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled={true}>{i18n.t("No results found")}</MenuItem>
|
||||
<MenuItem disabled={true}>
|
||||
<FormattedMessage defaultMessage="No results found" />
|
||||
</MenuItem>
|
||||
)}
|
||||
</RadioGroup>
|
||||
{hint && <FormHelperText>{hint}</FormHelperText>}
|
||||
|
|
|
@ -13,8 +13,7 @@ import {
|
|||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import { ContentState } from "draft-js";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface ImageEntityProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -88,7 +87,10 @@ const ImageEntity = withStyles(styles, {
|
|||
}}
|
||||
color="primary"
|
||||
>
|
||||
{i18n.t("Replace")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Replace"
|
||||
description="replace image, button"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton onClick={() => onRemove(entityKey)}>
|
||||
<DeleteIcon color="primary" />
|
||||
|
|
|
@ -6,8 +6,9 @@ import DialogTitle from "@material-ui/core/DialogTitle";
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import { AtomicBlockUtils, EditorState, EntityInstance } from "draft-js";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import Form from "../Form";
|
||||
|
||||
interface ImageSourceProps {
|
||||
|
@ -21,16 +22,19 @@ interface ImageSourceProps {
|
|||
onClose: () => void;
|
||||
}
|
||||
|
||||
class ImageSource extends React.Component<ImageSourceProps> {
|
||||
submit = (href: string) => {
|
||||
const {
|
||||
editorState,
|
||||
entity,
|
||||
entityKey,
|
||||
entityType,
|
||||
onComplete
|
||||
} = this.props;
|
||||
const ImageSource: React.FC<ImageSourceProps> = ({
|
||||
editorState,
|
||||
entity,
|
||||
entityKey,
|
||||
entityType,
|
||||
onComplete,
|
||||
onClose
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const initial = entity ? entity.getData().href : "";
|
||||
|
||||
const handleSubmit = (href: string) => {
|
||||
if (href) {
|
||||
const content = editorState.getCurrentContent();
|
||||
if (entity) {
|
||||
|
@ -60,41 +64,44 @@ class ImageSource extends React.Component<ImageSourceProps> {
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { entity, onClose } = this.props;
|
||||
const initial = entity ? entity.getData().href : "";
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
|
||||
<Form
|
||||
initial={{ href: initial }}
|
||||
onSubmit={({ href }) => handleSubmit(href)}
|
||||
>
|
||||
{({ data, change, submit }) => (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add Image Link"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
name="href"
|
||||
fullWidth
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Image URL"
|
||||
})}
|
||||
value={data.href}
|
||||
onChange={change}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<Button onClick={submit} color="primary" variant="contained">
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
|
||||
<Form
|
||||
initial={{ href: initial }}
|
||||
onSubmit={({ href }) => this.submit(href)}
|
||||
>
|
||||
{({ data, change, submit }) => (
|
||||
<>
|
||||
<DialogTitle>{i18n.t("Add Image Link")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
name="href"
|
||||
fullWidth
|
||||
label={i18n.t("Image URL")}
|
||||
value={data.href}
|
||||
onChange={change}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
</Button>
|
||||
<Button onClick={submit} color="primary" variant="contained">
|
||||
{i18n.t("Save", { context: "button" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default ImageSource;
|
||||
|
|
|
@ -14,8 +14,9 @@ import Typography from "@material-ui/core/Typography";
|
|||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import { ContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import Link from "../Link";
|
||||
|
||||
interface LinkEntityProps {
|
||||
|
@ -107,7 +108,7 @@ const LinkEntity = withStyles(styles, {
|
|||
}}
|
||||
color="primary"
|
||||
>
|
||||
{i18n.t("Edit")}
|
||||
<FormattedMessage {...buttonMessages.edit} />
|
||||
</Button>
|
||||
<IconButton onClick={() => onRemove(entityKey)}>
|
||||
<DeleteIcon color="primary" />
|
||||
|
|
|
@ -6,8 +6,9 @@ import DialogTitle from "@material-ui/core/DialogTitle";
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import { EditorState, EntityInstance, RichUtils } from "draft-js";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import Form from "../Form";
|
||||
|
||||
interface LinkSourceProps {
|
||||
|
@ -20,10 +21,17 @@ interface LinkSourceProps {
|
|||
onClose: () => void;
|
||||
}
|
||||
|
||||
class LinkSource extends React.Component<LinkSourceProps> {
|
||||
submit = (url: string) => {
|
||||
const { editorState, entityType, onComplete } = this.props;
|
||||
const LinkSource: React.FC<LinkSourceProps> = ({
|
||||
editorState,
|
||||
entity,
|
||||
entityType,
|
||||
onComplete,
|
||||
onClose
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const initial = entity ? entity.getData().url : "";
|
||||
|
||||
const handleSubmit = (url: string) => {
|
||||
if (url) {
|
||||
const content = editorState.getCurrentContent();
|
||||
const contentWithEntity = content.createEntity(
|
||||
|
@ -47,41 +55,44 @@ class LinkSource extends React.Component<LinkSourceProps> {
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { entity, onClose } = this.props;
|
||||
const initial = entity ? entity.getData().url : "";
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
|
||||
<Form
|
||||
initial={{ url: initial }}
|
||||
onSubmit={({ url }) => handleSubmit(url)}
|
||||
>
|
||||
{({ data, change, submit }) => (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add or Edit Link"
|
||||
description="button"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
name="url"
|
||||
fullWidth
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "URL Linked"
|
||||
})}
|
||||
value={data.url}
|
||||
onChange={change}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<Button onClick={submit} color="secondary" variant="contained">
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
|
||||
<Form
|
||||
initial={{ url: initial }}
|
||||
onSubmit={({ url }) => this.submit(url)}
|
||||
>
|
||||
{({ data, change, submit }) => (
|
||||
<>
|
||||
<DialogTitle>{i18n.t("Add or Edit Link")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
name="url"
|
||||
fullWidth
|
||||
label={i18n.t("URL Linked")}
|
||||
value={data.url}
|
||||
onChange={change}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
</Button>
|
||||
<Button onClick={submit} color="secondary" variant="contained">
|
||||
{i18n.t("Save", { context: "button" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default LinkSource;
|
||||
|
|
|
@ -3,8 +3,8 @@ import Select from "@material-ui/core/Select";
|
|||
import { Theme } from "@material-ui/core/styles";
|
||||
import { createStyles, makeStyles, useTheme } from "@material-ui/styles";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { ListSettings } from "../../types";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -51,7 +51,9 @@ const RowNumberSelect: React.FC<RowNumberSelectProps> = ({
|
|||
const classes = useStyles({ theme });
|
||||
return (
|
||||
<div className={className}>
|
||||
<span className={classes.label}>{i18n.t("No of Rows:")}</span>
|
||||
<span className={classes.label}>
|
||||
<FormattedMessage defaultMessage="No of Rows:" />
|
||||
</span>
|
||||
<Select
|
||||
className={classes.select}
|
||||
value={settings.rowNumber}
|
||||
|
|
|
@ -8,9 +8,10 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import useWindowScroll from "@saleor/hooks/useWindowScroll";
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { maybe } from "../../misc";
|
||||
import AppActionContext from "../AppLayout/AppActionContext";
|
||||
import ConfirmButton, {
|
||||
|
@ -82,6 +83,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
|
|||
onSave,
|
||||
...props
|
||||
}: SaveButtonBarProps) => {
|
||||
const intl = useIntl();
|
||||
const scrollPosition = useWindowScroll();
|
||||
const scrolledToBottom =
|
||||
scrollPosition.y + window.innerHeight >= document.body.scrollHeight;
|
||||
|
@ -107,7 +109,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
|
|||
>
|
||||
{labels && labels.delete
|
||||
? labels.delete
|
||||
: i18n.t("Remove")}
|
||||
: intl.formatMessage(buttonMessages.delete)}
|
||||
</Button>
|
||||
)}
|
||||
<div className={classes.spacer} />
|
||||
|
@ -119,9 +121,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
|
|||
>
|
||||
{maybe(
|
||||
() => labels.cancel,
|
||||
i18n.t("Cancel", {
|
||||
context: "button"
|
||||
})
|
||||
intl.formatMessage(buttonMessages.cancel)
|
||||
)}
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
|
@ -132,9 +132,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
|
|||
>
|
||||
{maybe(
|
||||
() => labels.save,
|
||||
i18n.t("Save", {
|
||||
context: "button"
|
||||
})
|
||||
intl.formatMessage(buttonMessages.save)
|
||||
)}
|
||||
</ConfirmButton>
|
||||
</Container>
|
||||
|
|
|
@ -5,8 +5,9 @@ import DialogContent from "@material-ui/core/DialogContent";
|
|||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import ConfirmButton, { ConfirmButtonTransitionState } from "../ConfirmButton";
|
||||
import Form from "../Form";
|
||||
|
||||
|
@ -31,6 +32,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
|
|||
onSubmit,
|
||||
open
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [errors, setErrors] = React.useState(false);
|
||||
const handleErrors = data => {
|
||||
if (data.name.length) {
|
||||
|
@ -44,9 +46,10 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
|
|||
return (
|
||||
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
|
||||
<DialogTitle>
|
||||
{i18n.t("Save Custom Search", {
|
||||
context: "save filter tab"
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Save Custom Search"
|
||||
description="save filter tab, header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<Form initial={initialForm} onSubmit={handleErrors}>
|
||||
{({ change, data, submit }) => (
|
||||
|
@ -54,8 +57,9 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
|
|||
<DialogContent>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={i18n.t("Search Name", {
|
||||
context: "save search"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Search Name",
|
||||
description: "save search tab"
|
||||
})}
|
||||
name={"name" as keyof SaveFilterTabDialogFormData}
|
||||
value={data.name}
|
||||
|
@ -66,7 +70,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{i18n.t("Cancel", { context: "button" })}
|
||||
<FormattedMessage {...buttonMessages.cancel} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
|
@ -74,7 +78,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
|
|||
variant="contained"
|
||||
onClick={submit}
|
||||
>
|
||||
{i18n.t("Save")}
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
|
|
@ -11,8 +11,8 @@ import TextField from "@material-ui/core/TextField";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import CardTitle from "../CardTitle";
|
||||
import FormSpacer from "../FormSpacer";
|
||||
|
||||
|
@ -88,16 +88,22 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
|
|||
titlePlaceholder,
|
||||
onChange
|
||||
}: SeoFormProps) => {
|
||||
const intl = useIntl();
|
||||
const [expanded, setExpansionStatus] = React.useState(false);
|
||||
const toggleExpansion = () => setExpansionStatus(!expanded);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Search Engine Preview")}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Search Engine Preview"
|
||||
})}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={toggleExpansion}>
|
||||
{i18n.t("Edit website SEO")}
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit website SEO"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -116,19 +122,24 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
|
|||
label={
|
||||
<div className={classes.labelContainer}>
|
||||
<div className={classes.label}>
|
||||
{i18n.t("Search engine title")}
|
||||
<FormattedMessage defaultMessage="Search engine title" />
|
||||
</div>
|
||||
<span>
|
||||
{i18n.t("{{ letters }} of {{ maxLetters }} characters", {
|
||||
letters: title.length,
|
||||
maxLetters: 70
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
||||
description="character limit"
|
||||
values={{
|
||||
maxCharacters: 70,
|
||||
numberOfCharacters: title.length
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
helperText={i18n.t(
|
||||
"If empty, the preview shows what will be autogenerated."
|
||||
)}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"If empty, the preview shows what will be autogenerated."
|
||||
})}
|
||||
value={title.slice(0, 69)}
|
||||
disabled={loading || disabled}
|
||||
placeholder={titlePlaceholder}
|
||||
|
@ -141,19 +152,24 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
|
|||
label={
|
||||
<div className={classes.labelContainer}>
|
||||
<div className={classes.label}>
|
||||
{i18n.t("Search engine description")}
|
||||
<FormattedMessage defaultMessage="Search engine description" />
|
||||
</div>
|
||||
<span>
|
||||
{i18n.t("{{ letters }} of {{ maxLetters }} characters", {
|
||||
letters: description.length,
|
||||
maxLetters: 300
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
||||
description="character limit"
|
||||
values={{
|
||||
maxCharacters: 300,
|
||||
numberOfCharacters: description.length
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
helperText={i18n.t(
|
||||
"If empty, the preview shows what will be autogenerated."
|
||||
)}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"If empty, the preview shows what will be autogenerated."
|
||||
})}
|
||||
value={description ? description.slice(0, 299) : undefined}
|
||||
onChange={onChange}
|
||||
disabled={loading || disabled}
|
||||
|
@ -170,9 +186,4 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
|
|||
}
|
||||
);
|
||||
SeoForm.displayName = "SeoForm";
|
||||
SeoForm.defaultProps = {
|
||||
helperText: i18n.t(
|
||||
"Add search engine title and description to make this product easier to find"
|
||||
)
|
||||
};
|
||||
export default SeoForm;
|
||||
|
|
|
@ -13,10 +13,10 @@ import TextField from "@material-ui/core/TextField";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import Downshift from "downshift";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { compareTwoStrings } from "string-similarity";
|
||||
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import i18n from "../../i18n";
|
||||
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
||||
import Debounce, { DebounceProps } from "../Debounce";
|
||||
|
||||
|
@ -177,7 +177,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
data-tc="singleautocomplete-select-option"
|
||||
>
|
||||
<Typography color="textSecondary">
|
||||
{i18n.t("None")}
|
||||
<FormattedMessage defaultMessage="None" />
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
@ -220,10 +220,13 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
})}
|
||||
data-tc="singleautocomplete-select-option"
|
||||
>
|
||||
{i18n.t("Add new value: {{ value }}", {
|
||||
context: "add custom option",
|
||||
value: inputValue
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Add new value: {value}"
|
||||
description="add custom select input option"
|
||||
values={{
|
||||
value: inputValue
|
||||
}}
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
|
@ -233,7 +236,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
component="div"
|
||||
data-tc="singleautocomplete-select-no-options"
|
||||
>
|
||||
{i18n.t("No results found")}
|
||||
<FormattedMessage defaultMessage="No results found" />
|
||||
</MenuItem>
|
||||
)}
|
||||
</Paper>
|
||||
|
|
|
@ -7,8 +7,7 @@ import Select, { SelectProps } from "@material-ui/core/Select";
|
|||
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const styles = createStyles({
|
||||
formControl: {
|
||||
|
@ -83,7 +82,9 @@ export const SingleSelectField = withStyles(styles, {
|
|||
</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled={true}>{i18n.t("No results found")}</MenuItem>
|
||||
<MenuItem disabled={true}>
|
||||
<FormattedMessage defaultMessage="No results found" />
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
{hint && <FormHelperText>{hint}</FormHelperText>}
|
||||
|
|
|
@ -6,8 +6,8 @@ import Typography from "@material-ui/core/Typography";
|
|||
import ClearIcon from "@material-ui/icons/Clear";
|
||||
import { createStyles, makeStyles, useTheme } from "@material-ui/styles";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import Filter, { FilterContentSubmitData, IFilter } from "../Filter";
|
||||
import Hr from "../Hr";
|
||||
import Link from "../Link";
|
||||
|
@ -163,9 +163,19 @@ export const FilterChips: React.FC<FilterChipProps> = ({
|
|||
))}
|
||||
</div>
|
||||
{isCustomSearch ? (
|
||||
<Link onClick={onFilterSave}>{i18n.t("Save Custom Search")}</Link>
|
||||
<Link onClick={onFilterSave}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Save Custom Search"
|
||||
description="button"
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
<Link onClick={onFilterDelete}>{i18n.t("Delete Search")}</Link>
|
||||
<Link onClick={onFilterDelete}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Delete Search"
|
||||
description="button"
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export { default } from "./FilterTabs";
|
||||
export { Filter } from "./FilterChips";
|
||||
export * from "./FilterTabs";
|
||||
export * from "./FilterTab";
|
||||
export * from "./FilterChips";
|
||||
|
|
|
@ -13,10 +13,10 @@ import TableRow from "@material-ui/core/TableRow";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Node } from "../../types";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import Checkbox from "../Checkbox";
|
||||
|
||||
export interface TableHeadProps extends MuiTableHeadProps {
|
||||
|
@ -129,9 +129,12 @@ const TableHead = withStyles(styles, {
|
|||
<div className={classes.container}>
|
||||
{selected && (
|
||||
<Typography>
|
||||
{i18n.t("Selected {{ number }} items", {
|
||||
number: selected
|
||||
})}
|
||||
<FormattedMessage
|
||||
defaultMessage="Selected {number} items"
|
||||
values={{
|
||||
number: selected
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
)}
|
||||
<div className={classes.spacer} />
|
||||
|
|
|
@ -11,8 +11,7 @@ import {
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import PersonIcon from "@material-ui/icons/Person";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -70,37 +69,44 @@ export const Timeline = withStyles(styles, { name: "Timeline" })(
|
|||
);
|
||||
|
||||
export const TimelineAddNote = withStyles(styles, { name: "TimelineAddNote" })(
|
||||
({ classes, message, onChange, onSubmit }: TimelineAddNoteProps) => (
|
||||
<div className={classes.noteRoot}>
|
||||
<CardContent className={classes.noteTitle}>
|
||||
<Avatar
|
||||
style={{ background: deepPurple[500] }}
|
||||
className={classes.avatar}
|
||||
>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
<TextField
|
||||
className={classes.input}
|
||||
placeholder={i18n.t("Leave your note here...")}
|
||||
onChange={onChange}
|
||||
value={message}
|
||||
name="message"
|
||||
fullWidth
|
||||
multiline
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<Button color="primary" onClick={onSubmit}>
|
||||
{i18n.t("Send", {
|
||||
context: "add order note"
|
||||
})}
|
||||
</Button>
|
||||
)
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</CardContent>
|
||||
</div>
|
||||
)
|
||||
({ classes, message, onChange, onSubmit }: TimelineAddNoteProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className={classes.noteRoot}>
|
||||
<CardContent className={classes.noteTitle}>
|
||||
<Avatar
|
||||
style={{ background: deepPurple[500] }}
|
||||
className={classes.avatar}
|
||||
>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
<TextField
|
||||
className={classes.input}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Leave your note here..."
|
||||
})}
|
||||
onChange={onChange}
|
||||
value={message}
|
||||
name="message"
|
||||
fullWidth
|
||||
multiline
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<Button color="primary" onClick={onSubmit}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Send"
|
||||
description="add order note, button"
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</CardContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
Timeline.displayName = "Timeline";
|
||||
export default Timeline;
|
||||
|
|
|
@ -8,12 +8,12 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
||||
import { FormSpacer } from "@saleor/components/FormSpacer";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import i18n from "../../i18n";
|
||||
import { DateContext } from "../Date/DateContext";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -54,11 +54,17 @@ export const VisibilityCard = withStyles(styles, {
|
|||
disabled,
|
||||
onChange
|
||||
}: VisibilityCardProps) => {
|
||||
const intl = useIntl();
|
||||
const localizeDate = useDateLocalize();
|
||||
const dateNow = React.useContext(DateContext);
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={i18n.t("Visibility")} />
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Visibility",
|
||||
description: "section header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<div
|
||||
className={
|
||||
|
@ -69,18 +75,32 @@ export const VisibilityCard = withStyles(styles, {
|
|||
>
|
||||
<ControlledSwitch
|
||||
name="isPublished"
|
||||
label={i18n.t("Visible")}
|
||||
uncheckedLabel={i18n.t("Hidden")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Visible"
|
||||
})}
|
||||
uncheckedLabel={intl.formatMessage({
|
||||
defaultMessage: "Hidden"
|
||||
})}
|
||||
secondLabel={
|
||||
publicationDate
|
||||
? isPublished
|
||||
? i18n.t("since {{ date }}", {
|
||||
date: localizeDate(publicationDate)
|
||||
})
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "since {date}"
|
||||
},
|
||||
{
|
||||
date: localizeDate(publicationDate)
|
||||
}
|
||||
)
|
||||
: Date.parse(publicationDate) > dateNow
|
||||
? i18n.t("will be visible from {{ date }}", {
|
||||
date: localizeDate(publicationDate)
|
||||
})
|
||||
? intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "will be visible from {date}"
|
||||
},
|
||||
{
|
||||
date: localizeDate(publicationDate)
|
||||
}
|
||||
)
|
||||
: null
|
||||
: null
|
||||
}
|
||||
|
@ -94,7 +114,10 @@ export const VisibilityCard = withStyles(styles, {
|
|||
<TextField
|
||||
error={!!errors.publicationDate}
|
||||
disabled={disabled}
|
||||
label={i18n.t("Publish on")}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Publish on",
|
||||
description: "publish on date"
|
||||
})}
|
||||
name="publicationDate"
|
||||
type="date"
|
||||
fullWidth={true}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import i18n from "../../i18n";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export interface Weight {
|
||||
unit: string;
|
||||
|
@ -9,10 +9,13 @@ export interface WeightProps {
|
|||
weight: Weight;
|
||||
}
|
||||
|
||||
const Weight: React.StatelessComponent<WeightProps> = ({ weight }) =>
|
||||
i18n.t("{{ value }} {{ unit }}", {
|
||||
context: "weight",
|
||||
...weight
|
||||
});
|
||||
const Weight: React.StatelessComponent<WeightProps> = ({ weight }) => (
|
||||
<FormattedMessage
|
||||
defaultMessage="{value} {unit}"
|
||||
description="weight"
|
||||
values={weight}
|
||||
/>
|
||||
);
|
||||
|
||||
Weight.displayName = "Weight";
|
||||
export default Weight;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import { Weight } from "../Weight";
|
||||
|
||||
export interface WeightRangeProps {
|
||||
|
@ -8,28 +8,32 @@ export interface WeightRangeProps {
|
|||
to?: Weight;
|
||||
}
|
||||
|
||||
const WeightRange: React.StatelessComponent<WeightRangeProps> = ({
|
||||
from,
|
||||
to
|
||||
}) =>
|
||||
from && to
|
||||
? i18n.t("{{ fromValue }} {{ fromUnit }} - {{ toValue }} {{ toUnit }}", {
|
||||
context: "weight",
|
||||
const WeightRange: React.FC<WeightRangeProps> = ({ from, to }) =>
|
||||
from && to ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="{fromValue} {fromUnit} - {toValue} {toUnit}"
|
||||
description="weight"
|
||||
values={{
|
||||
fromUnit: from.unit,
|
||||
fromValue: from.value,
|
||||
toUnit: to.unit,
|
||||
toValue: to.value
|
||||
})
|
||||
: from && !to
|
||||
? i18n.t("from {{ value }} {{ unit }}", {
|
||||
context: "weight",
|
||||
...from
|
||||
})
|
||||
: !from && to
|
||||
? i18n.t("to {{ value }} {{ unit }}", {
|
||||
context: "weight",
|
||||
...to
|
||||
})
|
||||
: "-";
|
||||
}}
|
||||
/>
|
||||
) : from && !to ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="from {value} {unit}"
|
||||
description="weight"
|
||||
values={from}
|
||||
/>
|
||||
) : !from && to ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="to {value} {unit}"
|
||||
description="weight"
|
||||
values={to}
|
||||
/>
|
||||
) : (
|
||||
<span>-</span>
|
||||
);
|
||||
WeightRange.displayName = "WeightRange";
|
||||
export default WeightRange;
|
||||
|
|
|
@ -8,12 +8,13 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { IconProps } from "@material-ui/core/Icon";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { User } from "../auth/types/User";
|
||||
import Container from "../components/Container";
|
||||
import PageHeader from "../components/PageHeader";
|
||||
import i18n from "../i18n";
|
||||
import { PermissionEnum } from "../types/globalTypes";
|
||||
|
||||
export interface MenuItem {
|
||||
|
@ -68,7 +69,7 @@ const styles = (theme: Theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
export interface ConfigurationPageProps extends WithStyles<typeof styles> {
|
||||
export interface ConfigurationPageProps {
|
||||
menu: MenuItem[];
|
||||
user: User;
|
||||
onSectionClick: (sectionName: string) => void;
|
||||
|
@ -76,35 +77,51 @@ export interface ConfigurationPageProps extends WithStyles<typeof styles> {
|
|||
|
||||
export const ConfigurationPage = withStyles(styles, {
|
||||
name: "ConfigurationPage"
|
||||
})(({ classes, menu, user, onSectionClick }: ConfigurationPageProps) => (
|
||||
<Container>
|
||||
<PageHeader title={i18n.t("Configuration")} />
|
||||
<div className={classes.root}>
|
||||
{menu
|
||||
.filter(menuItem =>
|
||||
user.permissions.map(perm => perm.code).includes(menuItem.permission)
|
||||
)
|
||||
.map((menuItem, menuItemIndex) => (
|
||||
<Card
|
||||
className={menuItem.url ? classes.card : classes.cardDisabled}
|
||||
onClick={() => onSectionClick(menuItem.url)}
|
||||
key={menuItemIndex}
|
||||
>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<div className={classes.icon}>{menuItem.icon}</div>
|
||||
<div>
|
||||
<Typography className={classes.sectionTitle} color="primary">
|
||||
{menuItem.title}
|
||||
</Typography>
|
||||
<Typography className={classes.sectionDescription}>
|
||||
{menuItem.description}
|
||||
</Typography>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
));
|
||||
})(
|
||||
({
|
||||
classes,
|
||||
menu,
|
||||
user,
|
||||
onSectionClick
|
||||
}: ConfigurationPageProps & WithStyles<typeof styles>) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.configuration)} />
|
||||
<div className={classes.root}>
|
||||
{menu
|
||||
.filter(menuItem =>
|
||||
user.permissions
|
||||
.map(perm => perm.code)
|
||||
.includes(menuItem.permission)
|
||||
)
|
||||
.map((menuItem, menuItemIndex) => (
|
||||
<Card
|
||||
className={menuItem.url ? classes.card : classes.cardDisabled}
|
||||
onClick={() => onSectionClick(menuItem.url)}
|
||||
key={menuItemIndex}
|
||||
>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<div className={classes.icon}>{menuItem.icon}</div>
|
||||
<div>
|
||||
<Typography
|
||||
className={classes.sectionTitle}
|
||||
color="primary"
|
||||
>
|
||||
{menuItem.title}
|
||||
</Typography>
|
||||
<Typography className={classes.sectionDescription}>
|
||||
{menuItem.description}
|
||||
</Typography>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
);
|
||||
ConfigurationPage.displayName = "ConfigurationPage";
|
||||
export default ConfigurationPage;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import { IntlShape, useIntl } from "react-intl";
|
||||
|
||||
import { attributeListUrl } from "@saleor/attributes/urls";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useUser from "@saleor/hooks/useUser";
|
||||
import i18n from "@saleor/i18n";
|
||||
import Navigation from "@saleor/icons/Navigation";
|
||||
import Pages from "@saleor/icons/Pages";
|
||||
import ProductTypes from "@saleor/icons/ProductTypes";
|
||||
|
@ -12,6 +12,7 @@ import ShippingMethods from "@saleor/icons/ShippingMethods";
|
|||
import SiteSettings from "@saleor/icons/SiteSettings";
|
||||
import StaffMembers from "@saleor/icons/StaffMembers";
|
||||
import Taxes from "@saleor/icons/Taxes";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { menuListUrl } from "@saleor/navigation/urls";
|
||||
import { pageListUrl } from "@saleor/pages/urls";
|
||||
|
@ -23,76 +24,103 @@ import { taxSection } from "@saleor/taxes/urls";
|
|||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
import ConfigurationPage, { MenuItem } from "./ConfigurationPage";
|
||||
|
||||
export const configurationMenu: MenuItem[] = [
|
||||
{
|
||||
description: i18n.t("Determine attributes used to create product types"),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: i18n.t("Attributes"),
|
||||
url: attributeListUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("Define types of products you sell"),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: i18n.t("Product Types"),
|
||||
url: productTypeListUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("Manage your employees and their permissions"),
|
||||
icon: <StaffMembers fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_STAFF,
|
||||
title: i18n.t("Staff Members"),
|
||||
url: staffListUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("Manage how you ship out orders."),
|
||||
icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SHIPPING,
|
||||
title: i18n.t("Shipping Methods"),
|
||||
url: shippingZonesListUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("Manage how your store charges tax"),
|
||||
icon: <Taxes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: i18n.t("Taxes"),
|
||||
url: taxSection
|
||||
},
|
||||
{
|
||||
description: i18n.t("Define how users can navigate through your store"),
|
||||
icon: <Navigation fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_MENUS,
|
||||
title: i18n.t("Navigation"),
|
||||
url: menuListUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("View and update your site settings"),
|
||||
icon: <SiteSettings fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SETTINGS,
|
||||
title: i18n.t("Site Settings"),
|
||||
url: siteSettingsUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("Manage and add additional pages"),
|
||||
icon: <Pages fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PAGES,
|
||||
title: i18n.t("Pages"),
|
||||
url: pageListUrl()
|
||||
}
|
||||
];
|
||||
export function createConfigurationMenu(intl: IntlShape): MenuItem[] {
|
||||
return [
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Determine attributes used to create product types",
|
||||
id: "configurationMenuAttributes"
|
||||
}),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: intl.formatMessage(sectionNames.attributes),
|
||||
url: attributeListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Define types of products you sell",
|
||||
id: "configurationMenuProductTypes"
|
||||
}),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: intl.formatMessage(sectionNames.productTypes),
|
||||
url: productTypeListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Manage your employees and their permissions",
|
||||
id: "configurationMenuStaff"
|
||||
}),
|
||||
icon: <StaffMembers fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_STAFF,
|
||||
title: intl.formatMessage(sectionNames.staff),
|
||||
url: staffListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Manage how you ship out orders",
|
||||
id: "configurationMenuShipping"
|
||||
}),
|
||||
icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SHIPPING,
|
||||
title: intl.formatMessage(sectionNames.shipping),
|
||||
url: shippingZonesListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Manage how your store charges tax",
|
||||
id: "configurationMenuTaxes"
|
||||
}),
|
||||
icon: <Taxes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: intl.formatMessage(sectionNames.taxes),
|
||||
url: taxSection
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Define how users can navigate through your store",
|
||||
id: "configurationMenuNavigation"
|
||||
}),
|
||||
icon: <Navigation fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_MENUS,
|
||||
title: intl.formatMessage(sectionNames.navigation),
|
||||
url: menuListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "View and update your site settings",
|
||||
id: "configurationMenuSiteSettings"
|
||||
}),
|
||||
icon: <SiteSettings fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_SETTINGS,
|
||||
title: intl.formatMessage(sectionNames.siteSettings),
|
||||
url: siteSettingsUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
defaultMessage: "Manage and add additional pages",
|
||||
id: "configurationMenuPages"
|
||||
}),
|
||||
icon: <Pages fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PAGES,
|
||||
title: intl.formatMessage(sectionNames.pages),
|
||||
url: pageListUrl()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export const configurationMenuUrl = "/configuration/";
|
||||
|
||||
export const ConfigurationSection: React.StatelessComponent = () => {
|
||||
export const ConfigurationSection: React.FC = () => {
|
||||
const navigate = useNavigator();
|
||||
const user = useUser();
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={i18n.t("Configuration")} />
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.configuration)} />
|
||||
<ConfigurationPage
|
||||
menu={configurationMenu}
|
||||
menu={createConfigurationMenu(intl)}
|
||||
user={maybe(() => user.user)}
|
||||
onSectionClick={navigate}
|
||||
/>
|
||||
|
|
|
@ -5,12 +5,13 @@ import CardContent from "@material-ui/core/CardContent";
|
|||
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import AddressFormatter from "@saleor/components/AddressFormatter";
|
||||
import CardMenu from "@saleor/components/CardMenu";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import i18n from "../../../i18n";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { AddressTypeEnum } from "../../../types/globalTypes";
|
||||
import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses";
|
||||
|
||||
|
@ -51,65 +52,81 @@ const CustomerAddress = withStyles(styles, { name: "CustomerAddress" })(
|
|||
onEdit,
|
||||
onRemove,
|
||||
onSetAsDefault
|
||||
}: CustomerAddressProps & WithStyles<typeof styles>) => (
|
||||
<Card className={classes.card}>
|
||||
<CardTitle
|
||||
title={
|
||||
address ? (
|
||||
<>
|
||||
{i18n.t("Address {{ addressNumber }}", {
|
||||
addressNumber
|
||||
})}
|
||||
<Typography variant="caption">
|
||||
{isDefaultBillingAddress && isDefaultShippingAddress
|
||||
? i18n.t("Default Address")
|
||||
: isDefaultShippingAddress
|
||||
? i18n.t("Default Shipping Address")
|
||||
: isDefaultBillingAddress
|
||||
? i18n.t("Default Billing Address")
|
||||
: null}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
}
|
||||
height="const"
|
||||
toolbar={
|
||||
<CardMenu
|
||||
disabled={disabled}
|
||||
menuItems={[
|
||||
{
|
||||
label: i18n.t("Set as default shipping address", {
|
||||
context: "button"
|
||||
}),
|
||||
onSelect: () => onSetAsDefault(AddressTypeEnum.SHIPPING)
|
||||
},
|
||||
{
|
||||
label: i18n.t("Set as default billing address", {
|
||||
context: "button"
|
||||
}),
|
||||
onSelect: () => onSetAsDefault(AddressTypeEnum.BILLING)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<AddressFormatter address={address} />
|
||||
</CardContent>
|
||||
<div className={classes.actionsContainer}>
|
||||
<CardActions className={classes.actions}>
|
||||
<Button color="primary" disabled={disabled} onClick={onEdit}>
|
||||
{i18n.t("Edit")}
|
||||
</Button>
|
||||
<Button color="primary" disabled={disabled} onClick={onRemove}>
|
||||
{i18n.t("Delete")}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}: CustomerAddressProps & WithStyles<typeof styles>) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
<CardTitle
|
||||
title={
|
||||
address ? (
|
||||
<>
|
||||
<FormattedMessage
|
||||
defaultMessage="Address {addressNumber}"
|
||||
description="addres card header"
|
||||
values={{
|
||||
addressNumber
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
{isDefaultBillingAddress && isDefaultShippingAddress
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Default Address"
|
||||
})
|
||||
: isDefaultShippingAddress
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Default Shipping Address"
|
||||
})
|
||||
: isDefaultBillingAddress
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Default Billing Address"
|
||||
})
|
||||
: null}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
}
|
||||
height="const"
|
||||
toolbar={
|
||||
<CardMenu
|
||||
disabled={disabled}
|
||||
menuItems={[
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Set as default shipping address",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: () => onSetAsDefault(AddressTypeEnum.SHIPPING)
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({
|
||||
defaultMessage: "Set as default billing address",
|
||||
description: "button"
|
||||
}),
|
||||
onSelect: () => onSetAsDefault(AddressTypeEnum.BILLING)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<AddressFormatter address={address} />
|
||||
</CardContent>
|
||||
<div className={classes.actionsContainer}>
|
||||
<CardActions className={classes.actions}>
|
||||
<Button color="primary" disabled={disabled} onClick={onEdit}>
|
||||
<FormattedMessage {...buttonMessages.edit} />
|
||||
</Button>
|
||||
<Button color="primary" disabled={disabled} onClick={onRemove}>
|
||||
<FormattedMessage {...buttonMessages.remove} />
|
||||
</Button>
|
||||
</CardActions>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
CustomerAddress.displayName = "CustomerAddress";
|
||||
export default CustomerAddress;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue