From 28d115af0e1714d1e13afa5af1e793754622bf92 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 22 May 2022 01:07:10 +0200 Subject: [PATCH] (feat): feather icons ratings --- clientapp/src/App.tsx | 5 +- .../components/FeatherIcons/FeatherIcon.tsx | 5 +- .../src/components/FeatherRating/index.tsx | 57 +++++++++++++++++++ .../components/FeatherRating/interfaces.ts | 36 ++++++++++++ clientapp/src/functions/getKeyValue.ts | 2 +- clientapp/src/functions/jwtDecode/atob.ts | 46 +++++++++++++++ .../functions/jwtDecode/base64_url_decode.ts | 34 +++++++++++ clientapp/src/functions/jwtDecode/index.ts | 42 ++++++++++++++ clientapp/src/interfaces/index.ts | 5 ++ clientapp/src/pages/Blog/Catalog/index.tsx | 45 ++------------- clientapp/src/pages/Blog/Item/index.tsx | 53 +++-------------- .../src/pages/Blog/SideWidgets/index.tsx | 52 ++++++++++++++++- clientapp/src/pages/Home/index.tsx | 41 ++++++------- clientapp/src/pages/Shop/Catalog/index.tsx | 55 ++++++++++++------ 14 files changed, 346 insertions(+), 132 deletions(-) create mode 100644 clientapp/src/components/FeatherRating/index.tsx create mode 100644 clientapp/src/components/FeatherRating/interfaces.ts create mode 100644 clientapp/src/functions/jwtDecode/atob.ts create mode 100644 clientapp/src/functions/jwtDecode/base64_url_decode.ts create mode 100644 clientapp/src/functions/jwtDecode/index.ts diff --git a/clientapp/src/App.tsx b/clientapp/src/App.tsx index bc91557..cc5a6b9 100644 --- a/clientapp/src/App.tsx +++ b/clientapp/src/App.tsx @@ -1,7 +1,8 @@ +// React import React, { FC, useEffect } from 'react' import { Route, Routes, useLocation } from 'react-router' -//Redux +// Redux import { useSelector, useDispatch } from 'react-redux' import { actionCreators as settingsActionCreators } from './store/reducers/Settings' @@ -33,8 +34,6 @@ const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) => }) } - - const App: FC = () => { const { pathname } = useLocation() const dispatch = useDispatch() diff --git a/clientapp/src/components/FeatherIcons/FeatherIcon.tsx b/clientapp/src/components/FeatherIcons/FeatherIcon.tsx index 59aa0dc..26aa5da 100644 --- a/clientapp/src/components/FeatherIcons/FeatherIcon.tsx +++ b/clientapp/src/components/FeatherIcons/FeatherIcon.tsx @@ -6,6 +6,7 @@ interface IFeatherIcon { size?: string | number, className?: string, fill?: string, + stroke?: string, otherProps?: any } @@ -17,7 +18,7 @@ interface IFeatherIcon { */ const FeatherIcon : FC = (props : IFeatherIcon) => { - const { icon, size = 24, className = '', fill = 'none', otherProps } = props + const { icon, size = 24, className = '', fill = 'none', stroke = 'black', otherProps } = props if (!icon) { return null @@ -29,7 +30,7 @@ const FeatherIcon : FC = (props : IFeatherIcon) => { height={size} viewBox="0 0 24 24" fill={fill} - stroke="currentColor" + stroke={stroke} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" diff --git a/clientapp/src/components/FeatherRating/index.tsx b/clientapp/src/components/FeatherRating/index.tsx new file mode 100644 index 0000000..68ef0cc --- /dev/null +++ b/clientapp/src/components/FeatherRating/index.tsx @@ -0,0 +1,57 @@ +import React, { FC } from 'react' + +import { FeatherIcon } from '../FeatherIcons' +import { ICreateIconProps, ICreateIconResponse, IFeatherRating } from './interfaces' + +const FeatherRating: FC = ({ + value, + icons = { + complete: { icon: "star", color: '#ffbf00' }, + half: { icon: "star", color: '#ffdf80' }, + empty: { icon: "star", color: '#ffffff' } + }, + max = 5, +}) => { + const createIcon = ({ value, icons, max }: ICreateIconProps): ICreateIconResponse[] => { + let i = +value + + const iconsToPrint: ICreateIconResponse[] = [] + const { complete, half, empty } = icons + + for (i; i >= 1; i -= 1) { + iconsToPrint.push({ + icon: complete.icon, + fill: complete.color, + stroke: complete.color + }) + } + + if (i >= 0.5 && i <= 1) { + iconsToPrint.push({ + icon: half.icon, + fill: half.color, + stroke: half.color + }) + } + + while (iconsToPrint.length < max) { + iconsToPrint.push({ + icon: empty.icon, + fill: empty.color, + stroke: empty.color + }) + } + + return iconsToPrint + } + + return
+ {createIcon({ value, icons, max }).map((icon, i) => + + )} +
+} + +export { + FeatherRating +} diff --git a/clientapp/src/components/FeatherRating/interfaces.ts b/clientapp/src/components/FeatherRating/interfaces.ts new file mode 100644 index 0000000..4ef8c7d --- /dev/null +++ b/clientapp/src/components/FeatherRating/interfaces.ts @@ -0,0 +1,36 @@ +export interface IIcon { + icon: string, + color: string +} + +export interface IAllowIconsType { + complete: IIcon + half: IIcon + empty: IIcon +} + +export interface IFeatherRating { + value: number + icons?: IAllowIconsType + max?: number +} + + +export interface ICreateIconProps { + value: number + icons: IAllowIconsType + max: number +} + +export interface ICreateIconResponse { + icon: string + fill: string + stroke: string +} + +export interface ICustomStylesProps { + current: string + icons: IAllowIconsType + colors: string[] + type: string +} diff --git a/clientapp/src/functions/getKeyValue.ts b/clientapp/src/functions/getKeyValue.ts index a192f83..ba27f42 100644 --- a/clientapp/src/functions/getKeyValue.ts +++ b/clientapp/src/functions/getKeyValue.ts @@ -1,4 +1,4 @@ -const getKeyValue = (key: U) => (obj: T) => obj[key]; +const getKeyValue = (key: U) => (obj: T) => obj[key] export { getKeyValue diff --git a/clientapp/src/functions/jwtDecode/atob.ts b/clientapp/src/functions/jwtDecode/atob.ts new file mode 100644 index 0000000..41c923e --- /dev/null +++ b/clientapp/src/functions/jwtDecode/atob.ts @@ -0,0 +1,46 @@ +/** + * The code was extracted from: + * https://github.com/davidchambers/Base64.js + */ + +var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + +class InvalidCharacterError extends Error { + message: string + + constructor(message: string) { + super(message) + + // Set the prototype explicitly. + Object.setPrototypeOf(this, InvalidCharacterError.prototype) + + this.message = message + this.name = 'InvalidCharacterError' + } +} + + +const polyfill = (input: any) => { + var str = String(input).replace(/=+$/, '') + if (str.length % 4 == 1) { + throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.") + } + for ( + // initialize result and counters + var bc = 0, bs = 0, buffer, idx = 0, output = ''; + // get next character + buffer = str.charAt(idx++); + // character found in table? initialize bit storage and add its ascii value; + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + // and if not first of each 4 characters, + // convert the first 8 bits to one ascii character + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + // try to find character in table (0-63, not found => -1) + buffer = chars.indexOf(buffer) + } + return output +} + +export default typeof window !== 'undefined' && window.atob && window.atob.bind(window) || polyfill + \ No newline at end of file diff --git a/clientapp/src/functions/jwtDecode/base64_url_decode.ts b/clientapp/src/functions/jwtDecode/base64_url_decode.ts new file mode 100644 index 0000000..bbfb5d4 --- /dev/null +++ b/clientapp/src/functions/jwtDecode/base64_url_decode.ts @@ -0,0 +1,34 @@ +import atob from './atob' + +const b64DecodeUnicode = (str: string) => { + return decodeURIComponent(atob(str).replace(/(.)/g, function (m, p) { + var code = p.charCodeAt(0).toString(16).toUpperCase() + if (code.length < 2) { + code = '0' + code + } + return '%' + code + })) +} + +export default (str: string) => { + var output = str.replace(/-/g, '+').replace(/_/g, '/') + switch (output.length % 4) { + case 0: + break + case 2: + output += '==' + break + case 3: + output += '=' + break + default: + throw 'Illegal base64url string!' + } + + try { + return b64DecodeUnicode(output) + } catch (err) { + return atob(output) + } +} + diff --git a/clientapp/src/functions/jwtDecode/index.ts b/clientapp/src/functions/jwtDecode/index.ts new file mode 100644 index 0000000..322da46 --- /dev/null +++ b/clientapp/src/functions/jwtDecode/index.ts @@ -0,0 +1,42 @@ +'use strict' + +import base64_url_decode from './base64_url_decode' + +class InvalidTokenError extends Error { + message: string + + constructor(message: string) { + super(message) + + // Set the prototype explicitly. + Object.setPrototypeOf(this, InvalidTokenError.prototype) + + this.message = message + this.name = 'InvalidTokenError' + } +} + +interface IOptions { + header: boolean | undefined +} + +const JwtDecode = function (token: string, options: IOptions) { + if (typeof token !== 'string') { + throw new InvalidTokenError('Invalid token specified') + } + + options = options || {} + var pos = options.header === true ? 0 : 1 + try { + return JSON.parse(base64_url_decode(token.split('.')[pos])) + } catch (e) { + if (e instanceof Error) { + throw new InvalidTokenError('Invalid token specified: ' + e.message) + } + } +} + +JwtDecode.InvalidTokenError = InvalidTokenError + +export default JwtDecode + diff --git a/clientapp/src/interfaces/index.ts b/clientapp/src/interfaces/index.ts index 2c897da..cc44dbd 100644 --- a/clientapp/src/interfaces/index.ts +++ b/clientapp/src/interfaces/index.ts @@ -20,4 +20,9 @@ export interface IMenuItem extends ISubMenuItem { items?: ISubMenuItem [] } +export interface IImage { + src: string, + alt: string +} + diff --git a/clientapp/src/pages/Blog/Catalog/index.tsx b/clientapp/src/pages/Blog/Catalog/index.tsx index 44a839c..c12ebb7 100644 --- a/clientapp/src/pages/Blog/Catalog/index.tsx +++ b/clientapp/src/pages/Blog/Catalog/index.tsx @@ -1,6 +1,7 @@ import React from 'react' import { Link } from 'react-router-dom' import { Card, CardBody, CardHeader, CardImg, Col, Container, Row } from 'reactstrap' +import { Categories, Empty, Search } from '../SideWidgets' const BlogCatalog = () => { const items = [ @@ -46,7 +47,7 @@ const BlogCatalog = () => { - {items.map((item, index) => + {items.map((item, index) => @@ -79,44 +80,10 @@ const BlogCatalog = () => { - - - - Search - -
- - -
-
-
- - - Categories - - - - - - - - - - - - - - Side Widget - You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component! - + + + + diff --git a/clientapp/src/pages/Blog/Item/index.tsx b/clientapp/src/pages/Blog/Item/index.tsx index ffc15d1..19e0093 100644 --- a/clientapp/src/pages/Blog/Item/index.tsx +++ b/clientapp/src/pages/Blog/Item/index.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Card, CardBody, CardHeader, Col, Container, Row } from 'reactstrap' +import { Categories, Empty, Search } from '../SideWidgets' const BlogItem = () => { @@ -8,9 +9,9 @@ const BlogItem = () => { comments: [] } - return + return - +
@@ -41,9 +42,8 @@ const BlogItem = () => {
-
- - {/**/} + +
@@ -84,45 +84,10 @@ const BlogItem = () => { - - - Search - -
- - -
-
-
- - - Categories - - - - - - - - - - - - - - Side Widget - - You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component! - - + + + + diff --git a/clientapp/src/pages/Blog/SideWidgets/index.tsx b/clientapp/src/pages/Blog/SideWidgets/index.tsx index 0387bb8..74d72ab 100644 --- a/clientapp/src/pages/Blog/SideWidgets/index.tsx +++ b/clientapp/src/pages/Blog/SideWidgets/index.tsx @@ -1,5 +1,53 @@ import React from 'react' +import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap' -const SideWidgets = () => { - return null +const Search = () => { + return + Search + +
+ + +
+
+
+} + +const Categories = () => { + return + Categories + + + + + + + + + + + +} + +const Empty = () => { + return + Side Widget + + You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component! + + +} + +export { + Search, + Categories, + Empty } \ No newline at end of file diff --git a/clientapp/src/pages/Home/index.tsx b/clientapp/src/pages/Home/index.tsx index 00583c2..9880905 100644 --- a/clientapp/src/pages/Home/index.tsx +++ b/clientapp/src/pages/Home/index.tsx @@ -2,32 +2,10 @@ import React, { FC } from 'react' import { Link } from 'react-router-dom' import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap' import { FeatherIcon } from '../../components/FeatherIcons' +import { IImage } from '../../interfaces' import style from './scss/style.module.scss' - -interface IImage { - src: string, - alt: string -} - -interface IBlogAuthor { - name: string, - image: IImage -} - -interface IBlogItem { - image: IImage, - badge: string, - title: string, - text: string, - author: IBlogAuthor, - date: string, - readTime: string -} - - - interface ITitleSection { title: string, text: string @@ -120,6 +98,21 @@ const TestimonialsSection: FC = (props) => {
} +interface IBlogAuthor { + name: string, + image: IImage +} + +interface IBlogItem { + image: IImage, + badge: string, + title: string, + text: string, + author: IBlogAuthor, + date: string, + readTime: string +} + interface IFromOurBlogSection { title: string, text: string, @@ -205,7 +198,7 @@ const Home = () => { ` } diff --git a/clientapp/src/pages/Shop/Catalog/index.tsx b/clientapp/src/pages/Shop/Catalog/index.tsx index ea8c9e8..86c1949 100644 --- a/clientapp/src/pages/Shop/Catalog/index.tsx +++ b/clientapp/src/pages/Shop/Catalog/index.tsx @@ -2,32 +2,55 @@ import * as React from 'react' import { Link } from 'react-router-dom' import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap' +import { FeatherRating } from '../../../components/FeatherRating' + const ShopCatalog = () => { const items = [ { - id: "1" + id: "1", + rating: 5, + price: "$20.00" }, { - id: "2" + id: "2", + rating: 3.5, + price: "$20.00", + newPrice: "$10.00" }, { - id: "3" + id: "3", + rating: 2, + price: "$20.00", + newPrice: "$10.00" }, { - id: "4" + id: "4", + rating: 4, + price: "$20.00" }, { - id: "5" + id: "5", + rating: 4.5, + price: "$20.00", + newPrice: "$10.00" }, { - id: "6" + id: "6", + rating: 5, + price: "$20.00", + newPrice: "$10.00" }, { - id: "7" + id: "7", + rating: 2, + price: "$20.00" }, { - id: "8" + id: "8", + rating: 3, + price: "$20.00", + newPrice: "$10.00" } ] @@ -55,16 +78,14 @@ const ShopCatalog = () => {
Fancy Product
-
-
-
-
-
-
-
+ + + {item.newPrice + ? <>{item.price} {item.newPrice} + : item.price} - $20.00 - $10.00