= ({ children }) => {
8 | return (
9 |
10 | { children}
11 |
12 | );
13 | };
14 |
15 | export default LoginLayout;
16 |
--------------------------------------------------------------------------------
/web-app/components/Material/ErrorMessage.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import Button from '@material-ui/core/Button';
5 | import Snackbar from '@material-ui/core/Snackbar';
6 | import IconButton from '@material-ui/core/IconButton';
7 | import CloseIcon from '@material-ui/icons/Close';
8 |
9 | const ErrorStyles = styled.div`
10 | padding: 2rem;
11 | background: white;
12 | margin: 2rem 0;
13 | border: 1px solid rgba(0, 0, 0, 0.05);
14 | border-left: 5px solid red;
15 | p {
16 | margin: 0;
17 | font-weight: 100;
18 | }
19 | strong {
20 | margin-right: 1rem;
21 | }
22 | `;
23 |
24 | const DisplayError = ({ error }) => {
25 |
26 | const [open, setOpen] = React.useState(true);
27 |
28 | const handleClose = (event, reason) => {
29 | if (reason === 'clickaway') {
30 | return;
31 | }
32 | setOpen(false);
33 | };
34 |
35 | if (!error || !error.message) return null;
36 | if (error.networkError && error.networkError.result && error.networkError.result.errors.length) {
37 | return error.networkError.result.errors.map((error, i) => (
38 |
39 |
40 | Shoot!
41 | {error.message.replace('GraphQL error: ', '')}
42 |
43 |
44 | ));
45 | }
46 | return (
47 |
48 |
49 | Shoot!
50 | {error.message.replace('GraphQL error: ', '')}
51 |
52 |
63 |
66 |
67 |
68 |
69 |
70 | }
71 | />
72 |
73 | );
74 | };
75 |
76 | DisplayError.defaultProps = {
77 | error: {},
78 | };
79 |
80 | DisplayError.propTypes = {
81 | error: PropTypes.object,
82 | };
83 |
84 | export default DisplayError;
85 |
--------------------------------------------------------------------------------
/web-app/components/Material/Snackbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import Snackbar from '@material-ui/core/Snackbar';
4 | import IconButton from '@material-ui/core/IconButton';
5 | import CloseIcon from '@material-ui/icons/Close';
6 |
7 | export default function ShowSnackbar() {
8 |
9 | const [open, setOpen] = React.useState(true);
10 | setOpen(true);
11 | const handleClose = (event: React.SyntheticEvent | React.MouseEvent, reason?: string) => {
12 | if (reason === 'clickaway') {
13 | return;
14 | }
15 |
16 | setOpen(false);
17 | };
18 |
19 | return (
20 |
21 |
32 |
35 |
36 |
37 |
38 |
39 | }
40 | />
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/web-app/components/Material/SuccessMessage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Snackbar from '@material-ui/core/Snackbar';
3 | import MuiAlert from '@material-ui/lab/Alert';
4 | import { useApolloClient, useQuery, useMutation } from '@apollo/react-hooks';
5 | import { SNACKBAR_STATE_QUERY } from '../../graphql/queries';
6 | // import { TOGGLE_SNACKBAR_MUTATION } from '../../graphql/mutations';
7 | import gql from 'graphql-tag';
8 |
9 | function Alert(props) {
10 | return ;
11 | }
12 |
13 | const Message = () => {
14 | const { data, error, loading } = useQuery(SNACKBAR_STATE_QUERY);
15 | const [toggleSnackBar] = useMutation(TOGGLE_SNACKBAR_MUTATION);
16 | if (!loading && data) {
17 | return (
18 | toggleSnackBar({ variables: { msg: '', type: 'success' } })}
26 | > toggleSnackBar({ variables: { msg: '', type: 'success' } })}
28 | severity={data.snackType}>
29 | {data.snackMsg}
30 |
31 | )
32 | } else {
33 | return <>>
34 | }
35 | };
36 |
37 | const TOGGLE_SNACKBAR_MUTATION = gql`
38 | mutation toggleSnackBar{
39 | toggleSnackBar(msg: $msg, type: $type) @client
40 | }
41 | `;
42 | export default Message;
43 | export { SNACKBAR_STATE_QUERY, TOGGLE_SNACKBAR_MUTATION };
--------------------------------------------------------------------------------
/web-app/components/Product/AddProductStyle.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | makeStyles,
3 | createStyles,
4 | Theme,
5 | } from '@material-ui/core'
6 |
7 | export const useStyles = makeStyles((theme: Theme) =>
8 | createStyles({
9 | root: {
10 | display: 'block',
11 | margin: '0 auto',
12 | },
13 | uploadButton: {
14 | paddingLeft: '20px',
15 | paddingRight: '20px'
16 | },
17 | textField: {
18 | '& > *': {
19 | width: '100%',
20 | },
21 | },
22 | submitButton: {
23 | marginTop: '24px',
24 | textAlign: 'right'
25 | },
26 | FileContainer: {
27 | display: 'flex',
28 | flexDirection: 'column',
29 | fontFamily: 'sans-serif'
30 | },
31 | thumbsContainer: {
32 | display: 'flex',
33 | flexDirection: 'row',
34 | flexWrap: 'wrap',
35 | marginTop: 16
36 | },
37 | thumb: {
38 | display: 'inline-flex',
39 | borderRadius: 2,
40 | border: '1px solid #eaeaea',
41 | marginBottom: 8,
42 | marginRight: 8,
43 | width: 100,
44 | height: 100,
45 | padding: 4,
46 | boxSizing: 'border-box'
47 | },
48 | thumbInner: {
49 | display: 'flex',
50 | minWidth: 0,
51 | overflow: 'hidden'
52 | },
53 | img: {
54 | display: 'block',
55 | width: 'auto',
56 | height: '100%'
57 | },
58 | media: {
59 | padding: '0 5px',
60 | height: '100px',
61 | textAlign: 'center',
62 | marginTop: '10px',
63 | margin: '0 auto',
64 | },
65 | icon: {
66 | color: 'rgba(255, 255, 255, 0.54)',
67 | },
68 | gridList: {
69 | width: 350,
70 | },
71 | titleBar: {
72 | background:
73 | 'linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.3) 70%, rgba(0,0,0,0) 100%)',
74 | },
75 | })
76 | )
--------------------------------------------------------------------------------
/web-app/components/Product/ProductMutation.ts:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | export const CREATE_PRODUCT = gql`
3 | mutation createProduct(
4 | $name: String!
5 | $description: String!
6 | $price: Int!
7 | $discount: Int!
8 | $salePrice: Int!
9 | $sku: String!
10 | $unit: String!
11 | $categoryId: ID!
12 | $images: [ProductImageCreateWithoutProductInput!]!
13 |
14 | ) {
15 | createProduct(
16 | name: $name
17 | description: $description
18 | price: $price
19 | discount: $discount
20 | salePrice: $salePrice
21 | sku: $sku
22 | unit: $unit
23 | categoryId: $categoryId
24 | images: $images
25 | ) {
26 | id
27 | name
28 | description
29 | price
30 | discount
31 | salePrice
32 | sku
33 | unit
34 | Category {
35 | name
36 | parent
37 | }
38 | ProductImages {
39 | image
40 | }
41 | }
42 | }
43 | `;
44 |
45 | export const UPDATE_PRODUCT_MUTATION = gql`
46 | mutation UPDATE_PRODUCT_MUTATION (
47 | $id: ID!
48 | $name: String!
49 | $description: String!
50 | $price: Int!
51 | $discount: Int!
52 | $salePrice: Int!
53 | $sku: String!
54 | $unit: String!
55 | $categoryId: ID!
56 | $images: [ProductImageCreateWithoutProductInput!]!
57 | $alreadyUploadedImages: [ProductImageCreateWithoutProductInput!]!
58 | ) {
59 | updateProduct(
60 | id: $id
61 | name: $name
62 | description: $description
63 | discount: $discount
64 | salePrice: $salePrice
65 | sku: $sku
66 | price: $price
67 | unit: $unit
68 | categoryId: $categoryId
69 | images: $images
70 | alreadyUploadedImages: $alreadyUploadedImages
71 | ) {
72 | id
73 | name
74 | description
75 | price
76 | discount
77 | salePrice
78 | sku
79 | unit
80 | Category {
81 | name
82 | parent
83 | }
84 | ProductImages {
85 | id
86 | image
87 | }
88 | }
89 | }
90 | `;
--------------------------------------------------------------------------------
/web-app/components/Product/ProductQuery.ts:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const GET_PRODUCTS = gql`
4 | query products(
5 | $orderBy: ProductOrderByInput = {updatedAt: desc},
6 | $first: Int=1,
7 | $skip: Int,
8 | $nameQuery: String,
9 | $discount: String
10 | ) {
11 | products(first: $first, skip: $skip, orderBy: $orderBy, nameQuery: $nameQuery, discountRange: $discount) {
12 | nodes {
13 | id
14 | name
15 | price
16 | discount
17 | salePrice
18 | sku
19 | unit
20 | description
21 | Category {
22 | id
23 | name
24 | parent
25 | }
26 | ProductImages{
27 | id
28 | image
29 | }
30 |
31 | }
32 | totalCount
33 | }
34 | }
35 | `;
--------------------------------------------------------------------------------
/web-app/components/Product/ProductStyle.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
2 | import { red } from '@material-ui/core/colors';
3 |
4 | export const useStyles = makeStyles((theme: Theme) =>
5 | createStyles({
6 | root: {
7 | width: 200,
8 | },
9 | formControl: {
10 | margin: theme.spacing(1),
11 | minWidth: 120,
12 | },
13 | title: {
14 | fontSize: '14px',
15 | height: '35px',
16 | overflow: 'hidden',
17 | lineHeight: '1.3em',
18 | textOverflow: 'ellipsis',
19 | color: '#161f6a',
20 | // color: theme.palette.primary.dark,
21 | fontWeight: theme.typography.fontWeightMedium,
22 | textAlign: 'center'
23 | },
24 | subheader: {
25 | fontSize: '12px',
26 | marginTop: '5px',
27 | textAlign: 'center'
28 | },
29 | cardContent: {
30 | paddingTop: 0,
31 | },
32 | discountedPrice: {
33 | paddingTop: 0,
34 | color: theme.palette.primary.dark
35 | },
36 | price: {
37 | marginLeft: '10px',
38 | color: theme.palette.grey[500],
39 | fontSize: '12px',
40 | textDecoration: 'line-through'
41 | },
42 | media: {
43 | // height: '240px',
44 | height: '120px',
45 | width: 120,
46 | textAlign: 'center',
47 | marginTop: '10px',
48 | margin: '0 auto',
49 | // paddingTop: '56.25%', // 16:9
50 | },
51 | expand: {
52 | transform: 'rotate(0deg)',
53 | marginLeft: 'auto',
54 | transition: theme.transitions.create('transform', {
55 | duration: theme.transitions.duration.shortest,
56 | }),
57 | },
58 | expandOpen: {
59 | transform: 'rotate(180deg)',
60 | },
61 | avatar: {
62 | backgroundColor: red[500],
63 | },
64 | parentImageContainer: {
65 | position: 'relative'
66 | },
67 | discountInPercent: {
68 | // ...$theme.typography.fontBold12,
69 | color: '#ffffff',
70 | lineHeight: '1.7',
71 | backgroundColor: theme.palette.secondary.main,
72 | paddingLeft: '7px',
73 | paddingRight: '7px',
74 | display: 'inline-block',
75 | position: 'absolute',
76 | bottom: '10px',
77 | right: '0',
78 | fontSize: '12px',
79 | fontWeight: theme.typography.fontWeightMedium,
80 | '&::before': {
81 | content: '""',
82 | position: 'absolute',
83 | left: '-8px',
84 | top: '0',
85 | width: '0',
86 | height: '0',
87 | borderStyle: 'solid',
88 | borderWidth: '0 8px 12px 0',
89 | borderColor: `transparent ${theme.palette.secondary.main} transparent transparent`,
90 | },
91 |
92 | '&::after': {
93 | content: '""',
94 | position: 'absolute',
95 | left: '-8px',
96 | bottom: ' 0',
97 | width: ' 0',
98 | height: '0',
99 | borderStyle: 'solid',
100 | borderWidth: '0 0 12px 8px',
101 | borderColor: `transparent transparent ${theme.palette.secondary.main}`,
102 | },
103 | },
104 | appBar: {
105 | position: 'relative',
106 | },
107 | dialogTitle: {
108 | marginLeft: theme.spacing(2),
109 | flex: 1,
110 | },
111 | drawerOpen: {
112 | color: 'red'
113 | },
114 | drawerClose: {
115 | position: 'relative!important' as any
116 | },
117 | newProduct: {
118 | bottom: theme.spacing(2),
119 | position: 'fixed',
120 | right: theme.spacing(2)
121 | },
122 | filterBox: {
123 | // marginTop: theme.spacing(2),
124 | marginBottom: theme.spacing(2),
125 | // padding: theme.spacing(2)
126 | },
127 | pagination: {
128 | padding: theme.spacing(2)
129 | },
130 | searchInput: {
131 | padding: theme.spacing(1)
132 | },
133 | searchBar: {
134 | background: theme.palette.grey[100],
135 | borderWidth: '1px',
136 | boxShadow: theme.shadows[0],
137 | marginBottom: theme.spacing(2),
138 | borderColor: theme.palette.primary.main,
139 | borderRadius: '5px',
140 | },
141 | searchLegend: {
142 | ...theme.typography.subtitle1,
143 | background: theme.palette.primary.main,
144 | color: theme.palette.primary.contrastText,
145 | paddingLeft: theme.spacing(1),
146 | paddingRight: theme.spacing(1),
147 | // borderRadius: '10%',
148 | fontWeight: theme.typography.fontWeightRegular
149 | }
150 | }),
151 | );
--------------------------------------------------------------------------------
/web-app/components/User/UserMutation.ts:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const UPDATE_USER_MUTATION = gql`
4 | mutation UPDATE_USER_MUTATION (
5 | $id: ID!
6 | $name: String!
7 | $status: String!
8 | $role: String!
9 | ) {
10 | updateUser(
11 | id: $id
12 | name: $name
13 | status: $status
14 | role: $role
15 | ) {
16 | id
17 | name
18 | email
19 | status
20 | role
21 | }
22 | }
23 | `;
24 |
25 | export const DELETE_USER_MUTATION = gql`
26 | mutation DELETE_USER_MUTATION($id: ID!) {
27 | deleteAccount(id: $id) {
28 | id
29 | }
30 | }
31 | `;
--------------------------------------------------------------------------------
/web-app/components/User/UserQuery.ts:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const USER_PERMISSIONS = gql`
4 |
5 | query users(
6 | $orderBy: QueryUsersOrderByInput = { name: desc }
7 | $first: Int = 10000
8 | $skip: Int
9 | ) {
10 | users(first: $first, skip: $skip, orderBy: $orderBy) {
11 | nodes {
12 | id,
13 | name,
14 | email,
15 | status,
16 | role
17 | }
18 | totalCount
19 | }
20 | }
21 |
22 | `;
--------------------------------------------------------------------------------
/web-app/graphql/generated/CompleteOnboardingMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: CompleteOnboardingMutation
8 | // ====================================================
9 |
10 | export interface CompleteOnboardingMutation {
11 | completeOnboarding: boolean;
12 | }
13 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/CurrentUserQuery.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { User_role } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL query operation: CurrentUserQuery
10 | // ====================================================
11 |
12 | export interface CurrentUserQuery_me {
13 | __typename: "User";
14 | id: string;
15 | email: string;
16 | name: string;
17 | role: User_role;
18 | hasVerifiedEmail: boolean | null;
19 | hasCompletedOnboarding: boolean;
20 | }
21 |
22 | export interface CurrentUserQuery {
23 | me: CurrentUserQuery_me | null;
24 | }
25 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/DELETE_CATEGORY_MUTATION.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: DELETE_CATEGORY_MUTATION
8 | // ====================================================
9 |
10 | export interface DELETE_CATEGORY_MUTATION_deleteCategory {
11 | __typename: "Category";
12 | id: string;
13 | }
14 |
15 | export interface DELETE_CATEGORY_MUTATION {
16 | deleteCategory: DELETE_CATEGORY_MUTATION_deleteCategory;
17 | }
18 |
19 | export interface DELETE_CATEGORY_MUTATIONVariables {
20 | id: string;
21 | }
22 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/DELETE_USER_MUTATION.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: DELETE_USER_MUTATION
8 | // ====================================================
9 |
10 | export interface DELETE_USER_MUTATION_deleteAccount {
11 | __typename: "User";
12 | id: string;
13 | }
14 |
15 | export interface DELETE_USER_MUTATION {
16 | deleteAccount: DELETE_USER_MUTATION_deleteAccount;
17 | }
18 |
19 | export interface DELETE_USER_MUTATIONVariables {
20 | id: string;
21 | }
22 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/DeleteAccountMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: DeleteAccountMutation
8 | // ====================================================
9 |
10 | export interface DeleteAccountMutation_deleteAccount {
11 | __typename: "User";
12 | id: string;
13 | }
14 |
15 | export interface DeleteAccountMutation {
16 | deleteAccount: DeleteAccountMutation_deleteAccount;
17 | }
18 |
19 | export interface DeleteAccountMutationVariables {
20 | id: string;
21 | }
22 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/LoginMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: LoginMutation
8 | // ====================================================
9 |
10 | export interface LoginMutation_login {
11 | __typename: "User";
12 | email: string;
13 | }
14 |
15 | export interface LoginMutation {
16 | login: LoginMutation_login;
17 | }
18 |
19 | export interface LoginMutationVariables {
20 | email: string;
21 | password: string;
22 | }
23 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/LogoutMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: LogoutMutation
8 | // ====================================================
9 |
10 | export interface LogoutMutation {
11 | logout: boolean;
12 | }
13 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/RequestResetPasswordMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: RequestResetPasswordMutation
8 | // ====================================================
9 |
10 | export interface RequestResetPasswordMutation {
11 | requestPasswordReset: boolean;
12 | }
13 |
14 | export interface RequestResetPasswordMutationVariables {
15 | email: string;
16 | }
17 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/ResetPasswordMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: ResetPasswordMutation
8 | // ====================================================
9 |
10 | export interface ResetPasswordMutation_resetPassword {
11 | __typename: "User";
12 | email: string;
13 | }
14 |
15 | export interface ResetPasswordMutation {
16 | resetPassword: ResetPasswordMutation_resetPassword;
17 | }
18 |
19 | export interface ResetPasswordMutationVariables {
20 | resetToken: string;
21 | password: string;
22 | confirmPassword: string;
23 | }
24 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/SignupMutation.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: SignupMutation
8 | // ====================================================
9 |
10 | export interface SignupMutation_signup {
11 | __typename: "User";
12 | email: string;
13 | }
14 |
15 | export interface SignupMutation {
16 | signup: SignupMutation_signup;
17 | }
18 |
19 | export interface SignupMutationVariables {
20 | name: string;
21 | email: string;
22 | password: string;
23 | confirmPassword: string;
24 | }
25 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/UPDATE_CATEGORY_MUTATION.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: UPDATE_CATEGORY_MUTATION
8 | // ====================================================
9 |
10 | export interface UPDATE_CATEGORY_MUTATION_updateCategory {
11 | __typename: "Category";
12 | id: string;
13 | name: string;
14 | slug: string;
15 | parent: string;
16 | }
17 |
18 | export interface UPDATE_CATEGORY_MUTATION {
19 | updateCategory: UPDATE_CATEGORY_MUTATION_updateCategory;
20 | }
21 |
22 | export interface UPDATE_CATEGORY_MUTATIONVariables {
23 | id: string;
24 | name?: string | null;
25 | slug?: string | null;
26 | parent?: string | null;
27 | }
28 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/UPDATE_PRODUCT_MUTATION.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { ProductImageCreateWithoutProductInput } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL mutation operation: UPDATE_PRODUCT_MUTATION
10 | // ====================================================
11 |
12 | export interface UPDATE_PRODUCT_MUTATION_updateProduct_Category {
13 | __typename: "Category";
14 | name: string;
15 | parent: string;
16 | }
17 |
18 | export interface UPDATE_PRODUCT_MUTATION_updateProduct_ProductImages {
19 | __typename: "ProductImage";
20 | image: string;
21 | }
22 |
23 | export interface UPDATE_PRODUCT_MUTATION_updateProduct {
24 | __typename: "Product";
25 | id: string;
26 | name: string;
27 | description: string;
28 | price: number;
29 | discount: number;
30 | salePrice: number;
31 | sku: string;
32 | unit: string;
33 | Category: UPDATE_PRODUCT_MUTATION_updateProduct_Category;
34 | ProductImages: UPDATE_PRODUCT_MUTATION_updateProduct_ProductImages[];
35 | }
36 |
37 | export interface UPDATE_PRODUCT_MUTATION {
38 | updateProduct: UPDATE_PRODUCT_MUTATION_updateProduct;
39 | }
40 |
41 | export interface UPDATE_PRODUCT_MUTATIONVariables {
42 | id: string;
43 | name: string;
44 | description: string;
45 | price: number;
46 | discount: number;
47 | salePrice: number;
48 | sku: string;
49 | unit: string;
50 | categoryId: string;
51 | images: ProductImageCreateWithoutProductInput[];
52 | }
53 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/UPDATE_USER_MUTATION.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { User_status, User_role } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL mutation operation: UPDATE_USER_MUTATION
10 | // ====================================================
11 |
12 | export interface UPDATE_USER_MUTATION_updateUser {
13 | __typename: "User";
14 | id: string;
15 | name: string;
16 | email: string;
17 | status: User_status;
18 | role: User_role;
19 | }
20 |
21 | export interface UPDATE_USER_MUTATION {
22 | updateUser: UPDATE_USER_MUTATION_updateUser;
23 | }
24 |
25 | export interface UPDATE_USER_MUTATIONVariables {
26 | id: string;
27 | name: string;
28 | status: string;
29 | role: string;
30 | }
31 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/categories.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { QueryCategoriesOrderByInput } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL query operation: categories
10 | // ====================================================
11 |
12 | export interface categories_categories_nodes {
13 | __typename: "Category";
14 | id: string;
15 | slug: string;
16 | name: string;
17 | parent: string;
18 | }
19 |
20 | export interface categories_categories {
21 | __typename: "QueryCategories_Connection";
22 | /**
23 | * Flattened list of Category type
24 | */
25 | nodes: categories_categories_nodes[];
26 | totalCount: number;
27 | }
28 |
29 | export interface categories {
30 | categories: categories_categories;
31 | }
32 |
33 | export interface categoriesVariables {
34 | orderBy?: QueryCategoriesOrderByInput | null;
35 | first?: number | null;
36 | skip?: number | null;
37 | parentQuery?: string | null;
38 | }
39 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/createCategory.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: createCategory
8 | // ====================================================
9 |
10 | export interface createCategory_createCategory {
11 | __typename: "Category";
12 | id: string;
13 | name: string;
14 | parent: string;
15 | slug: string;
16 | }
17 |
18 | export interface createCategory {
19 | createCategory: createCategory_createCategory;
20 | }
21 |
22 | export interface createCategoryVariables {
23 | name: string;
24 | parent: string;
25 | slug: string;
26 | }
27 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/createProduct.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { ProductImageCreateWithoutProductInput } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL mutation operation: createProduct
10 | // ====================================================
11 |
12 | export interface createProduct_createProduct_Category {
13 | __typename: "Category";
14 | name: string;
15 | parent: string;
16 | }
17 |
18 | export interface createProduct_createProduct_ProductImages {
19 | __typename: "ProductImage";
20 | image: string;
21 | }
22 |
23 | export interface createProduct_createProduct {
24 | __typename: "Product";
25 | id: string;
26 | name: string;
27 | description: string;
28 | price: number;
29 | discount: number;
30 | salePrice: number;
31 | sku: string;
32 | unit: string;
33 | Category: createProduct_createProduct_Category;
34 | ProductImages: createProduct_createProduct_ProductImages[];
35 | }
36 |
37 | export interface createProduct {
38 | createProduct: createProduct_createProduct;
39 | }
40 |
41 | export interface createProductVariables {
42 | name: string;
43 | description: string;
44 | price: number;
45 | discount: number;
46 | salePrice: number;
47 | sku: string;
48 | unit: string;
49 | categoryId: string;
50 | images: ProductImageCreateWithoutProductInput[];
51 | }
52 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/graphql-global-types.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | //==============================================================
7 | // START Enums and Input Objects
8 | //==============================================================
9 |
10 | export enum OrderByArg {
11 | asc = "asc",
12 | desc = "desc",
13 | }
14 |
15 | export enum User_role {
16 | ADMIN = "ADMIN",
17 | MANAGER = "MANAGER",
18 | USER = "USER",
19 | }
20 |
21 | export enum User_status {
22 | ACTIVE = "ACTIVE",
23 | BLOCKED = "BLOCKED",
24 | INACTIVE = "INACTIVE",
25 | }
26 |
27 | export interface ProductImageCreateWithoutProductInput {
28 | createdAt?: any | null;
29 | id?: string | null;
30 | image: string;
31 | updatedAt?: any | null;
32 | }
33 |
34 | export interface ProductOrderByInput {
35 | name?: OrderByArg | null;
36 | price?: OrderByArg | null;
37 | updatedAt?: OrderByArg | null;
38 | }
39 |
40 | export interface QueryCategoriesOrderByInput {
41 | name?: OrderByArg | null;
42 | updatedAt?: OrderByArg | null;
43 | }
44 |
45 | export interface QueryUsersOrderByInput {
46 | name?: OrderByArg | null;
47 | status?: OrderByArg | null;
48 | }
49 |
50 | //==============================================================
51 | // END Enums and Input Objects
52 | //==============================================================
53 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/isLeftDrawerOpen.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: isLeftDrawerOpen
8 | // ====================================================
9 |
10 | export interface isLeftDrawerOpen {
11 | isLeftDrawerOpen: boolean;
12 | }
13 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/products.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { ProductOrderByInput } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL query operation: products
10 | // ====================================================
11 |
12 | export interface products_products_nodes_Category {
13 | __typename: "Category";
14 | id: string;
15 | name: string;
16 | parent: string;
17 | }
18 |
19 | export interface products_products_nodes_ProductImages {
20 | __typename: "ProductImage";
21 | image: string;
22 | }
23 |
24 | export interface products_products_nodes {
25 | __typename: "Product";
26 | id: string;
27 | name: string;
28 | price: number;
29 | discount: number;
30 | salePrice: number;
31 | sku: string;
32 | unit: string;
33 | description: string;
34 | Category: products_products_nodes_Category;
35 | ProductImages: products_products_nodes_ProductImages[];
36 | }
37 |
38 | export interface products_products {
39 | __typename: "QueryProducts_Connection";
40 | /**
41 | * Flattened list of Product type
42 | */
43 | nodes: products_products_nodes[];
44 | totalCount: number;
45 | }
46 |
47 | export interface products {
48 | products: products_products;
49 | }
50 |
51 | export interface productsVariables {
52 | orderBy?: ProductOrderByInput | null;
53 | first?: number | null;
54 | skip?: number | null;
55 | nameQuery?: string | null;
56 | discount?: string | null;
57 | }
58 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/snackbar.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: snackbar
8 | // ====================================================
9 |
10 | export interface snackbar {
11 | snackBarOpen: boolean | null;
12 | snackMsg: string | null;
13 | snackType: string | null;
14 | }
15 |
--------------------------------------------------------------------------------
/web-app/graphql/generated/users.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { QueryUsersOrderByInput, User_status, User_role } from "./graphql-global-types";
7 |
8 | // ====================================================
9 | // GraphQL query operation: users
10 | // ====================================================
11 |
12 | export interface users_users_nodes {
13 | __typename: "User";
14 | id: string;
15 | name: string;
16 | email: string;
17 | status: User_status;
18 | role: User_role;
19 | }
20 |
21 | export interface users_users {
22 | __typename: "QueryUsers_Connection";
23 | /**
24 | * Flattened list of User type
25 | */
26 | nodes: users_users_nodes[];
27 | totalCount: number;
28 | }
29 |
30 | export interface users {
31 | users: users_users;
32 | }
33 |
34 | export interface usersVariables {
35 | orderBy?: QueryUsersOrderByInput | null;
36 | first?: number | null;
37 | skip?: number | null;
38 | }
39 |
--------------------------------------------------------------------------------
/web-app/graphql/mutations/index.ts:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const deleteAccountMutation = gql`
4 | mutation DeleteAccountMutation($id: ID!) {
5 | deleteAccount(id: $id) {
6 | id
7 | }
8 | }
9 | `;
10 |
11 | export const loginMutation = gql`
12 | mutation LoginMutation($email: String!, $password: String!) {
13 | login(email: $email, password: $password) {
14 | email
15 | }
16 | }
17 | `;
18 |
19 | export const logoutMutation = gql`
20 | mutation LogoutMutation {
21 | logout
22 | }
23 | `;
24 |
25 | export const signupMutation = gql`
26 | mutation SignupMutation($name: String!, $email: String!, $password: String!, $confirmPassword: String!) {
27 | signup(name: $name, email: $email, password: $password, confirmPassword: $confirmPassword) {
28 | email
29 | }
30 | }
31 | `;
32 |
33 | export const requestResetPasswordMutation = gql`
34 | mutation RequestResetPasswordMutation($email: String!) {
35 | requestPasswordReset(email: $email)
36 | }
37 | `;
38 |
39 | export const resetPasswordMutation = gql`
40 | mutation ResetPasswordMutation($resetToken: String!, $password: String!, $confirmPassword: String!) {
41 | resetPassword(resetToken: $resetToken, password: $password, confirmPassword: $confirmPassword) {
42 | email
43 | }
44 | }
45 | `;
46 |
47 | export const completeOnboardingMutation = gql`
48 | mutation CompleteOnboardingMutation {
49 | completeOnboarding
50 | }
51 | `;
52 |
53 | // export const TOGGLE_SNACKBAR_MUTATION = gql`
54 | // mutation toggleSnackBar{
55 | // toggleSnackBar(msg: $msg, type: $type) @client
56 | // }
57 | // `;
58 |
59 | // export const TOGGLE_LEFT_DRAWER_MUTATION = gql`
60 | // mutation toggleLeftDrawer{
61 | // toggleLeftDrawer(status: "") @client
62 | // }
63 | // `;
--------------------------------------------------------------------------------
/web-app/graphql/queries/index.ts:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export const currentUserQuery = gql`
4 | query CurrentUserQuery {
5 | me {
6 | id
7 | email
8 | name
9 | role
10 | hasVerifiedEmail
11 | hasCompletedOnboarding
12 | }
13 | }
14 | `;
15 |
16 | export const SNACKBAR_STATE_QUERY = gql`
17 | query snackbar {
18 | snackBarOpen @client
19 | snackMsg @client
20 | snackType @client
21 | }
22 | `;
23 |
24 | export const IS_LEFT_DRAWER_OPEN = gql`
25 | query isLeftDrawerOpen {
26 | isLeftDrawerOpen @client
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/web-app/next-env.d.ts:
--------------------------------------------------------------------------------
1 | // /
2 | // /
3 |
--------------------------------------------------------------------------------
/web-app/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | webpack: (config, { isServer }) => {
3 | config.module.rules.push({
4 | test: /\.svg$/,
5 | use: ['@svgr/webpack'],
6 | });
7 |
8 | config.module.rules.push({
9 | test: /\.(jpe?g|png|gif|ico|webp)$/,
10 | use: [
11 | {
12 | loader: require.resolve('url-loader'),
13 | options: {
14 | fallback: require.resolve('file-loader'),
15 | outputPath: `${isServer ? '../' : ''}static/images/`,
16 | // limit: config.inlineImageLimit,
17 | // publicPath: `${config.assetPrefix}/_next/static/images/`,
18 | // esModule: config.esModule || false,
19 | name: '[name]-[hash].[ext]',
20 | },
21 | },
22 | ],
23 | });
24 |
25 | // Fixes npm packages that depend on `fs` module
26 | // https://github.com/webpack-contrib/css-loader/issues/447
27 | // https://github.com/zeit/next.js/issues/7755
28 | if (!isServer) {
29 | config.node = {
30 | fs: 'empty',
31 | };
32 | }
33 |
34 | return config;
35 | },
36 | // This makes the environment variables available at runtime
37 | // Make sure to set the environment variables in now.json in order to have the environment variables in a deployed environment
38 | env: {
39 | COMMON_BACKEND_URL: process.env.COMMON_BACKEND_URL,
40 | COMMON_FRONTEND_URL: process.env.COMMON_FRONTEND_URL,
41 | COMMON_STRIPE_YEARLY_PLAN_ID: process.env.COMMON_STRIPE_YEARLY_PLAN_ID,
42 | COMMON_STRIPE_MONTHLY_PLAN_ID: process.env.COMMON_STRIPE_MONTHLY_PLAN_ID,
43 | WEB_APP_STRIPE_PUBLISHABLE_KEY: process.env.WEB_APP_STRIPE_PUBLISHABLE_KEY,
44 | WEB_APP_GOOGLE_API_KEY: process.env.WEB_APP_GOOGLE_API_KEY,
45 | WEB_APP_MARKETING_SITE: process.env.WEB_APP_MARKETING_SITE,
46 | WEB_APP_SENTRY_DSN: process.env.WEB_APP_SENTRY_DSN,
47 | IMAGE_UPLOAD_URL: process.env.IMAGE_UPLOAD_URL,
48 | IMAGE_UPLOAD_PRESET: process.env.IMAGE_UPLOAD_PRESET,
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/web-app/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Document, { Html, Head, Main, NextScript } from 'next/document';
3 | import * as Sentry from '@sentry/browser';
4 | import { ServerStyleSheets } from '@material-ui/core/styles';
5 | import theme from '../theme';
6 |
7 | process.on('unhandledRejection', (err) => {
8 | Sentry.captureException(err);
9 | });
10 |
11 | process.on('uncaughtException', (err) => {
12 | Sentry.captureException(err);
13 | });
14 |
15 | export default class MyDocument extends Document {
16 | render(): JSX.Element {
17 | return (
18 |
19 |
20 | {/* PWA primary color */}
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {/* */}
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | // `getInitialProps` belongs to `_document` (instead of `_app`),
48 | // it's compatible with server-side generation (SSG).
49 | MyDocument.getInitialProps = async (ctx) => {
50 | // Resolution order
51 | //
52 | // On the server:
53 | // 1. app.getInitialProps
54 | // 2. page.getInitialProps
55 | // 3. document.getInitialProps
56 | // 4. app.render
57 | // 5. page.render
58 | // 6. document.render
59 | //
60 | // On the server with error:
61 | // 1. document.getInitialProps
62 | // 2. app.render
63 | // 3. page.render
64 | // 4. document.render
65 | //
66 | // On the client
67 | // 1. app.getInitialProps
68 | // 2. page.getInitialProps
69 | // 3. app.render
70 | // 4. page.render
71 |
72 | // Render app and page and get the context of the page with collected side effects.
73 | const sheets = new ServerStyleSheets();
74 | const originalRenderPage = ctx.renderPage;
75 |
76 | ctx.renderPage = () =>
77 | originalRenderPage({
78 | enhanceApp: (App) => (props) => sheets.collect(),
79 | });
80 |
81 | const initialProps = await Document.getInitialProps(ctx);
82 |
83 | return {
84 | ...initialProps,
85 | // Styles fragment is rendered after the app and page rendering finish.
86 | styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
87 | };
88 | };
89 |
--------------------------------------------------------------------------------
/web-app/pages/add-category.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AddCategory from '../components/Category/AddCategory';
3 |
4 |
5 | type Props = {};
6 |
7 | const NewCategory: React.FC = () => {
8 | return (
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default NewCategory;
16 |
--------------------------------------------------------------------------------
/web-app/pages/add-product.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AddProduct from '../components/Product/AddProduct';
3 |
4 |
5 | type Props = {};
6 |
7 | const NewProduct: React.FC = () => {
8 | return (
9 |
10 | );
11 | };
12 |
13 | export default NewProduct;
14 |
--------------------------------------------------------------------------------
/web-app/pages/categories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Category from '../components/Category/Category';
3 |
4 |
5 | type Props = {};
6 |
7 | const Companies: React.FC = () => {
8 | return (
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default Companies;
16 |
--------------------------------------------------------------------------------
/web-app/pages/example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | type Props = {};
3 | // https://blog.bitsrc.io/react-hooks-5-beginners-tips-b1e3e55dc8dc
4 | const useService = (serviceObj: any) => {
5 | const [isSubscribed, setIsSubscribed] = useState(11);
6 |
7 | useEffect(() => {
8 | serviceObj.subscribe().then((a: any) => {
9 | console.log(a);
10 | setIsSubscribed(12)
11 | // code after subscription
12 | });
13 | return () => {
14 | serviceObj.unsubscribe().then((a: any) => {
15 | console.log(a);
16 | // clean up operations
17 | });
18 | };
19 | }, [name]);
20 |
21 | return isSubscribed;
22 | }
23 |
24 |
25 | const serviceA = {
26 | subscribe: () => Promise.resolve('subscribed'),
27 | unsubscribe: () => Promise.resolve('unsubscribe')
28 | };
29 |
30 | const Companies: React.FC = () => {
31 | const [name, setName] = useState('Dilantha');
32 | const [age, setAge] = useState(28);
33 | const [renderCount, setRenderCount] = useState(0);
34 | const isSubscribed = useService(serviceA);
35 |
36 | return (
37 | <>
38 | setName(e.target.value)} />
39 | Name : {name}
40 | Age : {age}
41 | Render Count : {renderCount}
42 | isSubscribed : {isSubscribed}
43 | >
44 | );
45 | };
46 |
47 | export default Companies;
48 |
--------------------------------------------------------------------------------
/web-app/pages/example1.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | type Props = {};
3 |
4 | const Companies: React.FC = () => {
5 |
6 | return (
7 | <>
8 | >
9 | );
10 | };
11 |
12 | export default Companies;
13 |
--------------------------------------------------------------------------------
/web-app/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Dashboard from '../components/Dashboard/Dashboard';
2 |
3 | type Props = {};
4 |
5 | const Home: React.FC = () => {
6 |
7 | return (
8 |
9 | );
10 | };
11 |
12 | export default Home;
13 |
--------------------------------------------------------------------------------
/web-app/pages/login.tsx:
--------------------------------------------------------------------------------
1 | import { NextPageContext } from 'next';
2 | import LoginOrSignup from '../components/LoginOrSignup/LoginOrSignup';
3 | import { PageProps } from './_app';
4 |
5 | export const handleAuthError = (ctx: NextPageContext): { isAuthenticationError: boolean } | undefined => {
6 | if (ctx.query.authError === 'true') {
7 | return { isAuthenticationError: true };
8 | }
9 | };
10 |
11 | interface Props extends PageProps {
12 | error?: string;
13 | }
14 |
15 | function Login(props: Props): JSX.Element {
16 | return ;
17 | }
18 |
19 | Login.getInitialProps = handleAuthError;
20 |
21 | export default Login;
22 |
--------------------------------------------------------------------------------
/web-app/pages/products.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Product from '../components/Product/Product';
3 |
4 |
5 | type Props = {};
6 |
7 | const Products: React.FC = () => {
8 | return (
9 |
10 | );
11 | };
12 |
13 | export default Products;
14 |
--------------------------------------------------------------------------------
/web-app/pages/settings.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Profile from '../components/Profile';
3 |
4 | type Props = {};
5 |
6 | const Settings: React.FC = (): JSX.Element => {
7 | return ;
8 | };
9 |
10 | export default Settings;
11 |
--------------------------------------------------------------------------------
/web-app/pages/signup.tsx:
--------------------------------------------------------------------------------
1 | import { NextPageContext } from 'next';
2 | import LoginOrSignup from '../components/LoginOrSignup/LoginOrSignup';
3 | import { PageProps } from './_app';
4 |
5 | export const handleAuthError = (ctx: NextPageContext): { error: string } | undefined => {
6 | if (ctx.query.authError === 'true') {
7 | return { error: `There's been a problem with your authentication.` };
8 | }
9 | };
10 |
11 | interface Props extends PageProps {
12 | error?: string;
13 | }
14 |
15 | function Signup(props: Props): JSX.Element {
16 | return ;
17 | }
18 |
19 | Signup.getInitialProps = handleAuthError;
20 |
21 | export default Signup;
22 |
--------------------------------------------------------------------------------
/web-app/pages/users.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import User from '../components/User/User';
3 |
4 |
5 | type Props = {};
6 |
7 | const Companies: React.FC = () => {
8 | return (
9 |
10 | );
11 | };
12 |
13 | export default Companies;
14 |
--------------------------------------------------------------------------------
/web-app/public/images/adminLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/adminLogo.png
--------------------------------------------------------------------------------
/web-app/public/images/conversation.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
96 |
--------------------------------------------------------------------------------
/web-app/public/images/customers.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/web-app/public/images/discuss-issue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
55 |
--------------------------------------------------------------------------------
/web-app/public/images/earning.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/web-app/public/images/image.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/web-app/public/images/no-product-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/no-product-image.png
--------------------------------------------------------------------------------
/web-app/public/images/product/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/1.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/10.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/2.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/3.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/4.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/5.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/6.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/7.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/8.jpg
--------------------------------------------------------------------------------
/web-app/public/images/product/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/product/9.jpg
--------------------------------------------------------------------------------
/web-app/public/images/user.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/images/user.jpg
--------------------------------------------------------------------------------
/web-app/public/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/web-app/public/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/web-app/public/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/web-app/public/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/web-app/public/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/favicon.ico
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Black.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Black.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-BlackItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-BlackItalic.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-BlackItalic.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Bold.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Bold.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-BoldItalic.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-BoldItalic.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Italic.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Italic.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Light.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Light.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-LightItalic.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-LightItalic.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Medium.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Medium.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-MediumItalic.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-MediumItalic.woff2
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Regular.woff
--------------------------------------------------------------------------------
/web-app/public/static/fonts/Rubik-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/n-dev27/-nextjs-graphql-adminpanel/fc9bfa44ac48c827c484d9e198e4c3fc5f6a227d/web-app/public/static/fonts/Rubik-Regular.woff2
--------------------------------------------------------------------------------
/web-app/public/static/manifest.json:
--------------------------------------------------------------------------------
1 | {"name":"Next GraphQL Admin","short_name":"Next JS GraphQL Admin Panel","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
2 |
--------------------------------------------------------------------------------
/web-app/routes.js:
--------------------------------------------------------------------------------
1 |
2 | // @material-ui/icons
3 | import Dashboard from "@material-ui/icons/Dashboard";
4 | import PeopleOutline from "@material-ui/icons/PeopleOutline";
5 | import Category from "@material-ui/icons/Category";
6 | import ShoppingBasket from "@material-ui/icons/ShoppingBasket";
7 | import SettingsIcon from '@material-ui/icons/Settings';
8 |
9 | const dashboardRoutes = [
10 | {
11 | path: "/",
12 | name: "Dashboard",
13 | icon: Dashboard,
14 | },
15 | {
16 | icon: Category,
17 | name: 'Categories',
18 | path: '/categories',
19 | },
20 | {
21 | icon: ShoppingBasket,
22 | name: 'Products',
23 | path: '/products',
24 | },
25 | {
26 | icon: PeopleOutline,
27 | name: 'Users',
28 | path: '/users',
29 | },
30 | {
31 | icon: SettingsIcon,
32 | name: 'Settings',
33 | path: '/settings',
34 | },
35 | ];
36 |
37 | export default dashboardRoutes;
38 |
--------------------------------------------------------------------------------
/web-app/tests/pages.test.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { render } from '../utils/testUtils';
3 | import { currentUserQuery } from '../graphql/queries';
4 | import Home from '../pages';
5 |
6 | jest.mock('next/router');
7 |
8 | (useRouter as jest.Mock).mockImplementation(() => ({
9 | pathname: '/somePathname',
10 | query: '',
11 | }));
12 |
13 | const aCurrentUserQuery = (overrides?: any): any => ({
14 | request: {
15 | query: currentUserQuery,
16 | },
17 | result: {
18 | data: {
19 | me: { id: '123', name: 'Jack' },
20 | },
21 | },
22 | ...overrides,
23 | });
24 |
25 | const mocks = [aCurrentUserQuery()];
26 |
27 | it('should display the page contents', async () => {
28 | const { queryByText } = render({ ui: , mocks });
29 |
30 | expect(queryByText(/admin/)).toBeInTheDocument();
31 | });
32 |
--------------------------------------------------------------------------------
/web-app/theme/index.tsx:
--------------------------------------------------------------------------------
1 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles';
2 | import { red } from '@material-ui/core/colors';
3 | import { purple } from '@material-ui/core/colors';
4 |
5 | // Create a theme instance.
6 | let theme = createMuiTheme({
7 | palette: {
8 | // type: 'dark', // For Dark Theme
9 |
10 | // primary: {
11 | // main: purple[500],
12 | // },
13 | // secondary: {
14 | // main: '#11cb5f',
15 | // },
16 | // error: {
17 | // main: red.A400,
18 | // },
19 | background: {
20 | default: '#fff',
21 | },
22 | },
23 | typography: {
24 | // In Chinese and Japanese the characters are usually larger,
25 | // so a smaller fontsize may be appropriate.
26 | fontSize: 14,
27 | },
28 | });
29 |
30 | theme.typography.h3 = {
31 | fontSize: '1.2rem',
32 | '@media (min-width:600px)': {
33 | fontSize: '1.5rem',
34 | },
35 | [theme.breakpoints.up('md')]: {
36 | fontSize: '2.4rem',
37 | },
38 | };
39 | // theme = responsiveFontSizes(theme);
40 |
41 | export default theme;
--------------------------------------------------------------------------------
/web-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | // "alwaysStrict": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "isolatedModules": true,
8 | "jsx": "preserve",
9 | "lib": [
10 | "dom",
11 | "es2017"
12 | ],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noEmit": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "resolveJsonModule": true,
18 | "skipLibCheck": true,
19 | "strict": true,
20 | "target": "ES2015",
21 | "types": [
22 | "node",
23 | "jest",
24 | "@emotion/core"
25 | ]
26 | },
27 | "exclude": [
28 | "node_modules"
29 | ],
30 | "include": [
31 | "types/typeDefs/next-env.d.ts",
32 | "**/*.ts",
33 | "**/*.tsx"
34 | ]
35 | }
--------------------------------------------------------------------------------
/web-app/types/ContactType.ts:
--------------------------------------------------------------------------------
1 | export type ContactType = {
2 | id: string;
3 | name: string;
4 | position: string | null;
5 | email: string | null;
6 | phone: string | null;
7 | notes: string | null;
8 | order: number;
9 | };
10 |
--------------------------------------------------------------------------------
/web-app/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ContactType';
2 |
3 | declare global {
4 | interface Window {
5 | // Loaded for google places API
6 | google: any;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/web-app/types/typeDefs/custom.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | ///
3 |
4 | declare module '*.jpeg' {
5 | const value: string;
6 | export = value;
7 | }
8 |
9 | declare module '*.jpg' {
10 | const value: string;
11 | export = value;
12 | }
13 |
14 | declare module '*.png' {
15 | const value: string;
16 | export = value;
17 | }
18 |
19 | declare module '*.svg' {
20 | const value: any;
21 | export default value;
22 | }
23 |
24 | declare module '*.gif' {
25 | const value: string;
26 | export = value;
27 | }
28 |
29 | declare module '*.ico' {
30 | const value: string;
31 | export = value;
32 | }
33 |
34 | declare module '*.webp' {
35 | const value: string;
36 | export = value;
37 | }
38 |
--------------------------------------------------------------------------------
/web-app/types/typeDefs/next-env.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | ///
3 | ///
4 |
--------------------------------------------------------------------------------
/web-app/types/typeDefs/react-tippy.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'react-tippy';
2 |
--------------------------------------------------------------------------------
/web-app/utils/constants/index.ts:
--------------------------------------------------------------------------------
1 | export enum QueryParamKeys {
2 | PAGE = 'page',
3 | ORDER_BY = 'orderBy',
4 | DIRECTION = 'direction',
5 | PAGE_SIZE = 'pageSize',
6 | }
7 |
8 | export enum pageSizes {
9 | level1 = '10',
10 | level2 = '25',
11 | level3 = '50',
12 | level4 = '100',
13 | }
14 |
15 | export const defaultNumberOfTableRows = parseInt(pageSizes.level1, 10);
16 |
--------------------------------------------------------------------------------
/web-app/utils/formatDate.ts:
--------------------------------------------------------------------------------
1 | import {
2 | format,
3 | parseISO,
4 | differenceInDays,
5 | differenceInHours,
6 | differenceInMinutes,
7 | differenceInSeconds,
8 | } from 'date-fns';
9 |
10 | export const formatDate = (isoDateString: string): string => {
11 | const date = parseISO(isoDateString);
12 | const currentDate = Date.now();
13 | const daysAgo = differenceInDays(currentDate, date);
14 | const hoursAgo = differenceInHours(currentDate, date);
15 | const minutesAgo = differenceInMinutes(currentDate, date);
16 | const secondsAgo = differenceInSeconds(currentDate, date);
17 |
18 | if (secondsAgo <= 2) {
19 | return `just now`;
20 | }
21 | if (minutesAgo < 1) {
22 | return `${secondsAgo} second${secondsAgo > 1 ? 's' : ''} ago`;
23 | }
24 | if (hoursAgo < 1) {
25 | return `${minutesAgo} minute${minutesAgo > 1 ? 's' : ''} ago`;
26 | }
27 | if (daysAgo < 1) {
28 | return `${hoursAgo} hour${hoursAgo > 1 ? 's' : ''} ago`;
29 | }
30 | if (daysAgo <= 30) {
31 | return `${daysAgo} day${daysAgo > 1 ? 's' : ''} ago`;
32 | }
33 | return format(date, 'MMM do, yyyy');
34 | };
35 |
36 | export const formateDateForInput = (isoDateString: string): string => {
37 | if (isoDateString) {
38 | return format(parseISO(isoDateString), 'yyyy-MM-dd');
39 | }
40 | return isoDateString;
41 | };
42 |
--------------------------------------------------------------------------------
/web-app/utils/getError.ts:
--------------------------------------------------------------------------------
1 | import { getIn, FormikTouched, FormikErrors } from 'formik';
2 |
3 | export type FormError = string | { [key: string]: string } | undefined;
4 |
5 | export const getError = ({
6 | touched,
7 | errors,
8 | }: {
9 | touched: FormikTouched;
10 | errors: FormikErrors;
11 | }) => (name: string): FormError => {
12 | return getIn(touched, name) ? getIn(errors, name) : undefined;
13 | };
14 |
--------------------------------------------------------------------------------
/web-app/utils/hooks/useGooglePlacesScript.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | const cachedScripts: string[] = [];
4 | let service: google.maps.places.AutocompleteService;
5 | const src = `https://maps.googleapis.com/maps/api/js?key=${process.env.WEB_APP_GOOGLE_API_KEY}&libraries=places`;
6 |
7 | export function useGooglePlacesScript(): google.maps.places.AutocompleteService | undefined {
8 | useEffect(
9 | () => {
10 | if (!cachedScripts.includes(src)) {
11 | cachedScripts.push(src);
12 |
13 | // Create script
14 | const script = document.createElement('script');
15 | script.src = src;
16 | script.async = true;
17 |
18 | // Script event listener callbacks for load and error
19 | const onScriptLoad = (): void => {
20 | service = new window.google.maps.places.AutocompleteService();
21 | };
22 |
23 | const onScriptError = (): void => {
24 | // Remove from cachedScripts we can try loading again
25 | const index = cachedScripts.indexOf(src);
26 | if (index >= 0) {
27 | cachedScripts.splice(index, 1);
28 | }
29 | script.remove();
30 | };
31 |
32 | script.addEventListener('load', onScriptLoad);
33 | script.addEventListener('error', onScriptError);
34 |
35 | // Add script to document body
36 | document.body.appendChild(script);
37 |
38 | // Remove event listeners on cleanup
39 | return (): void => {
40 | script.removeEventListener('load', onScriptLoad);
41 | script.removeEventListener('error', onScriptError);
42 | };
43 | }
44 | },
45 | [] // Only re-run effect if script src changes
46 | );
47 |
48 | return service;
49 | }
50 |
--------------------------------------------------------------------------------
/web-app/utils/hooks/useGooglePlacesService.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState, Dispatch, SetStateAction, ChangeEvent } from 'react';
2 | import debounce from 'lodash.debounce';
3 | import { useGooglePlacesScript } from './useGooglePlacesScript';
4 |
5 | type GooglePlacesOption = { googlePlacesId: string; name: string; id: string };
6 |
7 | export function useGooglePlacesService(): [
8 | (e: ChangeEvent) => void,
9 | GooglePlacesOption[],
10 | boolean,
11 | string,
12 | Dispatch>
13 | ] {
14 | const service = useGooglePlacesScript();
15 |
16 | const [isLoadingLocationResults, setIsLoadingLocationResults] = useState(false);
17 |
18 | const [googleMapsAutocompleteResults, setGoogleMapsAutocompleteResults] = useState<
19 | google.maps.places.AutocompletePrediction[]
20 | >();
21 |
22 | const [location, setLocation] = useState('');
23 |
24 | const debouncedGoogleMapsCall = useCallback(
25 | debounce<(searchQuery: string) => Promise>(async (searchQuery): Promise => {
26 | if (service && searchQuery) {
27 | await new Promise((resolve) => {
28 | service.getQueryPredictions(
29 | {
30 | input: searchQuery,
31 | },
32 | async (results, status) => {
33 | if (status === 'OK') {
34 | setGoogleMapsAutocompleteResults(
35 | results as google.maps.places.AutocompletePrediction[]
36 | );
37 | }
38 | resolve();
39 | }
40 | );
41 | });
42 | }
43 | setIsLoadingLocationResults(false);
44 | }, 500),
45 | [service, setGoogleMapsAutocompleteResults, setIsLoadingLocationResults]
46 | );
47 |
48 | const locationOptions: GooglePlacesOption[] = googleMapsAutocompleteResults
49 | ? googleMapsAutocompleteResults.map((location) => ({
50 | id: location.place_id,
51 | googlePlacesId: location.place_id,
52 | name: location.description,
53 | }))
54 | : [];
55 |
56 | const handleLocationSearchQueryChange = (e: ChangeEvent): void => {
57 | setLocation(e.target.value);
58 | setIsLoadingLocationResults(true);
59 | debouncedGoogleMapsCall(e.target.value);
60 | };
61 |
62 | return [handleLocationSearchQueryChange, locationOptions, isLoadingLocationResults, location, setLocation];
63 | }
64 |
--------------------------------------------------------------------------------
/web-app/utils/hooks/useModalQuery.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { useCallback } from 'react';
3 | import { QueryParamKeys } from '../constants';
4 |
5 | /**
6 | * Hook to be used for modals that need a query to persist in the URL
7 | */
8 | export const useModalQuery = (
9 | query: QueryParamKeys,
10 | id?: string
11 | ): {
12 | isOpen: boolean;
13 | onOpen: (options?: {
14 | queryToExclude?: QueryParamKeys;
15 | newlyCreatedId?: string;
16 | additionalQueries?: Partial<
17 | {
18 | [query in QueryParamKeys]: string;
19 | }
20 | >;
21 | }) => Promise;
22 | onClose: (additionalQuery?: { [key: string]: string }) => Promise;
23 | } => {
24 | const router = useRouter();
25 |
26 | const onOpen = useCallback(
27 | ({
28 | queryToExclude,
29 | newlyCreatedId,
30 | additionalQueries = {},
31 | }: {
32 | queryToExclude?: QueryParamKeys;
33 | newlyCreatedId?: string;
34 | additionalQueries?:
35 | | {
36 | [query in QueryParamKeys]: string;
37 | }
38 | | {};
39 | } = {}): Promise => {
40 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
41 | const { [queryToExclude ?? '']: excluded, ...existingQueries } = router.query;
42 |
43 | return router.push({
44 | pathname: router.pathname,
45 | query: {
46 | ...existingQueries,
47 | ...additionalQueries,
48 | [query]: id ?? newlyCreatedId ?? true,
49 | },
50 | });
51 | },
52 | [id, query, router]
53 | );
54 |
55 | const onClose = useCallback(
56 | (additionalQuery: { [key: string]: string } = {}): Promise => {
57 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
58 | const { [query]: queryToRemove, ...queryWithoutJob } = router.query;
59 |
60 | return router.push({
61 | pathname: router.pathname,
62 | query: {
63 | ...queryWithoutJob,
64 | ...additionalQuery,
65 | },
66 | });
67 | },
68 | [query, router]
69 | );
70 |
71 | return {
72 | isOpen: id ? router.query[query] === id : Boolean(router.query[query]),
73 | onOpen,
74 | onClose,
75 | };
76 | };
77 |
--------------------------------------------------------------------------------
/web-app/utils/hooks/useOnClickOutside.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, MutableRefObject } from 'react';
2 |
3 | export function useOnClickOutside(ref: MutableRefObject, handler: () => void, keyBoardEvents = false): void {
4 | useEffect(() => {
5 | const listener = (event: MouseEvent | TouchEvent | KeyboardEvent): void => {
6 | // Do nothing if clicking ref's element or descendent elements
7 | if (!ref.current || ref.current.contains(event.target)) {
8 | return;
9 | }
10 |
11 | handler();
12 | };
13 |
14 | document.addEventListener('mousedown', listener);
15 |
16 | if (keyBoardEvents) {
17 | document.addEventListener('touchstart', listener);
18 | document.addEventListener('keyup', listener);
19 | }
20 |
21 | return (): void => {
22 | document.removeEventListener('mousedown', listener);
23 | if (keyBoardEvents) {
24 | document.removeEventListener('touchstart', listener);
25 | document.removeEventListener('keyup', listener);
26 | }
27 | };
28 | }, [ref, handler, keyBoardEvents]);
29 | }
30 |
--------------------------------------------------------------------------------
/web-app/utils/hooks/usePaginationQuery.tsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { QueryParamKeys, defaultNumberOfTableRows } from '../constants';
3 | import {
4 | QueryCategoriesOrderByInput,
5 | QueryUsersOrderByInput,
6 | OrderByArg,
7 | } from '../../graphql/generated/graphql-global-types';
8 |
9 | export type TableOrderBy = QueryCategoriesOrderByInput | QueryUsersOrderByInput;
10 |
11 | // Converts a union type to an intersectino type: https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
12 | type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
13 |
14 | export type OrderByQueryParamKeys = keyof UnionToIntersection;
15 |
16 | export type PaginationQuery = {
17 | page: number;
18 | pageSize: number;
19 | orderBy: OrderByQueryParamKeys;
20 | direction: OrderByArg;
21 | };
22 |
23 | /**
24 | * Hook to be used to manage pagination related URL queries
25 | */
26 | export const usePaginationQuery = ({
27 | page: defaultPage = 1,
28 | pageSize: defaultPageSize = defaultNumberOfTableRows,
29 | orderBy: defaultOrderBy = 'updatedAt',
30 | direction: defaultDirection = OrderByArg.desc,
31 | }: Partial): PaginationQuery & {
32 | setQuery: ({ page, pageSize, orderBy }: Partial) => void;
33 | } => {
34 | const router = useRouter();
35 | const page = parseInt(router.query[QueryParamKeys.PAGE] as string, 10) || defaultPage;
36 | const pageSize = parseInt(router.query[QueryParamKeys.PAGE_SIZE] as string, 10) || defaultPageSize;
37 | const orderBy = (router.query[QueryParamKeys.ORDER_BY] as OrderByQueryParamKeys) || defaultOrderBy;
38 | const direction = (router.query[QueryParamKeys.DIRECTION] as OrderByArg) || defaultDirection;
39 |
40 | const setQuery = (newQuery: Partial): void => {
41 | router.push({
42 | pathname: router.pathname,
43 | query: {
44 | ...router.query,
45 | ...newQuery,
46 | },
47 | });
48 | };
49 |
50 | return {
51 | page,
52 | orderBy,
53 | direction,
54 | pageSize,
55 | setQuery,
56 | };
57 | };
58 |
--------------------------------------------------------------------------------
/web-app/utils/hooks/useWindowResize.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from 'react';
2 |
3 | export const useWindowSize = (): { width?: number; height?: number } => {
4 | const isClient = typeof window === 'object';
5 |
6 | const getSize = useCallback(() => {
7 | return {
8 | width: isClient ? window.innerWidth : undefined,
9 | height: isClient ? window.innerHeight : undefined,
10 | };
11 | }, [isClient]);
12 |
13 | const [windowSize, setWindowSize] = useState(getSize);
14 |
15 | useEffect(() => {
16 | if (!isClient) {
17 | return;
18 | }
19 | function handleResize(): void {
20 | setWindowSize(getSize());
21 | }
22 |
23 | window.addEventListener('resize', handleResize);
24 |
25 | return (): void => window.removeEventListener('resize', handleResize);
26 | }, [getSize, isClient]);
27 |
28 | return windowSize;
29 | };
30 |
--------------------------------------------------------------------------------
/web-app/utils/resolvers.tsx:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 | import { ApolloCache } from 'apollo-cache';
3 | import { Resolvers } from 'apollo-client'
4 | import { SNACKBAR_STATE_QUERY } from '../graphql/queries';
5 |
6 | export const typeDefs = gql`
7 | extend type Query {
8 | isLeftDrawerOpen: Boolean!,
9 | snackMsg : String,
10 | snackType : String,
11 | snackBarOpen: Boolean
12 | }
13 | `;
14 |
15 | type ResolverFn = (
16 | parent: any,
17 | args: any,
18 | { cache }: { cache: ApolloCache }
19 | ) => any;
20 |
21 | interface ResolverMap {
22 | [field: string]: ResolverFn;
23 | }
24 |
25 | interface AppResolvers extends Resolvers {
26 | Launch: ResolverMap;
27 | Mutation: ResolverMap;
28 | }
29 |
30 | export const resolvers: AppResolvers = {
31 | Launch: {
32 | },
33 | Mutation: {
34 | updateNetworkStatus: (_, { isConnected }, { cache }) => {
35 | cache.writeData({ data: { isConnected } });
36 | return null;
37 | },
38 | toggleSnackBar(_, variables, { cache }) {
39 | const cacheData = cache.readQuery({
40 | query: SNACKBAR_STATE_QUERY
41 | })
42 |
43 | const data = {
44 | data: {
45 | ...cacheData,
46 | snackBarOpen: variables.msg !== '' ? !cacheData.snackBarOpen : false,
47 | snackMsg: variables.msg,
48 | snackType: variables.type
49 | }
50 | }
51 | cache.writeData(data);
52 | return data;
53 | }
54 | },
55 | };
56 |
--------------------------------------------------------------------------------
/web-app/utils/testUtils.tsx:
--------------------------------------------------------------------------------
1 | import { render, RenderOptions, RenderResult } from '@testing-library/react';
2 | import { MockedProvider, MockedResponse } from '@apollo/react-testing';
3 | import '@testing-library/jest-dom';
4 |
5 | const allTheProviders = (mocks?: ReadonlyArray): React.FC => {
6 | const AllTheProviders: React.FC = ({ children }: any) => {
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | };
13 |
14 | return AllTheProviders;
15 | };
16 | const customRender = ({
17 | ui,
18 | options,
19 | mocks,
20 | }: {
21 | ui: React.ReactElement;
22 | options?: Omit;
23 | mocks?: ReadonlyArray;
24 | }): RenderResult => render(ui, { wrapper: allTheProviders(mocks), ...options });
25 |
26 | // re-export everything
27 | export * from '@testing-library/react';
28 |
29 | // override render method
30 | export { customRender as render };
31 |
--------------------------------------------------------------------------------
/web-app/utils/withApollo.ts:
--------------------------------------------------------------------------------
1 | import withApollo from 'next-with-apollo';
2 | import { ApolloClient } from 'apollo-client';
3 | import { InMemoryCache } from 'apollo-cache-inmemory';
4 | import { onError } from 'apollo-link-error';
5 | import { ApolloLink, Observable } from 'apollo-link';
6 | import { createUploadLink } from 'apollo-upload-client';
7 | import { resolvers, typeDefs } from './resolvers';
8 |
9 | export default withApollo(({ initialState, headers }) => {
10 | const request = (operation: any): void => {
11 | operation.setContext({
12 | fetchOptions: {
13 | credentials: 'include',
14 | },
15 | // https://github.com/apollographql/apollo-client/issues/4193#issuecomment-448682173
16 | headers: {
17 | cookie: headers && headers.cookie, // NOTE: client-side headers is undefined!
18 | },
19 | });
20 | };
21 |
22 | const requestLink = new ApolloLink(
23 | (operation, forward) =>
24 | new Observable((observer) => {
25 | let handle: any;
26 | Promise.resolve(operation)
27 | .then((oper) => request(oper))
28 | .then(() => {
29 | handle = forward(operation).subscribe({
30 | next: observer.next.bind(observer),
31 | error: observer.error.bind(observer),
32 | complete: observer.complete.bind(observer),
33 | });
34 | })
35 | .catch(observer.error.bind(observer));
36 |
37 | return (): void => {
38 | if (handle) {
39 | handle.unsubscribe();
40 | }
41 | };
42 | })
43 | );
44 |
45 | const cache = new InMemoryCache({ addTypename: false }).restore(initialState || {});
46 | cache.writeData({
47 | data: {
48 | isLeftDrawerOpen: false,
49 | leftDrawerWidth: 260,
50 | cartItems: [],
51 | snackMsg: 'default',
52 | snackType: 'success',
53 | snackBarOpen: false
54 | },
55 | });
56 |
57 |
58 | return new ApolloClient({
59 | link: ApolloLink.from([
60 | onError(({ graphQLErrors, networkError }) => {
61 | if (graphQLErrors) {
62 | graphQLErrors.forEach(({ message, locations, path }) =>
63 | // eslint-disable-next-line no-console
64 | console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
65 | );
66 | }
67 | if (networkError) {
68 | // eslint-disable-next-line no-console
69 | console.log(`[Network error]: ${networkError}`);
70 | }
71 | }),
72 | requestLink,
73 | createUploadLink({
74 | uri: process.env.COMMON_BACKEND_URL,
75 | }),
76 | ]),
77 | cache,
78 | resolvers,
79 | typeDefs,
80 | });
81 | });
82 |
--------------------------------------------------------------------------------