diff --git a/sales-embedding-demo/.env b/sales-embedding-demo/.env new file mode 100644 index 00000000..6f809cc2 --- /dev/null +++ b/sales-embedding-demo/.env @@ -0,0 +1 @@ +SKIP_PREFLIGHT_CHECK=true diff --git a/sales-embedding-demo/.gitignore b/sales-embedding-demo/.gitignore new file mode 100644 index 00000000..027707a2 --- /dev/null +++ b/sales-embedding-demo/.gitignore @@ -0,0 +1,25 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.gdcatalogrc diff --git a/sales-embedding-demo/.prettierrc b/sales-embedding-demo/.prettierrc new file mode 100644 index 00000000..5f1a4ce9 --- /dev/null +++ b/sales-embedding-demo/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 110, + "tabWidth": 4, + "trailingComma": "all" +} diff --git a/sales-embedding-demo/CONFIGURATION.md b/sales-embedding-demo/CONFIGURATION.md new file mode 100644 index 00000000..6f2e8fd7 --- /dev/null +++ b/sales-embedding-demo/CONFIGURATION.md @@ -0,0 +1,150 @@ +# Configuration + +The purpose of running this app on your localhost is that you can alter it to your needs any time you wish. Some places in the code have been designed to make it as easy as possible. + +> Please download [Visual Studio Code](https://code.visualstudio.com/) for easy files editing. + +(Jump back to [INSTRUCTIONS](INSTRUCTIONS.md) or [README](README.md).) + +## List of Features + +* [Change the domain and workspace](#change-the-domain-and-workspace) +* [Change the name of the application](#change-the-name-of-the-application) +* [Change the URL of the embedded dashboard](#change-the-url-of-the-embedded-dashboard) +* [Change the logo](#change-the-logo) +* [Show/hide the embedded dashboard navigation](#showhide-the-embedded-dashboard-navigation) +* [Show/hide the embedded dashboard top bar](#showhide-the-embedded-dashboard-top-bar) +* [Show/hide the embedded dashboard filter bar](#showhide-the-embedded-dashboard-filter-bar) +* [Show/hide the embedded dashboard widgets catalogue](#showhide-the-embedded-dashboard-widgets-catalogue) +* [Integrate an individual insight](#integrate-an-individual-insight) +* [Integrate a dashboard](#integrate-a-dashboard) + +## Opening sales-embedding-demo in Visual Studio Code + +* Open VSCode (if you don't have VSCode, get it at https://code.visualstudio.com/) +* At the top of your screen, click View --> Explorer +* Click `Open Folder` and select `sales-embedding-demo` folder to be found in `~/Documents/my-projects/ui-sdk-examples/` +* You can now open files by clicking Go --> Go to File… + + + + + + +--- + +### Change the Domain and Workspace + +* Kill the app (hit `CTRL + C` in the Terminal) +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* While in VSCode, click Go --> Go to File… and type in `constants` to open `src/constants.js` file +* Change the value of `backend` to your domain (e.g. https://leilani.na.gooddata.com/) +* Change the value of `workspace` to the workspace you'll be working with +* Save the changes in `src/constants.js` file +* Restart the app (run `yarn start` again in the Terminal) +* You'll now need to `Log out` from the application, and then `Log in` again + +--- + +### Change the Name of the Application + +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* While in VSCode, click Go --> Go to File… and type in `constants` to open `src/constants.js` file +* Change the value of `appName` to from `sales-embedding-demo` to `ShopBoard Demo` +* Save the changes in `src/constants.js` file +* Wait till your app refreshes in your browser (or refresh manually) + +--- + +### Change the URL of the Embedded Dashboard + +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* Open `src/routes/Home.js` in VSCode +* Look for the `iframe` tag +* Change the value of `src` attribute + +`src` attribute of the `iframe` should contain either absolute or relative URL of the dashboard, where there is word `/embedded/` added after `/dashboards/` and before `/#/`. See the example: + +Standard dashboard URL: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}` + +Dashboard URL for emedding: `https://my-domain.com/dashboards/embedded/#/project/{projId}/dashboard/{dashId}` + +See the [documentation](https://help.gooddata.com/doc/enterprise/en/expand-your-gooddata-platform/gooddata-integration-into-your-application/embed-gooddata-elements-into-your-applications/embed-a-kpi-dashboard#EmbedaKPIDashboard-Advancedconfigurationoptions) for more. + +--- + +### Change the Logo + +TODO + +--- + +### Show/hide the Embedded Dashboard Navigation + +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* Open `src/routes/Home.js` in VSCode +* Look for the `iframe` tag +* Append `?showNavigation=true` (or `false`) to the value of `src` attribute + +Standard dashboard URL: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}` + +Dashboard URL with navigation: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?showNavigation=true` + +See the [documentation](https://help.gooddata.com/doc/enterprise/en/expand-your-gooddata-platform/gooddata-integration-into-your-application/embed-gooddata-elements-into-your-applications/embed-a-kpi-dashboard#EmbedaKPIDashboard-Advancedconfigurationoptions) for more. + +--- + +### Show/hide the Embedded Dashboard Top Bar + +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* Open `src/routes/Home.js` in VSCode +* Look for the `iframe` tag +* Append `?hideControl=[topBar]` to the value of `src` attribute + +Standard dashboard URL: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}` + +Dashboard URL without top bar: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?hideControl=[topBar]` + +Dashboard URL with navigation without top bar: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?showNavigation=true&hideControl=[topBar]` + +See the [documentation](https://help.gooddata.com/doc/enterprise/en/expand-your-gooddata-platform/gooddata-integration-into-your-application/embed-gooddata-elements-into-your-applications/embed-a-kpi-dashboard#EmbedaKPIDashboard-Advancedconfigurationoptions) for more. + +--- + +### Show/hide the Embedded Dashboard Filter Bar + +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* Open `src/routes/Home.js` in VSCode +* Look for the `iframe` tag +* Append `?hideControl=[filterBar]` to the value of `src` attribute + +Standard dashboard URL: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}` + +Dashboard URL without filter bar: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?hideControl=[filterBar]` + +Dashboard URL with navigation without filter bar: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?showNavigation=true&hideControl=[filterBar]` + +Dashboard URL with navigation without filter bar and top bar: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?showNavigation=true&hideControl=[topBar,filterBar]` + +See the [documentation](https://help.gooddata.com/doc/enterprise/en/expand-your-gooddata-platform/gooddata-integration-into-your-application/embed-gooddata-elements-into-your-applications/embed-a-kpi-dashboard#EmbedaKPIDashboard-Advancedconfigurationoptions) for more. + +--- + +### Show/hide the Embedded Dashboard Widgets Catalogue + +* [Open sales-embedding-demo in VSCode](#opening-sales-embedding-demo-in-visual-studio-code) +* Open `src/routes/Home.js` in VSCode +* Look for the `iframe` tag +* Append `?hideControl=[widgetsCatalogue]` to the value of `src` attribute + +Standard dashboard URL: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}` + +Dashboard URL without widgets catalogue: `https://my-domain.com/dashboards/#/project/{projId}/dashboard/{dashId}?hideControl=[widgetsCatalogue]` + +See the [documentation](https://help.gooddata.com/doc/enterprise/en/expand-your-gooddata-platform/gooddata-integration-into-your-application/embed-gooddata-elements-into-your-applications/embed-a-kpi-dashboard#EmbedaKPIDashboard-Advancedconfigurationoptions) for more. + +--- + +### Integrate an Individual Insight + +TODO diff --git a/sales-embedding-demo/Dockerfile b/sales-embedding-demo/Dockerfile new file mode 100644 index 00000000..a42b1430 --- /dev/null +++ b/sales-embedding-demo/Dockerfile @@ -0,0 +1,10 @@ +FROM nginx:1.19-alpine + +# the port nginx will listen on +# you can either change it here, or by using --env PORT=1234 when running the container +ENV PORT=8080 + +COPY ./docker/nginx.conf.template /etc/nginx/templates/default.conf.template +COPY ./build/ /usr/share/nginx/html + +CMD ["nginx", "-g", "daemon off;"] diff --git a/sales-embedding-demo/HOWTO.md b/sales-embedding-demo/HOWTO.md new file mode 100644 index 00000000..6620e43f --- /dev/null +++ b/sales-embedding-demo/HOWTO.md @@ -0,0 +1,60 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting + +### Analyzing the Bundle Size + +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size + +### Making a Progressive Web App + +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app + +### Advanced Configuration + +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration diff --git a/sales-embedding-demo/INSTRUCTIONS.md b/sales-embedding-demo/INSTRUCTIONS.md new file mode 100644 index 00000000..832ce697 --- /dev/null +++ b/sales-embedding-demo/INSTRUCTIONS.md @@ -0,0 +1,130 @@ +# Instructions for Non-Developers + +The following steps will guide you through running this application on your computer for the very first time. The following steps do not aim to explain the basic concepts of frontend development. + +> Use at your own risk! + +(Jump back to [README](README.md), or skip forward to [CONFIGURATION](CONFIGURATION.md).) + +## Instructions for MacOS + +### Open Terminal + +1. Open Spotlight Search (hit `CMD + Spacebar`) +1. Search for `Terminal.app` +1. Hit `Enter` + + + +--- + +### Install brew + +Check if you have brew installed on your machine: + +* `brew -v` +* (specific version does not matter) + + + +If you have brew installed on your machine, proceed to [installing npm](#install-npm). Otherwise do: + +1. Visit https://brew.sh/ +1. Copy `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` into your clipboard +1. Paste into your Terminal window +1. Hit `Enter` + +Confirm that brew installed successfully by running `brew -v`, then proceed to [installing npm](#install-npm). + +--- + +### Install npm + +Check if you have npm installed on your machine: + +* `npm -v` +* (specific version does not matter) + + + +If you have npm installed on your machine, proceed to [installing yarn](#install-yarn). Otherwise do: + +* `brew install node` +* (npm will be installed with Node) + +Confirm that npm installed successfully by running `npm -v`, then proceed to [installing yarn](#install-yarn). + +--- + +### Install yarn + +Check if you have yarn installed on your machine: + +* `yarn -v` +* (specific version does not matter) + + + +If you have yarn installed on your machine, proceed to [checking if git is working](#check-that-git-is-working). Otherwise do: + +* `npm install -g yarn` + +Confirm that yarn installed successfully by running `yarn -v`, then proceed to [checking if git is working](#check-that-git-is-working). + +--- + +### Check that git is working + +* `git --version` +* (specific version does not matter) + + + +If git is working, proceed to [clonning the repository](#clone-the-repository-and-run-the-app). Otherwise do: + +* `xcode-select --install` + +Once Xcode command line tools are installed, confirm that git is working by running `git --version`, then proceed to [clonning the repository](#clone-the-repository-and-run-the-app). + +--- + +### Clone the repository and run the app + +First, navigate to the folder where you'd like to have this repository clonned. If the folder does not exist, you can create it first, for example: + +1. `mkdir ~/Documents/my-projects` (`mkdir` == "make directory", this command creates new directory) +1. `cd ~/Documents/my-projects/` (`cd` == "change directory", this command navigates to specified directory) + + + +Now, clone the repository, enter the repository folder, enter the project folder, install the dependencies and run the app: + +1. `git clone --depth 1 https://github.com/gooddata/ui-sdk-examples.git` +1. `cd ui-sdk-examples/sales-embedding-demo/` +1. `yarn install` +1. `yarn start` + + + + + + +Once the app is started, your browser will most likely warn you that "Your connection is not private." Click on `Advanced` and then `Proceed to localhost (unsafe)`. In case you don't see the the `Advanced` button or the `Proceed to localhost (unsafe)` link, simply click anywhere inside the page and start typing `thisisunsafe`; see https://stackoverflow.com/questions/58802767/no-proceed-anyway-option-on-neterr-cert-invalid-in-chrome-on-macos/58957322 for more. + + + + + + +The app should now be running at https://localhost:3000 and you may proceed to [CONFIGURATION](CONFIGURATION.md). Once you're done working with the, you may want to kill it by hitting `CTRL + C` in Terminal. + + + +## Instructions for Windows + +TODO + +## Screenshots + +![login](public/login.png) +![homepage](public/homepage.png) diff --git a/sales-embedding-demo/README.md b/sales-embedding-demo/README.md new file mode 100644 index 00000000..18e49bd4 --- /dev/null +++ b/sales-embedding-demo/README.md @@ -0,0 +1,34 @@ +# Sales Embedding Demo + +This demo serves as a playground for embedding GoodData. + +(Skip forward to [CONFIGURATION](CONFIGURATION.md).) + +## Instructions for Non-Developers + +If this is the **first time** you're running this application, see [INSTRUCTIONS](INSTRUCTIONS.md). + +## Getting Started + +1. Open Terminal.app +1. Run `cd ~/Documents/my-projects/ui-sdk-examples/sales-embedding-demo/` to enter the project path +1. Run `yarn install` to install depedencies. +1. Run `yarn start` to start the demo. +1. Open https://localhost:3000 in your browser + +## Deployment + +This project was never intended to be deployed. + +## Screenshots + +![login](public/login.png) +![homepage](public/homepage.png) + +## License +Copyright (C) 2007-2021, GoodData(R) Corporation. All rights reserved. + +For more information, please see +[LICENSE](https://github.com/gooddata/ui-sdk-examples/blob/master/LICENSE) + +> This project was bootstrapped with [GoodData.UI Accelerator Toolkit](https://sdk.gooddata.com/gooddata-ui/docs/ht_create_your_first_visualization_toolkit.html). diff --git a/sales-embedding-demo/docker/nginx.conf.template b/sales-embedding-demo/docker/nginx.conf.template new file mode 100644 index 00000000..27409364 --- /dev/null +++ b/sales-embedding-demo/docker/nginx.conf.template @@ -0,0 +1,60 @@ +gzip on; +gzip_disable "msie6"; +gzip_vary on; +gzip_proxied any; +gzip_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon; + +client_max_body_size 10M; + +log_format json escape=json '{' + '"timestamp": "$time_iso8601", ' + '"remoteAddress": "$remote_addr", ' + '"method": "$request_method", ' + '"url": "$request_uri", ' + '"httpVersion": "$server_protocol", ' + '"statusCode": "$status", ' + '"body_bytes_sent":"$body_bytes_sent",' + '"requestTime": "$request_time", ' + '"upstreamTime": "$upstream_response_time", ' + '"userAgent": "$http_user_agent", ' + '"referer": "$http_referer", ' + '"requestSize": "$request_length", ' + '"responseSize": "$bytes_sent"' +'}'; + +map $sent_http_content_type $expires { + default off; + text/html -1; + application/json -1; + text/css max; + application/javascript max; +} + +server { + listen ${PORT}; + + root /usr/share/nginx/html; + index index.html; + access_log /var/log/nginx/access.log json; + + expires $expires; + + location / { + try_files $uri $uri/ /index.html; + } + + location ^~ /gdc { + proxy_pass ${BACKEND_URL}; + proxy_set_header x-forwarded-host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host ${BACKEND_HOST}; + proxy_set_header Referer ${BACKEND_URL}; + proxy_set_header Origin ""; + proxy_cookie_domain ${BACKEND_HOST} $host; + } + + location ~* \.(?:css|js)$ { + add_header Cache-Control "public"; + } + +} diff --git a/sales-embedding-demo/jest.setup.js b/sales-embedding-demo/jest.setup.js new file mode 100644 index 00000000..05cc9c70 --- /dev/null +++ b/sales-embedding-demo/jest.setup.js @@ -0,0 +1,4 @@ +const enzyme = require("enzyme"); +const Adapter = require("enzyme-adapter-react-16"); + +enzyme.configure({ adapter: new Adapter() }); diff --git a/sales-embedding-demo/jsconfig.json b/sales-embedding-demo/jsconfig.json new file mode 100644 index 00000000..a01d49df --- /dev/null +++ b/sales-embedding-demo/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "checkJs": true, + "jsx": "react" + }, + "exclude": ["node_modules"] +} diff --git a/sales-embedding-demo/package.json b/sales-embedding-demo/package.json new file mode 100644 index 00000000..76ccff82 --- /dev/null +++ b/sales-embedding-demo/package.json @@ -0,0 +1,76 @@ +{ + "name": "sales-embedding-demo", + "version": "0.1.0", + "private": true, + "dependencies": { + "@gooddata/sdk-backend-bear": "^8.2.0", + "@gooddata/sdk-model": "^8.2.0", + "@gooddata/sdk-ui": "^8.2.0", + "@gooddata/sdk-ui-charts": "^8.2.0", + "@gooddata/sdk-ui-ext": "^8.2.0", + "@gooddata/sdk-ui-filters": "^8.2.0", + "@gooddata/sdk-ui-geo": "^8.2.0", + "@gooddata/sdk-ui-pivot": "^8.2.0", + "classnames": "^2.2.6", + "formik": "^1.5.7", + "lodash": "^4.17.15", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "react-helmet": "^5.2.1", + "react-router-dom": "^5.0.1", + "react-router-use-location-state": "^2.3.1", + "react-scripts": "3.4.3", + "use-location-state": "^2.3.1", + "yup": "^0.27.0" + }, + "scripts": { + "start": "cross-env HTTPS=true react-scripts start", + "build": "react-scripts build", + "build-with-explicit-hostname": "cross-env REACT_APP_SET_HOSTNAME=true npm run build", + "eject": "react-scripts eject", + "test": "react-scripts test --setupFilesAfterEnv ./jest.setup.js", + "testcafe-backend": "rm -R ./build & yarn build & serve -l 5000 build", + "testcafe": "cross-env TEST_BACKEND=http://localhost:5000 ./scripts/run-testcafe.js", + "testcafe-visual": "cross-env TEST_MODE=visual ./scripts/run-testcafe.js", + "refresh-ldm": "node ./scripts/refresh-ldm.js" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/core": "^7.13.8", + "@babel/plugin-transform-typescript": "^7.9.6", + "@babel/preset-env": "^7.13.9", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.8", + "@gooddata/catalog-export": "^8.2.0", + "babel-plugin-add-module-exports": "^1.0.4", + "cross-env": "^5.2.0", + "enzyme": "^3.10.0", + "enzyme-adapter-react-16": "^1.14.0", + "husky": "^3.0.0", + "node-sass": "^4.13.1", + "prettier": "^1.18.2", + "pretty-quick": "^1.11.1", + "serve": "^11.2.0", + "testcafe": "^1.5.0", + "typescript": "^4.1.5" + }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged --pattern \"**/*.*(js|jsx|ts|tsx|scss)\"" + } + } +} diff --git a/sales-embedding-demo/public/configuration/01-vscode-01.png b/sales-embedding-demo/public/configuration/01-vscode-01.png new file mode 100644 index 00000000..7fde2d96 Binary files /dev/null and b/sales-embedding-demo/public/configuration/01-vscode-01.png differ diff --git a/sales-embedding-demo/public/configuration/01-vscode-02.png b/sales-embedding-demo/public/configuration/01-vscode-02.png new file mode 100644 index 00000000..c1d29a33 Binary files /dev/null and b/sales-embedding-demo/public/configuration/01-vscode-02.png differ diff --git a/sales-embedding-demo/public/configuration/01-vscode-03.png b/sales-embedding-demo/public/configuration/01-vscode-03.png new file mode 100644 index 00000000..62fc4114 Binary files /dev/null and b/sales-embedding-demo/public/configuration/01-vscode-03.png differ diff --git a/sales-embedding-demo/public/configuration/01-vscode-04.png b/sales-embedding-demo/public/configuration/01-vscode-04.png new file mode 100644 index 00000000..e2837ef5 Binary files /dev/null and b/sales-embedding-demo/public/configuration/01-vscode-04.png differ diff --git a/sales-embedding-demo/public/favicon.ico b/sales-embedding-demo/public/favicon.ico new file mode 100644 index 00000000..07bd39b1 Binary files /dev/null and b/sales-embedding-demo/public/favicon.ico differ diff --git a/sales-embedding-demo/public/homepage.png b/sales-embedding-demo/public/homepage.png new file mode 100644 index 00000000..8a9f9b43 Binary files /dev/null and b/sales-embedding-demo/public/homepage.png differ diff --git a/sales-embedding-demo/public/index.html b/sales-embedding-demo/public/index.html new file mode 100644 index 00000000..50dc50d0 --- /dev/null +++ b/sales-embedding-demo/public/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + GoodData App + + + +
+ + diff --git a/sales-embedding-demo/public/instructions/01-terminal-01.png b/sales-embedding-demo/public/instructions/01-terminal-01.png new file mode 100644 index 00000000..cd66dfd5 Binary files /dev/null and b/sales-embedding-demo/public/instructions/01-terminal-01.png differ diff --git a/sales-embedding-demo/public/instructions/02-brew-01.png b/sales-embedding-demo/public/instructions/02-brew-01.png new file mode 100644 index 00000000..a60a9c47 Binary files /dev/null and b/sales-embedding-demo/public/instructions/02-brew-01.png differ diff --git a/sales-embedding-demo/public/instructions/03-npm-01.png b/sales-embedding-demo/public/instructions/03-npm-01.png new file mode 100644 index 00000000..cf29d859 Binary files /dev/null and b/sales-embedding-demo/public/instructions/03-npm-01.png differ diff --git a/sales-embedding-demo/public/instructions/04-yarn-01.png b/sales-embedding-demo/public/instructions/04-yarn-01.png new file mode 100644 index 00000000..67ecd0de Binary files /dev/null and b/sales-embedding-demo/public/instructions/04-yarn-01.png differ diff --git a/sales-embedding-demo/public/instructions/05-git-01.png b/sales-embedding-demo/public/instructions/05-git-01.png new file mode 100644 index 00000000..df90d576 Binary files /dev/null and b/sales-embedding-demo/public/instructions/05-git-01.png differ diff --git a/sales-embedding-demo/public/instructions/06-mkdir-01.png b/sales-embedding-demo/public/instructions/06-mkdir-01.png new file mode 100644 index 00000000..e0b0a6e4 Binary files /dev/null and b/sales-embedding-demo/public/instructions/06-mkdir-01.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-01-clone.png b/sales-embedding-demo/public/instructions/07-start-01-clone.png new file mode 100644 index 00000000..2164dcf1 Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-01-clone.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-02-install.png b/sales-embedding-demo/public/instructions/07-start-02-install.png new file mode 100644 index 00000000..cccb4450 Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-02-install.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-03-start.png b/sales-embedding-demo/public/instructions/07-start-03-start.png new file mode 100644 index 00000000..47b0102e Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-03-start.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-04-running.png b/sales-embedding-demo/public/instructions/07-start-04-running.png new file mode 100644 index 00000000..8d2ad6fe Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-04-running.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-05-private.png b/sales-embedding-demo/public/instructions/07-start-05-private.png new file mode 100644 index 00000000..3a3c4e9d Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-05-private.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-06-advanced.png b/sales-embedding-demo/public/instructions/07-start-06-advanced.png new file mode 100644 index 00000000..8b0d5b60 Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-06-advanced.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-07-login.png b/sales-embedding-demo/public/instructions/07-start-07-login.png new file mode 100644 index 00000000..fb9e1006 Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-07-login.png differ diff --git a/sales-embedding-demo/public/instructions/07-start-08-login.png b/sales-embedding-demo/public/instructions/07-start-08-login.png new file mode 100644 index 00000000..51a8cb55 Binary files /dev/null and b/sales-embedding-demo/public/instructions/07-start-08-login.png differ diff --git a/sales-embedding-demo/public/instructions/08-kill-01.png b/sales-embedding-demo/public/instructions/08-kill-01.png new file mode 100644 index 00000000..0e22af74 Binary files /dev/null and b/sales-embedding-demo/public/instructions/08-kill-01.png differ diff --git a/sales-embedding-demo/public/login.png b/sales-embedding-demo/public/login.png new file mode 100644 index 00000000..f895e2aa Binary files /dev/null and b/sales-embedding-demo/public/login.png differ diff --git a/sales-embedding-demo/public/manifest.json b/sales-embedding-demo/public/manifest.json new file mode 100644 index 00000000..7959d354 --- /dev/null +++ b/sales-embedding-demo/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "GoodData App", + "name": "Create GoodData App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/sales-embedding-demo/scripts/refresh-ldm.js b/sales-embedding-demo/scripts/refresh-ldm.js new file mode 100644 index 00000000..842a2a8f --- /dev/null +++ b/sales-embedding-demo/scripts/refresh-ldm.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +require("@babel/register")({ + presets: ["@babel/preset-typescript", "@babel/preset-env"], + plugins: ["add-module-exports"], + extensions: [".ts", ".js"], +}); + +const { workspace, backend } = require("../src/constants"); +const path = "./src/ldm/full.js"; +process.argv.push("--project-id", workspace, "--hostname", backend, "--output", path); +require("../node_modules/@gooddata/catalog-export"); diff --git a/sales-embedding-demo/scripts/run-testcafe.js b/sales-embedding-demo/scripts/run-testcafe.js new file mode 100755 index 00000000..81a31c44 --- /dev/null +++ b/sales-embedding-demo/scripts/run-testcafe.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +const createTestCafe = require("testcafe"); + +const LOCAL_CONCURRENCY = 4; +const ASSERTION_TIMEOUT = 15000; +const SELECTOR_TIMEOUT = 30000; +const WINDOW_WIDTH = 1920; +const WINDOW_HEIGHT = 1080; +// For some reason testcafe has problems using globstar so we need to specify +// maximum directory depth, otherwise it just picks up tests in the deepest directory. +const TESTS_PATH = "tests/**/**/**/*.spec.js"; + +const CHROME_HEADLESS_LOCAL = `chrome:headless --window-size='${WINDOW_WIDTH},${WINDOW_HEIGHT}' --no-sandbox`; +const CHROME_LOCAL = `chrome --window-size='${WINDOW_WIDTH},${WINDOW_HEIGHT}' --no-sandbox --disable-background-timer-throttling`; + +let testcafe = null; + +createTestCafe("localhost") + .then(tc => { + testcafe = tc; + const runner = testcafe.createRunner(); + const mode = process.env.TEST_MODE; + + const runnerBase = runner.src([TESTS_PATH]); + + if (mode === "visual") { + console.log("Starting TestCafe in visual-local mode without concurrency."); + return runnerBase.browsers([CHROME_LOCAL]).run({ + assertionTimeout: ASSERTION_TIMEOUT, + selectorTimeout: SELECTOR_TIMEOUT, + debugOnFail: true, + }); + } + + console.log(`Starting TestCafe in local mode with concurrency ${LOCAL_CONCURRENCY}.`); + return runnerBase + .browsers([CHROME_HEADLESS_LOCAL]) + .concurrency(LOCAL_CONCURRENCY) + .run({ + assertionTimeout: ASSERTION_TIMEOUT, + selectorTimeout: SELECTOR_TIMEOUT, + }); + }) + .then(failedCount => { + testcafe.close(); + if (failedCount > 0) { + process.exit(1); + } + }) + .catch(e => { + console.error(e.message); + testcafe.close(); + process.exit(1); + }); diff --git a/sales-embedding-demo/src/App.js b/sales-embedding-demo/src/App.js new file mode 100644 index 00000000..1bb9bf27 --- /dev/null +++ b/sales-embedding-demo/src/App.js @@ -0,0 +1,20 @@ +import React from "react"; +import { BackendProvider } from "@gooddata/sdk-ui"; + +import AppRouter from "./routes/AppRouter"; +import { useAuth } from "./contexts/Auth"; +import { WorkspaceListProvider } from "./contexts/WorkspaceList"; + +function App() { + const { backend } = useAuth(); + + return ( + + + + + + ); +} + +export default App; diff --git a/sales-embedding-demo/src/components/Auth/LoginForm.js b/sales-embedding-demo/src/components/Auth/LoginForm.js new file mode 100644 index 00000000..ac4e342f --- /dev/null +++ b/sales-embedding-demo/src/components/Auth/LoginForm.js @@ -0,0 +1,138 @@ +import React from "react"; +import cx from "classnames"; +import { withRouter } from "react-router-dom"; +import { withFormik } from "formik"; +import { string, object } from "yup"; + +import { backend } from "../../constants"; +import CustomLoading from "../CustomLoading"; +import Input from "../controls/Input"; +import Label from "../controls/Label"; +import Button from "../controls/Button"; + +import sharedStyles from "../../shared.module.scss"; +import styles from "./LoginForm.module.scss"; + +const LoginFormComponent = props => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + loginError, + } = props; + + return ( + <> + {loginError &&
{loginError}
} +
+
+ GoodData +
+

