diff --git a/.env b/.env new file mode 100644 index 0000000..c380565 --- /dev/null +++ b/.env @@ -0,0 +1,15 @@ +REACT_APP_DOMAIN=192.168.0.68 +REACT_APP_PATH=UniOilLoyaltyApp-BackEnd/public/index.php/api/cms +REACT_APP_DEV=http://$REACT_APP_DOMAIN/$REACT_APP_PATH +REACT_APP_QA=http://$REACT_APP_DOMAIN:4000/$REACT_APP_PATH +REACT_APP_IMG_URL=http://$REACT_APP_DOMAIN/UniOilLoyaltyApp-BackEnd/ +REACT_APP_API=https://mobileapi.unioilapps.com/api/cms +REACT_APP_TOKEN=X_TOKEN +NODE_PATH=src/ +GENERATE_SOURCEMAP=false +REACT_APP_PUBLIC=false +REACT_APP_NOTIF_API=http://unioilcms.taxikel.com:3000/api/ +REACT_APP_STATION_API_TESTING=https://testing-api-station-locator.herokuapp.com/api/ +REACT_APP_STATION_API_TESTINGX=https://station-locator-api.herokuapp.com/api/ +REACT_APP_STATION_API=https://cors-anywhere.herokuapp.com/13.212.2.253/api/ +REACT_APP_API_SYNC=https://mobileapi.unioilapps.com/api/mobile/ diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..c1aa61b --- /dev/null +++ b/.flowconfig @@ -0,0 +1,12 @@ +[ignore] +./components +[include] + +[libs] +.*/node_modules/* +[lints] + +[options] + + +[strict] diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fc06c5e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +image: node:latest + +before_script: + - apt-get update -qy + - apt-get install -y ruby-dev + - gem install dpl + +stages: + - testing + - staging + - production + +testing: + type: deploy + stage: testing + image: ruby:latest + script: + - dpl --provider=heroku --app=$HEROKU_APP_TESTING --api-key=$HEROKU_API_KEY + only: + - testing + +staging: + type: deploy + stage: staging + image: ruby:latest + script: + - dpl --provider=heroku --app=$HEROKU_APP_STAGING --api-key=$HEROKU_API_KEY + only: + - staging + +# production: +# type: deploy +# stage: production +# image: ruby:latest +# script: +# - dpl --provider=heroku --app=$HEROKU_APP_PRODUCTION --api-key=$HEROKU_API_KEY +# only: +# - master + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8f35816 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Stage 1 - the build process +FROM node:8.11.3 as builder +RUN mkdir /usr/src/app +WORKDIR /usr/src/app +ENV PATH /usr/src/app/node_modules/.bin:$PATH +ARG REACT_APP_PATH +ARG REACT_APP_DOMAIN +ARG REACT_APP_IMG_PATH +ARG REACT_APP_PUBLIC +ARG PUBLIC_URL +ARG GENERATE_SOURCEMAP +ENV REACT_APP_API $REACT_APP_DOMAIN/$REACT_APP_PATH +ENV REACT_APP_IMG_URL $REACT_APP_DOMAIN/$REACT_APP_IMG_PATH +RUN echo "DEBUG": $PUBLIC_URL + +COPY package.json yarn.lock /usr/src/app/ +RUN yarn install +COPY . /usr/src/app +RUN yarn build + +# Stage 2 - the production environment +FROM nginx:1.13.9-alpine + +RUN rm -rf /etc/nginx/conf.d +COPY conf /etc/nginx + +RUN rm -rf /usr/share/nginx/html/* +COPY --from=builder /usr/src/app/build /usr/share/nginx/html + +CMD ["nginx", "-g", "daemon off;"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c34862 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +dev-server: + docker-compose up -d --build dev-build +.PHONY: dev-server + +dev-server-stop: + docker-compose stop -t 1 dev-build +.PHONY: dev-server-stop + +test-server: + docker-compose up -d --build test-build +.PHONY: test-server + +test-server-stop: + docker-compose stop -t 1 test-build +.PHONY: test-server-stop + +prod-server: + docker-compose -f docker-compose-prod.yml up -d --build +.PHONY: prod-server + +prod-server-stop: + docker-compose -f docker-compose-prod.yml down +.PHONY: prod-server-stop + +dev-public-callback-server: + docker-compose up -d --build public-dev-build +.PHONY: dev-public-callback-server + +dev-public-callback-server-stop: + docker-compose stop -t 1 public-dev-build +.PHONY: dev-public-callback-server-stop + +test-public-callback-server: + docker-compose up -d --build public-test-build +.PHONY: test-public-callback-server + +test-public-callback-server-stop: + docker-compose stop -t 1 public-test-build +.PHONY: test-public-callback-server-stop + +prod-public-callback-server: + docker-compose -f docker-compose-public.yml up -d --build +.PHONY: prod-public-callback-server + +prod-public-callback-server-stop: + docker-compose -f docker-compose-public.yml down +.PHONY: prod-public-callback-server-stop + +stop: + docker-compose down +.PHONY: stop + +clean: + docker system prune --volumes -f \ No newline at end of file diff --git a/README.md b/README.md index 16eff96..29f94d3 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,74 @@ -**Edit a file, create a new file, and clone from Bitbucket in under 2 minutes** + +## Server Dependences -When you're done, you can delete the content in this README and update the file with details for others getting started with your repository. +* [Docker/Linux](https://docs.docker.com/install/linux/docker-ce/ubuntu/) +* [Docker Compose/Linux](https://docs.docker.com/compose/install/#install-compose) -*We recommend that you open this README in another tab as you perform the tasks below. You can [watch our video](https://youtu.be/0ocf7u76WSo) for a full demo of all the steps in this tutorial. Open the video in a new tab to avoid leaving Bitbucket.* +### `sudo apt-get update` +### `sudo apt-get install build-essential` ---- +## CMS - Local Development Scripts -## Edit a file +In the project directory, you can run: -You’ll start by editing this README file to learn how to edit a file in Bitbucket. +### `npm start` -1. Click **Source** on the left side. -2. Click the README.md link from the list of files. -3. Click the **Edit** button. -4. Delete the following text: *Delete this line to make a change to the README from Bitbucket.* -5. After making your change, click **Commit** and then **Commit** again in the dialog. The commit page will open and you’ll see the change you just made. -6. Go back to the **Source** page. +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. -## Create a file +## Development Server Scripts -Next, you’ll add a new file to this repository. +Up the server for staging development run: +### `make dev-server` -1. Click the **New file** button at the top of the **Source** page. -2. Give the file a filename of **contributors.txt**. -3. Enter your name in the empty file space. -4. Click **Commit** and then **Commit** again in the dialog. -5. Go back to the **Source** page. +Stop the server run: +### `make dev-server-stop` -Before you move on, go ahead and explore the repository. You've already seen the **Source** page, but check out the **Commits**, **Branches**, and **Settings** pages. +## QA/Testing Server Scripts ---- +Up the server for staging QA/Testing run: +### `make test-server` -## Clone a repository +Stop the server run: +### `make test-server-stop` -Use these steps to clone from SourceTree, our client for using the repository command-line free. Cloning allows you to work on your files locally. If you don't yet have SourceTree, [download and install first](https://www.sourcetreeapp.com/). If you prefer to clone from the command line, see [Clone a repository](https://confluence.atlassian.com/x/4whODQ). +## Production Server Scripts -1. You’ll see the clone button under the **Source** heading. Click that button. -2. Now click **Check out in SourceTree**. You may need to create a SourceTree account or log in. -3. When you see the **Clone New** dialog in SourceTree, update the destination path and name if you’d like to and then click **Clone**. -4. Open the directory you just created to see your repository’s files. +Up the server for production run: +### `make prod-server` + +Stop the server run: +### `make prod-server-stop` + +Clean image container run: +### `make clean` + + + +## Public Development Server Scripts + +Up the server for staging development run: +### `make dev-public-callback-server` + +Stop the server run: +### `make dev-public-callback-server-stop` + +## Public QA/Testing Server Scripts + +Up the server for staging QA/Testing run: +### `make test-public-callback-server` + +Stop the server run: +### `make test-public-callback-server-stop` + +## Public Production Server Scripts + +Up the server for staging QA/Testing run: +### `make prod-public-callback-server` + +Stop the server run: +### `make prod-public-callback-server-stop` -Now that you're more familiar with your Bitbucket repository, go ahead and add a new file locally. You can [push your change back to Bitbucket with SourceTree](https://confluence.atlassian.com/x/iqyBMg), or you can [add, commit,](https://confluence.atlassian.com/x/8QhODQ) and [push from the command line](https://confluence.atlassian.com/x/NQ0zDQ). \ No newline at end of file diff --git a/conf/conf.d/default.conf b/conf/conf.d/default.conf new file mode 100644 index 0000000..624816c --- /dev/null +++ b/conf/conf.d/default.conf @@ -0,0 +1,133 @@ +# read more here http://tautt.com/best-nginx-configuration-for-security/ + +# add_header Server "mystartup/1.0" always; + # config to don't allow the browser to render the page inside an frame or iframe + # and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking + # if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri + # https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options + add_header X-Frame-Options SAMEORIGIN; + + # when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header, + # to disable content-type sniffing on some browsers. + # https://www.owasp.org/index.php/List_of_useful_HTTP_headers + # currently suppoorted in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx + # http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx + # 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020 + add_header X-Content-Type-Options nosniff; + + # This header enables the Cross-site scripting (XSS) filter built into most recent web browsers. + # It's usually enabled by default anyway, so the role of this header is to re-enable the filter for + # this particular website if it was disabled by the user. + # https://www.owasp.org/index.php/List_of_useful_HTTP_headers + add_header X-XSS-Protection "1; mode=block"; + + # with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy), + # you can tell the browser that it can only download content from the domains you explicitly allow + # http://www.html5rocks.com/en/tutorials/security/content-security-policy/ + # https://www.owasp.org/index.php/Content_Security_Policy + # I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval' + # directives for css and js(if you have inline css or js, you will need to keep it too). + # more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful + # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'"; + # add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + + + + +server { + listen 80; + + sendfile on; + + default_type application/octet-stream; + # more_clear_headers Server; + # more_set_headers 'Server: Powered by Yondu'; + # don't send the nginx version number in error pages and Server header + server_tokens off; + + + + gzip on; + gzip_http_version 1.1; + gzip_disable "MSIE [1-6]\."; + gzip_min_length 256; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_types + application/atom+xml + application/javascript + application/json + application/ld+json + application/manifest+json + application/rss+xml + application/vnd.geo+json + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/bmp + image/svg+xml + image/x-icon + text/cache-manifest + text/css + text/plain + text/vcard + text/vnd.rim.location.xloc + text/vtt + text/x-component + text/x-cross-domain-policy; + # text/html is always compressed by gzip module + gzip_comp_level 9; + server_name *.example.org; + root /usr/share/nginx/html; + + # . files + location ~ /\. { + deny all; + } + # cache.appcache, your document html and data + location ~* \.(?:manifest|appcache|html?|xml|json)$ { + expires -1; + # access_log logs/static.log; # I don't usually include a static log + + + } + + + # Media: images, icons, video, audio, HTC + location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ { + expires 1M; + access_log off; + + } + + # location ~* (service-worker\.js)$ { + # add_header Cache-Control no-store, no-cache; + # expires -1; + # proxy_no_cache 1; + + # } + + # CSS and Javascript + location ~* /static/.*\.(?:css|js)$ { + expires 1y; + access_log off; + + } + + + # location /service-worker.js { + # expires -1; + # add_header Cache-Control no-store, no-cache; + # access_log off; + # } + + + location / { + try_files $uri $uri/ /index.html; + } + +} \ No newline at end of file diff --git a/config-overrides.js b/config-overrides.js new file mode 100644 index 0000000..fff9c17 --- /dev/null +++ b/config-overrides.js @@ -0,0 +1,491 @@ +const { injectBabelPlugin } = require("react-app-rewired"); +const rewireLess = require("react-app-rewire-less"); +// const VariablesOutput = require('less-plugin-variables-output'); + +// const fs = require('fs'); +// const path = require('path'); +// const less = require('less'); + +module.exports = function override(config, env) { + + config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config); // change importing css to less + config = rewireLess.withLoaderOptions({ + javascriptEnabled: true, + modifyVars: { + // "@primary-color": "#1DA57A" , + + + '@primary-color' : '#1890ff', + '@info-color' : '#1890ff', + '@success-color ' : '#52c41a', + '@processing-color' : '#1890ff', + '@error-color' : '#f5222d', + '@highlight-color' : '#f5222d', + '@warning-color' : '#faad14', + '@normal-color' : '#d9d9d9', + +// Color used by default to control hover and active backgrounds and for +// alert info backgrounds. +// @primary-1: color(~`colorPalette("@{primary-color}", 1)`); // replace tint(@primary-color, 90%) +// @primary-2: color(~`colorPalette("@{primary-color}", 2)`); // replace tint(@primary-color, 80%) +// @primary-3: color(~`colorPalette("@{primary-color}", 3)`); // unused +// @primary-4: color(~`colorPalette("@{primary-color}", 4)`); // unused +// @primary-5: color(~`colorPalette("@{primary-color}", 5)`); // color used to control the text color in many active and hover states, replace tint(@primary-color, 20%) +// @primary-6: @primary-color; // color used to control the text color of active buttons, don't use, use @primary-color +// @primary-7: color(~`colorPalette("@{primary-color}", 7)`); // replace shade(@primary-color, 5%) +// @primary-8: color(~`colorPalette("@{primary-color}", 8)`); // unused +// @primary-9: color(~`colorPalette("@{primary-color}", 9)`); // unused +// @primary-10: color(~`colorPalette("@{primary-color}", 10)`); // unused + +// Base Scaffolding Variables +// --- + +// Background color for `` +// @body-background : #fff; +// Base background color for most components +// @component-background : #fff; +// @font-family-no-number : "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; +// @font-family : "Monospaced Number", @font-family-no-number; +// @code-family : Consolas, Menlo, Courier, monospace; +// @heading-color : fade(#000, 85%); +// @text-color : fade(#000, 65%); +// @text-color-secondary : fade(#000, 45%); +// @heading-color-dark : fade(#fff, 100%); +// @text-color-dark : fade(#fff, 85%); +// @text-color-secondary-dark: fade(#fff, 65%); +'@font-size-base' : '14px' +// @font-size-lg : @font-size-base + 2px; +// @font-size-sm : 12px; +// @line-height-base : 1.5; +// @border-radius-base : 4px; +// @border-radius-sm : 2px; + +// vertical paddings +// @padding-lg : 24px; // containers +// @padding-md : 16px; // small containers and buttons +// @padding-sm : 12px; // Form controls and items +// @padding-xs : 8px; // small items + +// vertical padding for all form controls +// @control-padding-horizontal: @padding-sm; +// @control-padding-horizontal-sm: @padding-xs; + +// The background colors for active and hover states for things like +// list items or table cells. +// @item-active-bg : @primary-1; +// @item-hover-bg : @primary-1; + +// ICONFONT +// @iconfont-css-prefix : anticon; +// @icon-url : "https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i"; + +// LINK +// @link-color : @primary-color; +// @link-hover-color : color(~`colorPalette("@{link-color}", 5)`); +// @link-active-color : color(~`colorPalette("@{link-color}", 7)`); +// @link-decoration : none; +// @link-hover-decoration : none; + +// Animation +// @ease-out : cubic-bezier(0.215, 0.61, 0.355, 1); +// @ease-in : cubic-bezier(0.55, 0.055, 0.675, 0.19); +// @ease-in-out : cubic-bezier(0.645, 0.045, 0.355, 1); +// @ease-out-back : cubic-bezier(0.12, 0.4, 0.29, 1.46); +// @ease-in-back : cubic-bezier(0.71, -0.46, 0.88, 0.6); +// @ease-in-out-back : cubic-bezier(0.71, -0.46, 0.29, 1.46); +// @ease-out-circ : cubic-bezier(0.08, 0.82, 0.17, 1); +// @ease-in-circ : cubic-bezier(0.6, 0.04, 0.98, 0.34); +// @ease-in-out-circ : cubic-bezier(0.78, 0.14, 0.15, 0.86); +// @ease-out-quint : cubic-bezier(0.23, 1, 0.32, 1); +// @ease-in-quint : cubic-bezier(0.755, 0.05, 0.855, 0.06); +// @ease-in-out-quint : cubic-bezier(0.86, 0, 0.07, 1); + +// Border color +// @border-color-base : hsv(0, 0, 85%); // base border outline a component +// @border-color-split : hsv(0, 0, 91%); // split border inside a component +// @border-width-base : 1px; // width of the border for a component +// @border-style-base : solid; // style of a components border + +// Outline +// @outline-blur-size : 0; +// @outline-width : 2px; +// @outline-color : @primary-color; + +// @background-color-light : hsv(0, 0, 98%); // background of header and selected item +// @background-color-base : hsv(0, 0, 96%); // Default grey background color + +// Disabled states +// @disabled-color : fade(#000, 25%); +// @disabled-bg : @background-color-base; +// @disabled-color-dark : fade(#fff, 35%); + +// Shadow +// @shadow-color : rgba(0, 0, 0, .15); +// @box-shadow-base : @shadow-1-down; +// @shadow-1-up : 0 2px 8px @shadow-color; +// @shadow-1-down : 0 2px 8px @shadow-color; +// @shadow-1-left : -2px 0 8px @shadow-color; +// @shadow-1-right : 2px 0 8px @shadow-color; +// @shadow-2 : 0 4px 12px @shadow-color; + +// Buttons +// @btn-font-weight : 400; +// @btn-border-radius-base : @border-radius-base; +// @btn-border-radius-sm : @border-radius-base; + +// @btn-primary-color : #fff; +// @btn-primary-bg : @primary-color; + +// @btn-default-color : @text-color; +// @btn-default-bg : #fff; +// @btn-default-border : @border-color-base; + +// @btn-danger-color : @error-color; +// @btn-danger-bg : @background-color-base; +// @btn-danger-border : @border-color-base; + +// @btn-disable-color : @disabled-color; +// @btn-disable-bg : @disabled-bg; +// @btn-disable-border : @border-color-base; + +// @btn-padding-base : 0 @padding-md - 1px; +// @btn-font-size-lg : @font-size-lg; +// @btn-font-size-sm : @font-size-base; +// @btn-padding-lg : @btn-padding-base; +// @btn-padding-sm : 0 @padding-xs - 1px; + +// @btn-height-base : 32px; +// @btn-height-lg : 40px; +// @btn-height-sm : 24px; + +// @btn-circle-size : @btn-height-base; +// @btn-circle-size-lg : @btn-height-lg; +// @btn-circle-size-sm : @btn-height-sm; + +// @btn-group-border : @primary-5; + +// Checkbox +// @checkbox-size : 16px; +// @checkbox-color : @primary-color; + +// Radio +// @radio-size : 16px; +// @radio-dot-color : @primary-color; + +// Radio buttons +// @radio-button-bg : @btn-default-bg; +// @radio-button-color : @btn-default-color; +// @radio-button-hover-color : @primary-5; +// @radio-button-active-color : @primary-7; + +// Media queries breakpoints +// Extra small screen / phone +// @screen-xs : 480px; +// @screen-xs-min : @screen-xs; + +// Small screen / tablet +// @screen-sm : 576px; +// @screen-sm-min : @screen-sm; + +// Medium screen / desktop +// @screen-md : 768px; +// @screen-md-min : @screen-md; + +// Large screen / wide desktop +// @screen-lg : 992px; +// @screen-lg-min : @screen-lg; + +// Extra large screen / full hd +// @screen-xl : 1200px; +// @screen-xl-min : @screen-xl; + +// Extra extra large screen / large descktop +// @screen-xxl : 1600px; +// @screen-xxl-min : @screen-xxl; + +// provide a maximum +// @screen-xs-max : (@screen-sm-min - 1px); +// @screen-sm-max : (@screen-md-min - 1px); +// @screen-md-max : (@screen-lg-min - 1px); +// @screen-lg-max : (@screen-xl-min - 1px); +// @screen-xl-max : (@screen-xxl-min - 1px); + +// Grid system +// @grid-columns : 24; +// @grid-gutter-width : 0; + +// Layout +// @layout-body-background : #f0f2f5; +// @layout-header-background : #001529; +// @layout-footer-background : @layout-body-background; +// @layout-header-height : 64px; +// @layout-header-padding : 0 50px; +// @layout-footer-padding : 24px 50px; +// @layout-sider-background : @layout-header-background; +// @layout-trigger-height : 48px; +// @layout-trigger-background : #002140; +// @layout-trigger-color : #fff; +// @layout-zero-trigger-width : 36px; +// @layout-zero-trigger-height : 42px; +// Layout light theme +// @layout-sider-background-light : #fff; +// @layout-trigger-background-light: #fff; +// @layout-trigger-color-light : @text-color; + +// z-index list +// @zindex-affix : 10; +// @zindex-back-top : 10; +// @zindex-modal-mask : 1000; +// @zindex-modal : 1000; +// @zindex-notification : 1010; +// @zindex-message : 1010; +// @zindex-popover : 1030; +// @zindex-picker : 1050; +// @zindex-dropdown : 1050; +// @zindex-tooltip : 1060; + +// Animation +// @animation-duration-slow: .3s; // Modal +// @animation-duration-base: .2s; +// @animation-duration-fast: .1s; // Tooltip + +// Form +// --- +// @label-required-color : @highlight-color; +// @label-color : @heading-color; +// @form-item-margin-bottom : 24px; +// @form-item-trailing-colon : true; +// @form-vertical-label-padding : 0 0 8px; +// @form-vertical-label-margin : 0; + +// Input +// --- +// @input-height-base : 32px; +// @input-height-lg : 40px; +// @input-height-sm : 24px; +// @input-padding-horizontal : @control-padding-horizontal - 1px; +// @input-padding-horizontal-base: @input-padding-horizontal; +// @input-padding-horizontal-sm : @control-padding-horizontal-sm - 1px; +// @input-padding-horizontal-lg : @input-padding-horizontal; +// @input-padding-vertical-base : 4px; +// @input-padding-vertical-sm : 1px; +// @input-padding-vertical-lg : 6px; +// @input-placeholder-color : hsv(0, 0, 75%); +// @input-color : @text-color; +// @input-border-color : @border-color-base; +// @input-bg : #fff; +// @input-addon-bg : @background-color-light; +// @input-hover-border-color : @primary-color; +// @input-disabled-bg : @disabled-bg; + +// Tooltip +// --- +//* Tooltip max width +// @tooltip-max-width: 250px; +//** Tooltip text color +// @tooltip-color: #fff; +//** Tooltip background color +// @tooltip-bg: rgba(0, 0, 0, .75); +//** Tooltip arrow width +// @tooltip-arrow-width: 5px; +//** Tooltip distance with trigger +// @tooltip-distance: @tooltip-arrow-width - 1px + 4px; +//** Tooltip arrow color +// @tooltip-arrow-color: @tooltip-bg; + +// Popover +// --- +//** Popover body background color +// @popover-bg: #fff; +//** Popover text color +// @popover-color: @text-color; +//** Popover maximum width +// @popover-min-width: 177px; +//** Popover arrow width +// @popover-arrow-width: 6px; +//** Popover arrow color +// @popover-arrow-color: @popover-bg; +//** Popover outer arrow width +//** Popover outer arrow color +// @popover-arrow-outer-color: @popover-bg; +//** Popover distance with trigger +// @popover-distance: @popover-arrow-width + 4px; + +// Modal +// -- +// @modal-mask-bg: rgba(0, 0, 0, 0.65); + +// Progress +// -- +// @progress-default-color: @processing-color; +// @progress-remaining-color: @background-color-base; + +// Menu +// --- +// @menu-inline-toplevel-item-height: 40px; +// @menu-item-height: 40px; +// @menu-collapsed-width: 80px; +// @menu-bg: @component-background; +// @menu-item-color: @text-color; +// @menu-highlight-color: @primary-color; +// @menu-item-active-bg: @item-active-bg; +// @menu-item-group-title-color: @text-color-secondary; +// dark theme +// @menu-dark-color: @text-color-secondary-dark; +// @menu-dark-bg: @layout-header-background; +// @menu-dark-arrow-color: #fff; +// @menu-dark-submenu-bg: #000c17; +// @menu-dark-highlight-color: #fff; +// @menu-dark-item-active-bg: @primary-color; + +// Spin +// --- +// @spin-dot-size-sm: 14px; +// @spin-dot-size: 20px; +// @spin-dot-size-lg: 32px; + +// Table +// -- +// @table-header-bg: @background-color-light; +// @table-header-sort-bg: @background-color-base; +// @table-row-hover-bg: @primary-1; +// @table-selected-row-bg: #fafafa; +// @table-expanded-row-bg: #fbfbfb; +// @table-padding-vertical: 16px; +// @table-padding-horizontal: 16px; + +// Tag +// -- +// @tag-default-bg: @background-color-light; +// @tag-default-color: @text-color; +// @tag-font-size: @font-size-sm; + +// TimePicker +// --- +// @time-picker-panel-column-width: 56px; +// @time-picker-panel-width: @time-picker-panel-column-width * 3; +// @time-picker-selected-bg: @background-color-base; + +// Carousel +// --- +// @carousel-dot-width: 16px; +// @carousel-dot-height: 3px; +// @carousel-dot-active-width: 24px; + +// Badge +// --- +// @badge-height: 20px; +// @badge-dot-size: 6px; +// @badge-font-size: @font-size-sm; +// @badge-font-weight: normal; +// @badge-status-size: 6px; + +// Rate +// --- +// @rate-star-color: @yellow-6; +// @rate-star-bg: @border-color-split; + +// Card +// --- +// @card-head-color: @heading-color; +// @card-head-background: @component-background; +// @card-head-padding: 16px; +// @card-inner-head-padding: 12px; +// @card-padding-base: 24px; +// @card-padding-wider: 32px; +// @card-actions-background: @background-color-light; +// @card-shadow: 0 2px 8px rgba(0, 0, 0, .09); + +// Tabs +// --- +// '@tabs-card-head-background': '@background-color-light'; +// '@tabs-card-height': '40px'; +// '@tabs-card-active-color': '@primary-color'; +// '@tabs-title-font-size': '@font-size-base'; +// '@tabs-title-font-size-lg': '@font-size-lg'; +// '@tabs-title-font-size-sm': '@font-size-base'; +// '@tabs-ink-bar-color': '@primary-color'; +// '@tabs-bar-margin': '0 0 16px 0'; +// '@tabs-horizontal-margin': '0 32px 0 0'; +// '@tabs-horizontal-padding': '12px 16px'; +// '@tabs-vertical-padding': '8px 24px'; +// '@tabs-vertical-margin': '0 0 16px 0'; +// '@tabs-scrolling-size': '32px'; +// '@tabs-highlight-color': '@primary-color'; +// '@tabs-hover-color': '@primary-5'; +// '@tabs-active-color': '@primary-7'; + +// BackTop +// --- +// '@back-top-color': '#fff'; +// '@back-top-bg': '@text-color-secondary'; +// '@back-top-hover-bg': '@text-color'; + +// Avatar +// --- +// '@avatar-size-base': '32px'; +// '@avatar-size-lg': '40px'; +// '@avatar-size-sm': '24px'; +// '@avatar-font-size-base': '18px'; +// '@avatar-font-size-lg': '24px'; +// '@avatar-font-size-sm': '14px'; +// '@avatar-bg': '#ccc'; +// '@avatar-color': '#fff'; +// '@avatar-border-radius': '@border-radius-base;' + +// Switch +// --- +// '@switch-height': '22px'; +// '@switch-sm-height': '16px'; +// '@switch-sm-checked-margin-left': '-(@switch-sm-height - 3px)'; +// '@switch-disabled-opacity': '0.4'; +// '@switch-color': '@primary-color'; + +// Pagination +// --- +// '@pagination-item-size': '32px'; +// '@pagination-item-size-sm': '24px'; +// '@pagination-font-family': 'Arial'; +// '@pagination-font-weight-active': '500'; + +// Breadcrumb +// --- +// '@breadcrumb-base-color': '@text-color-secondary'; +// '@breadcrumb-last-item-color': '@text-color'; +// '@breadcrumb-font-size': '@font-size-base'; +// '@breadcrumb-icon-font-size': '@font-size-sm'; +// '@breadcrumb-link-color': '@text-color-secondary'; +// '@breadcrumb-link-color-hover': '@primary-5'; +// '@breadcrumb-separator-color': '@text-color-secondary'; +// '@breadcrumb-separator-margin': '0 @padding-xs'; + +// Slider +// --- +// '@slider-margin': '14px 6px 10px'; +// '@slider-rail-background-color': '@background-color-base'; +// '@slider-rail-background-color-hover': '#e1e1e1'; +// '@slider-track-background-color': '@primary-3'; +// '@slider-track-background-color-hover': '@primary-4'; +// '@slider-handle-color': '@primary-3'; +// '@slider-handle-color-hover': '@primary-4'; +// '@slider-handle-color-focus': 'tint(@primary-color, 20%)'; +// '@slider-handle-color-focus-shadow': 'tint(@primary-color, 50%)'; +// '@slider-handle-color-tooltip-open': '@primary-color'; +// '@slider-dot-border-color': '@border-color-split'; +// '@slider-dot-border-color-active': 'tint(@primary-color, 50%)'; +// '@slider-disabled-color': '@disabled-color'; +// '@slider-disabled-background-color': '@component-background'; + +// Collapse +// --- +// '@collapse-header-padding': '12px 0 12px 40px'; +// '@collapse-header-bg': '@background-color-light'; +// '@collapse-content-padding': '@padding-md'; +// '@collapse-content-bg': '@component-background'; + + + + }, + })(config, env); + return config; +}; \ No newline at end of file diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..434f58e --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,25 @@ +version: '3.5' +services: + prod-build: + container_name: prod-build + env_file: + - .env + environment: + - NODE_ENV=production + build: + context: . + dockerfile: Dockerfile + args: + # - REACT_APP_DOMAIN=http://mobilecorelb-1807532632.ap-southeast-1.elb.amazonaws.com/ + # - REACT_APP_DOMAIN=https://mobilelink.unioil.com + - REACT_APP_DOMAIN=https://mobileapi.unioilapps.com + - REACT_APP_PATH=api/cms + - REACT_APP_IMG_PATH=unioilQA/ + restart: always + ports: + - 8089:80 + volumes: + - '.:/usr/src/app' + - '/usr/src/app/node_modules' + + diff --git a/docker-compose-public.yml b/docker-compose-public.yml new file mode 100644 index 0000000..9ca6743 --- /dev/null +++ b/docker-compose-public.yml @@ -0,0 +1,24 @@ +version: '3.5' +services: + public-build: + container_name: public-build + env_file: + - .env + environment: + - NODE_ENV=production + build: + context: . + dockerfile: Dockerfile + args: + - REACT_APP_PUBLIC=true + # - PUBLIC_URL=/paypal + - REACT_APP_DOMAIN=https://mobileapid.unioil.com + - REACT_APP_PATH=api/cms + - REACT_APP_IMG_PATH=unioilQA + - GENERATE_SOURCEMAP=false + restart: always + ports: + - 8095:80 + volumes: + - '.:/usr/src/app' + - '/usr/src/app/node_modules' \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d61d0ca --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +version: '3.5' +services: + test-build: + container_name: test-build + env_file: + - .env + environment: + - NODE_ENV=production + build: + context: . + dockerfile: Dockerfile + args: + - REACT_APP_DOMAIN=http://192.168.0.68 + - REACT_APP_PATH=unioilQA/public/index.php/api/cms + - REACT_APP_IMG_PATH=unioilQA/ + restart: always + ports: + - 8089:80 + volumes: + - '.:/usr/src/app' + - '/usr/src/app/node_modules' + dev-build: + container_name: dev-build + env_file: + - .env + environment: + - NODE_ENV=production + build: + context: . + dockerfile: Dockerfile + args: + - REACT_APP_DOMAIN=http://192.168.0.68 + - REACT_APP_PATH=UniOilLoyaltyApp-BackEnd/public/index.php/api/cms + - REACT_APP_IMG_PATH=UniOilLoyaltyApp-BackEnd + restart: always + ports: + - 8081:80 + volumes: + - '.:/usr/src/app' + - '/usr/src/app/node_modules' + public-dev-build: + container_name: public-dev-build + env_file: + - .env + environment: + - NODE_ENV=production + build: + context: . + dockerfile: Dockerfile + args: + - REACT_APP_PUBLIC=true + - REACT_APP_DOMAIN=http://192.168.0.68 + - REACT_APP_PATH=UniOilLoyaltyApp-BackEnd/public/index.php/api/cms + - REACT_APP_IMG_PATH=UniOilLoyaltyApp-BackEnd/ + restart: always + ports: + - 8093:80 + volumes: + - '.:/usr/src/app' + - '/usr/src/app/node_modules' + public-test-build: + container_name: public-test-build + env_file: + - .env + environment: + - NODE_ENV=production + build: + context: . + dockerfile: Dockerfile + args: + - REACT_APP_PUBLIC=true + - REACT_APP_DOMAIN=http://192.168.0.68 + - REACT_APP_PATH=unioilQA/public/index.php/api/cms + - REACT_APP_IMG_PATH=unioilQA + restart: always + ports: + - 8090:80 + volumes: + - '.:/usr/src/app' + - '/usr/src/app/node_modules' \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..b91061d --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2015", + "baseUrl": "src", + }, + "exclude": [ + "node_modules", + "build", + "public", + "__test__" + ] +} \ No newline at end of file diff --git a/json-server/db.json b/json-server/db.json new file mode 100644 index 0000000..b17143f --- /dev/null +++ b/json-server/db.json @@ -0,0 +1,278 @@ +{ + "posts": [ + { + "id": 1441, + "title": "11-json-server", + "author": "11-server-deauthor" + }, + { + "title": "14-Title", + "author": "14-Title Data", + "id": 4414 + }, + { + "title": "tes wafe", + "author": "weataewtaew", + "id": 1446 + }, + { + "title": "1-json-server-test", + "author": "werewrwe", + "id": 1447 + }, + { + "id": 447, + "title": "7-json-server", + "author": "7-server-deauthor" + }, + { + "id": 449, + "title": "9-json-server", + "author": "9-tserver-deauthor" + }, + { + "id": 14440, + "title": "10-json-server", + "author": "10-server-deauthor" + }, + { + "id": 1441, + "title": "11-json-server", + "author": "11-server-deauthor" + }, + { + "id": 12443, + "title": "12-json-server", + "author": "12-server-deauthor" + }, + { + "title": "tes", + "author": "ewrwer", + "id": 1334444 + }, + { + "title": "14-Title", + "author": "14-Title Data", + "id": 143444 + }, + { + "title": "fytyiftyfy", + "author": "ftftcftc", + "id": 15344 + }, + { + "title": "tes wafe", + "author": "weataewtaew", + "id": 163444 + }, + { + "title": "1-json-server-test", + "author": "werewrwe", + "id": 17344 + }, + { + "id": 744, + "title": "7-json-server", + "author": "7-server-deauthor" + }, + { + "id": 944, + "title": "9-json-server", + "author": "9-tserver-deauthor" + }, + { + "id": 104, + "title": "10-json-server", + "author": "10-server-deauthor" + }, + { + "id": 141, + "title": "11-json-server", + "author": "11-server-deauthor" + }, + { + "id": 1244, + "title": "12-json-server", + "author": "12-server-deauthor" + }, + { + "title": "tes", + "author": "ewrwer", + "id": 1344 + }, + { + "title": "14-Title", + "author": "14-Title Data", + "id": 1444 + }, + { + "title": "fytyiftyfy", + "author": "ftftcftc", + "id": 1544 + }, + { + "title": "tes wafe", + "author": "weataewtaew", + "id": 164 + }, + { + "title": "1-json-server-test", + "author": "werewrwe", + "id": 1744 + }, + { + "id": 744, + "title": "7-json-server", + "author": "7-server-deauthor" + }, + { + "id": 944, + "title": "9-json-server", + "author": "9-tserver-deauthor" + }, + { + "id": 1044, + "title": "10-json-server", + "author": "10-server-deauthor" + }, + { + "id": 1144, + "title": "11-json-server", + "author": "11-server-deauthor" + }, + { + "id": 12344, + "title": "12-json-server", + "author": "12-server-deauthor" + }, + { + "title": "tes", + "author": "ewrwer", + "id": 13344 + }, + { + "title": "14-Title", + "author": "14-Title Data", + "id": 14344 + }, + { + "title": "fytyiftyfy", + "author": "ftftcftc", + "id": 15344 + }, + { + "title": "tes wafe", + "author": "weataewtaew", + "id": 16344 + }, + { + "title": "1-json-server-test", + "author": "werewrwe", + "id": 17344 + }, + { + "id": 744, + "title": "7-json-server", + "author": "7-server-deauthor" + }, + { + "id": 944, + "title": "9-json-server", + "author": "9-tserver-deauthor" + }, + { + "id": 1044, + "title": "10-json-server", + "author": "10-server-deauthor" + }, + { + "id": 114444, + "title": "11-json-server", + "author": "11-server-deauthor" + }, + { + "id": 1244, + "title": "12-json-server", + "author": "12-server-deauthor" + }, + { + "title": "tes", + "author": "ewrwer", + "id": 1344 + }, + { + "title": "14-Title", + "author": "14-Title Data", + "id": 1444 + }, + { + "title": "fytyiftyfy", + "author": "ftftcftc", + "id": 1544 + }, + { + "title": "tes wafe", + "author": "weataewtaew", + "id": 1644 + }, + { + "title": "1-json-server-test", + "author": "werewrwe", + "id": 174 + }, + { + "id": 744, + "title": "7-json-server", + "author": "7-server-deauthor" + }, + { + "id": 944, + "title": "9-json-server", + "author": "9-tserver-deauthor" + }, + { + "id": 104, + "title": "10-json-server", + "author": "10-server-deauthor" + }, + { + "id": 114, + "title": "11-json-server", + "author": "11-server-deauthor" + }, + { + "id": 1234, + "title": "12-json-server", + "author": "12-server-deauthor" + }, + { + "title": "tes", + "author": "ewrwer", + "id": 1334 + }, + { + "title": "14-Title", + "author": "14-Title Data", + "id": 1434 + }, + { + "title": "fytyiftyfy", + "author": "ftftcftc", + "id": 15344 + }, + { + "title": "tes wafe", + "author": "weataewtaew", + "id": 1634 + }, + { + "title": "1-json-server-test", + "author": "werewrwe", + "id": 1734 + } + ], + "comments": [], + "profile": { + "name": "typicode" + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c98c1a2 --- /dev/null +++ b/package.json @@ -0,0 +1,65 @@ +{ + "name": "my-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@ant-design/icons": "4.0.0", + "@react-google-maps/api": "^1.9.7", + "antd": "^3.9.1", + "antd-input-password": "^0.3.0", + "axios": "^0.18.0", + "crypto-js": "^3.1.9-1", + "dsbridge": "^3.1.3", + "filesize": "^3.6.1", + "formik": "^1.2.0", + "history": "^4.7.2", + "js-file-download": "^0.4.4", + "libsodium-wrappers": "^0.7.3", + "moment": "^2.27.0", + "query-string": "^5.1.1", + "react": "^16.7.0", + "react-copy-to-clipboard": "^5.0.1", + "react-csv": "^2.0.3", + "react-csv-reader": "^3.1.0", + "react-device-detect": "^1.6.1", + "react-dom": "^16.5.0", + "react-geocode": "^0.2.1", + "react-google-autocomplete": "^1.2.6", + "react-google-map": "^3.1.1", + "react-google-maps": "^9.4.5", + "react-helmet": "^5.2.0", + "react-idle-timer": "^4.0.9", + "react-input-mask": "^2.0.4", + "react-loadable": "^5.5.0", + "react-papaparse": "^3.7.1", + "react-redux": "^5.0.7", + "react-router-dom": "^4.2.2", + "react-router-redux": "^4.0.8", + "react-scripts": "1.1.5", + "redux": "^4.0.0", + "redux-logger": "^3.0.6", + "redux-saga": "^0.16.0", + "styled-components": "^3.4.5", + "universal-cookie": "3.0.4", + "yup": "^0.26.3" + }, + "scripts": { + "start": "react-app-rewired start", + "build": "react-app-rewired build", + "test": "react-app-rewired test --env=jsdom", + "eject": "react-app-rewired eject", + "dev": "docker-compose up -d --build", + "dev:stop": "docker-compose down", + "prod": "docker-compose -f docker-compose-prod.yml up -d --build", + "prod:stop": "docker-compose -f docker-compose-prod.yml down", + "flow": "flow" + }, + "devDependencies": { + "babel-plugin-import": "^1.7.0", + "debug": "^3.1.0", + "flow-bin": "0.80.0", + "less-plugin-variables-output": "^1.2.0", + "react-app-rewire-less": "^2.1.3", + "react-app-rewired": "^1.6.2" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon_unioil.ico b/public/favicon_unioil.ico new file mode 100644 index 0000000..3aded25 Binary files /dev/null and b/public/favicon_unioil.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..b5383e5 --- /dev/null +++ b/public/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + + React App + + + +
+ + + + diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..f5b7988 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon_unioil.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..d3a1401 --- /dev/null +++ b/src/App.js @@ -0,0 +1,215 @@ +import React, { Component, Fragment } from 'react'; +import { BrowserRouter as Router, Route, Redirect, Switch, withRouter } from 'react-router-dom'; +import Loadable from 'react-loadable'; +import { connect } from 'react-redux'; + +import LoginLayoutRoute from './components/Login/Routes'; +import DashboardRoute from './components/Dashboard/Routes'; +import Loading from './components/Loading'; + +import { getCookie } from './utils/cookie'; +import { customAction } from './actions'; +import { API_UNI_OIL, API_POST } from 'utils/Api'; + +const AsyncLogin = Loadable({ + loader: () => import('./containers/public/Login'), + loading: Loading, +}); + +const AsyncRegistration = Loadable({ + loader: () => import('./containers/public/Registration'), + loading: Loading, +}); + +const AsyncChangePassword = Loadable({ + loader: () => import('./containers/public/ChangePassword'), + loading: Loading, +}); + +const AsyncPublicTopSuccessPage = Loadable({ + loader: () => import('./containers/public/PublicTopSuccessPage'), + loading: () => { + return null; + }, +}); + +const AsyncPublicTopErrorPage = Loadable({ + loader: () => import('./containers/public/PublicTopErrorPage'), + loading: () => { + return null; + }, +}); + +const AsyncMyProfile = Loadable({ + loader: () => import('./containers/private/MyProfile'), + loading: Loading, +}); + +const AsyncNotification = Loadable({ + loader: () => import('./containers/private/Notifications'), + loading: Loading, +}); + +const AsyncUserManagement = Loadable({ + loader: () => import('./containers/private/UserManagement'), + loading: Loading, +}); + +const AsyncMemberManagement = Loadable({ + loader: () => import('./containers/private/MemberManagement'), + loading: Loading, +}); + +const AsyncPhotoSlider = Loadable({ + loader: () => import('./containers/private/HomePage/PhotoSlider'), + loading: Loading, +}); + +const AsyncPromotions = Loadable({ + loader: () => import('./containers/private/Promotions'), + loading: Loading, +}); + +const AsyncTopUp = Loadable({ + loader: () => import('./containers/private/TopUp'), + loading: Loading, +}); + +const AsyncCardTypes = Loadable({ + loader: () => import('./containers/private/AboutUs'), + loading: Loading, +}); + +const AsyncReports = Loadable({ + loader: () => import('./containers/private/Reports'), + loading: Loading, +}); + +const AsyncSystemPreferences = Loadable({ + loader: () => import('./containers/private/SystemPreferences'), + loading: Loading, +}); + +const AsyncPage404 = Loadable({ + loader: () => import('./components/PageError/404'), + loading: Loading, +}); +const AsyncLocator = Loadable({ + loader: () => import('./containers/private/StationLocator/Location'), + loading: Loading, +}); +const AsyncBranches = Loadable({ + loader: () => import('./containers/private/StationLocator/Branches'), + loading: Loading, +}) + +const AsyncFuels = Loadable({ + loader: () => import('./containers/private/StationLocator/Fuels'), + loading: Loading, +}) + +const CaptureRouteNotFound = withRouter(({ children, location }) => { + return location && location.state && location.state.pageNotFound ? : children; +}); + +const publicRoutes = [ + '/', + '/login', + '/registration', + '/forgot-password', + '/change-password', + '/topup-success-page', + '/topup-error-page', +]; + +class App extends Component { + state = { + accessAuth: false, + mounting: true, + }; + + componentDidMount = async () => { + if (getCookie('TOKEN')) { + let { history, customAction } = this.props; + let { replace, location } = history; + + API_UNI_OIL.defaults.headers.common['Authorization'] = `Bearer ${getCookie('TOKEN').token}`; + //customAction({type: 'LOGIN_SUCCESS' }); + + try { + let response = await API_POST(`adminProfile`); + response.data.data['userInfo'] = { ...response.data.data }; + + customAction({ + type: 'LOGIN_SUCCESS', + payload: { ...response.data.data }, + }); + } catch ({ response: error }) { + //notification.error({ message: "Error", description: "Something went wrong loading user data." , duration: 20, }); + } + + if (publicRoutes.includes(location.pathname)) replace('/user-management'); + } + this.setState({ mounting: false }); + }; + + render() { + console.log('===================================='); + console.log(process.env.REACT_APP_API, process.env.REACT_APP_IMG_URL, process.env.REACT_APP_PUBLIC, 'API LIST!!!'); + console.log('===================================='); + if (this.state.mounting) return null; + + if (process.env.REACT_APP_PUBLIC === 'false') { + return ( + + + + + + + {/* PRIVATE ROUTES */} + + + + + + + + + + + + + + + + + + + + ); + } else { + return ( + + + + + + + + + + + ); + } + } +} + +App = connect( + (state) => ({ + isAuthenticated: state.auth.isAuthenticated, + }), + { customAction } +)(App); + +export default withRouter(App); diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000..a754b20 --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 0000000..004fb73 --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,6 @@ +export function customAction({type, payload}) { + return { + type: type, + payload + }; +} \ No newline at end of file diff --git a/src/assets/img/bg_cms.png b/src/assets/img/bg_cms.png new file mode 100644 index 0000000..f802658 Binary files /dev/null and b/src/assets/img/bg_cms.png differ diff --git a/src/assets/img/ic_error.svg b/src/assets/img/ic_error.svg new file mode 100644 index 0000000..b6c6a66 --- /dev/null +++ b/src/assets/img/ic_error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/ic_success.svg b/src/assets/img/ic_success.svg new file mode 100644 index 0000000..e498c95 --- /dev/null +++ b/src/assets/img/ic_success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/logo_unioil.png b/src/assets/img/logo_unioil.png new file mode 100644 index 0000000..84159d2 Binary files /dev/null and b/src/assets/img/logo_unioil.png differ diff --git a/src/components/CustomTable/index.js b/src/components/CustomTable/index.js new file mode 100644 index 0000000..abc1a59 --- /dev/null +++ b/src/components/CustomTable/index.js @@ -0,0 +1,683 @@ +import React from "react"; +import { + Table, + Input, + Icon, + Divider, + Tooltip, + Popconfirm, + message, + Button, + Pagination, + Row, + Col +} from "antd"; +//import { callApi } from "utils/Api"; +import { Link, withRouter } from "react-router-dom"; +import querystring from "querystring"; +import { encrypt, decrypt } from 'utils/encrypto' +// const querystring = require("querystring"); +const { Column, ColumnGroup } = Table; +const Search = Input.Search; + +class CustomTable extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + data: [], + loading: false, + filteredInfo: null, + sortedInfo: null, + pageName: '_page', + currentPage: 1, + sizeName: '_limit', + pageSize: 10, + totalData: 100, + searchValue: null, + headerColumns: props.columns.map(column => { + if (column.sorter) { + column.sorter = (a, b) => a[column.dataIndex] - b[column.dataIndex]; + column.sortOrder = null; + } + return column; + }) + }; + } + + componentDidMount() { + this.handleInitialLoad() + } + + componentDidUpdate(prevProps, prevState, snapshot) { + const { currentPage, pageSize, pageName } = this.state; + + if (prevProps.location.search !== this.props.location.search) { + + if (this.props.location.search === "") { + this.handleclearAll(); + + } + } + + if (prevState.sortedInfo !== this.state.sortedInfo) { + //Sort Columns Function + this.setState({ + headerColumns: this.state.headerColumns.map(column => { + if ( + this.state.sortedInfo && + this.state.sortedInfo.columnKey === column.dataIndex + ) { + column.sortOrder = this.state.sortedInfo.order; + } + return column; + }) + }); + } + } + + + handleInitialLoad = () => { + const { search } = this.props.location; + const { currentPage, pageSize, pageName, sizeName } = this.state; + + if (search) { + let parsed = querystring.parse(search.substring(1)); + console.log('===================================='); + console.log(parsed,pageName, sizeName,parsed[pageName], parsed[sizeName], "&&&&&&SEARCH!!"); + console.log('===================================='); + + if (parsed) { + if (parsed[pageName] && parsed[sizeName] && parsed.q && parsed._sort && parsed._order ) { + this.handleUpdateData({ + page: parseInt(parsed[pageName]), + size: parseInt(parsed[sizeName]), + search: parsed.q + }); + } else if (parsed[pageName] && parsed[sizeName] && parsed.q) { + alert("Search") + this.handleUpdateData({ + page: parseInt(parsed[pageName]), + size: parseInt(parsed[sizeName]), + search: parsed.q + }); + } else if (parsed[pageName] && parsed[sizeName]) { + this.handleUpdateData({ + page: parseInt(parsed[pageName]), + size: parseInt(parsed[sizeName]), + }); + } + // this.fetch({ + // ...parsed + // }); + } + } else { + this.handleUpdateData({ + page: currentPage, + size: pageSize, + }); + + } + } + + handleTableChange = (pagination, filters, sorter) => { + const { currentPage, pageSize, searchValue } = this.state; + console.log("===================================="); + console.log(filters, sorter, "tesadasdas"); + console.log("===================================="); + // this.setState({ + // filteredInfo: filters, + // sortedInfo: sorter + // }); + if (Object.keys(sorter).length !== 0) { + // this.fetch({ + // _page: currentPage, + // _limit: pageSize, + // _sort: sorter.field, + // _order: sorter.order === "ascend" ? "asc" : "desc", + + // ...filters + // }); + + if (searchValue) { + this.handleUpdateData({ + sort: sorter, + filter: filters, + search: searchValue + }); + } else { + this.handleUpdateData({ sort: sorter, filter: filters }); + } + } else { + if (searchValue) { + this.handleUpdateData({ filter: filters, search: searchValue }); + } else { + this.handleUpdateData({ filter: filters }); + } + // this.fetch({ + // _page: currentPage, + // _limit: pageSize, + // ...filters + // }); + } + }; + + fetch = async (params = {}) => { + let { url, history } = this.props; + const stringified = querystring.stringify(params); + + console.log("GGGGG3333", url.default, stringified, params, window.location); + this.setState({ loading: true }); + // history.push(`${url.default}?${stringified}`); + history.push({ + pathname: url.default, + search: stringified + }); + try { + // let response = await callApi({ + // url: url.default, + // params: { + // _page: params._page, + // _limit: params._limit, + // ...params + // } + // }); + + // if (response.status === 200) { + // this.setState({ + // loading: false, + // data: response.data, + // totalData: response.data.total ? response.data.total : 100 + // }); + // } + } catch (error) {} + }; + + handleUpdateData = ({ search, sort, page, size, filter }) => { + const { + currentPage, + pageSize, + filteredInfo, + sortedInfo, + searchValue + } = this.state; + + console.log("===================================="); + console.log( + search, sort, page, size, filter, + "WWOOOPS!!" + ); + console.log("===================================="); + + if (search && sort && filter) { + + this.setState({ + filteredInfo: filter, + sortedInfo: sort, + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + q: search, + _sort: sort.field, + _order: sort.order === "ascend" ? "asc" : "desc", + ...filter + }); + + //filteredInfo value + } else if (filter) { + if (sort) { + + this.setState({ + filteredInfo: filter, + sortedInfo: sort, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + _sort: sort.field, + _order: sort.order === "ascend" ? "asc" : "desc", + ...filter + }); + } else if (search) { + + this.setState({ + filteredInfo: filter, + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + q: search, + ...filter + }); + } else { + + this.setState({ + filteredInfo: filter, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + ...filter + }); + } + //sortedInfo value + } else if (sort) { + if (filter) { + this.setState({ + filteredInfo: filter, + sortedInfo: sort, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + _sort: sort.field, + _order: sort.order === "ascend" ? "asc" : "desc", + ...filter + }); + } else if (search) { + + this.setState({ + sortedInfo: sort, + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + q: search, + _sort: sort.field, + _order: sort.order === "ascend" ? "asc" : "desc" + }); + } else { + this.setState({ + sortedInfo: sort, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + _sort: sort.field, + _order: sort.order === "ascend" ? "asc" : "desc" + }); + } + + //search Value + } else if (search) { + if (filter) { + + this.setState({ + filteredInfo: filter, + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + q: search, + ...filter + }); + } else if (sort) { + + this.setState({ + sortedInfo: sort, + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + q: search, + _sort: sort.field, + _order: sort.order === "ascend" ? "asc" : "desc" + }); + } else if (page && size) { + alert(page, size, "OOOPS@") + this.setState({ + currentPage: page, + pageSize: size, + searchValue: search, + }) + this.fetch({ + _page: page, + _limit: size, + q: search + }); + } else if (page) { + + this.setState({ + currentPage: page, + searchValue: search, + }) + + this.fetch({ + _page: page, + _limit: pageSize, + q: search + }); + } else if (size) { + + this.setState({ + pageSize: size, + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: size, + q: search + }); + } else { + + this.setState({ + searchValue: search, + }) + + this.fetch({ + _page: currentPage, + _limit: pageSize, + q: search + }); + } + } else { + if (page && size) { + this.setState({ + currentPage: page, + pageSize: size, + }) + + + this.fetch({ + _page: page, + _limit: size + }); + } else if (page) { + + this.setState({ + currentPage: page, + }) + + this.fetch({ + _page: page, + _limit: pageSize + }); + } else if (size) { + + this.setState({ + pageSize: size, + }) + + this.fetch({ + _page: currentPage, + _limit: size + }); + } else { + this.fetch({ + _page: currentPage, + _limit: pageSize + }); + } + } + }; + + handleSearch = e => { + const { currentPage, pageSize, filteredInfo, sortedInfo } = this.state; + console.log("===================================="); + console.log(e); + console.log("===================================="); + // this.setState({ + // searchValue: e + // }); + if (e) { + this.handleUpdateData({ search: e, page: 1 }); + } else { + this.handleUpdateData({ search: null, page: 1 }); + } + }; + + handleSearchChange = e => { + const { currentPage, pageSize, filteredInfo, sortedInfo } = this.state; + + // this.setState({ + // searchValue: e + // }); + if (e) { + this.setState({ + searchValue: e.target.value + }) + } else { + this.setState({ + searchValue: null + }) + } + }; + + handleDeleteConfirmYes = e => { + console.log(e); + message.success("Click on Yes"); + }; + + handleDeleteConfirmNo = e => { + console.log(e); + message.error("Click on No"); + }; + handleclearAll = () => { + console.log("===================================="); + console.log("reset"); + console.log("===================================="); + this.setState({ + filteredInfo: null, + sortedInfo: null, + searchValue: null, + // currentPage: 1, + // pageSize: 10 + }); + + this.handleUpdateData({ + page: 1, + size: 10, + }); + // this.fetch({ + // _page: 1, + // _limit: 10 + // }); + }; + handlePagination = page => { + const { pageSize, searchValue } = this.state; + + // this.fetch({ + // _page: page, + // _limit: pageSize + // }); + + if (searchValue) { + this.handleUpdateData({ page, search: searchValue }); + } else { + this.handleUpdateData({ page }); + } + + // this.setState({ + // currentPage: page + // }); + }; + + handleSizeChange = (current, pageSize) => { + console.log("TEST!", current, pageSize, searchValue); + const { searchValue } = this.state; + // this.fetch({ + // _page: current, + // _limit: pageSize + // }); + + if (searchValue) { + this.handleUpdateData({ + page: current, + size: pageSize, + search: searchValue + }); + } else { + this.handleUpdateData({ page: current, size: pageSize }); + } + + // this.setState({ + // currentPage: current, + // pageSize + // }); + }; + + + handleRenderActionButton = ({action, item}) => { + let {keyValue} = this.props + + let idValue = item[keyValue].toString() + console.log('===================================='); + + console.log('===================================='); + switch (action.type) { + case 'edit': + return + + + + + + break; + case 'view': + return + + + + + + break; + + case 'delete': + return + this.handleDeleteConfirmYes(item)} + onCancel={() => this.handleDeleteConfirmNo(item)} + okText="Yes" + cancelText="No" > + + + + + + break; + + default: + return null + break; + } + + } + + render() { + + let { columns, keyValue, actions } = this.props; + let { + sortedInfo, + filteredInfo, + headerColumns, + currentPage, + pageSize, + totalData, + data, + searchValue + } = this.state; + + // let headerColumns = columns.map(column => { + // if (column.sorter) { + // column.sorter = (a, b) => a[column.dataIndex] - b[column.dataIndex]; + // column.sortOrder = + // sortedInfo && + // sortedInfo.columnKey === column.dataIndex && + // sortedInfo.order; + // } + // return column; + // }); + + console.log("===================================="); + console.log(currentPage, pageSize, "TESTASDASD"); + console.log("===================================="); + + return ( +
+
+ +
+ + + {columns && + columns.map(column => ( + + ))} + { + actions && actions.length > 0 && ( + + actions.map(item => { + return item.access === true && this.handleRenderActionButton({action: item, item: record}) + }) + + )} + + + /> + + + } + + + +
+ + col-12 + + + + + +
+ ); + } +} + +export default withRouter(CustomTable); diff --git a/src/components/Dashboard/Layout/components/HeaderDropdown.js b/src/components/Dashboard/Layout/components/HeaderDropdown.js new file mode 100644 index 0000000..16f0261 --- /dev/null +++ b/src/components/Dashboard/Layout/components/HeaderDropdown.js @@ -0,0 +1,90 @@ +import React, { Component } from "react"; +import { Icon, Avatar, Dropdown, Menu, notification } from "antd"; +import { Link, withRouter } from "react-router-dom"; +import Helmet from 'react-helmet'; + +import { connect } from "react-redux"; +import { customAction } from 'actions' +import styled from 'styled-components'; + +import { API_UNI_OIL , API_POST } from 'utils/Api' + +const HeaderButton = styled.a` + /* This renders the buttons above... Edit me! */ + padding: 0 10px; + display: inline-block; + vertical-align: top; + + + cursor: pointer; + -webkit-transition: all .3s,padding 0s; + transition: all .3s,padding 0s; + + &:hover { + background-color: rgb(243, 243, 243); + color: #fff + } +` + + +class HeaderDropdown extends Component { + state = { + } + + + handleLogout = () => { + this.props.customAction({type: 'LOGOUT'}); + } + + render() { + + //const { userInfo } = this.state + const { history, userInfo } = this.props; + + const menu = ( + + + history.push("/my-profile")} + role="button" + rel="noopener noreferrer" > + My Profile + + + + + + Logout + + + + ); + return ( +
+ + + + { userInfo && (`${userInfo.firstname} ${userInfo.lastname}`) } + + +
+ ); + } +} + +HeaderDropdown = connect( + state => ({ + //fetchData: state.fetchData + // put initial values from account reducer + }), + { customAction } +)(HeaderDropdown) + +export default withRouter(HeaderDropdown); diff --git a/src/components/Dashboard/Layout/components/MainBreadcrumbs.js b/src/components/Dashboard/Layout/components/MainBreadcrumbs.js new file mode 100644 index 0000000..13a1a4a --- /dev/null +++ b/src/components/Dashboard/Layout/components/MainBreadcrumbs.js @@ -0,0 +1,75 @@ +import React from "react"; +import { Breadcrumb, Icon } from 'antd'; + +// import styled from 'styled-components'; +import { Link, withRouter } from 'react-router-dom'; + + +function MainBreadcrumbs(props) { + const { + pageRoutes, + // match, + location, + root + } = props; + + const pathSnippets = location.pathname.split('/').filter(i => i); + const extraBreadcrumbItems = pathSnippets.map((route, index) => { + const url = `/${pathSnippets.slice(0, index + 1).join('/')}`; + const routeCompare = pageRoutes.find((myRoute) => myRoute.path === url) + const paramsId = pathSnippets[pathSnippets.length - 1] + + if (routeCompare) { + if (routeCompare.params) { + return ( + + + {routeCompare.name} + + + ); + } else { + return ( + + + {routeCompare.name} + + + ); + } + } + + // return ; + }) + if (root) { + return ( + } + routeComparestyle={{ padding: '11px 24px 9px', fontSize: '12px' }} + > + + + {` Home`} + + + + ); + } else { + return ( + } + style={{ padding: '11px 24px 9px', fontSize: '12px' }} + > + + + {` Home`} + + + {extraBreadcrumbItems} + + ); + } + +} + +export default withRouter(MainBreadcrumbs); \ No newline at end of file diff --git a/src/components/Dashboard/Layout/components/MainContent.js b/src/components/Dashboard/Layout/components/MainContent.js new file mode 100644 index 0000000..ad7e998 --- /dev/null +++ b/src/components/Dashboard/Layout/components/MainContent.js @@ -0,0 +1,36 @@ +import React from "react"; +import { Layout } from 'antd'; +import { withRouter } from 'react-router-dom' + +import MainBreadcrumbs from './MainBreadcrumbs' +const { Content } = Layout; + +function MainContent(props) { + const { + children, + pageRoutes, + root + } = props; + + return ( + [ + props.location && props.location.key && ( +
+ +
+ ) + , + + {children} + , + ] + ); +} + +export default withRouter(MainContent); \ No newline at end of file diff --git a/src/components/Dashboard/Layout/components/MainFooter.js b/src/components/Dashboard/Layout/components/MainFooter.js new file mode 100644 index 0000000..908d218 --- /dev/null +++ b/src/components/Dashboard/Layout/components/MainFooter.js @@ -0,0 +1,19 @@ +import React from "react"; +import { Layout } from "antd"; + +// import styled from 'styled-components'; +// import { Link } from 'react-router-dom'; + +const { Footer } = Layout; + +function MainFooter(props) { + // const {} = props; + + return ( +
+ {/* YONDU-SDG ©2018 Created by Front End Team! */} +
+ ); +} + +export default MainFooter; diff --git a/src/components/Dashboard/Layout/components/MainHeader.js b/src/components/Dashboard/Layout/components/MainHeader.js new file mode 100644 index 0000000..74e8aed --- /dev/null +++ b/src/components/Dashboard/Layout/components/MainHeader.js @@ -0,0 +1,83 @@ +import React from "react"; +import { Layout, Icon } from "antd"; +import { connect } from "react-redux"; +import styled from 'styled-components'; + +import { Link } from 'react-router-dom'; +import HeaderDropdown from './HeaderDropdown'; + +const { Header } = Layout; + + +const HeaderLink = styled(Link)` + /* This renders the buttons above... Edit me! */ + padding: 0 10px; + display: inline-block; + vertical-align: top; + + + cursor: pointer; + -webkit-transition: all .3s,padding 0s; + transition: all .3s,padding 0s; + + &:hover { + background-color: #1890ff; + color: #fff + } +` + + + + +const RightHeader = styled.div` + /* This renders the buttons above... Edit me! */ + float: right; + + +` + +const IconTrigger = styled(Icon)` + /* This renders the buttons above... Edit me! */ + font-size: 20px; + line-height: 69px; + cursor: pointer; + -webkit-transition: all .3s,padding 0s; + transition: all .3s,padding 0s; + padding: 0 24px; + + &:hover { + color: #1890ff; + } +` + +function MainHeader(props) { + const { + collapsed, + toggle, + userInfo, + } = props + + return ( +
+ + + + +
+ ); +} + + +MainHeader = connect( + state => ({ + + // pull initial values from account reducer + }), + // { customAction } +)(MainHeader); + +export default MainHeader; diff --git a/src/components/Dashboard/Layout/components/MainSidebar.js b/src/components/Dashboard/Layout/components/MainSidebar.js new file mode 100644 index 0000000..f919a68 --- /dev/null +++ b/src/components/Dashboard/Layout/components/MainSidebar.js @@ -0,0 +1,262 @@ +import React from 'react'; +import { Layout, Icon, Menu } from 'antd'; +import styled from 'styled-components'; +import { withRouter, Link } from 'react-router-dom'; + +const { SubMenu } = Menu; +const { Sider } = Layout; + +const LogoPlaceholder = styled.div` + height: 32px; + margin: 16px; + + background-repeat: no-repeat; + background-size: contain; + background-position: center; +`; + +function MainSidebar(props) { + const { collapsed, match, location, userInfo, systemPreferences } = props; + + const navigation = [ + { + key: 0, + label: 'User Management', + path: '/user-management', + icon: 'team', + access: userInfo && userInfo.role == 1 ? true : false, + //access: userInfo && (userInfo.role == 1 || userInfo.role == 3) ? true : false, + }, + { + key: 9, + label: 'Notifications', + path: '/notifications', + icon: 'notification', + access: true, + //access: userInfo && (userInfo.role == 1 || userInfo.role == 3) ? true : false, + }, + { + key: 4, + label: 'Member Management', + path: '/member-management', + icon: 'credit-card', + access: userInfo && userInfo.role == 1 ? true : false, + child: [ + { + key: 0.0, + label: 'Card Member', + path: '/member-management/card-member', + access: true, + }, + { + key: 0.1, + label: 'Locked Accounts', + path: '/member-management/lock-account', + access: true, + }, + ], + }, + { + key: 8, + label: 'Home Page ( Mobile ) ', + path: '/home-page', + icon: 'home', + access: true, + child: [ + { + key: 0.0, + label: 'Photo Slider', + path: '/home-page/photo-slider', + access: true, + }, + ], + }, + { + key: 3, + label: 'Promotions', + path: '/promotions', + icon: 'tags', + access: true, + }, + + { + key: 2, + label: 'Top-Up', + path: '/top-up', + icon: 'plus-circle', + access: userInfo && userInfo.role == 1 ? true : false, + }, + { + key: 6, + label: 'About Us', + path: '/about-us', + icon: 'info-circle', + access: userInfo && userInfo.role == 1 ? true : false, + child: [ + { + key: 0.6, + label: 'Card Types', + path: '/about-us/card-types', + access: true, + }, + { + key: 0.5, + label: 'Terms & Privacy', + path: '/about-us/term-privacy', + access: true, + }, + ], + }, + { + key: 7, + label: 'Reports', + path: '/reports', + icon: 'file-text', + access: true, + child: [ + { + key: 0.7, + label: 'Registration Report', + path: '/reports/registration-report', + access: true, + }, + { + key: 0.8, + label: 'Top-Up Usage Report', + path: '/reports/top-up', + access: true, + }, + { + key: 0.9, + label: 'Mobile Usage Report', + path: '/reports/mobile-report', + access: true, + }, + { + key: 0.1, + label: 'Station Rating Report', + path: '/reports/station-rating', + access: true, + }, + ], + }, + { + key: 8, + label: 'System Parameters', + path: '/system-parameters', + icon: 'setting', + access: userInfo && userInfo.role == 1 ? true : false, + }, + { + key: 12, + label: 'Station Locator', + path:'', + icon:'environment', + access: true, + child: [ + { + key: 0.11, + label: 'Branches', + path:'/branches', + access: true + }, + { + key: 0.12, + label: 'Stations', + path:'/stations', + access: true + }, + { + key: 0.13, + label: 'Fuels', + path:'/fuels', + access: true + } + ], + }, + ]; + + let newURL = location.pathname.split('/'); + let appendedUrl = newURL[2]; + if (appendedUrl == 'create' || appendedUrl == 'view' || appendedUrl == 'edit') appendedUrl = null; + let isSeondDaryPathExist = appendedUrl ? `/${appendedUrl}` : ''; + let secondaryURL = `${match.path}${isSeondDaryPathExist}`; + + return ( + + {!collapsed ? ( +
+ {/* */} + {userInfo && ( + + )} +
+ ) : ( + + )} + + + {navigation.map((item) => { + if (item.access) { + if (item.child) { + return ( + + + {item.label} + + } + > + {item.child.map((subItem) => { + if (subItem.access) { + return ( + + + {subItem.icon && } + {subItem.label} + + + ); + } else { + return null; + } + })} + + ); + } else { + return ( + + + {item.icon && } + {item.label} + + + ); + } + } else { + return null; + } + })} + +
+ ); +} + +export default withRouter(MainSidebar); diff --git a/src/components/Dashboard/Layout/index.js b/src/components/Dashboard/Layout/index.js new file mode 100644 index 0000000..efa5fc7 --- /dev/null +++ b/src/components/Dashboard/Layout/index.js @@ -0,0 +1,107 @@ + +import React from 'react' +import { Layout } from 'antd'; +import { notification } from "antd"; +import { connect } from 'react-redux'; +import IdleTimer from 'react-idle-timer' + +import MainFooter from './components/MainFooter' +import MainHeader from './components/MainHeader' +import MainSidebar from './components/MainSidebar' + +import { API_UNI_OIL , API_POST } from 'utils/Api' +import { customAction } from 'actions' + +class DashboardLayout extends React.Component { + + + constructor(props) { + super(props) + this.idleTimer = null + this.onActive = this.handleActive.bind(this) + this.onIdle = this.handleIdle.bind(this) + + this.state = { + collapsed: false, + userInfo: null, + updatedLogo: null + }; + } + + componentWillReceiveProps(nexProps, prevProps) { + if(nexProps && nexProps.systemPreferences) { + if(nexProps.systemPreferences.data && nexProps.systemPreferences.data) { + this.setState({ + updatedLogo: nexProps.systemPreferences.data.logo + }) + } + } + } + + componentDidUpdate(nexProps) { + + } + + handleActive(e) { + // console.log('user is active', e) + } + + handleIdle(e) { + // console.log('user is idle', e) + notification.error({ + message: "Error", + description:
You are logout automatically for being idle more than 10 minutes.
, + duration: 0, + key: 'idle-notification-1' + }); + this.props.customAction({type: 'LOGOUT'}); + } + + toggle = () => { + this.setState({ + collapsed: !this.state.collapsed, + }); + } + render() { + + //const { userInfo } = this.state + const { children, userInfo } = this.props + + return ( + + + + +
+ { this.idleTimer = ref }} + element={document} + onActive={this.onActive} + onIdle={this.onIdle} + timeout={600000} + > + {children} + +
+
+
+ ); + } +} + + + +DashboardLayout = connect( + state => ({ + // pull initial values from account reducer + userInfo: state.login, + systemPreferences: state.systemPreferences + }), + { customAction } +)(DashboardLayout); + +export default DashboardLayout \ No newline at end of file diff --git a/src/components/Dashboard/Routes/index.js b/src/components/Dashboard/Routes/index.js new file mode 100644 index 0000000..690ec11 --- /dev/null +++ b/src/components/Dashboard/Routes/index.js @@ -0,0 +1,31 @@ +import React from 'react'; +import DashboardLayout from '../Layout' +import { Route, Redirect } from 'react-router-dom' +import { connect } from 'react-redux' + + +function DashboardRoute({ component: Component, isAuthenticated, ...rest }) { + return ( + isAuthenticated ? ( + + + + ) : + } + /> + ) +}; + +export default DashboardRoute = connect( + state => ({ + isAuthenticated: state.auth.isAuthenticated + }), +)(DashboardRoute); + + + \ No newline at end of file diff --git a/src/components/DataDisplay/List.js b/src/components/DataDisplay/List.js new file mode 100644 index 0000000..8a290ad --- /dev/null +++ b/src/components/DataDisplay/List.js @@ -0,0 +1,48 @@ +import React, { Component, Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import { List, Avatar } from 'antd'; + +import { fetchData } from 'utils/Api'; + +class ListDataDisplay extends Component { + state = { + data: [] + }; + + async componentDidMount() { + // const { url } = this.props; + // const response = await fetchData(url); + // this.setState({ + // data: response.data + // }) + } + + render() { + const { layout, avatar, viewPath, header, footer } = this.props; + + return ( + + ( + + } + title={{item.first_name}} + description={`${item.first_name.toLowerCase()}_${item.last_name.toLowerCase()}@gmail.com`} + /> + + )} + /> + + ) + } +} + + +export default ListDataDisplay; \ No newline at end of file diff --git a/src/components/DataDisplay/index.js b/src/components/DataDisplay/index.js new file mode 100644 index 0000000..0279445 --- /dev/null +++ b/src/components/DataDisplay/index.js @@ -0,0 +1,5 @@ +import List from './List'; + +export { + List, +} \ No newline at end of file diff --git a/src/components/Dropdown/DropdownExport.js b/src/components/Dropdown/DropdownExport.js new file mode 100644 index 0000000..b660ae7 --- /dev/null +++ b/src/components/Dropdown/DropdownExport.js @@ -0,0 +1,66 @@ +import React, { PureComponent } from "react"; +import { withRouter } from "react-router-dom"; +import { Menu, Dropdown, Button, notification } from "antd"; +import DownloadFile from "js-file-download"; +import queryString from "query-string"; +import moment from 'moment'; + +import { API_UNI_OIL } from "utils/Api"; + +class DropdownExport extends PureComponent { + state = { + loading: false + } + + handleExportCSV = async() => { + this.setState({ loading: true }); + + let { location } = this.props; + let { search } = location; + let params = queryString.parse(search); + + if(this.props.defaultFilter){ + params = { + ...params, + ...this.props.defaultFilter + } + } + + try { + + let response = await API_UNI_OIL.get(this.props.url.path, { + params, + responseType: 'blob' + }); + + if (response.status === 200 || response.status === 201) { + //let dateNow = moment(new Date()).format('DD-MMM-YYYY') + let dateNow = moment(new Date()).format('MMDDYYYY') + DownloadFile(response.data, `${this.props.url.fileName}_${dateNow}.csv`); + + this.setState({ loading: false }); + } + + } catch (error) { + this.setState({ loading: false }); + } + + } + + render() { + + const { loading } = this.state; + + return( + + ) + } +} + +export default withRouter(DropdownExport); \ No newline at end of file diff --git a/src/components/Dropdown/index.js b/src/components/Dropdown/index.js new file mode 100644 index 0000000..2a75bdd --- /dev/null +++ b/src/components/Dropdown/index.js @@ -0,0 +1 @@ +export { default as DropdownExport } from "./DropdownExport"; \ No newline at end of file diff --git a/src/components/Forms/AdvanceSearchFilter.js b/src/components/Forms/AdvanceSearchFilter.js new file mode 100644 index 0000000..b7cb02b --- /dev/null +++ b/src/components/Forms/AdvanceSearchFilter.js @@ -0,0 +1,69 @@ +import React, { Component } from 'react'; +//import { Field, reduxForm } from 'redux-form'; +import { Form, Row, Col, Button, Icon, Input,Select } from 'antd'; + +import { required } from 'constants/validation'; +import {AInput,ASelect,ARangePicker} from './AntdForms' + +const FormItem = Form.Item; +const { Option } = Select; + +class AdvancedSearchForm extends Component { + render() { + + const { handleSubmit,onSubmit, reset } = this.props; + + return ( +
+ + + {/* + + + + */} + + + {/* e.preventDefault()} + onBlur={e => e.preventDefault()} + // validate={required} + /> */} + + + + + + + + + +
+ ); + } +} + + +// AdvancedSearchForm = reduxForm({ +// form: "CreateEmployeeFieldsForm", +// })(AdvancedSearchForm); + +export default AdvancedSearchForm; + diff --git a/src/components/Forms/AntdForms.js b/src/components/Forms/AntdForms.js new file mode 100644 index 0000000..f594b64 --- /dev/null +++ b/src/components/Forms/AntdForms.js @@ -0,0 +1,47 @@ +import React from "react"; +import { Form, Input, Radio, Select, Checkbox, Button, DatePicker } from "antd"; + +const FormItem = Form.Item; +const RadioGroup = Radio.Group; +const { Option } = Select; +const { TextArea } = Input; +const { RangePicker } = DatePicker; + +const formItemLayout = { + // labelCol: { + // xs: { span: 24 }, + // sm: { span: 7 } + // }, + // wrapperCol: { + // xs: { span: 24 }, + // sm: { span: 14 } + // } +}; + + + +const makeField = Component => ({ input, meta, children, hasFeedback, label, ...rest }) => { + const hasError = meta.touched && meta.invalid; + const inputs = {...input}; + const rests = {...rest}; + console.log(inputs,'inputss', rests , 'ressttss') + return ( + + + + ); + }; + +export const AInput = makeField(Input); +export const ARadioGroup = makeField(RadioGroup); +export const ASelect = makeField(Select); +export const ACheckbox = makeField(Checkbox); +export const ATextarea = makeField(TextArea); +export const ARangePicker = makeField(RangePicker); + diff --git a/src/components/Forms/Cascader.js b/src/components/Forms/Cascader.js new file mode 100644 index 0000000..63a9d5e --- /dev/null +++ b/src/components/Forms/Cascader.js @@ -0,0 +1,67 @@ +import React, { Component } from 'react'; +import { Form, Cascader } from 'antd'; +import { fetchData } from 'utils/Api'; +const FormItem = Form.Item; + +const options = [{ + value: 'zhejiang', + label: 'Zhejiang', + children: [{ + value: 'hangzhou', + label: 'Hangzhou', + children: [{ + value: 'xihu', + label: 'West Lake', + }], + }], +}, { + value: 'jiangsu', + label: 'Jiangsu', + children: [{ + value: 'nanjing', + label: 'Nanjing', + children: [{ + value: 'zhonghuamen', + label: 'Zhong Hua Men', + }], + }], +}]; + +class CascaderForm extends Component { + async componentDidMount() { + const { url } = this.props; + const response = await fetchData(url); + this.setState({ + options: response.data.data + }) + } + + render() { + const { + field: { name, /* ...field */ }, + form: { errors, setFieldValue, /* ...form */ }, + layout, + label, + required, + ...props + } = this.props; + + return ( + + { setFieldValue(name, value) }} + options={options} + /> + + ); + } +} + +export default CascaderForm; \ No newline at end of file diff --git a/src/components/Forms/Checkbox.js b/src/components/Forms/Checkbox.js new file mode 100644 index 0000000..725d10d --- /dev/null +++ b/src/components/Forms/Checkbox.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { Checkbox, Form } from 'antd'; +const FormItem = Form.Item; + +const CheckboxForm = ({ + field: { name, ...field }, + form: { touched, errors, handleChange, setFieldValue, ...form }, + label, + inline, + layout, + required, + ...props, +}) => { + + if (inline) { + return ( + { setFieldValue(name, !field.value) }} + > + {label} + + ) + } else { + return ( + + { setFieldValue(name, !field.value) }} + > + {label} + + + + ); + } + +}; + +export default CheckboxForm; \ No newline at end of file diff --git a/src/components/Forms/DatePicker.js b/src/components/Forms/DatePicker.js new file mode 100644 index 0000000..7eeb02b --- /dev/null +++ b/src/components/Forms/DatePicker.js @@ -0,0 +1,160 @@ +import React from 'react'; +import { Form, DatePicker } from 'antd'; +import moment from 'moment' + +const FormItem = Form.Item; +const { RangePicker } = DatePicker; + +const DatePickerForm = ({ + field: { ...field }, + form: { touched, errors, handleSubmit, setFieldValue, handlePanelChange, ...form }, + type, + layout, + label, + format, + minDateToday, + required, + disabledDateStart, + dateStartEnd, + disabledDateStartEndPhotoSlider, + disabledDateStartEndPhotoSliderEndDate, + isEdit, + isAutoFill, + ...props +}) => { + + const onDateChange = (value, isDateRange = false) => { + + value && setFieldValue(field.name, isDateRange + ? [value[0].format(format), value[1].format(format)] + : value.format(format) + ) + + + if(value == null) { + setFieldValue(field.name, isDateRange + ? [value[0].format(format), value[1].format(format)] + : null + ) + } + + + } + + // Disable date less than `Today` + // use minDateToday props + // const disabledDate = (current) => { + // if (minDateToday) { + // var oneDay = (1 * 24 * 60 * 60 * 1000); + // return current && (current.valueOf() < (Date.now() - oneDay)); + // } + // } + + const disabledDate = (current) => { + // Can not select days before today and today + + // for promotions + if(disabledDateStart && !disabledDateStartEndPhotoSlider) { + if(form.values.date_start) { + return current && current < moment(form.values.date_start); + } else { + //return current && moment(current).add(2,'days') < moment().endOf('day').add(2,'days'); + } + } + // for photo slider Date Start + if(disabledDateStartEndPhotoSlider && !disabledDateStartEndPhotoSliderEndDate) { + if(dateStartEnd) { + if(current && current.format() < moment(dateStartEnd.date_start).format()) { + if(isEdit) { + //return current && current.format() < moment(dateStartEnd.date_start).subtract(1,'days').format() + } + return current && current.format() < moment(dateStartEnd.date_start).format() + } else { + if(isEdit) { + return current && current.format() > moment(dateStartEnd.date_end).format(); + } + //return current && current.format() > moment(dateStartEnd.date_end).add(1,'days').format(); + return current && current.format() > moment(dateStartEnd.date_end).format(); + } + } + } + + // for photo slider date End + if(disabledDateStartEndPhotoSliderEndDate) { + if(dateStartEnd) { + if(current && current.format() < form.values.date_start) { + // disabled previous date + return current && current < moment(form.values.date_start); + } else { + // diabled past date + if(isEdit) { + return current && current.format() > moment(dateStartEnd.date_end).format(); + } + //return current && current.format() > moment(dateStartEnd.date_end).add(1,'days').format(); + return current && current.format() > moment(dateStartEnd.date_end).format(); + } + } + } + + if(disabledDateStart) { + if(dateStartEnd) { + // return + } else { + if(form.values.date_start) { + return current && current < moment(form.values.date_start); + } + } + + } + + } + + let _props = {...props}; let _field = {...field}; + + + if(_field.value !== "") { + _props.value = _field.value && moment(_field.value,format) + } + + return ( + + { type === 'date' && + onDateChange(value)} + format={format} + disabledDate={disabledDateStartEndPhotoSlider || disabledDateStart ? disabledDate : ()=> { return false } } + style={{width: '250px'}} + /> + } + + { type === 'date-time' && + onDateChange(value)} + format={format} + style={{width: '250px'}} + /> + } + + { type === 'range' && + onDateChange(value, true)} + format={format} + disabledDate={disabledDate} + /> + } + + ); +}; + +export default DatePickerForm; \ No newline at end of file diff --git a/src/components/Forms/HeaderForm.js b/src/components/Forms/HeaderForm.js new file mode 100644 index 0000000..7b2a6d8 --- /dev/null +++ b/src/components/Forms/HeaderForm.js @@ -0,0 +1,145 @@ + +import React, { Component } from 'react'; +import { Button,Popconfirm, Icon, Dropdown, Menu } from 'antd'; + + +class HeaderForm extends Component { + + + confirm(action) { + action(); + // message.success('Click on Yes'); + } + + cancel(e) { + // console.log(e); + // message.error('Click on No'); + } + + + render() { + const { action, cancel, deleteAction , title , actionBtnName, cancelBtnName, + deleteBtnName, loading, withConfirm, styleBtn, isDropDown , actionPrivacy, + actionTerms, disabled, withCancelConfirm, isInsideForm } = this.props; + + const menu = ( + + {`Terms & Condition`} + Privacy Policy + + ); + + return ( +
+

