From 8539a30ca093fd17624b2ce52be04fee0207361e Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Sat, 21 Jan 2023 17:58:23 -0500 Subject: [PATCH 1/8] Add Search and Sort styles and components to dashboard pages. --- client/src/assets/styles/search.scss | 30 ++++++ client/src/components/Search.jsx | 17 ++++ client/src/components/Sort.jsx | 46 +++++++++ client/src/components/index.js | 4 +- client/src/pages/dashboard/Active.jsx | 33 +++++- client/src/pages/dashboard/Past.jsx | 14 ++- client/src/pages/dashboard/Trial.jsx | 14 ++- package-lock.json | 141 +++++++++++++++++++++++++- package.json | 3 + 9 files changed, 292 insertions(+), 10 deletions(-) create mode 100644 client/src/assets/styles/search.scss create mode 100644 client/src/components/Search.jsx create mode 100644 client/src/components/Sort.jsx diff --git a/client/src/assets/styles/search.scss b/client/src/assets/styles/search.scss new file mode 100644 index 0000000..b3740d9 --- /dev/null +++ b/client/src/assets/styles/search.scss @@ -0,0 +1,30 @@ +.search-container{ + width: 100%; + display: grid; + grid-template-columns: [first] auto [second] 10%; +} + +.search-input{ + display: grid; + width: 98%; + margin-top: 10px; + margin-left: 10px; + padding: 10px 40px; + font-size: 14px; + color: var(--primary-600); + background-color: white; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 10px center; + border-radius: 50px; + border: 1px solid gray; +} + +.sort-button{ + display: grid; + padding: 10px 15px; + margin: 10px; + border-radius: 50px; + border: 1px solid gray; + color: var(--primary-600); +} diff --git a/client/src/components/Search.jsx b/client/src/components/Search.jsx new file mode 100644 index 0000000..97a2ca3 --- /dev/null +++ b/client/src/components/Search.jsx @@ -0,0 +1,17 @@ +import '../assets/styles/search.scss'; + +const Search = () => { + const handleChange = () => {}; + return ( +
+ +
+ ); +}; + +export default Search; \ No newline at end of file diff --git a/client/src/components/Sort.jsx b/client/src/components/Sort.jsx new file mode 100644 index 0000000..969b146 --- /dev/null +++ b/client/src/components/Sort.jsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import '../assets/styles/search.scss'; +const Sort = () => { + //Initialize the sort button's open state to false + //Otherwise, the dropdown will be open upon initial render or click + const [sortStatus, setSortStatus] = useState({open:false}); + + //Sort dropdown options + //Should this ultimately be coming from the db? + const sortOptions = [ + "Alphabetical", + "Cost", + "Payment Due" + ]; + + //Map dropdown options to li component + const sortListItems = sortOptions.map((el,i) => { + return
  • {el}
  • + }); + + //handler that is invoked upon clicking on sort button, toggling its open status to the opposite boolean + const handleClick = () => { + setSortStatus((sortStatus) => { + return { + open: !sortStatus.open, + }; + }); + }; + + return( +
    + + {sortStatus.open && ( +
    +
      + {sortListItems} +
    +
    + )} +
    + ); +}; + +export default Sort; \ No newline at end of file diff --git a/client/src/components/index.js b/client/src/components/index.js index 0aeeb02..03a60a8 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -4,5 +4,7 @@ import Alert from './Alert'; import Navbar from './Navbar'; import LargeSidebar from './LargeSidebar'; import SmallSidebar from './SmallSidebar'; +import Search from './Search'; +import Sort from './Sort'; -export { Logo, FormRow, Alert, Navbar, LargeSidebar, SmallSidebar }; +export { Logo, FormRow, Alert, Navbar, LargeSidebar, SmallSidebar, Search, Sort }; diff --git a/client/src/pages/dashboard/Active.jsx b/client/src/pages/dashboard/Active.jsx index 04eebba..4d54afd 100644 --- a/client/src/pages/dashboard/Active.jsx +++ b/client/src/pages/dashboard/Active.jsx @@ -1,5 +1,34 @@ +import { Search, Sort } from '../../components'; +import { useAppContext } from '../../context/appContext'; +import '../../assets/styles/search.scss'; + const Active = () => { - return
    Active Subscription
    ; + /* Questions + // get active subscriptions tied to user via contextApi? how do i get the data? + // map all active subscriptions tied to user in array + */ + /*Sort + 1. Create a new array where subscriptions equal the chosen sort option, leveraging map and sort + 2. Create separate sort handler functions for each sort option + - Logic might be included here + - If I do it here, it's only contained in this page + - Logic might be included in contextAPI + - If I have the logic included in contextAPI, then it's central and can be accessed by other pages + 3. Potentially use select component + */ + + return ( +
    +
    Active Subscriptions
    + {/* add a div container to contain the search filter and sort components w/ className for styling*/} +
    + + +
    + + {/* render all cards that have been mapped to an array and/or retrieved from state/contexAPI */} +
    + ); }; -export default Active; +export default Active; \ No newline at end of file diff --git a/client/src/pages/dashboard/Past.jsx b/client/src/pages/dashboard/Past.jsx index 3db9250..20cca1d 100644 --- a/client/src/pages/dashboard/Past.jsx +++ b/client/src/pages/dashboard/Past.jsx @@ -1,5 +1,17 @@ +import { Search, Sort } from '../../components'; +import { useAppContext } from '../../context/appContext'; +import '../../assets/styles/search.scss'; + const Past = () => { - return
    Past
    ; + return ( +
    +
    Past Subscriptions
    +
    + + +
    +
    + ); }; export default Past; diff --git a/client/src/pages/dashboard/Trial.jsx b/client/src/pages/dashboard/Trial.jsx index 916437e..5dc94e9 100644 --- a/client/src/pages/dashboard/Trial.jsx +++ b/client/src/pages/dashboard/Trial.jsx @@ -1,5 +1,17 @@ +import { Search, Sort } from '../../components'; +import { useAppContext } from '../../context/appContext'; +import '../../assets/styles/search.scss'; + const Trial = () => { - return
    Trial
    ; + return ( +
    +
    Trial Subscriptions
    +
    + + +
    +
    + ); }; export default Trial; diff --git a/package-lock.json b/package-lock.json index d09db28..657c289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "subslify", "version": "0.1.0", "license": "MIT", + "dependencies": { + "ts-node": "^10.9.1" + }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/parser": "^5.48.2", @@ -2054,6 +2057,26 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -2127,7 +2150,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2144,8 +2166,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", @@ -2229,6 +2250,26 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", "dev": true }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -2241,6 +2282,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "peer": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -2485,7 +2532,6 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -2502,6 +2548,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2542,6 +2596,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2955,6 +3014,11 @@ "node": ">=10" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3042,6 +3106,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4647,6 +4719,11 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5454,6 +5531,48 @@ "node": ">=8.0" } }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -5541,7 +5660,6 @@ "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true, "peer": true, "bin": { "tsc": "bin/tsc", @@ -5641,6 +5759,11 @@ "punycode": "^2.1.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5737,6 +5860,14 @@ "node": ">= 6" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a7a3d74..1f4b0d3 100644 --- a/package.json +++ b/package.json @@ -38,5 +38,8 @@ "eslint-config-react-app": "^7.0.1", "eslint-plugin-react": "^7.32.1", "prettier": "^2.8.1" + }, + "dependencies": { + "ts-node": "^10.9.1" } } From f3d504333a6e1cfcf9c6914c2c101760d65ca778 Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Sun, 22 Jan 2023 13:02:12 -0500 Subject: [PATCH 2/8] Format code --- client/src/assets/styles/search.scss | 50 ++++++++++---------- client/src/components/Search.jsx | 25 +++++----- client/src/components/Sort.jsx | 71 ++++++++++++++-------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/client/src/assets/styles/search.scss b/client/src/assets/styles/search.scss index b3740d9..c1862e0 100644 --- a/client/src/assets/styles/search.scss +++ b/client/src/assets/styles/search.scss @@ -1,30 +1,30 @@ -.search-container{ - width: 100%; - display: grid; - grid-template-columns: [first] auto [second] 10%; +.search-container { + width: 100%; + display: grid; + grid-template-columns: [first] auto [second] 10%; } -.search-input{ - display: grid; - width: 98%; - margin-top: 10px; - margin-left: 10px; - padding: 10px 40px; - font-size: 14px; - color: var(--primary-600); - background-color: white; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: 10px center; - border-radius: 50px; - border: 1px solid gray; +.search-input { + display: grid; + width: 98%; + margin-top: 10px; + margin-left: 10px; + padding: 10px 40px; + font-size: 14px; + color: var(--primary-600); + background-color: white; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 10px center; + border-radius: 50px; + border: 1px solid gray; } -.sort-button{ - display: grid; - padding: 10px 15px; - margin: 10px; - border-radius: 50px; - border: 1px solid gray; - color: var(--primary-600); +.sort-button { + display: grid; + padding: 10px 15px; + margin: 10px; + border-radius: 50px; + border: 1px solid gray; + color: var(--primary-600); } diff --git a/client/src/components/Search.jsx b/client/src/components/Search.jsx index 97a2ca3..d34aa4c 100644 --- a/client/src/components/Search.jsx +++ b/client/src/components/Search.jsx @@ -1,17 +1,18 @@ import '../assets/styles/search.scss'; const Search = () => { - const handleChange = () => {}; - return ( -
    - -
    - ); + const handleChange = () => {}; + + return ( +
    + +
    + ); }; -export default Search; \ No newline at end of file +export default Search; diff --git a/client/src/components/Sort.jsx b/client/src/components/Sort.jsx index 969b146..3a48269 100644 --- a/client/src/components/Sort.jsx +++ b/client/src/components/Sort.jsx @@ -1,46 +1,45 @@ import { useState } from 'react'; import '../assets/styles/search.scss'; + const Sort = () => { - //Initialize the sort button's open state to false - //Otherwise, the dropdown will be open upon initial render or click - const [sortStatus, setSortStatus] = useState({open:false}); + //Initialize the sort button's open state to false + //Otherwise, the dropdown will be open upon initial render or click + const [sortStatus, setSortStatus] = useState({ open: false }); - //Sort dropdown options - //Should this ultimately be coming from the db? - const sortOptions = [ - "Alphabetical", - "Cost", - "Payment Due" - ]; + //Sort dropdown options + //Should this ultimately be coming from the db? + const sortOptions = ['Alphabetical', 'Cost', 'Payment Due']; - //Map dropdown options to li component - const sortListItems = sortOptions.map((el,i) => { - return
  • {el}
  • - }); + //Map dropdown options to li component + const sortListItems = sortOptions.map((el, i) => { + return ( +
  • + {el} +
  • + ); + }); - //handler that is invoked upon clicking on sort button, toggling its open status to the opposite boolean - const handleClick = () => { - setSortStatus((sortStatus) => { - return { - open: !sortStatus.open, - }; - }); - }; + //handler that is invoked upon clicking on sort button, toggling its open status to the opposite boolean + const handleClick = () => { + setSortStatus((sortStatus) => { + return { + open: !sortStatus.open, + }; + }); + }; - return( -
    - - {sortStatus.open && ( -
    -
      - {sortListItems} -
    -
    - )} + return ( +
    + + {sortStatus.open && ( +
    +
      {sortListItems}
    - ); + )} +
    + ); }; -export default Sort; \ No newline at end of file +export default Sort; From e09ec2fc043ef5b12a76c27a9014d6433ce7b22d Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Sun, 22 Jan 2023 16:44:48 -0500 Subject: [PATCH 3/8] Add functionality to show selected sort option and start appContext logic for gettting subscriptions. --- client/src/assets/styles/search.scss | 34 +++++++++++++++++--- client/src/components/Sort.jsx | 48 ++++++++++++++++++++-------- client/src/context/actions.js | 6 ++++ client/src/context/appContext.js | 26 ++++++++++++++- client/src/context/reducer.js | 14 ++++++++ 5 files changed, 109 insertions(+), 19 deletions(-) diff --git a/client/src/assets/styles/search.scss b/client/src/assets/styles/search.scss index c1862e0..c5e7047 100644 --- a/client/src/assets/styles/search.scss +++ b/client/src/assets/styles/search.scss @@ -1,17 +1,20 @@ .search-container { width: 100%; display: grid; - grid-template-columns: [first] auto [second] 10%; + grid-auto-flow: column; + justify-content: start; } .search-input { display: grid; - width: 98%; + width: 400px; margin-top: 10px; margin-left: 10px; padding: 10px 40px; font-size: 14px; - color: var(--primary-600); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + color: gray; background-color: white; background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); background-repeat: no-repeat; @@ -22,9 +25,32 @@ .sort-button { display: grid; - padding: 10px 15px; + padding: 12px 15px; margin: 10px; border-radius: 50px; border: 1px solid gray; color: var(--primary-600); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 12px; + font-weight: bolder; +} + +.sort-list { + padding: 5px; + padding-right: 30px; + background-color: white; + border: 1px solid gray; + border-radius: 5px; + font-size: 12px; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + :hover { + color: #ffa630; + cursor: pointer; + } +} + +.active { + color: #ffa630; } diff --git a/client/src/components/Sort.jsx b/client/src/components/Sort.jsx index 3a48269..a6833b1 100644 --- a/client/src/components/Sort.jsx +++ b/client/src/components/Sort.jsx @@ -3,37 +3,57 @@ import '../assets/styles/search.scss'; const Sort = () => { //Initialize the sort button's open state to false - //Otherwise, the dropdown will be open upon initial render or click - const [sortStatus, setSortStatus] = useState({ open: false }); + //Otherwise, the dropdown will be open upon initial render + const [sortStatus, setSortStatus] = useState({ + isOpen: false, + activeSortOpt: null, + }); + + const { activeSortOpt } = sortStatus; //Sort dropdown options //Should this ultimately be coming from the db? const sortOptions = ['Alphabetical', 'Cost', 'Payment Due']; - //Map dropdown options to li component - const sortListItems = sortOptions.map((el, i) => { - return ( -
  • - {el} -
  • - ); - }); + const handleSortOptClick = (e) => { + e.preventDefault(); + setSortStatus({ isOpen: false, activeSortOpt: e.target.innerText }); + }; //handler that is invoked upon clicking on sort button, toggling its open status to the opposite boolean - const handleClick = () => { + const handleSortClick = () => { setSortStatus((sortStatus) => { return { - open: !sortStatus.open, + ...sortStatus, + isOpen: !sortStatus.isOpen, }; }); }; + //Map dropdown options to li component + const sortListItems = sortOptions.map(el => { + //If the list item was selected, provide dynamic className for dynamic styling + //Will let user know which sort option is currently active + let activeClass = activeSortOpt === el ? 'active sort-list-item' : 'sort-list-item'; + + return ( +
  • + {el} +
  • + ); + }); + return (
    - - {sortStatus.open && ( + {sortStatus.isOpen && (
      {sortListItems}
    diff --git a/client/src/context/actions.js b/client/src/context/actions.js index 868f3ac..519b028 100644 --- a/client/src/context/actions.js +++ b/client/src/context/actions.js @@ -17,6 +17,10 @@ const UPDATE_USER_ERROR = 'UPDATE_USER_ERROR'; const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR'; +//Subscriptions +const SORT_SUBSCRIPTIONS_SUCCESS = 'SORT_SUBSCRIPTIONS_SUCCESS'; +const SORT_SUBSCRIPTIONS_ERROR = 'SORT_SUBSCRIPTIONS_ERROR'; + export { DISPLAY_ALERT, REMOVE_ALERT, @@ -31,4 +35,6 @@ export { UPDATE_USER_SUCCESS, UPDATE_USER_ERROR, TOGGLE_SIDEBAR, + SORT_SUBSCRIPTIONS_SUCCESS, + SORT_SUBSCRIPTIONS_ERROR, }; diff --git a/client/src/context/appContext.js b/client/src/context/appContext.js index 3c74410..03253d5 100644 --- a/client/src/context/appContext.js +++ b/client/src/context/appContext.js @@ -142,7 +142,7 @@ const AppProvider = ({ children }) => { const updateUser = async (currentUser) => { dispatch({ type: UPDATE_USER_BEGIN }); try { - const { data } = await authFetch.patch('/auth/updateUser', currentUser); + const { data } = await authFetch.patch('/api/v1/subscriptions', currentUser); const { user, token } = data; if (!user || !token) { @@ -169,6 +169,29 @@ const AppProvider = ({ children }) => { dispatch({ type: TOGGLE_SIDEBAR }); }; + const getSortedSubs = async (currentUser) => { + try{ + //Follow the same pattern as user login? Loading might be good. + //Need to add to body, params, etc the type of sorting: alphabetical, payment due, and cost + const { data } = await axios.get('/auth/updateUser', currentUser /* current user necessary for api authorization?*/ ); + const { subscriptions } = data; + + dispatch({ + type: SORT_SUBSCRIPTIONS_SUCCESS, + payload: subscriptions + }); + } + catch(err){ + dispatch( { + type: SORT_SUBSCRIPTIONS_ERROR, + payload: { + message: + error.response?.data?.message || 'Failed to retrieve sorted subscription data.' + } + }); + } + }; + return ( { toggleSidebar, logoutUser, updateUser, + getSortedSubs, }} > {children} diff --git a/client/src/context/reducer.js b/client/src/context/reducer.js index dffb289..4f829a3 100644 --- a/client/src/context/reducer.js +++ b/client/src/context/reducer.js @@ -140,6 +140,20 @@ const reducer = (state, action) => { }; } + if (action.type === SORT_SUBSCRIPTIONS_SUCCESS) { + return { + ...state, + + } + } + + if (action.type === SORT_SUBSCRIPTIONS_ERROR) { + return { + ...state, + //Should I follow the same pattern as in UPDATE_USER_ERROR? Alerts? + } + } + throw new Error(`Unhandled action type: ${action.type}`); }; From 7435436a1ea82237cdc6c1a2b9483a172c8d6dfe Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Mon, 23 Jan 2023 22:44:42 -0500 Subject: [PATCH 4/8] Add function to contextAPI to handle GET requests for subscriptions --- client/src/assets/styles/search.scss | 12 ++++++------ client/src/components/Search.jsx | 8 ++++++-- client/src/components/Sort.jsx | 6 +++++- client/src/context/actions.js | 10 ++++++---- client/src/context/appContext.js | 18 ++++++++++++------ client/src/context/reducer.js | 23 +++++++++++++++++++---- client/src/pages/dashboard/Active.jsx | 2 +- 7 files changed, 55 insertions(+), 24 deletions(-) diff --git a/client/src/assets/styles/search.scss b/client/src/assets/styles/search.scss index c5e7047..63aaa52 100644 --- a/client/src/assets/styles/search.scss +++ b/client/src/assets/styles/search.scss @@ -1,24 +1,24 @@ -.search-container { +.search-sort-container { width: 100%; display: grid; grid-auto-flow: column; justify-content: start; } +.search-container { + +} + .search-input { display: grid; width: 400px; margin-top: 10px; margin-left: 10px; - padding: 10px 40px; + padding: 10px 20px; font-size: 14px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; color: gray; - background-color: white; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: 10px center; border-radius: 50px; border: 1px solid gray; } diff --git a/client/src/components/Search.jsx b/client/src/components/Search.jsx index d34aa4c..9c86abb 100644 --- a/client/src/components/Search.jsx +++ b/client/src/components/Search.jsx @@ -1,16 +1,20 @@ import '../assets/styles/search.scss'; +import { useAppContext } from '../context/appContext'; const Search = () => { const handleChange = () => {}; return ( -
    +
    +
    + +
    ); }; diff --git a/client/src/components/Sort.jsx b/client/src/components/Sort.jsx index a6833b1..c4f60f1 100644 --- a/client/src/components/Sort.jsx +++ b/client/src/components/Sort.jsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { useAppContext } from '../context/appContext'; import '../assets/styles/search.scss'; const Sort = () => { @@ -10,18 +11,21 @@ const Sort = () => { }); const { activeSortOpt } = sortStatus; + const { getSubscriptions } = useAppContext(); //Sort dropdown options - //Should this ultimately be coming from the db? const sortOptions = ['Alphabetical', 'Cost', 'Payment Due']; const handleSortOptClick = (e) => { e.preventDefault(); + // getSubscriptions('sort', e.target.innerText); setSortStatus({ isOpen: false, activeSortOpt: e.target.innerText }); }; //handler that is invoked upon clicking on sort button, toggling its open status to the opposite boolean const handleSortClick = () => { + //invoke funtion in appContext here + //dispatch action? setSortStatus((sortStatus) => { return { ...sortStatus, diff --git a/client/src/context/actions.js b/client/src/context/actions.js index 519b028..b74a9f8 100644 --- a/client/src/context/actions.js +++ b/client/src/context/actions.js @@ -18,8 +18,9 @@ const UPDATE_USER_ERROR = 'UPDATE_USER_ERROR'; const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR'; //Subscriptions -const SORT_SUBSCRIPTIONS_SUCCESS = 'SORT_SUBSCRIPTIONS_SUCCESS'; -const SORT_SUBSCRIPTIONS_ERROR = 'SORT_SUBSCRIPTIONS_ERROR'; +const GET_SUBSCRIPTIONS_SUCCESS = 'GET_SUBSCRIPTIONS_SUCCESS'; +const GET_SUBSCRIPTIONS_ERROR = 'GET_SUBSCRIPTIONS_ERROR'; +const GET_SUBSCRIPTIONS_BEGIN = 'GET_SUBSCRIPTIONS_BEGIN'; export { DISPLAY_ALERT, @@ -35,6 +36,7 @@ export { UPDATE_USER_SUCCESS, UPDATE_USER_ERROR, TOGGLE_SIDEBAR, - SORT_SUBSCRIPTIONS_SUCCESS, - SORT_SUBSCRIPTIONS_ERROR, + GET_SUBSCRIPTIONS_SUCCESS, + GET_SUBSCRIPTIONS_ERROR, + GET_SUBSCRIPTIONS_BEGIN, }; diff --git a/client/src/context/appContext.js b/client/src/context/appContext.js index 03253d5..909bad7 100644 --- a/client/src/context/appContext.js +++ b/client/src/context/appContext.js @@ -15,6 +15,9 @@ import { UPDATE_USER_SUCCESS, UPDATE_USER_ERROR, TOGGLE_SIDEBAR, + GET_SUBSCRIPTIONS_BEGIN, + GET_SUBSCRIPTIONS_SUCCESS, + GET_SUBSCRIPTIONS_ERROR, } from './actions'; const user = localStorage.getItem('user'); @@ -169,27 +172,30 @@ const AppProvider = ({ children }) => { dispatch({ type: TOGGLE_SIDEBAR }); }; - const getSortedSubs = async (currentUser) => { + //do we want to consider optional parameters at all? + const getSubscriptions = async (type, query) => { try{ + dispatch({ type: GET_SUBSCRIPTIONS_BEGIN }); //Follow the same pattern as user login? Loading might be good. //Need to add to body, params, etc the type of sorting: alphabetical, payment due, and cost - const { data } = await axios.get('/auth/updateUser', currentUser /* current user necessary for api authorization?*/ ); + const { data } = await axios.get('/subscriptions', ); const { subscriptions } = data; dispatch({ - type: SORT_SUBSCRIPTIONS_SUCCESS, - payload: subscriptions + type: GET_SUBSCRIPTIONS_SUCCESS, + payload: { subscriptions } }); } catch(err){ dispatch( { - type: SORT_SUBSCRIPTIONS_ERROR, + type: GET_SUBSCRIPTIONS_ERROR, payload: { message: error.response?.data?.message || 'Failed to retrieve sorted subscription data.' } }); } + clearAlert(); }; return ( @@ -203,7 +209,7 @@ const AppProvider = ({ children }) => { toggleSidebar, logoutUser, updateUser, - getSortedSubs, + getSubscriptions, }} > {children} diff --git a/client/src/context/reducer.js b/client/src/context/reducer.js index 4f829a3..df645a4 100644 --- a/client/src/context/reducer.js +++ b/client/src/context/reducer.js @@ -12,6 +12,9 @@ import { UPDATE_USER_SUCCESS, UPDATE_USER_ERROR, TOGGLE_SIDEBAR, + GET_SUBSCRIPTIONS_BEGIN, + GET_SUBSCRIPTIONS_ERROR, + GET_SUBSCRIPTIONS_SUCCESS, } from './actions'; import { initialState } from './appContext'; @@ -140,17 +143,29 @@ const reducer = (state, action) => { }; } - if (action.type === SORT_SUBSCRIPTIONS_SUCCESS) { + if (action.type === GET_SUBSCRIPTIONS_BEGIN) { + return { ...state, isLoading: true }; + } + + if (action.type === GET_SUBSCRIPTIONS_SUCCESS) { return { ...state, - + isLoading: false, + subscriptions: action.payload.subscriptions, } } - if (action.type === SORT_SUBSCRIPTIONS_ERROR) { + if (action.type === GET_SUBSCRIPTIONS_ERROR) { return { ...state, - //Should I follow the same pattern as in UPDATE_USER_ERROR? Alerts? + isLoading: false, + showAlert: true, + alert: { + type: 'danger', + message: + action.payload.message || + 'Unexpected Error. Subscriptions could not be retrieved.', + } } } diff --git a/client/src/pages/dashboard/Active.jsx b/client/src/pages/dashboard/Active.jsx index 4d54afd..0a5bcb8 100644 --- a/client/src/pages/dashboard/Active.jsx +++ b/client/src/pages/dashboard/Active.jsx @@ -21,7 +21,7 @@ const Active = () => {
    Active Subscriptions
    {/* add a div container to contain the search filter and sort components w/ className for styling*/} -
    +
    From d16bd4595feece3c285be95acada4f6d726e88b7 Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Tue, 24 Jan 2023 10:05:36 -0500 Subject: [PATCH 5/8] Create button inside search input field --- client/src/assets/styles/search.scss | 25 ++++++++++++++++++++++--- client/src/components/Search.jsx | 16 ++++++++-------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/client/src/assets/styles/search.scss b/client/src/assets/styles/search.scss index 63aaa52..a24eee1 100644 --- a/client/src/assets/styles/search.scss +++ b/client/src/assets/styles/search.scss @@ -6,15 +6,15 @@ } .search-container { - + } .search-input { - display: grid; + display: block; width: 400px; margin-top: 10px; margin-left: 10px; - padding: 10px 20px; + padding: 10px 50px 10px 20px; font-size: 14px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; @@ -54,3 +54,22 @@ .active { color: #ffa630; } + +.search { + position: relative; + + button { + position: absolute; + top:0px; + right:5px; + width: 40px; + height: 100%; + cursor: pointer; + background-color: transparent; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + border-radius: 20px; + border: 0; + } +} \ No newline at end of file diff --git a/client/src/components/Search.jsx b/client/src/components/Search.jsx index 9c86abb..14444bd 100644 --- a/client/src/components/Search.jsx +++ b/client/src/components/Search.jsx @@ -6,14 +6,14 @@ const Search = () => { return (
    -
    - - + + +
    ); From 5d7752f7c2f14d3240543e91caf4b159adca7b84 Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Tue, 24 Jan 2023 20:46:33 -0500 Subject: [PATCH 6/8] Update search to disable the search button when being clicked during load --- client/src/components/Search.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Search.jsx b/client/src/components/Search.jsx index 14444bd..52d513b 100644 --- a/client/src/components/Search.jsx +++ b/client/src/components/Search.jsx @@ -13,7 +13,7 @@ const Search = () => { placeholder='Search...' onChange={handleChange} /> - +
    ); From 9af08967743067e1565195bb8f84e907383320a0 Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Tue, 24 Jan 2023 22:22:30 -0500 Subject: [PATCH 7/8] Add frontend logic to call subs api --- client/src/components/Search.jsx | 24 ++++++++++++++++++++---- client/src/components/Sort.jsx | 6 +++--- client/src/context/appContext.js | 19 +++++++++++++------ client/src/context/reducer.js | 3 +++ client/src/pages/dashboard/Active.jsx | 24 ++++++------------------ client/src/pages/dashboard/Past.jsx | 4 ++-- client/src/pages/dashboard/Trial.jsx | 6 ++++-- 7 files changed, 51 insertions(+), 35 deletions(-) diff --git a/client/src/components/Search.jsx b/client/src/components/Search.jsx index 52d513b..0e1bedd 100644 --- a/client/src/components/Search.jsx +++ b/client/src/components/Search.jsx @@ -1,19 +1,35 @@ +import { useState } from 'react'; import '../assets/styles/search.scss'; import { useAppContext } from '../context/appContext'; -const Search = () => { - const handleChange = () => {}; +const Search = (props) => { + const [search, setSearch] = useState({ + value: '' + }); + + const { getSubscriptions } = useAppContext(); + + const handleSubmit = () => { + event.preventDefault(); + getSubscriptions({type: props.type, sort: null, search: search.value}) + }; + const handleChange = event => { + setSearch({value: event.target.value}); + }; return (
    -
    + - +
    ); diff --git a/client/src/components/Sort.jsx b/client/src/components/Sort.jsx index c4f60f1..02b0e08 100644 --- a/client/src/components/Sort.jsx +++ b/client/src/components/Sort.jsx @@ -2,12 +2,12 @@ import { useState } from 'react'; import { useAppContext } from '../context/appContext'; import '../assets/styles/search.scss'; -const Sort = () => { +const Sort = (props) => { //Initialize the sort button's open state to false //Otherwise, the dropdown will be open upon initial render const [sortStatus, setSortStatus] = useState({ isOpen: false, - activeSortOpt: null, + activeSortOpt: '', }); const { activeSortOpt } = sortStatus; @@ -18,7 +18,7 @@ const Sort = () => { const handleSortOptClick = (e) => { e.preventDefault(); - // getSubscriptions('sort', e.target.innerText); + getSubscriptions({type: props.type, sort: e.target.innerText.toLowerCase()}); setSortStatus({ isOpen: false, activeSortOpt: e.target.innerText }); }; diff --git a/client/src/context/appContext.js b/client/src/context/appContext.js index 257588c..5dadc7e 100644 --- a/client/src/context/appContext.js +++ b/client/src/context/appContext.js @@ -200,25 +200,32 @@ const AppProvider = ({ children }) => { }, []); //do we want to consider optional parameters at all? - const getSubscriptions = async (type, query) => { + const getSubscriptions = async ({ type, sort, search }) => { + const url = `/subscriptions?status=${type}&sort=${sort}&search=${search}`; + console.log('url', url); try{ dispatch({ type: GET_SUBSCRIPTIONS_BEGIN }); - //Follow the same pattern as user login? Loading might be good. - //Need to add to body, params, etc the type of sorting: alphabetical, payment due, and cost - const { data } = await axios.get('/subscriptions', ); + + const { data } = await axios.get(url); + if (!data) { + throw new Error('Subscriptions not found'); + } const { subscriptions } = data; + if (!subscriptions) { + throw new Error('Subscriptions not found'); + } dispatch({ type: GET_SUBSCRIPTIONS_SUCCESS, payload: { subscriptions } }); } - catch(err){ + catch(error){ dispatch( { type: GET_SUBSCRIPTIONS_ERROR, payload: { message: - error.response?.data?.message || 'Failed to retrieve sorted subscription data.' + error.response?.data?.message || error.message || 'Getting subscriptions failed' } }); } diff --git a/client/src/context/reducer.js b/client/src/context/reducer.js index 96c26c4..907c31c 100644 --- a/client/src/context/reducer.js +++ b/client/src/context/reducer.js @@ -154,10 +154,12 @@ const reducer = (state, action) => { } if (action.type === GET_SUBSCRIPTIONS_BEGIN) { + console.log('begin'); return { ...state, isLoading: true }; } if (action.type === GET_SUBSCRIPTIONS_SUCCESS) { + console.log('success'); return { ...state, isLoading: false, @@ -166,6 +168,7 @@ const reducer = (state, action) => { } if (action.type === GET_SUBSCRIPTIONS_ERROR) { + console.log('error'); return { ...state, isLoading: false, diff --git a/client/src/pages/dashboard/Active.jsx b/client/src/pages/dashboard/Active.jsx index 0a5bcb8..8d007bb 100644 --- a/client/src/pages/dashboard/Active.jsx +++ b/client/src/pages/dashboard/Active.jsx @@ -2,28 +2,16 @@ import { Search, Sort } from '../../components'; import { useAppContext } from '../../context/appContext'; import '../../assets/styles/search.scss'; -const Active = () => { - /* Questions - // get active subscriptions tied to user via contextApi? how do i get the data? - // map all active subscriptions tied to user in array - */ - /*Sort - 1. Create a new array where subscriptions equal the chosen sort option, leveraging map and sort - 2. Create separate sort handler functions for each sort option - - Logic might be included here - - If I do it here, it's only contained in this page - - Logic might be included in contextAPI - - If I have the logic included in contextAPI, then it's central and can be accessed by other pages - 3. Potentially use select component - */ - +const Active = () => { + const { isLoading, alert, showAlert } = useAppContext(); + console.log({isLoading, alert, showAlert}); return (
    Active Subscriptions
    {/* add a div container to contain the search filter and sort components w/ className for styling*/} -
    - - +
    + +
    {/* render all cards that have been mapped to an array and/or retrieved from state/contexAPI */} diff --git a/client/src/pages/dashboard/Past.jsx b/client/src/pages/dashboard/Past.jsx index 86c525e..0a2ee4b 100644 --- a/client/src/pages/dashboard/Past.jsx +++ b/client/src/pages/dashboard/Past.jsx @@ -7,8 +7,8 @@ const Past = () => {
    Past Subscriptions
    - - + +
    ); diff --git a/client/src/pages/dashboard/Trial.jsx b/client/src/pages/dashboard/Trial.jsx index 54995b6..6e2fc21 100644 --- a/client/src/pages/dashboard/Trial.jsx +++ b/client/src/pages/dashboard/Trial.jsx @@ -3,12 +3,14 @@ import { useAppContext } from '../../context/appContext'; import '../../assets/styles/search.scss'; const Trial = () => { + const { isLoading, alert, showAlert } = useAppContext(); + console.log({isLoading, alert, showAlert}); return (
    Trial Subscriptions
    - - + +
    ); From 7f40e9dd6cf312b54d950e10d5f6dac4f0594a23 Mon Sep 17 00:00:00 2001 From: Tim Chang Date: Tue, 24 Jan 2023 22:53:18 -0500 Subject: [PATCH 8/8] Connect frontend to backend with subscriptions --- client/src/context/appContext.js | 6 +++--- client/src/context/reducer.js | 3 --- client/src/pages/dashboard/Active.jsx | 2 -- client/src/pages/dashboard/Trial.jsx | 2 -- package-lock.json | 3 +-- server/src/Config/google.ts | 2 +- server/src/controllers/subscriptionsController.js | 1 + server/src/server.ts | 3 ++- 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/client/src/context/appContext.js b/client/src/context/appContext.js index 5dadc7e..d6cd1c6 100644 --- a/client/src/context/appContext.js +++ b/client/src/context/appContext.js @@ -39,7 +39,7 @@ const AppProvider = ({ children }) => { // Axios config // TODO move to separate file const authFetch = axios.create({ - baseURL: '/api/v1/', + baseURL: '/api/v1', }); // alternative way to add token to header @@ -202,11 +202,11 @@ const AppProvider = ({ children }) => { //do we want to consider optional parameters at all? const getSubscriptions = async ({ type, sort, search }) => { const url = `/subscriptions?status=${type}&sort=${sort}&search=${search}`; - console.log('url', url); + try{ dispatch({ type: GET_SUBSCRIPTIONS_BEGIN }); - const { data } = await axios.get(url); + const { data } = await authFetch.get(url); if (!data) { throw new Error('Subscriptions not found'); } diff --git a/client/src/context/reducer.js b/client/src/context/reducer.js index 8a46019..937d222 100644 --- a/client/src/context/reducer.js +++ b/client/src/context/reducer.js @@ -155,12 +155,10 @@ const reducer = (state, action) => { } if (action.type === GET_SUBSCRIPTIONS_BEGIN) { - console.log('begin'); return { ...state, isLoading: true }; } if (action.type === GET_SUBSCRIPTIONS_SUCCESS) { - console.log('success'); return { ...state, isLoading: false, @@ -169,7 +167,6 @@ const reducer = (state, action) => { } if (action.type === GET_SUBSCRIPTIONS_ERROR) { - console.log('error'); return { ...state, isLoading: false, diff --git a/client/src/pages/dashboard/Active.jsx b/client/src/pages/dashboard/Active.jsx index 8d007bb..d8ac609 100644 --- a/client/src/pages/dashboard/Active.jsx +++ b/client/src/pages/dashboard/Active.jsx @@ -3,8 +3,6 @@ import { useAppContext } from '../../context/appContext'; import '../../assets/styles/search.scss'; const Active = () => { - const { isLoading, alert, showAlert } = useAppContext(); - console.log({isLoading, alert, showAlert}); return (
    Active Subscriptions
    diff --git a/client/src/pages/dashboard/Trial.jsx b/client/src/pages/dashboard/Trial.jsx index 6e2fc21..190d0e5 100644 --- a/client/src/pages/dashboard/Trial.jsx +++ b/client/src/pages/dashboard/Trial.jsx @@ -3,8 +3,6 @@ import { useAppContext } from '../../context/appContext'; import '../../assets/styles/search.scss'; const Trial = () => { - const { isLoading, alert, showAlert } = useAppContext(); - console.log({isLoading, alert, showAlert}); return (
    Trial Subscriptions
    diff --git a/package-lock.json b/package-lock.json index 653b327..deffc0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2286,8 +2286,7 @@ "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", - "dev": true + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, "node_modules/@types/parse-json": { "version": "4.0.0", diff --git a/server/src/Config/google.ts b/server/src/Config/google.ts index b4ea1a8..d87f447 100644 --- a/server/src/Config/google.ts +++ b/server/src/Config/google.ts @@ -6,7 +6,7 @@ import { VerifyCallback, } from 'passport-google-oauth20'; import generator from 'generate-password'; -import BadRequestError from 'src/errors/bad-request.js'; +import { BadRequestError } from '../errors/index.js'; const randomPw: string = generator.generate({ length: 10, diff --git a/server/src/controllers/subscriptionsController.js b/server/src/controllers/subscriptionsController.js index fb790df..252323c 100644 --- a/server/src/controllers/subscriptionsController.js +++ b/server/src/controllers/subscriptionsController.js @@ -32,6 +32,7 @@ const deleteSubscription = async (req, res) => { }; const getSubscriptions = async (req, res) => { + const user = req.user.id; const filter = { user, diff --git a/server/src/server.ts b/server/src/server.ts index c2c192d..c7cccb5 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -5,6 +5,7 @@ import session from 'express-session'; import passport from 'passport'; import passportConfig from './Config/google.js'; import morgan from 'morgan'; +import authenticateUser from './middleware/auth.js'; // db and authenticateUser import connectDB from './db/connect.js'; @@ -55,7 +56,7 @@ app.get('/', (_req: Request, res: Response, _next: NextFunction) => { // Register the authRouter and subscriptionsRouter to their respective endpoints. app.use('/api/v1/auth', authRouter); -app.use('/api/v1/subscriptions', subscriptionsRouter); +app.use('/api/v1/subscriptions', authenticateUser, subscriptionsRouter); app.use(notFoundMiddleware); app.use(errorHandlerMiddleware);