+ Please sign in to the{" "} + + {backend.replace(/https?:\/\//, "")} + {" "} + domain +

+
+ + + + {errors.email && touched.email &&
{errors.email}
} +
+
+ + + + {errors.password && touched.password && ( +
{errors.password}
+ )} +
+
+ +
+
+ + ); +}; + +const formikConnector = withFormik({ + mapPropsToValues: ({ email = "", password = "" }) => ({ + email, + password, + }), + + validationSchema: object().shape({ + email: string() + .email("Invalid e-mail address") + .required("E-mail is required"), + password: string().required("Password is required"), + }), + + handleSubmit: ({ email, password }, { props: { login, history }, setFieldError, setSubmitting }) => { + return login(email, password).then( + () => history.push("/"), + error => { + setSubmitting(false); + if (error.response && error.response.status === 401) { + setFieldError("password", "E-mail or password is invalid"); + } else { + setFieldError("password", "Unknown error"); + } + }, + ); + }, +}); + +export default withRouter(formikConnector(LoginFormComponent)); diff --git a/sales-embedding-demo/src/components/Auth/LoginForm.module.scss b/sales-embedding-demo/src/components/Auth/LoginForm.module.scss new file mode 100644 index 00000000..04bb8777 --- /dev/null +++ b/sales-embedding-demo/src/components/Auth/LoginForm.module.scss @@ -0,0 +1,119 @@ +@import "../../theme.scss"; +@import "../../mixins.scss"; + +.Login { + padding: $spacing; + background-color: $color-box; + margin: $spacing auto; + display: flex; + max-width: 300px; + flex-direction: column; + justify-content: center; + align-items: stretch; + align-self: center; + text-align: center; + border-radius: $border-radius; + box-shadow: rgba(0, 0, 0, 0.25) 0px $spacing $spacing/2 0px; + color: $color-text; +} + +.LoginLogo { + text-align: center; +} + +.InputBlock { + text-align: left; + margin: $spacing/2 0; + width: 300px; +} + +.Label { + margin-bottom: $spacing/4; +} + +.Error { + position: relative; + box-sizing: border-box; + display: block; + max-width: 100%; + margin-top: $spacing/4; + padding: $spacing/4 $spacing/2; + min-height: 1px; + border-radius: 3px; + font-family: inherit; + font-size: $font-size-body; + text-align: left; + line-height: in; + color: $color-error; + background: transparentize($color-error, 0.85); + pointer-events: all; +} + +.Action { + margin-top: $spacing/2; +} + +.Input.Input { + max-width: 100%; + padding: $spacing/4 $spacing/2; + border: 1px solid $color-border; + color: $color-text; + background: $color-paper; + border-radius: $border-radius; + box-shadow: inset 0 1px 1px 0 rgba(31, 53, 74, 0.15); + transition: 0.25s border-color ease, 0.25s color ease, 0.25s box-shadow ease; + + &:focus, + &:active { + border-color: $color-primary; + outline: 0; + box-shadow: inset 0 1px 1px 0 rgba(31, 53, 74, 0.2); + } + &::placeholder { + @include reset-appearance; + color: $color-text-muted; + } +} + +.Error { + box-shadow: inset 0 0 2px 0 transparentize($color-error, 0.5); +} + +// This button is styled so that it matches the usual login forms of the GoodData platform +.SubmitButton { + background-color: #fcfcfd; + border-radius: 3px; + border: 1px solid #ccd8e2; + box-shadow: 0 1px 1px 0 rgba(20, 56, 93, 0.15); + color: #778491; + cursor: pointer; + display: block; + font-family: avenir, "Helvetica Neue", arial, sans-serif; + font-size: 16px; + font-weight: 700; + height: 38px; + line-height: 22px; + outline: none; + padding: 7px 19px; + text-align: center; + transition-duration: 0.25s; + transition-property: all; + transition-timing-function: ease-in-out; + width: 100%; + + &:hover { + border-color: rgba(31, 52, 73, 0.2); + box-shadow: 0 1px 1px 0 rgba(20, 56, 93, 0.15), inset 0 -1px 0 0 rgba(177, 193, 209, 0.6); + color: #464e56; + background: #f5f8fa; + } + + &:active, + &:focus { + border-color: #b1c1d1; + box-shadow: inset 0 1px 0 0 rgba(177, 193, 209, 0.65); + color: #464e56; + background: #ecf0f5; + background-image: linear-gradient(to top, #dee6ef, #ecf0f5); + } +} diff --git a/sales-embedding-demo/src/components/Auth/LogoutForm.js b/sales-embedding-demo/src/components/Auth/LogoutForm.js new file mode 100644 index 00000000..5ed5a3ee --- /dev/null +++ b/sales-embedding-demo/src/components/Auth/LogoutForm.js @@ -0,0 +1,18 @@ +import React, { useEffect } from "react"; +import { withRouter } from "react-router-dom"; + +import CustomLoading from "../CustomLoading"; + +const LogoutForm = ({ history, logout }) => { + useEffect( + () => { + logout().then(() => history.push("/login")); + }, + // only call the logout on initial mount -> the empty array is correct here + [], // eslint-disable-line react-hooks/exhaustive-deps + ); + + return ; +}; + +export default withRouter(LogoutForm); diff --git a/sales-embedding-demo/src/components/CustomLoading.js b/sales-embedding-demo/src/components/CustomLoading.js new file mode 100644 index 00000000..da4101d3 --- /dev/null +++ b/sales-embedding-demo/src/components/CustomLoading.js @@ -0,0 +1,72 @@ +import React from "react"; +import cx from "classnames"; + +import styles from "./CustomLoading.module.scss"; + +const baseAnimationDuration = 1.8; + +export const CustomLoading = ({ + label = null, + inline = false, + height = "100%", + width = undefined, + imageHeight = "38px", + imageWidth = undefined, + color = "#14b2e2", + speed = 1, + className = null, +}) => { + const barStyle = { + transformOrigin: "0 100%", + animation: `GDC-pump ${baseAnimationDuration / speed}s infinite`, + fill: color, + }; + + const barStyle1 = { + ...barStyle, + animationDelay: `${(baseAnimationDuration / speed) * (-2 / 3)}s`, + }; + + const barStyle2 = { + ...barStyle, + animationDelay: `${baseAnimationDuration / speed / -3}s`, + }; + + return ( +
+ + + + + + + {label ?

{label}

: null} +
+ ); +}; + +export default CustomLoading; diff --git a/sales-embedding-demo/src/components/CustomLoading.module.scss b/sales-embedding-demo/src/components/CustomLoading.module.scss new file mode 100644 index 00000000..e0c7124b --- /dev/null +++ b/sales-embedding-demo/src/components/CustomLoading.module.scss @@ -0,0 +1,19 @@ +@import "../theme.scss"; + +.CustomLoading { + display: flex; + text-align: center; + flex-direction: column; + align-content: center; + justify-content: center; +} + +.Inline { + display: inline-flex; +} + +.SVG { + max-height: 100%; + max-width: 100%; + flex: 0 1 auto; +} diff --git a/sales-embedding-demo/src/components/Footer.js b/sales-embedding-demo/src/components/Footer.js new file mode 100644 index 00000000..8ab268bf --- /dev/null +++ b/sales-embedding-demo/src/components/Footer.js @@ -0,0 +1,63 @@ +import React from "react"; + +import styles from "./Footer.module.scss"; + +// import githubUri from "../media/github-logo.png"; +// import stackOverflowUri from "../media/stack-overflow-logo.png"; +// import twitterUri from "../media/twitter-logo.png"; +// import npmUri from "../media/npm-logo.png"; + +// const SocialItem = ({ link, title, imageUri }) => ( +// +// +// {title} +// +// ); + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/sales-embedding-demo/src/components/Footer.module.scss b/sales-embedding-demo/src/components/Footer.module.scss new file mode 100644 index 00000000..a4aa33e1 --- /dev/null +++ b/sales-embedding-demo/src/components/Footer.module.scss @@ -0,0 +1,54 @@ +.Footer { + font-size: 15px; + line-height: 24px; + background: rgba(89, 98, 107, 0.11); + color: #6d7680; + text-align: center; + + &Link { + color: inherit; + text-decoration: none; + + &:hover, + &:visited, + &:active, + &:focus { + color: inherit; + text-decoration: underline; + } + } +} + +.FooterLinks { + padding: 20px; + + &Header { + font-size: 1.5rem; + font-weight: 500; + margin: 1.5rem 0; + } +} + +.Social { + display: inline-block; + border-top: 1px solid rgba(109, 118, 128, 0.5); + margin: 0 auto; + padding: 1.5rem 0 0; + + .FooterLink { + margin-left: 10px; + margin-right: 10px; + } + + img { + height: 1.875rem; + margin-right: 0.6rem; + vertical-align: middle; + } +} + +.Copyright { + padding: 1rem 20px; + color: rgba(255, 255, 255, 0.8); + background-color: #404040; +} diff --git a/sales-embedding-demo/src/components/Header/Aside.js b/sales-embedding-demo/src/components/Header/Aside.js new file mode 100644 index 00000000..d18385be --- /dev/null +++ b/sales-embedding-demo/src/components/Header/Aside.js @@ -0,0 +1,32 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import cx from "classnames"; + +import { useAuth } from "../../contexts/Auth"; +import { AuthStatus } from "../../contexts/Auth/state"; +import InlineLoading from "../InlineLoading"; + +import styles from "./Header.module.scss"; + +const Aside = () => { + const { authStatus } = useAuth(); + const { AUTHORIZED, LOGGING_IN, LOGGING_OUT, AUTHORIZING } = AuthStatus; + + return ( +
+ {[LOGGING_IN, LOGGING_OUT, AUTHORIZING].includes(authStatus) ? ( + + ) : authStatus === AUTHORIZED ? ( + + Logout + + ) : ( + + Login + + )} +
+ ); +}; + +export default Aside; diff --git a/sales-embedding-demo/src/components/Header/Header.js b/sales-embedding-demo/src/components/Header/Header.js new file mode 100644 index 00000000..1be38c03 --- /dev/null +++ b/sales-embedding-demo/src/components/Header/Header.js @@ -0,0 +1,98 @@ +import React, { useState, useEffect, useCallback } from "react"; +import throttle from "lodash/throttle"; + +import { useAuth } from "../../contexts/Auth"; +import { AuthStatus } from "../../contexts/Auth/state"; +// uncomment the next line to use WorkspacePicker +// import WorkspacePicker from "../controls/WorkspacePicker"; + +import Logo from "./Logo"; +import Links from "./Links"; +import Aside from "./Aside"; + +import styles from "./Header.module.scss"; + +const VerticalDelimiter = ({ ...restProps }) =>
; + +// a wrapper for items visible to authenticated users only (e.g. WorkspacePicker) +const ProtectedItems = ({ children }) => { + const { authStatus } = useAuth(); + if (authStatus !== AuthStatus.AUTHORIZED) return null; + return <>{children || null}; +}; + +const BurgerMenu = () => { + const [isOpen, setIsOpen] = useState(false); + return ( +
+ + {isOpen && ( +
+ + + {/* Uncomment these lines to add a workspace picker into the burger menu */} + {/*
+ +
*/} +
+
+ )} +
+ ); +}; + +const Header = () => { + const [windowWidth, setWindowWidth] = useState(window ? window.innerWidth : null); + + const handleResize = useCallback( + throttle(() => { + if (window) { + setWindowWidth(window.innerWidth); + } + }, 200), + [], + ); + + useEffect(() => { + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [handleResize]); + + // You need to set this manually to the smallest window width that can still fit all menu items + const isTooSmall = windowWidth && windowWidth < 666; + return ( + + ); +}; + +export default Header; diff --git a/sales-embedding-demo/src/components/Header/Header.module.scss b/sales-embedding-demo/src/components/Header/Header.module.scss new file mode 100644 index 00000000..2729d756 --- /dev/null +++ b/sales-embedding-demo/src/components/Header/Header.module.scss @@ -0,0 +1,121 @@ +@import "../../theme.scss"; +@import "../../mixins.scss"; + +.Header { + position: fixed; + top: 0px; + right: 0px; + left: 0px; + min-height: 4em; + display: flex; + flex-direction: row; + align-items: stretch; + z-index: 100; + padding: 0; + color: #4a4a4a; + background: #fdfdfd; + border-bottom: 1px solid $color-active; +} + +.Aside { + flex: 1 0 auto; + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: flex-end; + padding-right: 10px; +} + +.Logo { + font-weight: bold; + text-transform: uppercase; +} + +.LogoImage { + height: 35px; +} + +.VerticalDelimiter { + width: 2px; + background-color: $color-border; + align-self: stretch; + margin: $spacing 0; +} + +.Centered { + margin: 0 $spacing / 2; + padding: 0 $spacing; + display: flex; + flex: 0 0 auto; + flex-direction: column; + justify-content: center; +} + +.Link { + margin: 0 $spacing / 2; + padding: 0 $spacing; + display: flex; + flex: 0 0 auto; + flex-direction: column; + justify-content: center; + text-decoration: none; + color: rgba(0, 0, 0, 0.9); + font-size: 15px; + border-bottom: 5px solid transparent; + transition: $transition-fast; + + &:hover, + &:visited, + &:active, + &:focus { + color: inherit; + border-color: rgb(70, 78, 86); + } + + &Active, + &Active:hover, + &Active:visited { + border-color: $color-active; + } +} + +.BurgerMenu { + display: flex; + flex-direction: column; + justify-content: center; + position: relative; +} + +.BurgerToggle { + @include reset-appearance; + margin-left: $spacing; + font-size: 1.5em; + margin-top: -4px; + width: 2em; + height: 2em; + text-align: center; + cursor: pointer; + border-radius: $border-radius; + + &:hover { + background-color: $color-accent-lighter; + } +} + +.BurgerContent { + position: fixed; + left: 0; + top: 64px; + min-width: 230px; + max-width: 100vw; + background-color: $color-paper; + z-index: 1000; + padding-bottom: $spacing; + @include box-shadow; + + .Link, + .Centered { + padding-bottom: $spacing/4; + margin-top: $spacing; + } +} diff --git a/sales-embedding-demo/src/components/Header/Links.js b/sales-embedding-demo/src/components/Header/Links.js new file mode 100644 index 00000000..bf4fc334 --- /dev/null +++ b/sales-embedding-demo/src/components/Header/Links.js @@ -0,0 +1,33 @@ +import React from "react"; +import cx from "classnames"; +import { NavLink } from "react-router-dom"; + +import styles from "./Header.module.scss"; + +const Links = () => { + return ( + <> + + Welcome + + + Home + + + My Work + + + Reports + + + Settings + + + ); +}; + +export default Links; diff --git a/sales-embedding-demo/src/components/Header/Logo.js b/sales-embedding-demo/src/components/Header/Logo.js new file mode 100644 index 00000000..078ef537 --- /dev/null +++ b/sales-embedding-demo/src/components/Header/Logo.js @@ -0,0 +1,17 @@ +import React from "react"; +import cx from "classnames"; +import { NavLink } from "react-router-dom"; + +import { appName } from "../../constants"; + +import styles from "./Header.module.scss"; + +const Logo = () => { + return ( + + {appName} + + ); +}; + +export default Logo; diff --git a/sales-embedding-demo/src/components/Header/tests/Logo.spec.js b/sales-embedding-demo/src/components/Header/tests/Logo.spec.js new file mode 100644 index 00000000..141a6eb1 --- /dev/null +++ b/sales-embedding-demo/src/components/Header/tests/Logo.spec.js @@ -0,0 +1,12 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import Logo from "../Logo"; +import { appName } from "../../../constants"; + +describe("Logo component", () => { + it("should render correctly app name", () => { + const wrapper = shallow(); + expect(wrapper.children().text()).toEqual(appName); + }); +}); diff --git a/sales-embedding-demo/src/components/InlineLoading.js b/sales-embedding-demo/src/components/InlineLoading.js new file mode 100644 index 00000000..5f2609a5 --- /dev/null +++ b/sales-embedding-demo/src/components/InlineLoading.js @@ -0,0 +1,7 @@ +import React from "react"; + +import CustomLoading from "./CustomLoading"; + +const InlineLoading = () => ; + +export default InlineLoading; diff --git a/sales-embedding-demo/src/components/Page.js b/sales-embedding-demo/src/components/Page.js new file mode 100644 index 00000000..8018006b --- /dev/null +++ b/sales-embedding-demo/src/components/Page.js @@ -0,0 +1,23 @@ +import React from "react"; +import Helmet from "react-helmet"; +import cx from "classnames"; + +import Header from "./Header/Header"; +import Footer from "./Footer"; + +import styles from "./Page.module.scss"; + +const Page = ({ children, className = null, mainClassName = null, title = "GoodData App" }) => { + return ( +
+ + {title} + +
+
{children}
+
+
+ ); +}; + +export default Page; diff --git a/sales-embedding-demo/src/components/Page.module.scss b/sales-embedding-demo/src/components/Page.module.scss new file mode 100644 index 00000000..2d76fd62 --- /dev/null +++ b/sales-embedding-demo/src/components/Page.module.scss @@ -0,0 +1,28 @@ +@import "../theme.scss"; + +.Page { + display: flex; + flex-direction: column; + align-items: stretch; + min-height: 100vh; +} + +.Main { + flex: 1 0 auto; + padding: $spacing * 2 + 56px $spacing $spacing * 2 $spacing; + max-width: 1200px; + width: calc(100% - (#{$spacing} * 2)); + margin: 0 auto; +} + +.VerticalCenter { + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; +} + +.Inverse { + background-color: $color-paper-inverse; + color: $color-text-inverse; +} diff --git a/sales-embedding-demo/src/components/controls/Button.js b/sales-embedding-demo/src/components/controls/Button.js new file mode 100644 index 00000000..51801463 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/Button.js @@ -0,0 +1,14 @@ +import React from "react"; +import cx from "classnames"; + +import styles from "./Button.module.scss"; + +const Button = ({ className = null, type = "button", active = false, children, ...restProps }) => { + return ( + + ); +}; + +export default Button; diff --git a/sales-embedding-demo/src/components/controls/Button.module.scss b/sales-embedding-demo/src/components/controls/Button.module.scss new file mode 100644 index 00000000..6e8e6ac1 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/Button.module.scss @@ -0,0 +1,26 @@ +@import "../../theme.scss"; +@import "../../mixins.scss"; + +.Button { + @include reset-appearance; + min-width: calc(1em + #{$spacing/2}); + padding: 0 ($spacing/4); + text-align: center; + border-radius: $border-radius; + cursor: pointer; + color: $color-accent; + transition: 0.3 background-color ease, 0.3 color ease; + background: $color-paper; + + &.Active { + background-color: $color-accent; + color: $color-paper; + } + + &:hover, + :focus, + :active { + background-color: $color-accent-lighter; + color: $color-accent; + } +} diff --git a/sales-embedding-demo/src/components/controls/Input.js b/sales-embedding-demo/src/components/controls/Input.js new file mode 100644 index 00000000..44e1b41a --- /dev/null +++ b/sales-embedding-demo/src/components/controls/Input.js @@ -0,0 +1,12 @@ +import React from "react"; +import cx from "classnames"; + +import styles from "./Input.module.scss"; + +const Input = ({ className = null, type = "text", hasError = false, ...restProps }) => { + return ( + + ); +}; + +export default Input; diff --git a/sales-embedding-demo/src/components/controls/Input.module.scss b/sales-embedding-demo/src/components/controls/Input.module.scss new file mode 100644 index 00000000..3d7b5c17 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/Input.module.scss @@ -0,0 +1,23 @@ +@import "../../theme.scss"; +@import "../../mixins.scss"; + +.Input { + @include reset-appearance; + width: 100%; + display: block; + font-size: inherit; + border-bottom: 2px solid $color-grey; + + &::placeholder { + color: $color-grey; + } + + &:hover, + &:focus { + border-color: $color-primary; + } +} + +.Error { + border-color: transparentize($color-error, 0.5); +} diff --git a/sales-embedding-demo/src/components/controls/Label.js b/sales-embedding-demo/src/components/controls/Label.js new file mode 100644 index 00000000..ab7af0f4 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/Label.js @@ -0,0 +1,14 @@ +import React from "react"; +import cx from "classnames"; + +import styles from "./Label.module.scss"; + +const Label = ({ className = null, type = "text", children, hasError = false, ...restProps }) => { + return ( + + ); +}; + +export default Label; diff --git a/sales-embedding-demo/src/components/controls/Label.module.scss b/sales-embedding-demo/src/components/controls/Label.module.scss new file mode 100644 index 00000000..ca8a6e56 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/Label.module.scss @@ -0,0 +1,13 @@ +@import "../../theme.scss"; + +.Label { + display: block; + color: $color-text-muted; + font-size: inherit; + text-transform: uppercase; + font-weight: 500; +} + +.Error { + color: desaturate($color-error, 25%); +} diff --git a/sales-embedding-demo/src/components/controls/WorkspacePicker.js b/sales-embedding-demo/src/components/controls/WorkspacePicker.js new file mode 100644 index 00000000..8dfab936 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/WorkspacePicker.js @@ -0,0 +1,62 @@ +import React from "react"; +import last from "lodash/last"; + +import InlineLoading from "../InlineLoading"; +import { useWorkspace } from "../../contexts/Workspace"; +import { useWorkspaceList } from "../../contexts/WorkspaceList"; + +import styles from "./WorkspacePicker.module.scss"; + +const getWorkspaceId = workspace => workspace && last(workspace.id.split("/")); + +const isInList = (workspaceId, workspaceList) => { + return ( + workspaceId && + workspaceList && + workspaceList.some(workspace => getWorkspaceId(workspace) === workspace.id) + ); +}; + +const workspaceOptions = workspaces => + workspaces.map(workspace => { + const constWorkspace = getWorkspaceId(workspace); + return ( + + ); + }); + +const WorkspacePicker = () => { + const { workspace, setWorkspace } = useWorkspace(); + const workspaceList = useWorkspaceList(); + + if (workspaceList.isLoading) return ; + + if (workspaceList.error) return
{"Error loading workspaces"}
; + + if (!workspaceList.data || !workspaceList.data.length) return
{"No workspaces available."}
; + + if (workspaceList.data.length === 1) + return
{workspaceList.data[0].title}
; + + return ( +
+ +
+ ); +}; + +export default WorkspacePicker; diff --git a/sales-embedding-demo/src/components/controls/WorkspacePicker.module.scss b/sales-embedding-demo/src/components/controls/WorkspacePicker.module.scss new file mode 100644 index 00000000..c1e78a92 --- /dev/null +++ b/sales-embedding-demo/src/components/controls/WorkspacePicker.module.scss @@ -0,0 +1,36 @@ +@import "../../theme.scss"; + +$color-workspacepicker: rgba(75, 75, 75, 0.9); +$textsize: 15px; + +.WorkspacePickerContainer { + height: 100%; +} + +.WorkspacePicker { + width: 100%; + height: 100%; + font-size: $textsize; + cursor: pointer; + border-radius: 0; + background-color: #fdfdfd; + border: none; + color: $color-workspacepicker; + + &:hover, + &:visited, + &:active, + &:focus { + outline: none; + } + + /* For IE <= 11 */ + ::-ms-expand { + display: none; + } +} + +.OneWorkspace { + font-size: $textsize; + color: $color-workspacepicker; +} diff --git a/sales-embedding-demo/src/components/controls/tests/Button.spec.js b/sales-embedding-demo/src/components/controls/tests/Button.spec.js new file mode 100644 index 00000000..10badf3a --- /dev/null +++ b/sales-embedding-demo/src/components/controls/tests/Button.spec.js @@ -0,0 +1,13 @@ +import React from "react"; +import { shallow } from "enzyme"; + +import Button from "../Button"; + +describe("Button component", () => { + it("should render children", () => { + const childrenClassName = "children"; + const children =
; + const wrapper = shallow(