{title}

+
+ { + action && +
+ { + withConfirm + ? + ( + this.confirm(action)} + onCancel={this.cancel} okText="Yes" cancelText="No" + title={withConfirm && withConfirm.message} + > + + + ) : + !isDropDown && ( + + ) + } +
+ + } + { + cancel && + + + + } + { + deleteAction && + ( + } + > + + + ) + } + { + isDropDown && ( +
+ + + +
+ ) + } +
+
+ ); + } +} + +export default HeaderForm; \ No newline at end of file diff --git a/src/components/Forms/InputMaskNumber.js b/src/components/Forms/InputMaskNumber.js new file mode 100644 index 0000000..d9b83dd --- /dev/null +++ b/src/components/Forms/InputMaskNumber.js @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { Form, Icon, Input, Row, Col, Button } from 'antd'; +import ReactInputMask from 'react-input-mask'; + +const FormItem = Form.Item; + +const InputNumberForm = ({ + field: { ...field }, + form: { touched, errors, ...form }, + required, + icon, + layout, + withActionBtn, + action, + loading, + mask, + ...props +}) => { + return ( + + + + + ); +}; + +export default InputNumberForm; \ No newline at end of file diff --git a/src/components/Forms/InputNumberAntD.js b/src/components/Forms/InputNumberAntD.js new file mode 100644 index 0000000..8fd5568 --- /dev/null +++ b/src/components/Forms/InputNumberAntD.js @@ -0,0 +1,71 @@ +import React, { Component } from 'react'; +import { Form, Icon, InputNumber } from 'antd'; + + + +const FormItem = Form.Item; + +class InputNumberAntD extends Component { + + handleChange = (value) => { + const { setFieldValue } = this.props.form; + const { name } = this.props.field; + + //let valueNum = parseFloat(value).toFixed(2) + // Add custom action `onChange` + return setFieldValue(name, value); + } + + render() { + const children = []; + + + const { + field: { ...field }, + form: { touched, errors, ...form }, + required, + icon, + layout, + withActionBtn, + action, + min, + max, + step, + ...props + } = this.props; + + const _props = {...props} + + _props.value = field.value + + return ( + + + + value && `${value}`} + //parser={value => value && value.replace('', '')} + // formatter={value => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')} + // parser={value => value.replace(/\$\s?|(,*)/g, '')} + onChange={this.handleChange} + /> + + + ); + } +} + +export default InputNumberAntD; \ No newline at end of file diff --git a/src/components/Forms/InputPassword.js b/src/components/Forms/InputPassword.js new file mode 100644 index 0000000..14e189b --- /dev/null +++ b/src/components/Forms/InputPassword.js @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { Form, Icon, Input, Row, Col, Button } from 'antd'; +import InputPassword from 'antd-input-password'; + +const FormItem = Form.Item; + +const InputPasswords = ({ + field: { ...field }, + form: { touched, errors, ...form }, + required, + icon, + layout, + withActionBtn, + action, + ...props +}) => { + return ( + + + + ); +}; + +export default InputPasswords; \ No newline at end of file diff --git a/src/components/Forms/InputTextArea.js b/src/components/Forms/InputTextArea.js new file mode 100644 index 0000000..15b7769 --- /dev/null +++ b/src/components/Forms/InputTextArea.js @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { Form, Icon, Input, Tooltip } from 'antd'; + +const FormItem = Form.Item; +const { TextArea } = Input; + + +const content = ( + +
This content will be used in the
+
"Enter ID Number" page as part of
+
the Apply for a Card process of
+
the Unioil Mobile App.
+
+); + + +const InputTextArea = ({ + field: { ...field }, + form: { touched, setFieldValue, errors, ...form }, + required, + icon, + layout, + withActionBtn, + action, + onCountText, + charsperpage, + pagecount, + hasIcon, + ...props +}) => { + + return ( + + {`${props.label} `} + { + hasIcon && + + + + } + + } + style={{marginBottom: '10px'}} + validateStatus={touched[field.name] && errors[field.name] && 'error'} + help={touched[field.name] && errors[field.name]} + > + +