initial setting
BIN
Doc/.DS_Store
vendored
Normal file
BIN
com.twin.app.shoptime/.DS_Store
vendored
Normal file
267
com.twin.app.shoptime/README.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
This project was bootstrapped with [@enact/cli](https://github.com/enactjs/cli).
|
||||||
|
|
||||||
|
Below you will find some information on how to perform common tasks.
|
||||||
|
You can find the most recent version of this guide [here](https://github.com/enactjs/templates/blob/master/packages/webostv/template/README.md).
|
||||||
|
Additional documentation on @enact/cli can be found [here](https://github.com/enactjs/cli/blob/master/docs/index.md).
|
||||||
|
|
||||||
|
## Folder Structure
|
||||||
|
|
||||||
|
After creation, your project should look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-app/
|
||||||
|
README.md
|
||||||
|
.gitignore
|
||||||
|
node_modules/
|
||||||
|
package.json
|
||||||
|
src/
|
||||||
|
App/
|
||||||
|
App.js
|
||||||
|
App.less
|
||||||
|
package.json
|
||||||
|
components/
|
||||||
|
views/
|
||||||
|
MainPanel.js
|
||||||
|
index.js
|
||||||
|
reportWebVitals.js
|
||||||
|
resources/
|
||||||
|
webos-meta/
|
||||||
|
```
|
||||||
|
|
||||||
|
For the project to build, **these files must exist with exact filenames**:
|
||||||
|
|
||||||
|
* `package.json` is the core package manifest for the project
|
||||||
|
* `src/index.js` is the JavaScript entry point.
|
||||||
|
|
||||||
|
You can delete or rename the other files.
|
||||||
|
|
||||||
|
You can update the `license` entry in `package.json` to match the license of your choice. For more
|
||||||
|
information on licenses, please see the [npm documentation](https://docs.npmjs.com/files/package.json#license).
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm run serve`
|
||||||
|
|
||||||
|
Builds and serves the app in the development mode.<br>
|
||||||
|
Open [http://localhost:8080](http://localhost:8080) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.<br>
|
||||||
|
|
||||||
|
### `npm run pack` and `npm run pack-p`
|
||||||
|
|
||||||
|
Builds the project in the working directory. Specifically, `pack` builds in development mode with code un-minified and with debug code included, whereas `pack-p` builds in production mode, with everything minified and optimized for performance. Be sure to avoid shipping or performance testing on development mode builds.
|
||||||
|
|
||||||
|
### `npm run watch`
|
||||||
|
|
||||||
|
Builds the project in development mode and keeps watch over the project directory. Whenever files are changed, added, or deleted, the project will automatically get rebuilt using an active shared cache to speed up the process. This is similar to the `serve` task, but without the http server.
|
||||||
|
|
||||||
|
### `npm run clean`
|
||||||
|
|
||||||
|
Deletes previous build fragments from ./dist.
|
||||||
|
|
||||||
|
### `npm run lint`
|
||||||
|
|
||||||
|
Runs the Enact configuration of Eslint on the project for syntax analysis.
|
||||||
|
|
||||||
|
### `npm run test` and `npm run test-watch`
|
||||||
|
|
||||||
|
These tasks will execute all valid tests (files that end in `-specs.js`) that are within the project directory. The `test` is a standard single execution pass, while `test-watch` will set up a watcher to re-execute tests when files change.
|
||||||
|
|
||||||
|
## Enact Build Options
|
||||||
|
|
||||||
|
The @enact/cli tool will check the project's `package.json` looking for an optional `enact` object for a few customization options:
|
||||||
|
|
||||||
|
* `template` _[string]_ - Filepath to an alternate HTML template to use with the [Webpack html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin).
|
||||||
|
* `isomorphic` _[string]_ - Alternate filepath to a custom isomorphic-compatible entry point. Not needed if main entry point is already isomorphic-compatible.
|
||||||
|
* `title` _[string]_ - Title text that should be put within the HTML's `<title></title>` tags. Note: if this is a webOS-project, the title will, by default, be auto-detected from the **appinfo.json** content.
|
||||||
|
* `theme` _[object]_ - A simplified string name to extrapolate `fontGenerator`, `ri`, and `screenTypes` preset values from. For example, `"sandstone"`.
|
||||||
|
* `fontGenerator` _[string]_ - Filepath to a CommonJS fontGenerator module which will build locale-specific font CSS to inject into the HTML. By default, will use any preset for a specified theme or fallback to sandstone.
|
||||||
|
* `ri` _[object]_ - Resolution independence options to be forwarded to the [postcss-resolution-independence](https://github.com/enactjs/postcss-resolution-independence). By default, will use any preset for a specified theme or fallback to sandstone.
|
||||||
|
* `baseSize` _[number]_ - The root font-size to use when converting the value of the base unit to a resolution-independent unit. For example, when `baseSize` is set to 24, 48px in the LESS file will be converted to 2rem.
|
||||||
|
* `screenTypes` _[array|string]_ - Array of 1 or more screentype definitions to be used with prerender HTML initialization. Can alternatively reference a json filepath to read for screentype definitions. By default, will use any preset for a specified theme or fallback to sandstone.
|
||||||
|
* `nodeBuiltins` _[object]_ - Configuration settings for polyfilling NodeJS built-ins. See `node` [webpack option](https://webpack.js.org/configuration/node/).
|
||||||
|
* `resolveFallback` _[object]_ - Configuration settings for redirecting module requests when normal resolving fails. See `resolve.fallback` [webpack option](https://webpack.js.org/configuration/resolve/#resolvefallback).
|
||||||
|
* `deep` _[string|array]_ - 1 or more JavaScript conditions that, when met, indicate deeplinking and any prerender should be discarded.
|
||||||
|
* `target` _[string|array]_ - A build-type generic preset string (see `target` [webpack option](https://webpack.js.org/configuration/target/)) or alternatively a specific [browserslist array](https://github.com/browserslist/browserslist) of desired targets.
|
||||||
|
* `proxy` _[string]_ - Proxy target during project `serve` to be used within the [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware).
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"enact": {
|
||||||
|
"theme": "sandstone",
|
||||||
|
"resolveFallback": {
|
||||||
|
fs: false,
|
||||||
|
net: false,
|
||||||
|
tls: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Displaying Lint Output in the Editor
|
||||||
|
|
||||||
|
Some editors, including Sublime Text, Atom, and Visual Studio Code, provide plugins for ESLint.
|
||||||
|
|
||||||
|
They are not required for linting. You should see the linter output right in your terminal as well as the browser console. However, if you prefer the lint results to appear right in your editor, there are some extra steps you can do.
|
||||||
|
|
||||||
|
You would need to install an ESLint plugin for your editor first.
|
||||||
|
|
||||||
|
Ever since ESLint 6, global installs of ESLint configs are no longer supported.
|
||||||
|
To work around this new limitation, while still supporting in-editor linting, we've created a new [eslint-config-enact-proxy](https://github.com/enactjs/eslint-config-enact-proxy) package.
|
||||||
|
The [eslint-config-enact-proxy](https://github.com/enactjs/eslint-config-enact-proxy) acts like a small proxy config, redirecting ESLint to use a globally-installed Enact ESLint config.
|
||||||
|
`eslint-config-enact-proxy` needs to be installed locally on a project to enable in-editor linting:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save-dev eslint-config-enact-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, you need to modify `eslintConfig` property in `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "enact-proxy"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
>**NOTE**: For strict mode, use `"extends": "enact-proxy/strict"`.
|
||||||
|
|
||||||
|
In order for in-editor linting to work with our updated ESLint config, you'll need to upgrade to ESLint 7 or later. This can be installed globally by running:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install -g eslint
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you will need to uninstall any previous globally-installed Enact linting package (everything but eslint itself):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm uninstall -g eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-babel @babel/eslint-parser eslint-plugin-jest eslint-plugin-enact eslint-config-enact
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing a Dependency
|
||||||
|
|
||||||
|
The generated project includes Enact (and all its libraries). It also includes React and ReactDOM. For test writing, both Jest and @testing-library/react are as development dependencies. You may install other dependencies with `npm`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save <package-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Importing a Component
|
||||||
|
|
||||||
|
This project setup supports ES6 modules thanks to Babel.
|
||||||
|
While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](http://exploringjs.com/es6/ch_modules.html) instead.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
### `Button.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import kind from '@enact/core/kind';
|
||||||
|
|
||||||
|
const Button = kind({
|
||||||
|
render() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Button; // Don’t forget to use export default!
|
||||||
|
```
|
||||||
|
|
||||||
|
### `DangerButton.js`
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
import kind from '@enact/core/kind';
|
||||||
|
import Button from './Button'; // Import a component from another file
|
||||||
|
|
||||||
|
const DangerButton = kind({
|
||||||
|
render(props) {
|
||||||
|
return <Button {...props} color="red" />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DangerButton;
|
||||||
|
```
|
||||||
|
|
||||||
|
Be aware of the [difference between default and named exports](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281). It is a common source of mistakes.
|
||||||
|
|
||||||
|
We suggest that you stick to using default imports and exports when a module only exports a single thing (for example, a component). That’s what you get when you use `export default Button` and `import Button from './Button'`.
|
||||||
|
|
||||||
|
Named exports are useful for utility modules that export several functions. A module may have at most one default export and as many named exports as you like.
|
||||||
|
|
||||||
|
Learn more about ES6 modules:
|
||||||
|
|
||||||
|
* [When to use the curly braces?](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281)
|
||||||
|
* [Exploring ES6: Modules](http://exploringjs.com/es6/ch_modules.html)
|
||||||
|
* [Understanding ES6: Modules](https://leanpub.com/understandinges6/read#leanpub-auto-encapsulating-code-with-modules)
|
||||||
|
|
||||||
|
## Adding a LESS or CSS Stylesheet
|
||||||
|
|
||||||
|
This project setup uses [Webpack](https://webpack.github.io/) for handling all assets. Webpack offers a custom way of “extending” the concept of `import` beyond JavaScript. To express that a JavaScript file depends on a LESS/CSS file, you need to **import the CSS from the JavaScript file**:
|
||||||
|
|
||||||
|
### `Button.less`
|
||||||
|
|
||||||
|
```css
|
||||||
|
.button {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Button.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import kind from '@enact/core/kind';
|
||||||
|
import styles './Button.css'; // Tell Webpack that Button.js uses these styles
|
||||||
|
|
||||||
|
const Button = kind({
|
||||||
|
render() {
|
||||||
|
// You can use them as regular CSS styles
|
||||||
|
return <div className={styles['button']} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Upon importing a css/less files, the resulting object will be a mapping of class names from that document. This allows correct access to the class name string regardless how the build process mangles it up.
|
||||||
|
|
||||||
|
In development, expressing dependencies this way allows your styles to be reloaded on the fly as you edit them. In production, all CSS files will be concatenated into a single minified `.css` file in the build output.
|
||||||
|
|
||||||
|
Additionally, this system setup supports [CSS module spec](https://github.com/css-modules/css-modules) with allows for compositional CSS classes and inheritance of styles.
|
||||||
|
|
||||||
|
## Adding Images and Custom Fonts
|
||||||
|
|
||||||
|
With Webpack, using static assets like images and fonts works similarly to CSS.
|
||||||
|
|
||||||
|
You can **`import` an image right in a JavaScript module**. This tells Webpack to include that image in the bundle. Unlike CSS imports, importing an image or a font gives you a string value. This value is the final image path you can reference in your code.
|
||||||
|
|
||||||
|
Here is an example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import kind from '@enact/core/kind';
|
||||||
|
import logo from './logo.png'; // Tell Webpack this JS file uses this image
|
||||||
|
|
||||||
|
console.log(logo); // /logo.84287d09.png
|
||||||
|
|
||||||
|
const Header = kind({
|
||||||
|
render: function() {
|
||||||
|
// Import result is the URL of your image
|
||||||
|
return <img src={logo} alt="Logo" />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Header;
|
||||||
|
```
|
||||||
|
|
||||||
|
This is currently required for local images. This ensures that when the project is built, webpack will correctly move the images into the build folder, and provide us with correct paths.
|
||||||
|
|
||||||
|
This works in LESS/CSS too:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.logo {
|
||||||
|
background-image: url(./logo.png);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Webpack finds all relative module references in CSS (they start with `./`) and replaces them with the final paths from the compiled bundle. If you make a typo or accidentally delete an important file, you will see a compilation error, just like when you import a non-existent JavaScript module. The final filenames in the compiled bundle are generated by Webpack from content hashes.
|
||||||
BIN
com.twin.app.shoptime/assets/.DS_Store
vendored
Normal file
BIN
com.twin.app.shoptime/assets/arial.ttf
Normal file
BIN
com.twin.app.shoptime/assets/arialbd.ttf
Normal file
BIN
com.twin.app.shoptime/assets/ariblk.ttf
Normal file
BIN
com.twin.app.shoptime/assets/fonts/arial.ttf
Normal file
BIN
com.twin.app.shoptime/assets/fonts/arialbd.ttf
Normal file
BIN
com.twin.app.shoptime/assets/fonts/ariblk.ttf
Normal file
BIN
com.twin.app.shoptime/assets/fonts/lgfont/LGSmartUI-Bold2.woff
Normal file
BIN
com.twin.app.shoptime/assets/fonts/tahoma.ttf
Normal file
BIN
com.twin.app.shoptime/assets/fonts/tahomabd.ttf
Normal file
4
com.twin.app.shoptime/assets/ic-lnb-cart-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M5.501 5.882c-.832 0-1.508.68-1.508 1.52s.676 1.52 1.508 1.52h2.663a1.51 1.51 0 0 1 1.476 1.216l4.544 22.494c.429 2.124 2.281 3.651 4.431 3.651h.292a3.041 3.041 0 0 0-1.347 2.532c0 1.679 1.348 3.04 3.014 3.04 1.666 0 3.015-1.361 3.015-3.04a3.042 3.042 0 0 0-1.348-2.532h10.734a3.044 3.044 0 0 0-1.347 2.532c0 1.679 1.349 3.04 3.014 3.04 1.665 0 3.015-1.361 3.015-3.04a3.042 3.042 0 0 0-1.348-2.532h1.348c.832 0 1.507-.681 1.507-1.521 0-.84-.675-1.52-1.507-1.52H18.615a1.508 1.508 0 0 1-1.476-1.217l-.573-2.837h22.136a3.022 3.022 0 0 0 2.957-2.443l2.09-10.545c.235-1.181-.599-2.307-1.788-2.415l-29.045-2.663-.322-1.59c-.429-2.124-2.281-3.65-4.43-3.65H5.501z"/>
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v48H0V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 838 B |
4
com.twin.app.shoptime/assets/ic-lnb-category-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M11 7.1c-1.657 0-3.9 2.243-3.9 3.9v8.1c0 2.556 2.243 3 3.9 3h8.1c2.557 0 3.9-.444 3.9-3V11c0-1.657-1.343-3.9-3.9-3.9H11zm0 18.899c-1.657 0-3.9 1.344-3.9 3.001v8.1c0 2.556 2.243 3 3.9 3h8.1c2.557 0 3.9-.444 3.9-3V29c0-1.657-1.343-3.001-3.9-3.001H11zM25.999 29A3.002 3.002 0 0 1 29 25.999h8.1c2.557 0 3 1.344 3 3.001v8.1c0 2.556-.443 3-3 3H29c-1.657 0-3.001-.444-3.001-3V29zm7.501-6.9c-.829 0-1.501.172-1.501-.727v-3.374c0-.551-.447-1.899-1.899-1.899h-2.474c-.899 0-1.627.228-1.627-.6s.728-1.501 1.627-1.501H30.1c1.452 0 1.899-.447 1.899-1.899V9.626c0-.898.672-2.526 1.501-2.526.829 0 1.501 1.628 1.501 2.526V12.1c0 1.452.447 1.899.099 1.899h4.274c.898 0 .726.673.726 1.501 0 .828.172.6-.726.6H35.1c.348 0-.099 1.348-.099 1.899v3.374c0 .899-.672.727-1.501.727z"/>
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v48H0V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 940 B |
4
com.twin.app.shoptime/assets/ic-lnb-home-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v48H0V0z"/>
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M8.827 16.606a4.039 4.039 0 0 0-1.828 3.383V36.95c0 2.226 1.792 4.029 4.002 4.029H37c2.21 0 4-1.803 4-4.029V19.989c0-1.367-.687-2.64-1.827-3.383L25.087 7.425a1.988 1.988 0 0 0-2.173 0L8.827 16.606zM29.001 40.1v-9.23A2.007 2.007 0 0 0 27 28.856h-6.9c-.204 0-1.1.901-1.1 2.014v9.23"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 461 B |
4
com.twin.app.shoptime/assets/ic-lnb-hotpicks-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M22.723 42.112a2.016 2.016 0 0 1-2.852 0L5.614 27.856a2.018 2.018 0 0 1 0-2.852l19.962-19.96 15.695 1.44 1.413 15.669-19.961 19.959zm10.762-27.866a3.78 3.78 0 0 0-5.344 5.345 3.78 3.78 0 0 0 5.344-5.345z"/>
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v47.1H0V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 387 B |
4
com.twin.app.shoptime/assets/ic-lnb-mypage-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M24 22c4.419 0 7.1-3.582 7.1-8.9 0-3.519-2.681-8-7.1-8-4.418 0-7.999 4.481-7.999 8 0 5.318 3.581 8.9 7.999 8.9zm-6.001 3.999C11.373 25.999 5.1 30.373 5.1 37v1.1c0 2.557 2.243 3 3.9 3h29.1c2.556 0 3-.443 3-3V37c0-6.627-4.473-11.001-11.1-11.001H17.999z"/>
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v48H0V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 432 B |
4
com.twin.app.shoptime/assets/ic-lnb-partners-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M42.1 17.999c0 2.762-2.672 5.166-5.434 5.166s-5.833-2.238-5.833-4.999c0 2.761-4.071 4.999-6.833 4.999-2.761 0-6.833-2.238-6.833-4.999 0 2.761-3.072 4.999-5.834 4.999-2.761 0-7.233-2.404-7.233-5.166 0-.284.854-.727 0-1.899l3.772-8.406C7.944 6.831 9.133 5.1 10.001 5.1h27.998c.869 0 2.057 1.731 2.129 2.594L42.1 16.1c.945 1.172 0 1.615 0 1.899zm-24.933 5.759c1.327 1.187 4.912 1.91 6.833 1.91 1.922 0 5.506-.723 6.833-1.91 1.327 1.187 3.912 1.91 5.833 1.91a7.473 7.473 0 0 0 3.334-.78v13.445c0 1.841-1.158 2.767-3.9 2.767H29v-9c0-.021-1.079-2-2.9-2h-5.101c-.919 0-1.999 1.979-1.999 2v9h-8c-1.841 0-3.001-.926-3.001-2.767V24.888a7.476 7.476 0 0 0 3.334.78c1.922 0 4.506-.723 5.834-1.91z"/>
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v47.1H0V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 867 B |
4
com.twin.app.shoptime/assets/ic-lnb-sale-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v48H0V0z"/>
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M24 42.999c10.493 0 19.001-8.506 19.001-19.899C43.001 13.506 34.493 5 24 5 13.507 5 5 13.506 5 23.1c0 11.393 8.507 19.899 19 19.899zm-6.147-22.351a2.795 2.795 0 1 0 0-5.588 2.795 2.795 0 0 0 0 5.588zm14.038-2.168a1.676 1.676 0 1 0-2.37-2.371L16.109 29.52a1.676 1.676 0 1 0 2.37 2.371L31.891 18.48zm1.051 11.667a2.794 2.794 0 1 1-5.589-.003 2.794 2.794 0 0 1 5.589.003z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 550 B |
4
com.twin.app.shoptime/assets/ic-lnb-search-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path opacity=".302" fill="none" d="M0 0h48v47.1H0V0z"/>
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="M20.424 5.985c7.973 0 14.438 6.515 14.438 14.552 0 8.036-6.465 14.55-14.438 14.55-7.974 0-14.439-6.514-14.439-14.55 0-8.037 6.465-14.552 14.439-14.552zM35.082 32.209l6.32 6.369a2.012 2.012 0 0 1 0 2.831 1.974 1.974 0 0 1-2.808 0l-6.321-6.369a2.014 2.014 0 0 1 0-2.831 1.975 1.975 0 0 1 2.809 0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 478 B |
BIN
com.twin.app.shoptime/assets/ic-lnb-shoptime-symbol@3x.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
4
com.twin.app.shoptime/assets/ic-lnb-topdeals-nor.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48">
|
||||||
|
<path fill-rule="evenodd" fill="#606060" d="m40.657 39.908-5.195-.883a1.091 1.091 0 0 0-1.207.685l-1.82 4.87c-.319.852-1.504.952-1.965.166l-7.221-12.308c.234.009.468.014.703.014 4.536 0 8.641-1.821 11.601-4.76l6.232 10.625c.46.786-.219 1.746-1.128 1.591zM23.951 29.241c-7.215 0-13.065-5.757-13.065-12.858 0-7.102 5.85-12.858 13.065-12.858s13.066 5.756 13.066 12.858c0 7.101-5.851 12.858-13.066 12.858zm4.836-15.436-1.989-.424a1.206 1.206 0 0 1-.785-.56L24.99 11.09c-.463-.782-1.611-.782-2.075 0l-1.024 1.731c-.169.286-.453.49-.783.56l-1.989.424c-.899.191-1.254 1.266-.641 1.941l1.357 1.494c.224.247.332.576.299.905l-.205 1.994c-.093.901.837 1.564 1.677 1.199l1.863-.807c.309-.133.66-.133.968 0l1.863.807c.84.365 1.771-.298 1.677-1.199l-.205-1.994a1.173 1.173 0 0 1 .299-.905l1.357-1.494c.612-.675.258-1.75-.641-1.941zM19.06 31.719l3.006 5.126-4.633 7.898c-.461.787-1.647.687-1.964-.165l-1.819-4.87a1.093 1.093 0 0 0-1.208-.686l-5.195.883c-.91.156-1.59-.804-1.128-1.591l6.231-10.622a16.372 16.372 0 0 0 6.71 4.027z"/>
|
||||||
|
<path opacity=".302" fill="none" d="M-.015.012h48v48h-48v-48z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
12
com.twin.app.shoptime/assets/ic-profile.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="84">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="q3k5xqzwfa" x1="0%" x2="79.864%" y1="0%" y2="60.182%">
|
||||||
|
<stop offset="0%" stop-color="#CA507E"/>
|
||||||
|
<stop offset="41%" stop-color="#BD54A2"/>
|
||||||
|
<stop offset="100%" stop-color="#6C57DC"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill-rule="evenodd" fill="url(#q3k5xqzwfa)" d="M42 0c23.196 0 41.1 18.804 41.1 41.1 0 24.096-17.904 42-41.1 42S0 65.196 0 41.1C0 18.804 18.804 0 42 0z"/>
|
||||||
|
<path fill-rule="evenodd" fill="#FFF" d="M34 41.1h16c6.627 0 11.1 6.273 11.1 12.9v3.889c0 1.657-.443 3-2.1 3H24.1c-.757 0-2.1-1.343-2.1-3V54c0-6.627 5.373-12.9 12-12.9zM42 19.1c4.971 0 9 4.929 9 9 0 5.871-4.029 9-9 9s-9.9-3.129-9.9-9c0-4.071 4.929-9 9.9-9z"/>
|
||||||
|
<path fill="none" d="M0 0h83.1v83.1H0V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 841 B |
BIN
com.twin.app.shoptime/assets/ic-profile@3x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
com.twin.app.shoptime/assets/img-banner-myinfo-login@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
com.twin.app.shoptime/assets/intro/splash_02_stop.webp
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
com.twin.app.shoptime/assets/intro/splash_03_end.webp
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
com.twin.app.shoptime/assets/lgfont/LGSmartUI-Bold2.woff
Normal file
BIN
com.twin.app.shoptime/assets/lgfont/LGSmartUI-Regular2.woff
Normal file
BIN
com.twin.app.shoptime/assets/tahoma.ttf
Normal file
BIN
com.twin.app.shoptime/assets/tahomabd.ttf
Normal file
1550
com.twin.app.shoptime/package-lock.json
generated
Normal file
59
com.twin.app.shoptime/package.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"name": "shopping-app-2.0",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A general template for an Enact Sandstone application for webOS TVs",
|
||||||
|
"author": "",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"serve": "enact serve",
|
||||||
|
"pack": "enact pack",
|
||||||
|
"pack-p": "enact pack -p",
|
||||||
|
"watch": "enact pack --watch",
|
||||||
|
"clean": "enact clean",
|
||||||
|
"lint": "enact lint .",
|
||||||
|
"license": "enact license",
|
||||||
|
"test": "enact test",
|
||||||
|
"test-watch": "enact test --watch"
|
||||||
|
},
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"private": true,
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"enact": {
|
||||||
|
"theme": "sandstone",
|
||||||
|
"ri": {
|
||||||
|
"baseSize": 24
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "enact-proxy"
|
||||||
|
},
|
||||||
|
"eslintIgnore": [
|
||||||
|
"node_modules/*",
|
||||||
|
"build/*",
|
||||||
|
"dist/*"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@enact/core": "^4.7.8",
|
||||||
|
"@enact/i18n": "^4.7.8",
|
||||||
|
"@enact/sandstone": "^2.7.12",
|
||||||
|
"@enact/spotlight": "^4.7.8",
|
||||||
|
"@enact/ui": "^4.7.8",
|
||||||
|
"@enact/webos": "^4.7.8",
|
||||||
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
|
"axios": "^1.6.5",
|
||||||
|
"ilib": "npm:ilib-webos@^14.19.0-webos1",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"less-loader": "^12.0.0",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-redux": "^9.1.0",
|
||||||
|
"web-vitals": "^3.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint-config-enact-proxy": "^1.0.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
com.twin.app.shoptime/resources/ilibmanifest.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"files": []
|
||||||
|
}
|
||||||
11
com.twin.app.shoptime/resources/strings.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"Welcome": "Welcome"
|
||||||
|
},
|
||||||
|
"de": {
|
||||||
|
"Welcome": "Willkommen"
|
||||||
|
},
|
||||||
|
"ru": {
|
||||||
|
"Welcome": "Добро пожаловать"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
com.twin.app.shoptime/src/.DS_Store
vendored
Normal file
23
com.twin.app.shoptime/src/App/App.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import ThemeDecorator from "@enact/sandstone/ThemeDecorator";
|
||||||
|
|
||||||
|
import css from "./App.module.less";
|
||||||
|
import MainView from "../views/MainView/MainView";
|
||||||
|
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { fetchAuthenticationCode } from "../features/auth/authThunks";
|
||||||
|
|
||||||
|
function AppBase(props) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchAuthenticationCode());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return <MainView />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = ThemeDecorator({ noAutoFocus: true }, AppBase);
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
export { App, AppBase };
|
||||||
3
com.twin.app.shoptime/src/App/App.module.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.app {
|
||||||
|
/* styles can be put here */
|
||||||
|
}
|
||||||
34
com.twin.app.shoptime/src/App/attachErrorHandler.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { onWindowReady } from "@enact/core/snapshot";
|
||||||
|
import { error } from "@enact/webos/pmloglib";
|
||||||
|
|
||||||
|
// Logs any uncaught exceptions to the system logs for future troubleshooting. Payload can be
|
||||||
|
// customized by the application for its particular requirements.
|
||||||
|
const handleError = (ev) => {
|
||||||
|
let stack = (ev.error && ev.error.stack) || null;
|
||||||
|
|
||||||
|
if (stack && stack.length > 512) {
|
||||||
|
// JSON must be limitted to 1024 characters so we truncate the stack to 512 for safety
|
||||||
|
stack = ev.error.stack.substring(0, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(
|
||||||
|
"app.onerror",
|
||||||
|
{
|
||||||
|
message: ev.message,
|
||||||
|
url: ev.filename,
|
||||||
|
line: ev.lineno,
|
||||||
|
column: ev.colno,
|
||||||
|
stack,
|
||||||
|
},
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calling preventDefault() will avoid logging the error to the console
|
||||||
|
// ev.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
onWindowReady(() => {
|
||||||
|
if (typeof window === "object") {
|
||||||
|
window.addEventListener("error", handleError);
|
||||||
|
}
|
||||||
|
});
|
||||||
3
com.twin.app.shoptime/src/App/package.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"main": "App.js"
|
||||||
|
}
|
||||||
22
com.twin.app.shoptime/src/api/apiConfig.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const SHOPTIME_BASE_URL = "https://qt3-aic.lgshopsvc.lgappstv.com/";
|
||||||
|
|
||||||
|
export const URLS = {
|
||||||
|
//device controller
|
||||||
|
GET_AUTHENTICATION_CODE: "/lgsp/v1/device/auth.lge",
|
||||||
|
GET_DEVICE_ADDITION_INFO: "/lgsp/v1/device/info/addition.lge",
|
||||||
|
DELETE_DEVICE_ADDITION_INFO: "/lgsp/v1/device/info/addition/delete.lge",
|
||||||
|
DELETE_DEVICE_PAIRING: "/lgsp/v1/device/pairing/delete.lge",
|
||||||
|
REGISTER_DEVICE_PAIRING: "/lgsp/v1/device/pairing/register.lge",
|
||||||
|
REGISTER_DEVICE: "/lgsp/v1/device/register.lge",
|
||||||
|
REGISTER_DEVICE_INFO: "/lgsp/v1/device/register/info.lge",
|
||||||
|
|
||||||
|
//home controller
|
||||||
|
GET_HOME_TERMS: "/lgsp/v1/home/terms.lge",
|
||||||
|
|
||||||
|
//on-sale controller
|
||||||
|
GET_ON_SALE_INFO: "/lgsp/v1/onsale/onsale.lge",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUrl = (url) => {
|
||||||
|
return SHOPTIME_BASE_URL + url;
|
||||||
|
};
|
||||||
35
com.twin.app.shoptime/src/api/axiosConfig.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { store } from "../store/store";
|
||||||
|
import { SHOPTIME_BASE_URL } from "./apiConfig";
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: SHOPTIME_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
api.defaults.headers.common["app_id"] = "com.lgshop.app";
|
||||||
|
api.defaults.headers.common["app_ver"] = "1.0.0";
|
||||||
|
api.defaults.headers.common["dvc_id"] = "testdeviceid";
|
||||||
|
api.defaults.headers.common["cntry_cd"] = "US";
|
||||||
|
api.defaults.headers.common["prod_cd"] = "webOSTV 5.0";
|
||||||
|
api.defaults.headers.common["plat_cd"] = "W20H";
|
||||||
|
api.defaults.headers.common["lang_cd"] = "en-US";
|
||||||
|
api.defaults.headers.common["os_ver"] = "3.0";
|
||||||
|
api.defaults.headers.common["sdk_ver"] = "1.0.0";
|
||||||
|
api.defaults.headers.common["publish_flag"] = "Y";
|
||||||
|
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const accessToken = store.getState().auth.accessToken;
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
config.headers["lgsp_auth"] = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default api;
|
||||||
42
com.twin.app.shoptime/src/api/deviceApi.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { URLS } from "./apiConfig";
|
||||||
|
import api from "./axiosConfig";
|
||||||
|
|
||||||
|
// 인증코드 요청 IF-LGSP-000
|
||||||
|
export async function getAuthenticationCode() {
|
||||||
|
try {
|
||||||
|
const response = await api.get(URLS.GET_AUTHENTICATION_CODE);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error : ", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 디바이스 부가 정보 조회 IF-LGSP-003
|
||||||
|
export async function getDeviceAdditionInfo() {}
|
||||||
|
|
||||||
|
// 디바이스 부가 정보 삭제 요청 IF-LGSP-087
|
||||||
|
export async function deleteDeviceAdditionInfo() {}
|
||||||
|
|
||||||
|
// 페어링 삭제 요청 IF-LGSP-078
|
||||||
|
export async function deleteDevicePairing() {}
|
||||||
|
|
||||||
|
// 페어링 생성 요청 IF-LGSP-077
|
||||||
|
export async function registerDevicePairing() {}
|
||||||
|
|
||||||
|
// 디바이스 등록 및 약관 동의 IF-LGSP-001
|
||||||
|
export async function registerDevice() {}
|
||||||
|
|
||||||
|
// 디바이스 부가 정보 저장 IF-LGSP-002
|
||||||
|
export async function registerDeviceInfo() {}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAuthenticationCode,
|
||||||
|
getDeviceAdditionInfo,
|
||||||
|
deleteDeviceAdditionInfo,
|
||||||
|
deleteDevicePairing,
|
||||||
|
registerDevice,
|
||||||
|
registerDeviceInfo,
|
||||||
|
registerDevicePairing,
|
||||||
|
};
|
||||||
53
com.twin.app.shoptime/src/api/homeApi.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { URLS } from "./apiConfig";
|
||||||
|
import api from "./axiosConfig";
|
||||||
|
|
||||||
|
// 광고주 AD 정보 상세 조회 - Multiple 전시 IF-LGSP-082
|
||||||
|
export async function getAdDetailAMD() {}
|
||||||
|
|
||||||
|
// 광고주 AD 정보 상세 조회 - Single 전시 IF-LGSP-083
|
||||||
|
export async function getAdDetailAPD() {}
|
||||||
|
|
||||||
|
// 광고주 AD 정보 상세 조회 - Theme 전시 IF-LGSP-081
|
||||||
|
export async function getAdDetailATD() {}
|
||||||
|
|
||||||
|
// HOME Main Contents Banner 정보 조회 IF-LGSP-301
|
||||||
|
export async function getHomeMainContents() {}
|
||||||
|
|
||||||
|
// HOME LAYOUT 정보 조회 IF-LGSP-300
|
||||||
|
export async function getHomeLayout() {}
|
||||||
|
|
||||||
|
// 메뉴 목록 조회 IF-LGSP-044
|
||||||
|
export async function getHomeMenu() {}
|
||||||
|
|
||||||
|
// 약관 정보 조회 IF-LGSP-005
|
||||||
|
export async function getHomeTerms(props) {
|
||||||
|
const { trmsTpCdList, mbrNo } = props;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.get(URLS.GET_HOME_TERMS, {
|
||||||
|
params: {
|
||||||
|
trmsTpCdList,
|
||||||
|
mbrNo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error : ", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme 전시 정보 상세 조회 IF-LGSP-060
|
||||||
|
export async function getThemeCurationDetailInfo() {}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAdDetailAMD,
|
||||||
|
getAdDetailAPD,
|
||||||
|
getAdDetailATD,
|
||||||
|
getHomeMainContents,
|
||||||
|
getHomeLayout,
|
||||||
|
getHomeMenu,
|
||||||
|
getHomeTerms,
|
||||||
|
getThemeCurationDetailInfo,
|
||||||
|
};
|
||||||
27
com.twin.app.shoptime/src/api/onSaleApi.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { URLS } from "./apiConfig";
|
||||||
|
import api from "./axiosConfig";
|
||||||
|
|
||||||
|
// Onsale 조회 IF-LGSP-086
|
||||||
|
export async function getOnSaleInfo(props) {
|
||||||
|
const { lgCatCd, categoryIncFlag } = props;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.get(URLS.GET_ON_SALE_INFO, {
|
||||||
|
params: {
|
||||||
|
categoryIncFlag,
|
||||||
|
lgCatCd,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
const { response } = error;
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
const statusCode = response.status;
|
||||||
|
const statusText = response.statusText;
|
||||||
|
|
||||||
|
console.error(`Error: ${statusCode} ${statusText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
com.twin.app.shoptime/src/components/.DS_Store
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Custom Image
|
||||||
|
*
|
||||||
|
* @module CustomImage
|
||||||
|
*/
|
||||||
|
import React, { useCallback, useEffect, useState, useRef } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { Job } from "@enact/core/util";
|
||||||
|
import css from "./CustomImage.module.less";
|
||||||
|
|
||||||
|
//animationSpeed : "slow", "normal", "fast", ==> 500ms, 250ms, 10ms
|
||||||
|
export default function CustomImage({
|
||||||
|
src,
|
||||||
|
className,
|
||||||
|
hide,
|
||||||
|
delay = 100,
|
||||||
|
animationSpeed = "slow",
|
||||||
|
onLoad,
|
||||||
|
}) {
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
|
const [imgSrc, setImgSrc] = useState("");
|
||||||
|
|
||||||
|
const showImageJob = useRef(
|
||||||
|
new Job((setImageLoaded) => {
|
||||||
|
setImageLoaded(true);
|
||||||
|
}, delay)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImageLoaded(false);
|
||||||
|
setImgSrc(src);
|
||||||
|
showImageJob.current.stop();
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
const _onLoad = useCallback(() => {
|
||||||
|
showImageJob.current.start(setImageLoaded);
|
||||||
|
if (onLoad) onLoad();
|
||||||
|
}, [onLoad]);
|
||||||
|
|
||||||
|
const onError = useCallback(() => {}, []);
|
||||||
|
|
||||||
|
if (!src) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className={classNames(
|
||||||
|
css.customimage,
|
||||||
|
className,
|
||||||
|
!imageLoaded || hide ? css.hidden : null,
|
||||||
|
css[animationSpeed]
|
||||||
|
)}
|
||||||
|
src={imgSrc}
|
||||||
|
onLoad={_onLoad}
|
||||||
|
onError={onError}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
.customimage {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.hidden) {
|
||||||
|
&.slow {
|
||||||
|
transition: opacity 500ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.normal {
|
||||||
|
transition: opacity 250ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fast {
|
||||||
|
transition: opacity 10ms ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"main": "CustomImage.jsx"
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React, { useRef, useCallback } from "react";
|
||||||
|
import Image from "@enact/sandstone/Image";
|
||||||
|
import Repeater from "@enact/ui/Repeater";
|
||||||
|
import css from "./PreloadImage.module.less";
|
||||||
|
|
||||||
|
const ImgItem = ({ children, onLoad }) => {
|
||||||
|
return <Image src={children} className={css.visibleHidden} onLoad={onLoad} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PreloadImage({ preloadImages = [], onLoadComplete }) {
|
||||||
|
const loadedCountRef = useRef(0);
|
||||||
|
|
||||||
|
const onLoad = useCallback(() => {
|
||||||
|
loadedCountRef.current++;
|
||||||
|
|
||||||
|
if (loadedCountRef.current === preloadImages.length) {
|
||||||
|
if (onLoadComplete) {
|
||||||
|
onLoadComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [onLoadComplete]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Repeater
|
||||||
|
className={css.repeater}
|
||||||
|
childComponent={ImgItem}
|
||||||
|
itemProps={{ onLoad }}
|
||||||
|
>
|
||||||
|
{preloadImages}
|
||||||
|
</Repeater>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.visibleHidden {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repeater {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React, { useMemo, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
export default function SmoodShowingAnimation(WrappedComponent) {
|
||||||
|
return (props) => {
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
return () => {
|
||||||
|
setIsMounted(false);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const aniStyle = useMemo(() => {
|
||||||
|
if (isMounted) {
|
||||||
|
return { opacity: 1, transition: "opacity 0.3s ease" };
|
||||||
|
}
|
||||||
|
return { opacity: 0 };
|
||||||
|
}, [isMounted]);
|
||||||
|
|
||||||
|
return <WrappedComponent {...props} style={aniStyle} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"main": "SmoodShowingAnimation.jsx"
|
||||||
|
}
|
||||||
79
com.twin.app.shoptime/src/components/TButton/TButton.jsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
import compose from "ramda/src/compose";
|
||||||
|
|
||||||
|
import css from "./TButton.module.less";
|
||||||
|
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
import Spottable from "@enact/spotlight/Spottable";
|
||||||
|
import { Marquee, MarqueeController } from "@enact/ui/Marquee";
|
||||||
|
|
||||||
|
const SpottableComponent = Spottable("div");
|
||||||
|
|
||||||
|
function TButtonBase({
|
||||||
|
children,
|
||||||
|
spotlightId,
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
disabled,
|
||||||
|
selected,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
const [pressed, setPressed] = useState(false);
|
||||||
|
|
||||||
|
const _onClick = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (disabled) {
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPressed(true);
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClick, disabled]
|
||||||
|
);
|
||||||
|
|
||||||
|
const _onFocus = useCallback(() => {
|
||||||
|
setIsFocused(true);
|
||||||
|
|
||||||
|
if (onFocus) {
|
||||||
|
onFocus();
|
||||||
|
}
|
||||||
|
}, [onFocus]);
|
||||||
|
|
||||||
|
const _onBlur = useCallback(() => {
|
||||||
|
setIsFocused(false);
|
||||||
|
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur();
|
||||||
|
}
|
||||||
|
}, [onBlur]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpottableComponent
|
||||||
|
className={classNames(
|
||||||
|
css.tButton,
|
||||||
|
isFocused && css.focused,
|
||||||
|
selected && css.selected
|
||||||
|
)}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
onFocus={_onFocus}
|
||||||
|
onBlur={_onBlur}
|
||||||
|
onClick={_onClick}
|
||||||
|
>
|
||||||
|
<Marquee marqueeOn="focus">{children}</Marquee>
|
||||||
|
</SpottableComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonDecorator = compose(MarqueeController({ marqueeOnFocus: true }));
|
||||||
|
const TButton = ButtonDecorator(TButtonBase);
|
||||||
|
|
||||||
|
export default TButton;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.focused {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "TButton.jsx",
|
||||||
|
"styles": [
|
||||||
|
"TButton.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
47
com.twin.app.shoptime/src/components/TPanel/TPanel.jsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { Panel } from "@enact/sandstone/Panels";
|
||||||
|
import { Cancelable } from "@enact/ui/Cancelable";
|
||||||
|
import { popPanel } from "../../features/panels/panelsSlice";
|
||||||
|
import css from "./TPanel.module.less";
|
||||||
|
import SmoodShowingAnimation from "../SmoodShowingAnimation/SmoodShowingAnimation";
|
||||||
|
|
||||||
|
const CancelablePanel = Cancelable(
|
||||||
|
{ modal: true, onCancel: "handleCancel" },
|
||||||
|
Panel
|
||||||
|
);
|
||||||
|
|
||||||
|
const TPanel = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
handleCancel,
|
||||||
|
isTabActivated,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
delete rest.panelInfo;
|
||||||
|
|
||||||
|
const onCancel = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (isTabActivated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (handleCancel) {
|
||||||
|
handleCancel(e);
|
||||||
|
} else {
|
||||||
|
dispatch(popPanel());
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, handleCancel, isTabActivated]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CancelablePanel {...rest} handleCancel={onCancel}>
|
||||||
|
{children}
|
||||||
|
</CancelablePanel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SmoodShowingAnimation(TPanel);
|
||||||
6
com.twin.app.shoptime/src/components/TPanel/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "TPanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"TPanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
74
com.twin.app.shoptime/src/components/TPopUp/TPopUp.jsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||||
|
import Spottable from "@enact/spotlight/Spottable";
|
||||||
|
import Spotlight from "@enact/spotlight";
|
||||||
|
import Alert from "@enact/sandstone/Alert";
|
||||||
|
import { $L } from "../../utils/helperMethods";
|
||||||
|
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
import TButton from "../TButton/TButton";
|
||||||
|
|
||||||
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: "default-element", preserveId: true },
|
||||||
|
Spottable("div")
|
||||||
|
);
|
||||||
|
const SpottableComponent = Spottable("div");
|
||||||
|
|
||||||
|
export default function TPopUp({
|
||||||
|
kind,
|
||||||
|
children,
|
||||||
|
onExit,
|
||||||
|
onClose,
|
||||||
|
hasButton,
|
||||||
|
hasText,
|
||||||
|
button1Text,
|
||||||
|
button2Text,
|
||||||
|
open,
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
Spotlight.focus("tPopupBtn1");
|
||||||
|
Spotlight.focus("tPopupBtn2");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const _onClose = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (onClose) {
|
||||||
|
onClose(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClose]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert open={open} onClose={_onClose} type="overlay" kind={kind}>
|
||||||
|
<Container>
|
||||||
|
{hasText && (
|
||||||
|
<>
|
||||||
|
{title && <div>{title}</div>}
|
||||||
|
{text && <div>{text}</div>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
{hasButton && (
|
||||||
|
<>
|
||||||
|
{button1Text && (
|
||||||
|
<TButton
|
||||||
|
spotlightId="tPopupBtn1"
|
||||||
|
onClick={kind === "exit" ? onExit : onClose}
|
||||||
|
>
|
||||||
|
{button1Text}
|
||||||
|
</TButton>
|
||||||
|
)}
|
||||||
|
{button2Text && (
|
||||||
|
<TButton spotlightId="tPopupBtn2" onClick={onClose}>
|
||||||
|
{button2Text}
|
||||||
|
</TButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
6
com.twin.app.shoptime/src/components/TPopUp/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "TPopUp.jsx",
|
||||||
|
"styles": [
|
||||||
|
"TPopUp.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
45
com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { addPanels } from "../../features/panels/panelsSlice";
|
||||||
|
|
||||||
|
import * as Config from "../../utils/Config";
|
||||||
|
import { $L } from "../../utils/helperMethods";
|
||||||
|
|
||||||
|
export default function TabLayout(props) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const menuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
{ label: $L("MY PAGE"), panel: Config.panel_names.MY_PAGE_PANEL },
|
||||||
|
{ label: $L("CATEGORY"), panel: Config.panel_names.CATEGORY_PANEL },
|
||||||
|
{ label: $L("SEARCH"), panel: Config.panel_names.SEARCH_PANEL },
|
||||||
|
{ label: $L("HOME"), panel: Config.panel_names.HOME_PANEL },
|
||||||
|
{ label: $L("ON SALE"), panel: Config.panel_names.ON_SALE_PANEL },
|
||||||
|
{
|
||||||
|
label: $L("TRENDING NOW"),
|
||||||
|
panel: Config.panel_names.TRENDING_NOW_PANEL,
|
||||||
|
},
|
||||||
|
{ label: $L("HOT PICKS"), panel: Config.panel_names.HOT_PICKS_PANEL },
|
||||||
|
{ label: $L("CART"), panel: Config.panel_names.CART_PANEL },
|
||||||
|
{
|
||||||
|
label: $L("FEATURED BRANDS"),
|
||||||
|
panel: Config.panel_names.FEATURED_BRANDS_PANEL,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNavigation = (panel) => {
|
||||||
|
dispatch(addPanels({ name: panel, panelInfo: {} }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{menuItems.map((item, index) => (
|
||||||
|
<button key={index} onClick={() => handleNavigation(item.panel)}>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
BIN
com.twin.app.shoptime/src/features/.DS_Store
vendored
Normal file
19
com.twin.app.shoptime/src/features/appData/appDataSlice.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
export const appDataSlice = createSlice({
|
||||||
|
name: "appData",
|
||||||
|
initialState: {
|
||||||
|
mainIndex: null,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
addMainIndex: (state, action) => {
|
||||||
|
state.mainIndex = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { addMainIndex, decrement, incrementByAmount } =
|
||||||
|
appDataSlice.actions;
|
||||||
|
|
||||||
|
export default appDataSlice.reducer;
|
||||||
35
com.twin.app.shoptime/src/features/auth/authSlice.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { fetchAuthenticationCode } from "./authThunks";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
accessToken: null,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authSlice = createSlice({
|
||||||
|
name: "auth",
|
||||||
|
initialState,
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchAuthenticationCode.pending, (state) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
state.error = null;
|
||||||
|
})
|
||||||
|
.addCase(fetchAuthenticationCode.fulfilled, (state, action) => {
|
||||||
|
state.accessToken = action.payload;
|
||||||
|
state.isLoading = false;
|
||||||
|
})
|
||||||
|
.addCase(fetchAuthenticationCode.rejected, (state, action) => {
|
||||||
|
state.isLoading = false;
|
||||||
|
state.error = action.payload;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectAccessToken = (state) => state.auth.accessToken;
|
||||||
|
export const selectAuthLoading = (state) => state.auth.isLoading;
|
||||||
|
export const selectAuthError = (state) => state.auth.error;
|
||||||
|
|
||||||
|
export default authSlice.reducer;
|
||||||
16
com.twin.app.shoptime/src/features/auth/authThunks.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import { getAuthenticationCode } from "../../api/deviceApi";
|
||||||
|
|
||||||
|
export const fetchAuthenticationCode = createAsyncThunk(
|
||||||
|
"auth/fetchAuthenticationCode",
|
||||||
|
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await getAuthenticationCode();
|
||||||
|
|
||||||
|
return response.data.accessToken;
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue("unknown Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
40
com.twin.app.shoptime/src/features/common/commonSlice.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
// TODO: 각종 Status 추가
|
||||||
|
appStatus: {
|
||||||
|
showLoadingPanel: { show: true, type: "launching" },
|
||||||
|
isLoading: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const commonSlice = createSlice({
|
||||||
|
name: "common",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
changeAppStatus: (state, action) => {
|
||||||
|
let isUpdated = false;
|
||||||
|
for (let i in action.payload) {
|
||||||
|
if (typeof action.payload[i] === "object") {
|
||||||
|
if (
|
||||||
|
JSON.stringify(action.payload[i]) !==
|
||||||
|
JSON.stringify(state.appStatus[i])
|
||||||
|
) {
|
||||||
|
isUpdated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (state.appStatus[i] !== action.payload[i]) {
|
||||||
|
isUpdated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isUpdated) {
|
||||||
|
state.appStatus = { ...state.appStatus, ...action.payload };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { changeAppStatus } = commonSlice.actions;
|
||||||
|
|
||||||
|
export default commonSlice.reducer;
|
||||||
104
com.twin.app.shoptime/src/features/panels/panelsSlice.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { panel_names } from "../../utils/Config";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
panels: [],
|
||||||
|
isModalOpen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const forceTopPanels = [panel_names.ERROR_PANEL, panel_names.DEBUG_PANEL];
|
||||||
|
|
||||||
|
export const panels = createSlice({
|
||||||
|
name: "panels",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
addPanels: (state, action) => {
|
||||||
|
if (!action.payload.panelInfo) {
|
||||||
|
action.payload.panelInfo = {};
|
||||||
|
}
|
||||||
|
const forceTopPanelsInfo = [];
|
||||||
|
const newState = [];
|
||||||
|
let forceTopIndex;
|
||||||
|
for (let index in state.panels) {
|
||||||
|
forceTopIndex = forceTopPanels.indexOf(state.panels[index].name);
|
||||||
|
if (forceTopIndex >= 0) {
|
||||||
|
forceTopPanelsInfo[forceTopIndex] = state.panels[index];
|
||||||
|
} else if (state.panels[index].name !== action.payload.name) {
|
||||||
|
newState.push(state.panels[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forceTopIndex = forceTopPanels.indexOf(action.payload.name);
|
||||||
|
if (forceTopIndex >= 0) {
|
||||||
|
forceTopPanelsInfo[forceTopIndex] = action.payload;
|
||||||
|
} else {
|
||||||
|
newState.push(action.payload);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < forceTopPanels.length; i++) {
|
||||||
|
if (forceTopPanelsInfo[i]) {
|
||||||
|
newState.push(forceTopPanelsInfo[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.panels = newState;
|
||||||
|
},
|
||||||
|
popPanel: (state, action) => {
|
||||||
|
let existIndex = -1;
|
||||||
|
if (action?.payload) {
|
||||||
|
for (let index in state.panels) {
|
||||||
|
if (state.panels[index].name === action.payload) {
|
||||||
|
existIndex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existIndex >= 0) {
|
||||||
|
// exist
|
||||||
|
state.panels = [
|
||||||
|
...state.panels.filter((value) => value.name !== action.payload),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (!action.payload) {
|
||||||
|
state.panels = [...state.panels.slice(0, state.panels.length - 1)];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updatePanel: (state, action) => {
|
||||||
|
let existIndex = -1;
|
||||||
|
for (let index in state.panels) {
|
||||||
|
if (state.panels[index].name === action.payload.name) {
|
||||||
|
existIndex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existIndex >= 0 && action.payload.panelInfo) {
|
||||||
|
state.panels[existIndex].panelInfo = Object.assign(
|
||||||
|
{},
|
||||||
|
state.panels[existIndex].panelInfo,
|
||||||
|
action.payload.panelInfo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateModalStatus: (state, action) => {
|
||||||
|
state.isModalOpen = action.payload;
|
||||||
|
},
|
||||||
|
resetPanels: (state, action) => {
|
||||||
|
state.isModalOpen = false;
|
||||||
|
if (action.payload) {
|
||||||
|
action.payload.forEach(function (panel) {
|
||||||
|
if (!panel.panelInfo) {
|
||||||
|
panel.panelInfo = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
state.panels = action.payload ? action.payload : [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
addPanels,
|
||||||
|
popPanel,
|
||||||
|
updatePanel,
|
||||||
|
updateModalStatus,
|
||||||
|
resetPanels,
|
||||||
|
} = panels.actions;
|
||||||
|
|
||||||
|
export default panels.reducer;
|
||||||
20
com.twin.app.shoptime/src/index.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { store } from "./store/store";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
let appElement = (
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// In a browser environment, render instead of exporting
|
||||||
|
if (typeof window === "object") {
|
||||||
|
createRoot(document.getElementById("root")).render(appElement);
|
||||||
|
appElement = null;
|
||||||
|
window.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default appElement;
|
||||||
15
com.twin.app.shoptime/src/store/store.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
import appDataReducer from "../features/appData/appDataSlice";
|
||||||
|
import authReducer from "../features/auth/authSlice";
|
||||||
|
import panelsReducer from "../features/panels/panelsSlice";
|
||||||
|
import commonReducer from "../features/common/commonSlice";
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
panels: panelsReducer,
|
||||||
|
auth: authReducer,
|
||||||
|
appData: appDataReducer,
|
||||||
|
common: commonReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
121
com.twin.app.shoptime/src/style/CommonStyle.module.less
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/* global */
|
||||||
|
/* font */
|
||||||
|
@font-face {
|
||||||
|
font-family: "LGSmartUI"; //font-weight: 400;
|
||||||
|
src: url("../../assets/fonts/lgfont/LGSmartUI-Regular2.woff") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "LGSmartUIBold"; //font-weight: 600;
|
||||||
|
src: url("../../assets/fonts/lgfont/LGSmartUI-Bold2.woff") format("woff");
|
||||||
|
}
|
||||||
|
@baseFont: "LGSmartUI";
|
||||||
|
|
||||||
|
@globalHeight: 1080px;
|
||||||
|
@globalWidth: 1920px;
|
||||||
|
@globalMainWidth: 1800px;
|
||||||
|
|
||||||
|
/* color */
|
||||||
|
/* white base */
|
||||||
|
@colorWhite01: #fff;
|
||||||
|
@colorWhite02: #f8f8f8;
|
||||||
|
@colorWhite03: #e6e6e6;
|
||||||
|
@colorWhite04: #dadada;
|
||||||
|
|
||||||
|
/* red base */
|
||||||
|
@colorRed01: #c70850;
|
||||||
|
|
||||||
|
/* grey */
|
||||||
|
@colorGrey01: #606060;
|
||||||
|
@colorGrey02: #999999;
|
||||||
|
@colorGrey03: #808080;
|
||||||
|
@colorGrey04: #9ba5b5;
|
||||||
|
@colorGrey05: #666;
|
||||||
|
@colorGrey06: #555;
|
||||||
|
@colorGrey07: #ccc;
|
||||||
|
|
||||||
|
/* black base */
|
||||||
|
@colorBlack01: #000;
|
||||||
|
@colorBlack02: #1a1a1a;
|
||||||
|
@colorBlack03: #141414;
|
||||||
|
@colorBlack04: #222;
|
||||||
|
@colorBlack05: #2c343f;
|
||||||
|
@colorBlack06: #333;
|
||||||
|
|
||||||
|
/* btns */
|
||||||
|
/* btns size */
|
||||||
|
@btn42: 42px;
|
||||||
|
@btn48: 48px;
|
||||||
|
@btn60: 60px;
|
||||||
|
@btn84: 84px;
|
||||||
|
@btn90: 90px;
|
||||||
|
@btn120: 120px;
|
||||||
|
/* icon */
|
||||||
|
/* icon width */
|
||||||
|
@icon40: 40px;
|
||||||
|
@icon48: @btn48;
|
||||||
|
/* video icon */
|
||||||
|
@icon60: @btn60;
|
||||||
|
@icon78: 78px;
|
||||||
|
@icon80: 80px;
|
||||||
|
@icon120: @btn120;
|
||||||
|
@icon138: 138px;
|
||||||
|
|
||||||
|
/* btns color */
|
||||||
|
@btnNormalGrey01: #7a808d;
|
||||||
|
@btnNormalGrey02: #808080;
|
||||||
|
@btnNormalGrey03: #f9fafa;
|
||||||
|
@btnNormalGrey04: #7d848c;
|
||||||
|
@btnNormalGrey05: #e4e4e4;
|
||||||
|
@btnNormalGrey06: #686464;
|
||||||
|
@btnNormalFont01: @colorWhite01;
|
||||||
|
@btnNormalFont02: @colorWhite01;
|
||||||
|
@btnNormalFont03: #808080;
|
||||||
|
@btnNormalFont04: #e6e6e6;
|
||||||
|
@btnNormalFont05: #4c5059;
|
||||||
|
@btnNormalFont06: #f2f6fb;
|
||||||
|
@btnNormalFont07: #666666;
|
||||||
|
@btnNormalFont08: @colorGrey04;
|
||||||
|
|
||||||
|
@btnNormalRed: @colorRed01;
|
||||||
|
|
||||||
|
@btnNormalWhite: @colorWhite01;
|
||||||
|
|
||||||
|
@btnFocusRed: @colorRed01;
|
||||||
|
@btnFocusFont: @colorWhite01;
|
||||||
|
|
||||||
|
@btnDimmed01: #4a4c50;
|
||||||
|
@btnDimmed02: #4c4c4c;
|
||||||
|
@btnDimmed03: #717171;
|
||||||
|
@btnDimmedFont01: #8f9092;
|
||||||
|
@btnDimmedFont02: #727272;
|
||||||
|
@btnDimmedFont03: #676767;
|
||||||
|
|
||||||
|
@btnDropDown01: @btnNormalGrey02;
|
||||||
|
@btnDropDown02: @colorRed01;
|
||||||
|
|
||||||
|
@btnDropDownSelect: #4f172c;
|
||||||
|
|
||||||
|
/* padding 값 LR 부분 */
|
||||||
|
@smallPaddingLR: 12px;
|
||||||
|
@bigPaddingLR: 30px;
|
||||||
|
|
||||||
|
/* popup */
|
||||||
|
@popupBoxNormalWidth: 780px;
|
||||||
|
@popupBoxOptionWidth: 820px;
|
||||||
|
|
||||||
|
/* tab */
|
||||||
|
@tabLineColor: @btnDropDownSelect;
|
||||||
|
|
||||||
|
/* tab color */
|
||||||
|
@tabNormal: @colorWhite01;
|
||||||
|
@tabFocus: @colorRed01;
|
||||||
|
@tabSelected: @btnDropDownSelect;
|
||||||
|
|
||||||
|
/* gnb */
|
||||||
|
@gnbOffWidth: 120px;
|
||||||
|
@gnbOnWidth: 402px;
|
||||||
|
@gnbSubWidth: 386px;
|
||||||
|
|
||||||
|
/* category guide */
|
||||||
|
@cateTabMinWidth: 120px;
|
||||||
|
@cateTabTwoFixWidth: 240px;
|
||||||
49
com.twin.app.shoptime/src/style/utils.module.less
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//mixin 공통으로 사용할 수 있는 범용적 css ========================================================
|
||||||
|
.flex(@display: flex, @justifyCenter: center, @alignCenter: center, @direction: row) {
|
||||||
|
display: @display;
|
||||||
|
justify-content: @justifyCenter;
|
||||||
|
align-items: @alignCenter;
|
||||||
|
flex-direction: @direction;
|
||||||
|
}
|
||||||
|
.size(@w: 50px, @h: 50px) {
|
||||||
|
width: @w;
|
||||||
|
height: @h;
|
||||||
|
}
|
||||||
|
.position(@position: relative, @top: 0, @right: 0, @bottom: 0, @left: 0) {
|
||||||
|
position: @position;
|
||||||
|
top: @top;
|
||||||
|
right: @right;
|
||||||
|
bottom: @bottom;
|
||||||
|
left: @left;
|
||||||
|
}
|
||||||
|
|
||||||
|
//border-solid
|
||||||
|
.border-solid(@size, @color) {
|
||||||
|
border: @size solid @color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
//font
|
||||||
|
.font (@fontFamily, @fontSize, @color, @fontWeight) {
|
||||||
|
font-family: @fontFamily;
|
||||||
|
font-size: @fontSize;
|
||||||
|
color: @color;
|
||||||
|
font-weight: @fontWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
//말줄임
|
||||||
|
.elip(@clamp) {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
// -webkit-box-orient: vertical;
|
||||||
|
// display: -webkit-box;
|
||||||
|
white-space: nowrap;
|
||||||
|
-webkit-line-clamp: @clamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
//background 이미지 삽입
|
||||||
|
.imgElement (@backgroundW, @backgroundH, @positionX, @positionY) {
|
||||||
|
background-size: @backgroundW, @backgroundH;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: @positionX @positionY;
|
||||||
|
}
|
||||||
27
com.twin.app.shoptime/src/utils/Config.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export const SUPPORT_COUNTRIES = { US: "en", DE: "de", UK: "uk", RU: "ru" };
|
||||||
|
|
||||||
|
// debug
|
||||||
|
export const DEBUG_KEY = "5286";
|
||||||
|
export const TESTPANEL_KEY = "5325";
|
||||||
|
export const SFT_TEST = "5555";
|
||||||
|
export const SFT_TEST2 = "6666";
|
||||||
|
export const ACTIVITY_SCENERY = "8282";
|
||||||
|
|
||||||
|
export const panel_names = {
|
||||||
|
INTRO_PANEL: "intropanel",
|
||||||
|
HOME_PANEL: "homepanel",
|
||||||
|
MY_PAGE_PANEL: "mypagepanel",
|
||||||
|
CATEGORY_PANEL: "categorypanel",
|
||||||
|
SEARCH_PANEL: "searchpanel",
|
||||||
|
ON_SALE_PANEL: "onsalepanel",
|
||||||
|
TRENDING_NOW_PANEL: "trendingnowpanel",
|
||||||
|
HOT_PICKS_PANEL: "hotpickpanel",
|
||||||
|
CART_PANEL: "cartpanel",
|
||||||
|
FEATURED_BRANDS_PANEL: "featuredbrandspanel",
|
||||||
|
|
||||||
|
// error
|
||||||
|
ERROR_PANEL: "errorpanel",
|
||||||
|
|
||||||
|
// debug
|
||||||
|
DEBUG_PANEL: "debugpanel",
|
||||||
|
};
|
||||||
50
com.twin.app.shoptime/src/utils/helperMethods.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Enact_$L from "@enact/i18n/$L";
|
||||||
|
import stringReSource from "../../resources/strings.json";
|
||||||
|
|
||||||
|
export const $L = (str) => {
|
||||||
|
let languageSetting = "system";
|
||||||
|
let language = "en";
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof window === "object" &&
|
||||||
|
window.store &&
|
||||||
|
window.store.getState().common &&
|
||||||
|
window.store.getState().common.localSettings
|
||||||
|
) {
|
||||||
|
languageSetting =
|
||||||
|
window.store.getState().common.localSettings.languageSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof window === "object" &&
|
||||||
|
window.PalmSystem &&
|
||||||
|
languageSetting === "system"
|
||||||
|
) {
|
||||||
|
return Enact_$L(str).replace(/{br}/g, "{br}");
|
||||||
|
} else if (typeof window === "object") {
|
||||||
|
language =
|
||||||
|
typeof window.navigator === "object"
|
||||||
|
? window.navigator.language || window.navigator.userLanguage
|
||||||
|
: "en-US";
|
||||||
|
|
||||||
|
if (languageSetting !== "system") {
|
||||||
|
language = languageSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
language = language.split("-")[0];
|
||||||
|
|
||||||
|
const resource = stringReSource[language] || stringReSource.en;
|
||||||
|
|
||||||
|
if (typeof str === "object") {
|
||||||
|
if (resource && resource[str.key]) {
|
||||||
|
return resource[str.key].replace(/{br}/g, "{br}");
|
||||||
|
} else {
|
||||||
|
return str.value;
|
||||||
|
}
|
||||||
|
} else if (resource && resource[str]) {
|
||||||
|
return resource[str].replace(/{br}/g, "{br}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str && str.replace(/{br}/g, "{br}");
|
||||||
|
};
|
||||||
BIN
com.twin.app.shoptime/src/views/.DS_Store
vendored
Normal file
5
com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
|
||||||
|
export default function CartPanel() {
|
||||||
|
return <TPanel>Cart</TPanel>;
|
||||||
|
}
|
||||||
6
com.twin.app.shoptime/src/views/CartPanel/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "CartPanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"CartPanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
|
||||||
|
export default function CategoryPanel() {
|
||||||
|
return <TPanel>Category</TPanel>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "CategoryPanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"CategoryPanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
|
||||||
|
export default function DebugPanel() {
|
||||||
|
return <TPanel>Debug</TPanel>;
|
||||||
|
}
|
||||||
6
com.twin.app.shoptime/src/views/DebugPanel/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "DebugPanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"Debugpanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
|
||||||
|
export default function ErrorPanel() {
|
||||||
|
return <TPanel>Error</TPanel>;
|
||||||
|
}
|
||||||
6
com.twin.app.shoptime/src/views/ErrorPanel/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "ErrorPanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"ErrorPanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
|
||||||
|
export default function FeaturedBrandsPanel() {
|
||||||
|
return <TPanel>Featured Brands</TPanel>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "FeaturedBrandsPanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"FeaturedBrandsPanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
6
com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
import { $L } from "../../utils/helperMethods";
|
||||||
|
|
||||||
|
export default function HomePanel() {
|
||||||
|
return <TPanel>{$L("Welcome")}</TPanel>;
|
||||||
|
}
|
||||||
6
com.twin.app.shoptime/src/views/HomePanel/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"main": "HomePanel.jsx",
|
||||||
|
"styles": [
|
||||||
|
"HomePanel.module.less"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import TPanel from "../../components/TPanel/TPanel";
|
||||||
|
|
||||||
|
export default function HotPicksPanel() {
|
||||||
|
return <TPanel>Hot Picks</TPanel>;
|
||||||
|
}
|
||||||