├── .gitignore
├── README.md
├── cdk
├── README.md
├── cdk.context.json
├── cdk.iml
├── cdk.json
├── cdk.out
│ ├── BudilovCognitoLambdaStack.template.json
│ ├── BudilovCognitoStack.template.json
│ ├── BudilovUsersDDBTable.template.json
│ ├── SPAStack.template.json
│ ├── cdk.out
│ ├── manifest.json
│ └── tree.json
├── pom.xml
├── src
│ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── budilov
│ │ │ └── cdk
│ │ │ ├── BudilovApp.java
│ │ │ ├── cognito
│ │ │ ├── CognitoLambdaStack.java
│ │ │ └── CognitoStack.java
│ │ │ ├── ddb
│ │ │ └── DDBUserTableStack.java
│ │ │ ├── util
│ │ │ ├── Properties.java
│ │ │ └── SSM.java
│ │ │ └── web
│ │ │ └── SPAStack.java
│ │ └── resources
│ │ ├── cognitoAutoConfirmUser.js
│ │ └── cognitoToDynamoDBLambda.js
└── target
│ ├── cdk-0.1.jar
│ ├── classes
│ ├── app.properties
│ ├── cognitoAutoConfirmUser.js
│ ├── cognitoToDynamoDBLambda.js
│ ├── com
│ │ └── budilov
│ │ │ └── cdk
│ │ │ ├── BudilovApp.class
│ │ │ ├── cognito
│ │ │ ├── CognitoLambdaStack.class
│ │ │ └── CognitoStack.class
│ │ │ ├── ddb
│ │ │ └── DDBUserTableStack.class
│ │ │ ├── util
│ │ │ ├── Properties.class
│ │ │ └── SSM.class
│ │ │ └── web
│ │ │ └── SPAStack.class
│ └── elasticsearch-access-policy.json
│ ├── maven-archiver
│ └── pom.properties
│ └── maven-status
│ └── maven-compiler-plugin
│ └── compile
│ └── default-compile
│ ├── createdFiles.lst
│ └── inputFiles.lst
└── reactjs-webapp
├── .dockerignore
├── .env
├── .gitlab-ci.yml
├── Dockerfile
├── README.md
├── devfile.yaml
├── index.html
├── migration-guide.md
├── package.json
├── public
├── favicon.ico
├── images
│ ├── auth
│ │ ├── forgot.webp
│ │ ├── login.webp
│ │ └── register.webp
│ └── email.png
├── index.html
├── logos
│ └── logo-md.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── layout
│ │ ├── full-layout.js
│ │ ├── header
│ │ │ ├── drop-down-navbar-menu.js
│ │ │ └── navbar.js
│ │ ├── my-footer.js
│ │ └── sidenav.js
│ ├── mixins.scss
│ ├── search-field.js
│ └── search
│ │ └── search-input-field.js
├── configs
│ └── aws-configs.js
├── index.css
├── index.js
├── logo.svg
├── routes.js
├── screens
│ ├── alttext
│ │ └── upload-new-image.js
│ ├── auth
│ │ ├── auth-screen.js
│ │ ├── login-form.js
│ │ ├── quotes.js
│ │ ├── register-confirm-form.js
│ │ ├── register-form.js
│ │ ├── reset-password-step-1.js
│ │ ├── reset-password-step-2.js
│ │ ├── service
│ │ │ ├── auth-service.js
│ │ │ └── constants.js
│ │ └── styles.js
│ ├── dashboard
│ │ └── dashboard.js
│ ├── documents
│ │ └── documents.js
│ ├── shared
│ │ ├── components
│ │ │ ├── AppLogo
│ │ │ │ └── index.js
│ │ │ ├── AppLogoWhite
│ │ │ │ └── index.js
│ │ │ ├── HeaderUser
│ │ │ │ └── index.js
│ │ │ └── UserInfo
│ │ │ │ └── index.js
│ │ ├── constants
│ │ │ ├── ActionTypes.js
│ │ │ ├── AppConst.js
│ │ │ ├── AppEnums.js
│ │ │ ├── ColorSets.js
│ │ │ └── MonochromeTheme.js
│ │ ├── jss
│ │ │ └── common
│ │ │ │ ├── common.style.js
│ │ │ │ └── theme.js
│ │ ├── localization
│ │ │ ├── entries
│ │ │ │ ├── ar_SA.js
│ │ │ │ ├── en-US.js
│ │ │ │ ├── es_ES.js
│ │ │ │ ├── fr_FR.js
│ │ │ │ ├── it_IT.js
│ │ │ │ └── zh-Hans-CN.js
│ │ │ ├── index.js
│ │ │ └── locales
│ │ │ │ ├── ar_SA.json
│ │ │ │ ├── en_US.json
│ │ │ │ ├── es_ES.json
│ │ │ │ ├── fr_FR.json
│ │ │ │ ├── it_IT.json
│ │ │ │ └── zh-Hans.json
│ │ └── styles
│ │ │ ├── base.css
│ │ │ ├── index.css
│ │ │ └── vendors
│ │ │ ├── calendar.css
│ │ │ ├── chat-window.css
│ │ │ ├── ck-editor.css
│ │ │ ├── index.css
│ │ │ ├── maps.css
│ │ │ ├── react-notification.css
│ │ │ ├── react-table.css
│ │ │ ├── slider.css
│ │ │ └── timeline.css
│ └── uploads
│ │ ├── camera-screen.js
│ │ └── upload-floating-button.js
├── serviceWorker.js
├── services
│ ├── constants.js
│ ├── dao
│ │ └── uploader-service.js
│ ├── http-service.js
│ └── local-storage.js
└── shared
│ └── styles
│ ├── animations.css
│ └── index.css
├── vite.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .idea
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ReactJS + CDK + GitLab CI/CD Starter
2 | =====================================
3 |
4 | ### Author: Vladimir Budilov
5 |
6 | * [YouTube](https://www.youtube.com/channel/UCBl-ENwdTlUsLY05yGgXyxw)
7 | * [LinkedIn](https://www.linkedin.com/in/vbudilov/)
8 | * [Medium](https://medium.com/@budilov)
9 |
10 | ### About
11 |
12 | Use this project to quickly get started with ReactJS + Amazon Cognito + CDK + GitLab CI/CD
13 |
14 | You will find the CDK code under the cdk/ directory and the reactJS app under reactjs-webapp/
15 |
16 | ### Directions
17 |
18 | [Video instructions](https://www.youtube.com/watch?v=6OC6AqF-IMc)
19 |
20 | #### Summary
21 |
22 | You'll need to first go through the CDK instructions in order to provision the requried resources. Then move on to the
23 | reactJS part. Each of the two subdirectories has its own README.md file which contains instructions. Please review them
24 | first before proceeding.
25 |
--------------------------------------------------------------------------------
/cdk/README.md:
--------------------------------------------------------------------------------
1 | #### Deployment
2 |
3 | ```
4 |
5 | mvn package && cdk synth
6 |
7 | cdk deploy "*"
8 |
9 | -- OR --
10 |
11 | // DDB
12 | cdk deploy "BudilovUsersDDBTable"
13 |
14 | // Cognito
15 | cdk deploy "BudilovCognitoLambdaStack"
16 | cdk deploy "BudilovCognitoStack"
17 |
18 | // CloudFront & S3
19 | cdk deploy "SPAStack"
20 |
21 | ```
22 |
23 | ###### Useful commands
24 |
25 | * `mvn package` compile and run tests
26 | * `cdk ls` list all stacks in the app
27 | * `cdk synth` emits the synthesized CloudFormation template
28 | * `cdk deploy` deploy this stack to your default AWS account/region
29 | * `cdk diff` compare deployed stack with current state
30 | * `cdk docs` open CDK documentation
31 |
--------------------------------------------------------------------------------
/cdk/cdk.context.json:
--------------------------------------------------------------------------------
1 | {
2 | "@aws-cdk/core:enableStackNameDuplicates": "true",
3 | "aws-cdk:enableDiffNoFail": "true"
4 | }
5 |
--------------------------------------------------------------------------------
/cdk/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "mvn -e -q exec:java"
3 | }
4 |
--------------------------------------------------------------------------------
/cdk/cdk.out/BudilovCognitoLambdaStack.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "SecretWitCognitoToDDB32311C26": {
4 | "Type": "AWS::IAM::Role",
5 | "Properties": {
6 | "AssumeRolePolicyDocument": {
7 | "Statement": [
8 | {
9 | "Action": "sts:AssumeRole",
10 | "Effect": "Allow",
11 | "Principal": {
12 | "Service": "lambda.amazonaws.com"
13 | }
14 | }
15 | ],
16 | "Version": "2012-10-17"
17 | },
18 | "ManagedPolicyArns": [
19 | {
20 | "Fn::Join": [
21 | "",
22 | [
23 | "arn:",
24 | {
25 | "Ref": "AWS::Partition"
26 | },
27 | ":iam::aws:policy/AmazonDynamoDBFullAccess"
28 | ]
29 | ]
30 | },
31 | {
32 | "Fn::Join": [
33 | "",
34 | [
35 | "arn:",
36 | {
37 | "Ref": "AWS::Partition"
38 | },
39 | ":iam::aws:policy/CloudWatchLogsFullAccess"
40 | ]
41 | ]
42 | }
43 | ],
44 | "RoleName": "SecretWitCognitoToDynamoDBRole"
45 | },
46 | "Metadata": {
47 | "aws:cdk:path": "BudilovCognitoLambdaStack/SecretWitCognitoToDDB/Resource"
48 | }
49 | },
50 | "copyUserToDynamoDBLambdaC5ADE641": {
51 | "Type": "AWS::Lambda::Function",
52 | "Properties": {
53 | "Code": {
54 | "ZipFile": "const aws = require('aws-sdk');\nconst ddb = new aws.DynamoDB({apiVersion: '2012-10-08'});\n\n/**\n * @author Vladimir Budilov\n *\n * Upon Cognito SignUp, a user is added to the DDB table\n *\n * Cognito event:\n * https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-lambda-trigger-examples.html#aws-lambda-triggers-post-confirmation-example\n *\n * Writing to DDB:\n * https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-table-read-write.html\n *\n * Sample input:\n *\n {\n version:'1',\n region:'us-east-1',\n userPoolId:'us-east-1_9xasdfasdf3A',\n userName:'budilov@domain.com',\n callerContext:{\n awsSdkVersion:'aws-sdk-unknown-unknown',\n clientId:'1asdfasdfasdfasdf3hjjgp'\n },\n triggerSource:'PostConfirmation_ConfirmSignUp',\n request: {\n userAttributes: {\n sub: '517cd3b9-4e99-4135-bccf-0300f42a96fb',\n 'cognito:user_status': 'CONFIRMED',\n email_verified: 'true',\n 'cognito:email_alias': 'budilov@gmail.com',\n email: 'budilov@gmail.com'\n }\n },\n response:{\n }\n }\n * @param event\n * @param context\n */\nexports.handler = async (event, context) => {\n console.log(event);\n\n const date = new Date();\n\n const tableName = process.env.TABLE_NAME;\n const region = process.env.REGION;\n const partitionId = process.env.PARTITION_ID;\n const sortKey = process.env.SORT_KEY;\n\n console.log(`table=${tableName} -- region=${region}`);\n\n aws.config.update({region});\n\n if (!event.request.userAttributes.sub) {\n // Nothing to do, the user's email ID is unknown\n console.log(\"Error: Nothing was written to DDB or SQS\");\n return context.done(null, event);\n }\n\n // -- Write data to DDB\n // If the required parameters are present, proceed\n let ddbParams = {\n TableName: tableName,\n Item: {\n 'email': {S: event.request.userAttributes.email},\n 'createdDate': {S: date.toISOString()},\n 'firstLogin': {BOOL: true}\n }\n };\n\n ddbParams.Item[partitionId] = {S: event.request.userAttributes.sub};\n ddbParams.Item[sortKey] = {S: \"user\"};\n\n console.log(\"ddbParams: \" + ddbParams);\n\n // Call DynamoDB\n try {\n ddbResult = await ddb.putItem(ddbParams).promise();\n console.log(\"Success\");\n } catch (err) {\n console.log(\"Error\", err);\n }\n\n console.log(\"Success: Everything executed correctly\");\n context.done(null, event);\n};"
55 | },
56 | "Handler": "index.handler",
57 | "Role": {
58 | "Fn::GetAtt": [
59 | "SecretWitCognitoToDDB32311C26",
60 | "Arn"
61 | ]
62 | },
63 | "Runtime": "nodejs12.x",
64 | "Environment": {
65 | "Variables": {
66 | "PARTITION_ID": "userId",
67 | "SORT_KEY": "sortKey",
68 | "REGION": "us-east-1",
69 | "TABLE_NAME": "SecretWitUsers"
70 | }
71 | },
72 | "FunctionName": "SecretWitCognitoToDynamoDBLambda"
73 | },
74 | "DependsOn": [
75 | "SecretWitCognitoToDDB32311C26"
76 | ],
77 | "Metadata": {
78 | "aws:cdk:path": "BudilovCognitoLambdaStack/copyUserToDynamoDBLambda/Resource"
79 | }
80 | },
81 | "copyUserToDynamoDBLambdaPostConfirmationCognito2C554ABF": {
82 | "Type": "AWS::Lambda::Permission",
83 | "Properties": {
84 | "Action": "lambda:InvokeFunction",
85 | "FunctionName": {
86 | "Fn::GetAtt": [
87 | "copyUserToDynamoDBLambdaC5ADE641",
88 | "Arn"
89 | ]
90 | },
91 | "Principal": "cognito-idp.amazonaws.com"
92 | },
93 | "Metadata": {
94 | "aws:cdk:path": "BudilovCognitoLambdaStack/copyUserToDynamoDBLambda/PostConfirmationCognito"
95 | }
96 | },
97 | "SecretWitCognitoUpdate6E1AFC66": {
98 | "Type": "AWS::IAM::Role",
99 | "Properties": {
100 | "AssumeRolePolicyDocument": {
101 | "Statement": [
102 | {
103 | "Action": "sts:AssumeRole",
104 | "Effect": "Allow",
105 | "Principal": {
106 | "Service": "lambda.amazonaws.com"
107 | }
108 | }
109 | ],
110 | "Version": "2012-10-17"
111 | },
112 | "ManagedPolicyArns": [
113 | {
114 | "Fn::Join": [
115 | "",
116 | [
117 | "arn:",
118 | {
119 | "Ref": "AWS::Partition"
120 | },
121 | ":iam::aws:policy/AmazonCognitoPowerUser"
122 | ]
123 | ]
124 | }
125 | ],
126 | "RoleName": "SecretWitCognitoUpdateRole"
127 | },
128 | "Metadata": {
129 | "aws:cdk:path": "BudilovCognitoLambdaStack/SecretWitCognitoUpdate/Resource"
130 | }
131 | },
132 | "autoConfirmFunction43DC0E74": {
133 | "Type": "AWS::Lambda::Function",
134 | "Properties": {
135 | "Code": {
136 | "ZipFile": "'use strict';\n\nmodule.exports.confirm = (event, context, callback) => {\n\n console.log('Received event:', JSON.stringify(event, null, 2));\n\n const modifiedEvent = event;\n\n // check that we're acting on the right trigger\n if (event.triggerSource === \"PreSignUp_SignUp\") {\n\n // auto confirm the user\n modifiedEvent.response.autoConfirmUser = true;\n\n // Set the email as verified if it is in the request\n modifiedEvent.response.autoVerifyEmail = true;\n\n callback(null, modifiedEvent);\n return\n }\n\n // Throw an error if invoked from the wrong trigger\n callback(`Misconfigured Cognito Trigger ${event.triggerSource}`)\n};"
137 | },
138 | "Handler": "index.confirm",
139 | "Role": {
140 | "Fn::GetAtt": [
141 | "SecretWitCognitoUpdate6E1AFC66",
142 | "Arn"
143 | ]
144 | },
145 | "Runtime": "nodejs12.x",
146 | "FunctionName": "SecretWitCognitoAutoConfirmUser"
147 | },
148 | "DependsOn": [
149 | "SecretWitCognitoUpdate6E1AFC66"
150 | ],
151 | "Metadata": {
152 | "aws:cdk:path": "BudilovCognitoLambdaStack/autoConfirmFunction/Resource"
153 | }
154 | },
155 | "autoConfirmFunctionPreSignUpCognito11E59816": {
156 | "Type": "AWS::Lambda::Permission",
157 | "Properties": {
158 | "Action": "lambda:InvokeFunction",
159 | "FunctionName": {
160 | "Fn::GetAtt": [
161 | "autoConfirmFunction43DC0E74",
162 | "Arn"
163 | ]
164 | },
165 | "Principal": "cognito-idp.amazonaws.com"
166 | },
167 | "Metadata": {
168 | "aws:cdk:path": "BudilovCognitoLambdaStack/autoConfirmFunction/PreSignUpCognito"
169 | }
170 | }
171 | },
172 | "Outputs": {
173 | "ExportsOutputFnGetAttcopyUserToDynamoDBLambdaC5ADE641Arn314C8B0C": {
174 | "Value": {
175 | "Fn::GetAtt": [
176 | "copyUserToDynamoDBLambdaC5ADE641",
177 | "Arn"
178 | ]
179 | },
180 | "Export": {
181 | "Name": "BudilovCognitoLambdaStack:ExportsOutputFnGetAttcopyUserToDynamoDBLambdaC5ADE641Arn314C8B0C"
182 | }
183 | },
184 | "ExportsOutputFnGetAttautoConfirmFunction43DC0E74ArnBBC2691A": {
185 | "Value": {
186 | "Fn::GetAtt": [
187 | "autoConfirmFunction43DC0E74",
188 | "Arn"
189 | ]
190 | },
191 | "Export": {
192 | "Name": "BudilovCognitoLambdaStack:ExportsOutputFnGetAttautoConfirmFunction43DC0E74ArnBBC2691A"
193 | }
194 | }
195 | }
196 | }
--------------------------------------------------------------------------------
/cdk/cdk.out/BudilovCognitoStack.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "SecretWitPoolsmsRoleC86E39C0": {
4 | "Type": "AWS::IAM::Role",
5 | "Properties": {
6 | "AssumeRolePolicyDocument": {
7 | "Statement": [
8 | {
9 | "Action": "sts:AssumeRole",
10 | "Condition": {
11 | "StringEquals": {
12 | "sts:ExternalId": "BudilovCognitoStackSecretWitPoolAAD066A4"
13 | }
14 | },
15 | "Effect": "Allow",
16 | "Principal": {
17 | "Service": "cognito-idp.amazonaws.com"
18 | }
19 | }
20 | ],
21 | "Version": "2012-10-17"
22 | },
23 | "Policies": [
24 | {
25 | "PolicyDocument": {
26 | "Statement": [
27 | {
28 | "Action": "sns:Publish",
29 | "Effect": "Allow",
30 | "Resource": "*"
31 | }
32 | ],
33 | "Version": "2012-10-17"
34 | },
35 | "PolicyName": "sns-publish"
36 | }
37 | ]
38 | },
39 | "Metadata": {
40 | "aws:cdk:path": "BudilovCognitoStack/SecretWitPool/smsRole/Resource"
41 | }
42 | },
43 | "SecretWitPool94B33D66": {
44 | "Type": "AWS::Cognito::UserPool",
45 | "Properties": {
46 | "AdminCreateUserConfig": {
47 | "AllowAdminCreateUserOnly": false
48 | },
49 | "AutoVerifiedAttributes": [
50 | "email"
51 | ],
52 | "EmailVerificationMessage": "Hello {username}, Your verification code is {####}",
53 | "EmailVerificationSubject": "Verify your new account",
54 | "LambdaConfig": {
55 | "PostConfirmation": {
56 | "Fn::ImportValue": "BudilovCognitoLambdaStack:ExportsOutputFnGetAttcopyUserToDynamoDBLambdaC5ADE641Arn314C8B0C"
57 | },
58 | "PreSignUp": {
59 | "Fn::ImportValue": "BudilovCognitoLambdaStack:ExportsOutputFnGetAttautoConfirmFunction43DC0E74ArnBBC2691A"
60 | }
61 | },
62 | "Policies": {
63 | "PasswordPolicy": {
64 | "MinimumLength": 6,
65 | "RequireLowercase": false,
66 | "RequireNumbers": false,
67 | "RequireSymbols": false,
68 | "RequireUppercase": false
69 | }
70 | },
71 | "SmsConfiguration": {
72 | "ExternalId": "BudilovCognitoStackSecretWitPoolAAD066A4",
73 | "SnsCallerArn": {
74 | "Fn::GetAtt": [
75 | "SecretWitPoolsmsRoleC86E39C0",
76 | "Arn"
77 | ]
78 | }
79 | },
80 | "SmsVerificationMessage": "The verification code to your new account is {####}",
81 | "UsernameAttributes": [
82 | "email"
83 | ],
84 | "UserPoolName": "SecretWit",
85 | "VerificationMessageTemplate": {
86 | "DefaultEmailOption": "CONFIRM_WITH_CODE",
87 | "EmailMessage": "Hello {username}, Your verification code is {####}",
88 | "EmailSubject": "Verify your new account",
89 | "SmsMessage": "The verification code to your new account is {####}"
90 | }
91 | },
92 | "Metadata": {
93 | "aws:cdk:path": "BudilovCognitoStack/SecretWitPool/Resource"
94 | }
95 | },
96 | "SecretWitPoolClient5E8D82FE": {
97 | "Type": "AWS::Cognito::UserPoolClient",
98 | "Properties": {
99 | "UserPoolId": {
100 | "Ref": "SecretWitPool94B33D66"
101 | },
102 | "ClientName": "webClient",
103 | "GenerateSecret": false
104 | },
105 | "Metadata": {
106 | "aws:cdk:path": "BudilovCognitoStack/SecretWitPoolClient/Resource"
107 | }
108 | },
109 | "cognitoPoolIdSecret3F8350DC": {
110 | "Type": "AWS::SSM::Parameter",
111 | "Properties": {
112 | "Type": "String",
113 | "Value": {
114 | "Ref": "SecretWitPool94B33D66"
115 | },
116 | "AllowedPattern": ".*",
117 | "Description": "cognitoPoolIdSecret",
118 | "Name": "cognitoPoolIdSecret",
119 | "Tier": "Standard"
120 | },
121 | "Metadata": {
122 | "aws:cdk:path": "BudilovCognitoStack/cognitoPoolIdSecret/Resource"
123 | }
124 | },
125 | "cognitoPoolClientIdSecretF7886D2A": {
126 | "Type": "AWS::SSM::Parameter",
127 | "Properties": {
128 | "Type": "String",
129 | "Value": {
130 | "Ref": "SecretWitPoolClient5E8D82FE"
131 | },
132 | "AllowedPattern": ".*",
133 | "Description": "cognitoPoolClientId",
134 | "Name": "cognitoPoolClientId",
135 | "Tier": "Standard"
136 | },
137 | "Metadata": {
138 | "aws:cdk:path": "BudilovCognitoStack/cognitoPoolClientIdSecret/Resource"
139 | }
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/cdk/cdk.out/BudilovUsersDDBTable.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "SecretWitUsers2319B3AA": {
4 | "Type": "AWS::DynamoDB::Table",
5 | "Properties": {
6 | "KeySchema": [
7 | {
8 | "AttributeName": "userId",
9 | "KeyType": "HASH"
10 | },
11 | {
12 | "AttributeName": "sortKey",
13 | "KeyType": "RANGE"
14 | }
15 | ],
16 | "AttributeDefinitions": [
17 | {
18 | "AttributeName": "userId",
19 | "AttributeType": "S"
20 | },
21 | {
22 | "AttributeName": "sortKey",
23 | "AttributeType": "S"
24 | }
25 | ],
26 | "ProvisionedThroughput": {
27 | "ReadCapacityUnits": 5,
28 | "WriteCapacityUnits": 5
29 | },
30 | "TableName": "SecretWitUsers"
31 | },
32 | "UpdateReplacePolicy": "Retain",
33 | "DeletionPolicy": "Retain",
34 | "Metadata": {
35 | "aws:cdk:path": "BudilovUsersDDBTable/SecretWitUsers/Resource"
36 | }
37 | },
38 | "usersTable12EF4ADD": {
39 | "Type": "AWS::SSM::Parameter",
40 | "Properties": {
41 | "Type": "String",
42 | "Value": {
43 | "Ref": "SecretWitUsers2319B3AA"
44 | },
45 | "AllowedPattern": ".*",
46 | "Description": "usersTable",
47 | "Name": "usersTable",
48 | "Tier": "Standard"
49 | },
50 | "Metadata": {
51 | "aws:cdk:path": "BudilovUsersDDBTable/usersTable/Resource"
52 | }
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/cdk/cdk.out/SPAStack.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "SecretWitCertificateBAD7857E": {
4 | "Type": "AWS::CertificateManager::Certificate",
5 | "Properties": {
6 | "DomainName": "secretwit.com",
7 | "DomainValidationOptions": [
8 | {
9 | "DomainName": "secretwit.com",
10 | "ValidationDomain": "secretwit.com"
11 | },
12 | {
13 | "DomainName": "*.secretwit.com",
14 | "ValidationDomain": "secretwit.com"
15 | }
16 | ],
17 | "SubjectAlternativeNames": [
18 | "*.secretwit.com"
19 | ]
20 | },
21 | "Metadata": {
22 | "aws:cdk:path": "SPAStack/SecretWitCertificate/Resource"
23 | }
24 | },
25 | "SecretWitBucket235C6680": {
26 | "Type": "AWS::S3::Bucket",
27 | "Properties": {
28 | "BucketName": "secretwit.website",
29 | "CorsConfiguration": {
30 | "CorsRules": [
31 | {
32 | "AllowedMethods": [
33 | "GET",
34 | "HEAD"
35 | ],
36 | "AllowedOrigins": [
37 | "*.secretwit.com",
38 | "www.secretwit.com"
39 | ]
40 | }
41 | ]
42 | },
43 | "WebsiteConfiguration": {
44 | "ErrorDocument": "index.html",
45 | "IndexDocument": "index.html"
46 | }
47 | },
48 | "UpdateReplacePolicy": "Delete",
49 | "DeletionPolicy": "Delete",
50 | "Metadata": {
51 | "aws:cdk:path": "SPAStack/SecretWitBucket/Resource"
52 | }
53 | },
54 | "SecretWitBucketPolicyDD62A02B": {
55 | "Type": "AWS::S3::BucketPolicy",
56 | "Properties": {
57 | "Bucket": {
58 | "Ref": "SecretWitBucket235C6680"
59 | },
60 | "PolicyDocument": {
61 | "Statement": [
62 | {
63 | "Action": "s3:GetObject",
64 | "Effect": "Allow",
65 | "Principal": "*",
66 | "Resource": {
67 | "Fn::Join": [
68 | "",
69 | [
70 | {
71 | "Fn::GetAtt": [
72 | "SecretWitBucket235C6680",
73 | "Arn"
74 | ]
75 | },
76 | "/*"
77 | ]
78 | ]
79 | }
80 | }
81 | ],
82 | "Version": "2012-10-17"
83 | }
84 | },
85 | "Metadata": {
86 | "aws:cdk:path": "SPAStack/SecretWitBucket/Policy/Resource"
87 | }
88 | },
89 | "SecretWitCloudFrontCFDistribution66317D44": {
90 | "Type": "AWS::CloudFront::Distribution",
91 | "Properties": {
92 | "DistributionConfig": {
93 | "Aliases": [
94 | "secretwit.com",
95 | "*.secretwit.com"
96 | ],
97 | "CustomErrorResponses": [
98 | {
99 | "ErrorCode": 403,
100 | "ResponseCode": 200,
101 | "ResponsePagePath": "/index.html"
102 | }
103 | ],
104 | "DefaultCacheBehavior": {
105 | "AllowedMethods": [
106 | "DELETE",
107 | "GET",
108 | "HEAD",
109 | "OPTIONS",
110 | "PATCH",
111 | "POST",
112 | "PUT"
113 | ],
114 | "CachedMethods": [
115 | "GET",
116 | "HEAD"
117 | ],
118 | "Compress": true,
119 | "ForwardedValues": {
120 | "Cookies": {
121 | "Forward": "none"
122 | },
123 | "QueryString": false
124 | },
125 | "TargetOriginId": "origin1",
126 | "ViewerProtocolPolicy": "redirect-to-https"
127 | },
128 | "DefaultRootObject": "index.html",
129 | "Enabled": true,
130 | "HttpVersion": "http2",
131 | "IPV6Enabled": true,
132 | "Origins": [
133 | {
134 | "DomainName": {
135 | "Fn::GetAtt": [
136 | "SecretWitBucket235C6680",
137 | "RegionalDomainName"
138 | ]
139 | },
140 | "Id": "origin1",
141 | "S3OriginConfig": {}
142 | }
143 | ],
144 | "PriceClass": "PriceClass_100",
145 | "ViewerCertificate": {
146 | "AcmCertificateArn": {
147 | "Ref": "SecretWitCertificateBAD7857E"
148 | },
149 | "MinimumProtocolVersion": "TLSv1",
150 | "SslSupportMethod": "sni-only"
151 | }
152 | }
153 | },
154 | "Metadata": {
155 | "aws:cdk:path": "SPAStack/SecretWitCloudFront/CFDistribution"
156 | }
157 | }
158 | }
159 | }
--------------------------------------------------------------------------------
/cdk/cdk.out/cdk.out:
--------------------------------------------------------------------------------
1 | {"version":"1.33.0"}
--------------------------------------------------------------------------------
/cdk/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.budilov
7 | cdk
8 | 0.1
9 |
10 |
11 | UTF-8
12 |
13 |
14 |
15 |
16 |
17 | org.apache.maven.plugins
18 | maven-compiler-plugin
19 | 3.8.1
20 |
21 | 11
22 | 11
23 |
24 |
25 |
26 |
27 | org.codehaus.mojo
28 | exec-maven-plugin
29 | 1.6.0
30 |
31 | com.budilov.cdk.BudilovApp
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | software.amazon.awscdk
41 | core
42 | 1.33.1
43 |
44 |
45 |
46 | software.amazon.awscdk
47 | ec2
48 | 1.33.1
49 |
50 |
51 |
52 | software.amazon.awscdk
53 | dynamodb
54 | 1.33.1
55 |
56 |
57 |
58 | software.amazon.awscdk
59 | cognito
60 | 1.33.1
61 |
62 |
63 |
64 | software.amazon.awscdk
65 | cloudfront
66 | 1.33.1
67 |
68 |
69 |
70 | software.amazon.awscdk
71 | s3
72 | 1.33.1
73 |
74 |
75 |
76 | software.amazon.awscdk
77 | s3-deployment
78 | 1.33.1
79 |
80 |
81 |
82 |
83 | software.amazon.awssdk
84 | ssm
85 | 2.11.4
86 |
87 |
88 |
89 |
90 |
91 | junit
92 | junit
93 | 4.12
94 | test
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/BudilovApp.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk;
2 |
3 | import com.budilov.cdk.cognito.CognitoLambdaStack;
4 | import com.budilov.cdk.cognito.CognitoStack;
5 | import com.budilov.cdk.ddb.DDBUserTableStack;
6 | import com.budilov.cdk.web.SPAStack;
7 | import software.amazon.awscdk.core.App;
8 |
9 | public class BudilovApp {
10 |
11 | public static void main(final String[] args) throws Exception {
12 | App app = new App();
13 |
14 | // DynamoDB Stack
15 | new DDBUserTableStack(app, "BudilovUsersDDBTable");
16 |
17 | // Cognito
18 | CognitoLambdaStack cognitoLambdaStack = new CognitoLambdaStack(app, "BudilovCognitoLambdaStack");
19 | CognitoStack cognitoStack = new CognitoStack(app, "BudilovCognitoStack");
20 | cognitoStack.addDependency(cognitoLambdaStack);
21 |
22 | // SPA
23 | new SPAStack(app, "SPAStack");
24 | app.synth();
25 | }
26 | }
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/cognito/CognitoLambdaStack.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk.cognito;
2 |
3 | import com.budilov.cdk.util.Properties;
4 | import org.jetbrains.annotations.NotNull;
5 | import software.amazon.awscdk.core.Construct;
6 | import software.amazon.awscdk.core.Stack;
7 | import software.amazon.awscdk.core.StackProps;
8 | import software.amazon.awscdk.services.iam.CompositePrincipal;
9 | import software.amazon.awscdk.services.iam.ManagedPolicy;
10 | import software.amazon.awscdk.services.iam.Role;
11 | import software.amazon.awscdk.services.iam.ServicePrincipal;
12 | import software.amazon.awscdk.services.lambda.Code;
13 | import software.amazon.awscdk.services.lambda.Function;
14 | import software.amazon.awscdk.services.lambda.FunctionProps;
15 | import software.amazon.awscdk.services.lambda.Runtime;
16 |
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.nio.file.Files;
20 | import java.util.List;
21 | import java.util.Map;
22 |
23 | /**
24 | * @author Vladimir Budilov
25 | *
26 | * Simple way to create a Cognito User Pool with Lambda triggers
27 | */
28 | public class CognitoLambdaStack extends Stack {
29 | static Function copyUserToDynamoDBLambda;
30 | static Function autoConfirmFunction;
31 |
32 | public CognitoLambdaStack(final Construct scope, final String id) throws IOException {
33 | this(scope, id, null);
34 | }
35 |
36 | public CognitoLambdaStack(final Construct scope, final String id, final StackProps props) throws IOException {
37 | super(scope, id, props);
38 |
39 | // Copy users to DDB
40 | Role ddbAccess = Role.Builder.create(this, "SecretWitCognitoToDDB")
41 | .assumedBy(
42 | new CompositePrincipal(
43 | ServicePrincipal.Builder.create("lambda").build()
44 | )
45 | )
46 | .managedPolicies(List.of(ManagedPolicy.fromAwsManagedPolicyName("AmazonDynamoDBFullAccess"),
47 | ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess")))
48 | .roleName("SecretWitCognitoToDynamoDBRole")
49 | .build();
50 |
51 | copyUserToDynamoDBLambda = new Function(this, "copyUserToDynamoDBLambda", FunctionProps.builder()
52 | .runtime(Runtime.NODEJS_12_X)
53 | .functionName("SecretWitCognitoToDynamoDBLambda")
54 | .role(ddbAccess)
55 | .handler("index.handler")
56 | .code(Code.fromInline(getLambdaFunctionFromFile("cognitoToDynamoDBLambda")))
57 | .environment(Map.of("TABLE_NAME", Properties.DDB_USERS_TABLE,
58 | "REGION", Properties.REGION,
59 | "PARTITION_ID", Properties.DDB_USERS_TABLE_PARTITION_ID,
60 | "SORT_KEY", Properties.DDB_USERS_TABLE_SORT_KEY))
61 | .build());
62 |
63 | // Auto-confirm users
64 | Role cognitoAccess = Role.Builder.create(this, "SecretWitCognitoUpdate")
65 | .assumedBy(
66 | new CompositePrincipal(
67 | ServicePrincipal.Builder.create("lambda").build()
68 | )
69 | )
70 | .managedPolicies(List.of(ManagedPolicy.fromAwsManagedPolicyName("AmazonCognitoPowerUser")))
71 | .roleName("SecretWitCognitoUpdateRole")
72 | .build();
73 |
74 |
75 | autoConfirmFunction = new Function(this, "autoConfirmFunction", FunctionProps.builder()
76 | .runtime(Runtime.NODEJS_12_X)
77 | .functionName("SecretWitCognitoAutoConfirmUser")
78 | .role(cognitoAccess)
79 | .handler("index.confirm")
80 | .code(Code.fromInline(getLambdaFunctionFromFile("cognitoAutoConfirmUser")))
81 | .build());
82 |
83 | }
84 |
85 |
86 | private String getLambdaFunctionFromFile(@NotNull String fileName) throws IOException {
87 |
88 | File file = new File(
89 | getClass().getClassLoader().getResource(fileName + ".js").getFile()
90 | );
91 |
92 | return new String(Files.readAllBytes(file.toPath()));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/cognito/CognitoStack.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk.cognito;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import software.amazon.awscdk.core.Construct;
5 | import software.amazon.awscdk.core.Stack;
6 | import software.amazon.awscdk.core.StackProps;
7 | import software.amazon.awscdk.services.cognito.*;
8 | import software.amazon.awscdk.services.ssm.ParameterTier;
9 | import software.amazon.awscdk.services.ssm.StringParameter;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.nio.file.Files;
14 |
15 | /**
16 | * @author Vladimir Budilov
17 | *
18 | * Simple way to create a Cognito User Pool with Lambda triggers
19 | */
20 | public class CognitoStack extends Stack {
21 | UserPool pool;
22 | UserPoolClient poolClient;
23 |
24 | public CognitoStack(final Construct scope, final String id) throws IOException {
25 | this(scope, id, null);
26 | }
27 |
28 | public CognitoStack(final Construct scope, final String id, final StackProps props) throws IOException {
29 | super(scope, id, props);
30 |
31 | pool = UserPool.Builder.create(this, "SecretWitPool")
32 | .userPoolName("SecretWit")
33 | .selfSignUpEnabled(true)
34 | .passwordPolicy(PasswordPolicy.builder()
35 | .minLength(6)
36 | .requireDigits(false)
37 | .requireLowercase(false)
38 | .requireSymbols(false)
39 | .requireUppercase(false)
40 | .build())
41 | .signInAliases(SignInAliases.builder()
42 | .email(true)
43 | .build())
44 | .lambdaTriggers(UserPoolTriggers.builder()
45 | .postConfirmation(CognitoLambdaStack.copyUserToDynamoDBLambda) // Copy the user to a DynamoDB Table
46 | .preSignUp(CognitoLambdaStack.autoConfirmFunction) // Auto-confirm user
47 | .build())
48 | .build();
49 |
50 | poolClient = UserPoolClient.Builder.create(this, "SecretWitPoolClient")
51 | .userPoolClientName("webClient")
52 | .userPool(pool)
53 | .generateSecret(false)
54 | .build();
55 |
56 | StringParameter.Builder.create(this, "cognitoPoolIdSecret")
57 | .allowedPattern(".*")
58 | .description("cognitoPoolIdSecret")
59 | .parameterName("cognitoPoolIdSecret")
60 | .stringValue(this.pool.getUserPoolId())
61 | .tier(ParameterTier.STANDARD)
62 | .build();
63 |
64 | StringParameter.Builder.create(this, "cognitoPoolClientIdSecret")
65 | .allowedPattern(".*")
66 | .description("cognitoPoolClientId")
67 | .parameterName("cognitoPoolClientId")
68 | .stringValue(this.poolClient.getUserPoolClientId())
69 | .tier(ParameterTier.STANDARD)
70 | .build();
71 |
72 | // CfnOutput output = CfnOutput.Builder.create(this, "OutputName")
73 | // .value(this.pool.getUserPoolId())
74 | // .description("The name of an S3 bucket")
75 | // .exportName("cognitoPoolId")
76 | // .build();
77 |
78 | // SSM.addParameter("cognitoPoolId", this.pool.getUserPoolId());
79 | // SSM.addParameter("cognitoPoolClientId", this.upClient.getUserPoolClientId());
80 |
81 | }
82 |
83 |
84 | private String getLambdaFunctionFromFile(@NotNull String fileName) throws IOException {
85 |
86 | File file = new File(
87 | getClass().getClassLoader().getResource(fileName + ".js").getFile()
88 | );
89 |
90 | return new String(Files.readAllBytes(file.toPath()));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/ddb/DDBUserTableStack.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk.ddb;
2 |
3 | import com.budilov.cdk.util.Properties;
4 | import software.amazon.awscdk.core.Construct;
5 | import software.amazon.awscdk.core.Stack;
6 | import software.amazon.awscdk.core.StackProps;
7 | import software.amazon.awscdk.services.dynamodb.Attribute;
8 | import software.amazon.awscdk.services.dynamodb.AttributeType;
9 | import software.amazon.awscdk.services.dynamodb.Table;
10 | import software.amazon.awscdk.services.dynamodb.TableProps;
11 | import software.amazon.awscdk.services.ssm.ParameterTier;
12 | import software.amazon.awscdk.services.ssm.StringParameter;
13 |
14 | public class DDBUserTableStack extends Stack {
15 | final public Table usersTable;
16 |
17 | public DDBUserTableStack(final Construct scope, final String id) {
18 | this(scope, id, null);
19 | }
20 |
21 | public DDBUserTableStack(final Construct scope, final String id, final StackProps props) {
22 | super(scope, id, props);
23 |
24 | usersTable = new Table(this, Properties.DDB_USERS_TABLE, TableProps.builder()
25 | .tableName(Properties.DDB_USERS_TABLE)
26 | .partitionKey(Attribute.builder()
27 | .name(Properties.DDB_USERS_TABLE_PARTITION_ID)
28 | .type(AttributeType.STRING)
29 | .build())
30 | .sortKey(Attribute.builder()
31 | .name(Properties.DDB_USERS_TABLE_SORT_KEY)
32 | .type(AttributeType.STRING)
33 | .build())
34 | .build());
35 |
36 | StringParameter.Builder.create(this, "usersTable")
37 | .allowedPattern(".*")
38 | .description("usersTable")
39 | .parameterName("usersTable")
40 | .stringValue(usersTable.getTableName())
41 | .tier(ParameterTier.STANDARD)
42 | .build();
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/util/Properties.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk.util;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.util.Map;
9 |
10 | public class Properties {
11 |
12 | // General
13 | public static String REGION = "us-east-1";
14 | public static String ACCOUNT = "000000000"; //todo
15 |
16 | // DynamoDB
17 | public static String DDB_USERS_TABLE = "SecretWitUsers";
18 | public static String DDB_USERS_TABLE_PARTITION_ID = "userId";
19 | public static String DDB_USERS_TABLE_SORT_KEY = "sortKey";
20 |
21 | public static String readResourceFileContents(@NotNull String fileName, Map replacements) throws IOException {
22 |
23 | File file = new File(
24 | Properties.class.getClassLoader().getResource(fileName).getFile()
25 | );
26 |
27 | String resourceFile = new String(Files.readAllBytes(file.toPath()));
28 |
29 | if (replacements != null)
30 | for (Map.Entry entry : replacements.entrySet()) {
31 | resourceFile = resourceFile.replaceAll(entry.getKey(), entry.getValue());
32 | }
33 |
34 | return resourceFile;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/util/SSM.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk.util;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import software.amazon.awssdk.services.ssm.SsmClient;
5 | import software.amazon.awssdk.services.ssm.model.ParameterType;
6 | import software.amazon.awssdk.services.ssm.model.PutParameterRequest;
7 | import software.amazon.awssdk.services.ssm.model.PutParameterResponse;
8 |
9 | public class SSM {
10 |
11 | static private final SsmClient client = SsmClient.builder().build();
12 |
13 | static public void addParameter(@NotNull String name, @NotNull String value) {
14 | PutParameterRequest request = PutParameterRequest.builder()
15 | .name(name)
16 | .value(value)
17 | .type(ParameterType.STRING)
18 | .build();
19 | try {
20 | PutParameterResponse response = client.putParameter(request);
21 |
22 | } catch (Exception e) {
23 |
24 | }
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/cdk/src/main/java/com/budilov/cdk/web/SPAStack.java:
--------------------------------------------------------------------------------
1 | package com.budilov.cdk.web;
2 |
3 | import software.amazon.awscdk.core.Construct;
4 | import software.amazon.awscdk.core.RemovalPolicy;
5 | import software.amazon.awscdk.core.Stack;
6 | import software.amazon.awscdk.core.StackProps;
7 | import software.amazon.awscdk.services.certificatemanager.Certificate;
8 | import software.amazon.awscdk.services.certificatemanager.CertificateProps;
9 | import software.amazon.awscdk.services.cloudfront.*;
10 | import software.amazon.awscdk.services.s3.Bucket;
11 | import software.amazon.awscdk.services.s3.CorsRule;
12 | import software.amazon.awscdk.services.s3.HttpMethods;
13 |
14 | import java.io.IOException;
15 | import java.util.List;
16 |
17 | /**
18 | * Stack that creates IAM roles for EKS Pods
19 | */
20 | public class SPAStack extends Stack {
21 |
22 | public SPAStack(final Construct scope, final String id) throws IOException {
23 | this(scope, id, null);
24 | }
25 |
26 | public SPAStack(final Construct scope, final String id, final StackProps props) throws IOException {
27 | super(scope, id, props);
28 |
29 | Certificate certificate = new Certificate(this, "SecretWitCertificate", new CertificateProps.Builder()
30 | .domainName("secretwit.com")
31 | .subjectAlternativeNames(List.of("*.secretwit.com")).build());
32 |
33 | Bucket websiteBucket = Bucket.Builder.create(this, "SecretWitBucket")
34 | .bucketName("secretwit.website")
35 | .publicReadAccess(true)
36 | .websiteIndexDocument("index.html")
37 | .websiteErrorDocument("index.html")
38 | .cors(List.of(CorsRule.builder()
39 | .allowedMethods(List.of(HttpMethods.GET, HttpMethods.HEAD))
40 | .allowedOrigins(List.of("*.secretwit.com", "www.secretwit.com"))
41 | .build()))
42 | .removalPolicy(RemovalPolicy.DESTROY)
43 | .build();
44 |
45 | CloudFrontWebDistribution distribution = new CloudFrontWebDistribution(this, "SecretWitCloudFront",
46 | new CloudFrontWebDistributionProps.Builder()
47 | .originConfigs(List.of(new SourceConfiguration.Builder()
48 | .s3OriginSource(new S3OriginConfig.Builder()
49 | .s3BucketSource(websiteBucket)
50 | .build())
51 | .behaviors(List.of(new Behavior.Builder()
52 | .isDefaultBehavior(true)
53 | .cachedMethods(CloudFrontAllowedCachedMethods.GET_HEAD)
54 | .allowedMethods(CloudFrontAllowedMethods.ALL)
55 | .build()))
56 | .build()))
57 | .defaultRootObject("index.html")
58 | .errorConfigurations(List.of(CfnDistribution.CustomErrorResponseProperty.builder()
59 | .errorCode(403)
60 | .responsePagePath("/index.html")
61 | .responseCode(200)
62 | .build()))
63 | .enableIpV6(true)
64 | .viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS)
65 | .viewerCertificate(ViewerCertificate.fromAcmCertificate(certificate, ViewerCertificateOptions.builder()
66 | .aliases(List.of("secretwit.com", "*.secretwit.com"))
67 | .securityPolicy(SecurityPolicyProtocol.TLS_V1)// default
68 | .sslMethod(SSLMethod.SNI).build()))
69 | .build());
70 |
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/cdk/src/main/resources/cognitoAutoConfirmUser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports.confirm = (event, context, callback) => {
4 |
5 | console.log('Received event:', JSON.stringify(event, null, 2));
6 |
7 | const modifiedEvent = event;
8 |
9 | // check that we're acting on the right trigger
10 | if (event.triggerSource === "PreSignUp_SignUp") {
11 |
12 | // auto confirm the user
13 | modifiedEvent.response.autoConfirmUser = true;
14 |
15 | // Set the email as verified if it is in the request
16 | modifiedEvent.response.autoVerifyEmail = true;
17 |
18 | callback(null, modifiedEvent);
19 | return
20 | }
21 |
22 | // Throw an error if invoked from the wrong trigger
23 | callback(`Misconfigured Cognito Trigger ${event.triggerSource}`)
24 | };
--------------------------------------------------------------------------------
/cdk/src/main/resources/cognitoToDynamoDBLambda.js:
--------------------------------------------------------------------------------
1 | const aws = require('aws-sdk');
2 | const ddb = new aws.DynamoDB({apiVersion: '2012-10-08'});
3 |
4 | /**
5 | * @author Vladimir Budilov
6 | *
7 | * Upon Cognito SignUp, a user is added to the DDB table
8 | *
9 | * Cognito event:
10 | * https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-lambda-trigger-examples.html#aws-lambda-triggers-post-confirmation-example
11 | *
12 | * Writing to DDB:
13 | * https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-table-read-write.html
14 | *
15 | * Sample input:
16 | *
17 | {
18 | version:'1',
19 | region:'us-east-1',
20 | userPoolId:'us-east-1_9xasdfasdf3A',
21 | userName:'budilov@domain.com',
22 | callerContext:{
23 | awsSdkVersion:'aws-sdk-unknown-unknown',
24 | clientId:'1asdfasdfasdfasdf3hjjgp'
25 | },
26 | triggerSource:'PostConfirmation_ConfirmSignUp',
27 | request: {
28 | userAttributes: {
29 | sub: '517cd3b9-4e99-4135-bccf-0300f42a96fb',
30 | 'cognito:user_status': 'CONFIRMED',
31 | email_verified: 'true',
32 | 'cognito:email_alias': 'budilov@gmail.com',
33 | email: 'budilov@gmail.com'
34 | }
35 | },
36 | response:{
37 | }
38 | }
39 | * @param event
40 | * @param context
41 | */
42 | exports.handler = async (event, context) => {
43 | console.log(event);
44 |
45 | const date = new Date();
46 |
47 | const tableName = process.env.TABLE_NAME;
48 | const region = process.env.REGION;
49 | const partitionId = process.env.PARTITION_ID;
50 | const sortKey = process.env.SORT_KEY;
51 |
52 | console.log(`table=${tableName} -- region=${region}`);
53 |
54 | aws.config.update({region});
55 |
56 | if (!event.request.userAttributes.sub) {
57 | // Nothing to do, the user's email ID is unknown
58 | console.log("Error: Nothing was written to DDB or SQS");
59 | return context.done(null, event);
60 | }
61 |
62 | // -- Write data to DDB
63 | // If the required parameters are present, proceed
64 | let ddbParams = {
65 | TableName: tableName,
66 | Item: {
67 | 'email': {S: event.request.userAttributes.email},
68 | 'createdDate': {S: date.toISOString()},
69 | 'firstLogin': {BOOL: true}
70 | }
71 | };
72 |
73 | ddbParams.Item[partitionId] = {S: event.request.userAttributes.sub};
74 | ddbParams.Item[sortKey] = {S: "user"};
75 |
76 | console.log("ddbParams: " + ddbParams);
77 |
78 | // Call DynamoDB
79 | try {
80 | ddbResult = await ddb.putItem(ddbParams).promise();
81 | console.log("Success");
82 | } catch (err) {
83 | console.log("Error", err);
84 | }
85 |
86 | console.log("Success: Everything executed correctly");
87 | context.done(null, event);
88 | };
--------------------------------------------------------------------------------
/cdk/target/cdk-0.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/cdk-0.1.jar
--------------------------------------------------------------------------------
/cdk/target/classes/app.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/app.properties
--------------------------------------------------------------------------------
/cdk/target/classes/cognitoAutoConfirmUser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports.confirm = (event, context, callback) => {
4 |
5 | console.log('Received event:', JSON.stringify(event, null, 2));
6 |
7 | const modifiedEvent = event;
8 |
9 | // check that we're acting on the right trigger
10 | if (event.triggerSource === "PreSignUp_SignUp") {
11 |
12 | // auto confirm the user
13 | modifiedEvent.response.autoConfirmUser = true;
14 |
15 | // Set the email as verified if it is in the request
16 | modifiedEvent.response.autoVerifyEmail = true;
17 |
18 | callback(null, modifiedEvent);
19 | return
20 | }
21 |
22 | // Throw an error if invoked from the wrong trigger
23 | callback(`Misconfigured Cognito Trigger ${event.triggerSource}`)
24 | };
--------------------------------------------------------------------------------
/cdk/target/classes/cognitoToDynamoDBLambda.js:
--------------------------------------------------------------------------------
1 | const aws = require('aws-sdk');
2 | const ddb = new aws.DynamoDB({apiVersion: '2012-10-08'});
3 |
4 | /**
5 | * @author Vladimir Budilov
6 | *
7 | * Upon Cognito SignUp, a user is added to the DDB table
8 | *
9 | * Cognito event:
10 | * https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-lambda-trigger-examples.html#aws-lambda-triggers-post-confirmation-example
11 | *
12 | * Writing to DDB:
13 | * https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-table-read-write.html
14 | *
15 | * Sample input:
16 | *
17 | {
18 | version:'1',
19 | region:'us-east-1',
20 | userPoolId:'us-east-1_9xasdfasdf3A',
21 | userName:'budilov@domain.com',
22 | callerContext:{
23 | awsSdkVersion:'aws-sdk-unknown-unknown',
24 | clientId:'1asdfasdfasdfasdf3hjjgp'
25 | },
26 | triggerSource:'PostConfirmation_ConfirmSignUp',
27 | request: {
28 | userAttributes: {
29 | sub: '517cd3b9-4e99-4135-bccf-0300f42a96fb',
30 | 'cognito:user_status': 'CONFIRMED',
31 | email_verified: 'true',
32 | 'cognito:email_alias': 'budilov@gmail.com',
33 | email: 'budilov@gmail.com'
34 | }
35 | },
36 | response:{
37 | }
38 | }
39 | * @param event
40 | * @param context
41 | */
42 | exports.handler = async (event, context) => {
43 | console.log(event);
44 |
45 | const date = new Date();
46 |
47 | const tableName = process.env.TABLE_NAME;
48 | const region = process.env.REGION;
49 | const partitionId = process.env.PARTITION_ID;
50 | const sortKey = process.env.SORT_KEY;
51 |
52 | console.log(`table=${tableName} -- region=${region}`);
53 |
54 | aws.config.update({region});
55 |
56 | if (!event.request.userAttributes.sub) {
57 | // Nothing to do, the user's email ID is unknown
58 | console.log("Error: Nothing was written to DDB or SQS");
59 | return context.done(null, event);
60 | }
61 |
62 | // -- Write data to DDB
63 | // If the required parameters are present, proceed
64 | let ddbParams = {
65 | TableName: tableName,
66 | Item: {
67 | 'email': {S: event.request.userAttributes.email},
68 | 'createdDate': {S: date.toISOString()},
69 | 'firstLogin': {BOOL: true}
70 | }
71 | };
72 |
73 | ddbParams.Item[partitionId] = {S: event.request.userAttributes.sub};
74 | ddbParams.Item[sortKey] = {S: "user"};
75 |
76 | console.log("ddbParams: " + ddbParams);
77 |
78 | // Call DynamoDB
79 | try {
80 | ddbResult = await ddb.putItem(ddbParams).promise();
81 | console.log("Success");
82 | } catch (err) {
83 | console.log("Error", err);
84 | }
85 |
86 | console.log("Success: Everything executed correctly");
87 | context.done(null, event);
88 | };
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/BudilovApp.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/BudilovApp.class
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/cognito/CognitoLambdaStack.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/cognito/CognitoLambdaStack.class
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/cognito/CognitoStack.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/cognito/CognitoStack.class
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/ddb/DDBUserTableStack.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/ddb/DDBUserTableStack.class
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/util/Properties.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/util/Properties.class
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/util/SSM.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/util/SSM.class
--------------------------------------------------------------------------------
/cdk/target/classes/com/budilov/cdk/web/SPAStack.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/cdk/target/classes/com/budilov/cdk/web/SPAStack.class
--------------------------------------------------------------------------------
/cdk/target/classes/elasticsearch-access-policy.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": "2012-10-17",
3 | "Statement": [
4 | {
5 | "Effect": "Allow",
6 | "Principal": {
7 | "AWS": "arn:aws:iam::ACCOUNT_NAME_REPLACE_ME:role/ROLE_NAME_REPLACE_ME"
8 | },
9 | "Action": "es:*",
10 | "Resource": "arn:aws:es:REGION_REPLACE_ME:ACCOUNT_NAME_REPLACE_ME:domain/ES_DOMAIN_NAME_REPLACE_ME/*"
11 | },
12 | {
13 | "Effect": "Allow",
14 | "Principal": {
15 | "AWS": "*"
16 | },
17 | "Action": "es:*",
18 | "Resource": "arn:aws:es:REGION_REPLACE_ME:ACCOUNT_NAME_REPLACE_ME:domain/ES_DOMAIN_NAME_REPLACE_ME/*",
19 | "Condition": {
20 | "IpAddress": {
21 | "aws:SourceIp": "EXTERNAL_IP_ADDRESS_REPLACE_ME"
22 | }
23 | }
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/cdk/target/maven-archiver/pom.properties:
--------------------------------------------------------------------------------
1 | #Generated by Maven
2 | #Fri May 01 01:24:25 EDT 2020
3 | groupId=com.budilov
4 | artifactId=cdk
5 | version=0.1
6 |
--------------------------------------------------------------------------------
/cdk/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst:
--------------------------------------------------------------------------------
1 | com/budilov/cdk/util/Properties.class
2 | com/budilov/cdk/cognito/CognitoLambdaStack.class
3 | com/budilov/cdk/web/SPAStack.class
4 | com/budilov/cdk/BudilovApp.class
5 | com/budilov/cdk/util/SSM.class
6 | com/budilov/cdk/cognito/CognitoStack.class
7 | com/budilov/cdk/ddb/DDBUserTableStack.class
8 |
--------------------------------------------------------------------------------
/cdk/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst:
--------------------------------------------------------------------------------
1 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/elasticsearch/ElasticsearchStack.java
2 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/web/SPAStack.java
3 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/util/SSM.java
4 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/containers/EcrStack.java
5 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/cognito/CognitoStack.java
6 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/elasticsearch/ElasticsearchIamStack.java
7 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/cognito/CognitoLambdaStack.java
8 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/S3PublicBucketStack.java
9 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/ddb/DDBUserTableStack.java
10 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/SecretWitApp.java
11 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/containers/EksIamStack.java
12 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/util/Properties.java
13 | /opt/dev/ideas/secretwit/cdk/src/main/java/com/budilov/cdk/ddb/DDBGamesTableStack.java
14 |
--------------------------------------------------------------------------------
/reactjs-webapp/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .dockerignore
4 | Dockerfile
5 |
--------------------------------------------------------------------------------
/reactjs-webapp/.env:
--------------------------------------------------------------------------------
1 | # Rename any REACT_APP_ environment variables to VITE_
2 | # Example:
3 | # VITE_API_URL=http://localhost:3001
4 | # VITE_AWS_REGION=us-east-1
--------------------------------------------------------------------------------
/reactjs-webapp/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | #This declares the pipeline stages
2 | stages:
3 | - build
4 | - deploy
5 |
6 | cache:
7 | paths:
8 | - node_modules/
9 | build:
10 | stage: build
11 | image: node:latest
12 | script:
13 | - yarn install
14 | - yarn build
15 | artifacts:
16 | paths:
17 | # Build folder
18 | - build/
19 | expire_in: 1 hour
20 |
21 | deploy:
22 | stage: deploy
23 | image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
24 | script:
25 | - aws s3 cp ./build/ s3://$S3_BUCKET_NAME/ --recursive --include "*"
26 | - aws cloudfront create-invalidation --distribution-id $CDN_DISTRIBUTION_ID --paths "/*"
--------------------------------------------------------------------------------
/reactjs-webapp/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Node runtime as a parent image
2 | FROM node:21-alpine
3 |
4 | # Set the working directory to /app
5 | WORKDIR /app
6 |
7 | # Copy package.json and package-lock.json to the container
8 | COPY package*.json ./
9 |
10 | # Install dependencies
11 | RUN yarn install
12 |
13 | # Copy the rest of the application code to the container
14 | COPY . .
15 |
16 | # Expose port 3000 for the app to be accessible
17 | EXPOSE 3000
18 |
19 | # Define the command to run the app
20 | CMD ["yarn", "start"]
21 |
--------------------------------------------------------------------------------
/reactjs-webapp/README.md:
--------------------------------------------------------------------------------
1 | ReactJS + [Amazon Cognito](https://aws.amazon.com/cognito/) + [Amazon Amplify Framework](https://aws-amplify.github.io/docs/js/start) Starter Project
2 | ===================================================
3 |
4 | ### Author: Vladimir Budilov
5 |
6 | * [YouTube](https://www.youtube.com/channel/UCBl-ENwdTlUsLY05yGgXyxw)
7 | * [LinkedIn](https://www.linkedin.com/in/vbudilov/)
8 | * [Medium](https://medium.com/@budilov)
9 |
10 | Use this project to quickly get started with ReactJS + Amazon Cognito.
11 |
12 | ### Update the Cognito configuration
13 |
14 | First and foremost, create a Cognito User Pool. Then open 'src/configs/aws-configs.js' and update
15 | the `aws_user_pools_id` and the `aws_user_pools_web_client_id` properties.
16 |
17 | ```json
18 | const awsConfig = {
19 | aws_app_analytics: 'enable',
20 |
21 | aws_user_pools: 'enable',
22 | aws_user_pools_id: 'us-east-1_x',
23 | aws_user_pools_mfa_type: 'OFF',
24 | aws_user_pools_web_client_id: 'x',
25 | aws_user_settings: 'enable',
26 | };
27 |
28 | export default awsConfig
29 | ```
30 |
31 | ### Build the project and run it locally (the default url is 'http://localhost:3000')
32 |
33 | ```yarn install && yarn start```
34 |
35 | ### Build for PRD
36 |
37 | ```yarn build```
38 |
39 | ### Screenshots
40 |
41 |
43 |
44 |
46 |
47 |
49 |
50 | ### Docker
51 |
52 | ```shell
53 | docker build -t my-react-app .
54 | docker run -p 3000:3000 my-react-app
55 |
56 | ```
57 |
--------------------------------------------------------------------------------
/reactjs-webapp/devfile.yaml:
--------------------------------------------------------------------------------
1 | schemaVersion: 2.2.0
2 | metadata:
3 | name: reactjs-app
4 | version: 1.0.0
5 | attributes:
6 | controller.devfile.io/storage-type: ephemeral
7 | components:
8 | - name: tools
9 | container:
10 | image: node:16
11 | memoryLimit: 4Gi
12 | endpoints:
13 | - name: web
14 | targetPort: 3000
15 | exposure: public
16 | mountSources: true
17 | commands:
18 | - id: install-dependencies
19 | exec:
20 | component: tools
21 | commandLine: npm install
22 | workingDir: ${PROJECT_SOURCE}
23 | group:
24 | kind: build
25 | isDefault: true
26 | - id: start-dev
27 | exec:
28 | component: tools
29 | commandLine: npm start
30 | workingDir: ${PROJECT_SOURCE}
31 | group:
32 | kind: run
33 | isDefault: true
34 | - id: build
35 | exec:
36 | component: tools
37 | commandLine: npm run build
38 | workingDir: ${PROJECT_SOURCE}
39 | group:
40 | kind: build
41 | - id: test
42 | exec:
43 | component: tools
44 | commandLine: npm test
45 | workingDir: ${PROJECT_SOURCE}
46 | group:
47 | kind: test
--------------------------------------------------------------------------------
/reactjs-webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | AWS Architecture
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/reactjs-webapp/migration-guide.md:
--------------------------------------------------------------------------------
1 | # Migration to Vite Complete
2 |
3 | The project has been migrated from Create React App to Vite. Here are the key changes made:
4 |
5 | 1. Added Vite configuration (vite.config.js)
6 | 2. Updated package.json:
7 | - Removed react-scripts
8 | - Added Vite dependencies
9 | - Updated npm scripts
10 | 3. Moved and updated index.html
11 | 4. Environment Variables:
12 | - Environment variables now need to be prefixed with `VITE_` instead of `REACT_APP_`
13 | - Update any environment variables in your .env files accordingly
14 |
15 | ## Next Steps
16 | 1. Update any environment variables in your deployment configurations from `REACT_APP_` to `VITE_`
17 | 2. Test the application thoroughly in development and production modes
18 | 3. Update any CI/CD pipelines to use the new build commands
19 |
20 | ## Commands
21 | - Development: `npm start` or `yarn start`
22 | - Build: `npm run build` or `yarn build`
23 | - Preview production build: `npm run preview` or `yarn preview`
--------------------------------------------------------------------------------
/reactjs-webapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-app",
3 | "author": "Vladimir Budilov",
4 | "version": "1.0.0",
5 | "private": true,
6 | "dependencies": {
7 | "@aws-amplify/cache": "5.1.11",
8 | "@aws-amplify/core": "5.8.5",
9 | "@emotion/react": "11.11.1",
10 | "@emotion/styled": "11.11.0",
11 | "@material-ui/data-grid": "4.0.0-alpha.34",
12 | "@mui/icons-material": "5.0.1",
13 | "@mui/lab": "5.0.0-alpha.49",
14 | "@mui/material": "5.0.2",
15 | "@mui/styles": "5.0.1",
16 | "@mui/x-data-grid": "5.0.0-beta.7",
17 | "@stripe/react-stripe-js": "1.7.2",
18 | "@stripe/stripe-js": "1.16.0",
19 | "aws-amplify": "5.3.11",
20 | "axios": "0.21.1",
21 | "enquire-js": "0.2.1",
22 | "file-saver": "^2.0.5",
23 | "file-type": "16.5.3",
24 | "moment": "2.23.0",
25 | "prop-types": "latest",
26 | "react": "17.0.2",
27 | "react-box-shadow": "1.0.1",
28 | "react-data-table-component": "7.4.5",
29 | "react-dom": "17.0.1",
30 | "react-infinite-scroller": "1.2.4",
31 | "react-router-dom": "5.2.0",
32 | "react-scripts": "5.0.1",
33 | "react-scrollable-feed": "1.2.0",
34 | "react-webcam": "^7.1.1",
35 | "react-window": "1.8.6",
36 | "styled-components": "5.2.0"
37 | },
38 | "scripts": {
39 | "start": "react-scripts --openssl-legacy-provider start",
40 | "build": "unset CI && react-scripts build",
41 | "test": "react-scripts test",
42 | "eject": "react-scripts eject"
43 | },
44 | "eslintConfig": {
45 | "rules": {
46 | "no-whitespace-before-property": "off",
47 | "no-mixed-operators": "off",
48 | "no-useless-constructor": "off",
49 | "no-unused-vars": "off",
50 | "array-callback-return": "off"
51 | }
52 | },
53 | "browserslist": {
54 | "production": [
55 | ">0.2%",
56 | "not dead",
57 | "not op_mini all"
58 | ],
59 | "development": [
60 | "last 1 chrome version",
61 | "last 1 firefox version",
62 | "last 1 safari version"
63 | ]
64 | },
65 | "devDependencies": {
66 | "react-perfect-scrollbar": "1.5.8",
67 | "shave": "2.5.10"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/reactjs-webapp/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/reactjs-webapp/public/favicon.ico
--------------------------------------------------------------------------------
/reactjs-webapp/public/images/auth/forgot.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/reactjs-webapp/public/images/auth/forgot.webp
--------------------------------------------------------------------------------
/reactjs-webapp/public/images/auth/login.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/reactjs-webapp/public/images/auth/login.webp
--------------------------------------------------------------------------------
/reactjs-webapp/public/images/auth/register.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/reactjs-webapp/public/images/auth/register.webp
--------------------------------------------------------------------------------
/reactjs-webapp/public/images/email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/reactjs-webapp/public/images/email.png
--------------------------------------------------------------------------------
/reactjs-webapp/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
30 | AWS Architecture
31 |
32 |
33 |
34 |
35 |
36 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/reactjs-webapp/public/logos/logo-md.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vbudilov/reactjs-cognito-starter/da69c57bb4dd62de9d1311d9931e21d7e139f03e/reactjs-webapp/public/logos/logo-md.png
--------------------------------------------------------------------------------
/reactjs-webapp/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "AssetsMaven.com",
3 | "name": "Expense Calculator",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/reactjs-webapp/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/App.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: none;
3 | }
4 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
3 | import Amplify from '@aws-amplify/core'
4 | // AWS Config Files
5 | import awsConfig from './configs/aws-configs'
6 | import {FullLayout} from "./components/layout/full-layout";
7 | import {enquireScreen} from 'enquire-js';
8 |
9 | import './App.css';
10 | import {createTheme, StyledEngineProvider, ThemeProvider} from '@mui/material/styles';
11 | import makeStyles from '@mui/styles/makeStyles';
12 | import {AuthScreen} from "./screens/auth/auth-screen";
13 | import {config} from "./services/constants";
14 |
15 |
16 | Amplify.configure(awsConfig);
17 |
18 | Amplify.Logger.LOG_LEVEL = config.logLevel;
19 |
20 | let isMobile = false;
21 |
22 | enquireScreen((b) => {
23 | isMobile = b;
24 | });
25 |
26 | // const theme = {
27 | // background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
28 | // spacing: 8,
29 | // };
30 | const theme = createTheme();
31 | const useStyles = makeStyles((theme) => {
32 | {
33 | // some css that access to theme
34 | }
35 | });
36 |
37 | export function App() {
38 |
39 | return (
40 |
41 |
42 |
43 |
44 | {/* Authentication */}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/layout/full-layout.js:
--------------------------------------------------------------------------------
1 | // Modified FullLayout.js
2 | import React, {useEffect, useState} from "react";
3 | import MyTopAppBar from "./header/navbar";
4 | import {Routes} from "../../routes";
5 | import {Footer} from "./my-footer";
6 | import Grid from "@mui/material/Grid";
7 | import {Auth, Logger} from "aws-amplify";
8 | import {AuthService} from "../../screens/auth/service/auth-service";
9 | import {Hub} from "@aws-amplify/core";
10 | import FloatingActionButtonZoom from "../../screens/uploads/upload-floating-button";
11 | import makeStyles from "@mui/styles/makeStyles";
12 | import Drawer from "@mui/material/Drawer";
13 | import IconButton from "@mui/material/IconButton";
14 | import MenuIcon from "@mui/icons-material/Menu";
15 | import useMediaQuery from "@mui/material/useMediaQuery";
16 | import {useTheme} from "@mui/material/styles";
17 | import Box from "@mui/material/Box";
18 | import { SideNavbar } from './sidenav';
19 |
20 | const logger = new Logger('FullLayout');
21 | const styles = makeStyles((theme) => ({
22 | root: {
23 | flexGrow: 1,
24 | backgroundColor: "#faf6f6",
25 | display: 'flex',
26 | flexDirection: 'column',
27 | minHeight: '100vh'
28 | },
29 | header: {
30 | position: 'sticky',
31 | top: 0,
32 | zIndex: 1200,
33 | backgroundColor: theme.palette.background.paper
34 | },
35 | paper: {
36 | textAlign: 'center',
37 | color: theme.palette.text.secondary,
38 | },
39 | menuButton: {
40 | marginRight: theme.spacing(2),
41 | position: 'fixed',
42 | left: theme.spacing(2),
43 | top: theme.spacing(9),
44 | zIndex: 1100,
45 | backgroundColor: theme.palette.background.paper,
46 | '&:hover': {
47 | backgroundColor: theme.palette.action.hover,
48 | },
49 | },
50 | drawerPaper: {
51 | width: 240,
52 | backgroundColor: '#f8f9fa',
53 | borderRight: 'none',
54 | boxShadow: '0 3px 10px rgba(0,0,0,0.08)',
55 | marginTop: '70px',
56 | height: 'calc(100vh - 70px)',
57 | background: 'linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%)',
58 | },
59 | content: {
60 | flexGrow: 1,
61 | display: 'flex',
62 | marginTop: '0', // Added to ensure no gap between header and content
63 | },
64 | mainContent: {
65 | flexGrow: 1,
66 | padding: theme.spacing(3),
67 | },
68 | sidebar: {
69 | width: 240,
70 | flexShrink: 0,
71 | [theme.breakpoints.down('sm')]: {
72 | display: 'none'
73 | }
74 | }
75 | }));
76 |
77 | export function FullLayout(props) {
78 | const [loggedIn, setLoggedIn] = useState(false);
79 | const [loggedInUser, setLoggedInUser] = useState({});
80 | const [mobileOpen, setMobileOpen] = useState(false);
81 | const classes = styles();
82 | const theme = useTheme();
83 | const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
84 |
85 | const handleDrawerToggle = () => {
86 | setMobileOpen(!mobileOpen);
87 | };
88 |
89 | useEffect(() => {
90 | const onHubCapsule = (capsule) => {
91 | const {channel, payload} = capsule;
92 | if (channel === AuthService.CHANNEL &&
93 | payload.event === AuthService.AUTH_EVENTS.LOGIN) {
94 | if (payload.success) {
95 | setLoggedIn(true);
96 | }
97 | } else if (channel === AuthService.CHANNEL &&
98 | payload.event === AuthService.AUTH_EVENTS.SIGN_OUT) {
99 | if (payload.success) {
100 | setLoggedIn(false);
101 | }
102 | }
103 | };
104 |
105 | Hub.listen(AuthService.CHANNEL, onHubCapsule);
106 |
107 | Auth.currentAuthenticatedUser({
108 | bypassCache: true
109 | }).then(user => {
110 | if (user) {
111 | setLoggedIn(true);
112 | setLoggedInUser(user);
113 | } else {
114 | setLoggedIn(false);
115 | setLoggedInUser({});
116 | }
117 | }).catch(err => {
118 | setLoggedIn(false);
119 | setLoggedInUser({});
120 | });
121 | return function cleanup() {
122 | logger.info("Removing HUB subscription to " + AuthService.CHANNEL);
123 | Hub.remove(AuthService.CHANNEL, onHubCapsule);
124 | };
125 |
126 | }, [])
127 |
128 | return (
129 |
130 |
131 |
132 |
133 |
134 |
135 | {/* Permanent sidebar for desktop */}
136 |
137 |
144 |
145 |
146 |
147 |
148 | {/* Mobile menu button */}
149 | {isMobile && (
150 |
158 |
159 |
160 | )}
161 |
162 | {/* Temporary drawer for mobile */}
163 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | {loggedIn && }
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | );
195 | }
196 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/layout/header/drop-down-navbar-menu.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {Link} from "react-router-dom";
3 | import {Logger} from "@aws-amplify/core";
4 | import {Avatar, Button, Divider, Fade, ListItemIcon, Menu, MenuItem, Typography} from "@mui/material";
5 | import {makeStyles} from '@mui/styles';
6 | import ExitToAppIcon from '@mui/icons-material/ExitToApp';
7 | import VpnKeyIcon from '@mui/icons-material/VpnKey';
8 | import PermIdentityTwoToneIcon from '@mui/icons-material/PermIdentityTwoTone';
9 | import SettingsIcon from '@mui/icons-material/Settings';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | menuButton: {
13 | color: theme.palette.getContrastText(theme.palette.background.paper),
14 | textTransform: 'none',
15 | },
16 | menuItem: {
17 | '&:hover': {
18 | backgroundColor: theme.palette.action.selected,
19 | },
20 | },
21 | menuItemIcon: {
22 | minWidth: 'auto',
23 | marginRight: theme.spacing(2),
24 | },
25 | menuLink: {
26 | textDecoration: 'none',
27 | color: 'inherit',
28 | },
29 | }));
30 |
31 | export function DropDownNavBarMenu(props) {
32 | const logger = new Logger('DropDownNavBarMenu');
33 |
34 | const classes = useStyles();
35 |
36 | const {loggedIn, logoutFunction, loggedInUser} = props;
37 |
38 | const [anchorEl, setAnchorEl] = useState(null);
39 | const open = Boolean(anchorEl);
40 |
41 | const handleClick = (event) => {
42 | setAnchorEl(event.currentTarget);
43 | };
44 |
45 | const handleClose = () => {
46 | setAnchorEl(null);
47 | };
48 |
49 | const logout = async (event) => {
50 | handleClose(event);
51 | logoutFunction();
52 | }
53 |
54 | return (
55 |
56 |
61 |
62 |
104 |
105 | );
106 | }
107 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/layout/header/navbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {DropDownNavBarMenu} from "./drop-down-navbar-menu";
3 |
4 | import AppBar from '@mui/material/AppBar';
5 | import Button from '@mui/material/Button';
6 | import CssBaseline from '@mui/material/CssBaseline';
7 | import Toolbar from '@mui/material/Toolbar';
8 | import Typography from '@mui/material/Typography';
9 | import makeStyles from '@mui/styles/makeStyles';
10 | import {Link, useHistory} from "react-router-dom";
11 | import {Box, Container} from "@mui/material";
12 | import Hidden from "@mui/material/Hidden";
13 | import {AuthService} from "../../../screens/auth/service/auth-service";
14 | import {Search} from "@mui/icons-material";
15 | import ReceiptLongIcon from '@mui/icons-material/ReceiptLong';
16 | import AccountBalanceIcon from '@mui/icons-material/AccountBalance';
17 |
18 | const showDrawer = () => {
19 | this.setState({
20 | visible: true
21 | });
22 | };
23 |
24 | const onClose = () => {
25 | this.setState({
26 | visible: false
27 | });
28 | };
29 |
30 | const useStyles = makeStyles((theme) => ({
31 | '@global': {
32 | ul: {
33 | margin: 0,
34 | padding: 0,
35 | listStyle: 'none',
36 | },
37 | },
38 | appBar: {
39 | borderBottom: 'none',
40 | backgroundColor: '#f8f9fa',
41 | color: '#2d3436',
42 | boxShadow: '0 2px 12px rgba(0,0,0,0.06)',
43 | },
44 | toolbar: {
45 | flexWrap: 'wrap',
46 | minHeight: '70px',
47 | padding: '0 24px',
48 | },
49 | toolbarTitle: {
50 | flexGrow: 1,
51 | display: 'flex',
52 | alignItems: 'center',
53 | },
54 | link: {
55 | margin: theme.spacing(1, 1.5),
56 | textDecoration: 'none',
57 | },
58 | button: {
59 | borderRadius: '8px',
60 | padding: '8px 20px',
61 | transition: 'all 0.2s ease',
62 | backgroundColor: '#2d3436',
63 | color: '#ffffff',
64 | '&:hover': {
65 | transform: 'translateY(-2px)',
66 | boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
67 | backgroundColor: '#1e2122',
68 | },
69 | },
70 | searchButton: {
71 | marginRight: theme.spacing(2),
72 | color: '#2d3436',
73 | },
74 | logo: {
75 | width: '160px',
76 | transition: 'transform 0.2s ease',
77 | filter: 'none',
78 | '&:hover': {
79 | transform: 'scale(1.05)',
80 | },
81 | },
82 | }));
83 |
84 | export function MyTopAppBar(props) {
85 | const classes = useStyles();
86 |
87 | const loggedIn = props.loggedIn;
88 | const user = props.loggedInUser;
89 | const history = useHistory();
90 |
91 | const logout = async (event) => {
92 | await AuthService.signOut();
93 | history.push("/login");
94 | }
95 |
96 | const addArtifact = async (event) => {
97 | history.push("/uploads")
98 | }
99 |
100 | return (
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
110 |
111 |
112 |
113 | {/**/}
114 | {/* */}
115 | {/* */}
121 |
122 | {props.loggedIn &&
123 |
124 | }
125 |
126 | {!loggedIn &&
127 |
128 |
136 |
137 | }
138 |
139 |
140 |
141 |
142 | );
143 | }
144 |
145 | export default MyTopAppBar;
146 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/layout/my-footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Box, Container, Typography} from '@mui/material';
3 | import {makeStyles} from '@mui/styles';
4 |
5 | const useStyles = makeStyles((theme) => ({
6 | footer: {
7 | borderTop: '1px solid #E7E7E7',
8 | marginTop: theme.spacing(3),
9 | paddingTop: theme.spacing(3),
10 | paddingBottom: theme.spacing(3),
11 | backgroundColor: theme.palette.background.paper, // Or any color that suits your theme
12 | // Uncomment the next line if you want the footer to be fixed at the bottom of the page
13 | // position: 'fixed', bottom: 0, left: 0,
14 | },
15 | text: {
16 | textAlign: 'center',
17 | color: theme.palette.text.secondary, // Adjust text color to match your theme
18 | },
19 | }));
20 |
21 | export function Footer() {
22 | const classes = useStyles();
23 |
24 | return (
25 |
26 |
27 |
28 | brought to you by Vladimir Budilov {new Date().getFullYear()}
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/layout/sidenav.js:
--------------------------------------------------------------------------------
1 | // SideNavbar.js
2 | import React from 'react';
3 | import { Link } from "react-router-dom";
4 | import Grid from "@mui/material/Grid";
5 | import Button from "@mui/material/Button";
6 | import ArticleIcon from "@mui/icons-material/Article";
7 | import ReceiptLongIcon from "@mui/icons-material/ReceiptLong";
8 | import Box from "@mui/material/Box";
9 | import makeStyles from "@mui/styles/makeStyles";
10 | import { useTheme } from "@mui/material/styles";
11 | import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | navContainer: {
15 | padding: theme.spacing(3, 2),
16 | margin: theme.spacing(1),
17 | },
18 | navButton: {
19 | marginBottom: theme.spacing(1.5),
20 | borderRadius: theme.spacing(1),
21 | padding: theme.spacing(2),
22 | textTransform: 'none',
23 | boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
24 | transition: 'all 0.3s ease',
25 | backgroundColor: '#ffffff',
26 | color: '#2d3436',
27 | fontSize: '0.95rem',
28 | fontWeight: 500,
29 | justifyContent: 'space-between',
30 | display: 'flex',
31 | alignItems: 'center',
32 | border: '1px solid #e0e0e0',
33 | '&:hover': {
34 | boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
35 | },
36 | },
37 | buttonContent: {
38 | display: 'flex',
39 | flexDirection: 'column',
40 | alignItems: 'flex-start',
41 | },
42 | buttonTitle: {
43 | fontWeight: 600,
44 | color: '#000000',
45 | },
46 | buttonSubtitle: {
47 | fontSize: '0.8rem',
48 | color: '#7d7d7d',
49 | },
50 | buttonIcon: {
51 | color: '#7d7d7d',
52 | }
53 | }));
54 |
55 | export const SideNavbar = ({ loggedIn }) => {
56 | const classes = useStyles();
57 | const theme = useTheme();
58 |
59 | return (
60 |
61 |
62 | {loggedIn &&
63 |
64 |
74 |
75 |
76 |
86 |
87 | }
88 |
89 |
90 | {!loggedIn &&
91 |
92 |
102 |
103 | }
104 |
105 |
106 |
107 | );
108 | };
109 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/mixins.scss:
--------------------------------------------------------------------------------
1 | // this is shared mixins file for all components from the app,
2 | // there you can easily add your own variables / imports or redefine
3 | // existing without touching default packages for future updates
4 |
5 | @import 'src/components/kit/core/mixins.scss';
6 | // import KIT mixins
7 | @import 'src/components/cleanui/styles/mixins.scss'; // import CLEANUI mixins
8 |
9 | // $text: #000;
10 | // $success: green;
11 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/search-field.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import makeStyles from '@mui/styles/makeStyles';
3 | import Paper from '@mui/material/Paper';
4 | import InputBase from '@mui/material/InputBase';
5 | import IconButton from '@mui/material/IconButton';
6 | import SearchIcon from '@mui/icons-material/Search';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | padding: '2px 4px',
11 | display: 'flex',
12 | alignItems: 'center'
13 | },
14 | input: {
15 | marginLeft: theme.spacing(1),
16 | flex: 1,
17 | },
18 | iconButton: {
19 | padding: 10,
20 | },
21 | divider: {
22 | height: 28,
23 | margin: 4,
24 | },
25 | }));
26 |
27 | export default function SearchField(props) {
28 | const classes = useStyles();
29 | const [state, setState] = React.useState("");
30 | const handleChange = props.handleChange;
31 | const onChange = (event) => {
32 | setState(event.target.value);
33 |
34 | handleChange(event.target.value);
35 |
36 | };
37 | return (
38 |
39 | {/**/}
40 | {/* */}
41 | {/**/}
42 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/components/search/search-input-field.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Input} from 'antd';
3 | import {Logger} from '@aws-amplify/core';
4 | import {createBrowserHistory} from 'history';
5 | import {withRouter} from 'react-router-dom'
6 |
7 | const history = createBrowserHistory();
8 | const {Search} = Input;
9 |
10 | class SearchInputField extends React.Component {
11 |
12 | state = {
13 | query: "",
14 | };
15 |
16 | logger = new Logger("SearchInputField");
17 |
18 | constructor(props) {
19 | super(props);
20 | }
21 |
22 | componentDidMount() {
23 | this.logger.info("this.props: " + JSON.stringify(this.props));
24 | // const { query } = this.props.match.params
25 | // this.logger.info("query: " + query)
26 | }
27 |
28 | kickoffSearch = async (query) => {
29 | this.logger.info("search query: " + query + " location: " + JSON.stringify(this.props));
30 |
31 | if (query === null || query.trim() === "")
32 | return;
33 |
34 | let location = {
35 | pathname: '/search',
36 | state: {fromDashboard: true}
37 | };
38 |
39 | this.props.history.push('/search?query=' + query)
40 | // history.push('/search?query=' + query)
41 | // window.location = '/search?query=' + query;
42 | };
43 |
44 | render() {
45 | let size = "350px";
46 |
47 | if (this.props.size != null)
48 | size = this.props.size;
49 |
50 | return (
51 |
52 | this.kickoffSearch(value)}
55 | // onChange={value => this.searchOnTyping(value)}
56 | style={{
57 | padding: "18px 0"
58 | }}
59 | enterButton
60 | />
61 |
)
62 | }
63 | }
64 |
65 | export default withRouter(SearchInputField)
66 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/configs/aws-configs.js:
--------------------------------------------------------------------------------
1 | const awsConfig = {
2 | aws_app_analytics: 'enable',
3 |
4 | aws_user_pools: 'enable',
5 | aws_user_pools_id: 'us-east-1_mUslL7yZG',
6 | aws_user_pools_mfa_type: 'OFF',
7 | aws_user_pools_web_client_id: '5oqb0es098ngv842g7e22ofh44',
8 | aws_user_settings: 'enable',
9 | };
10 |
11 | export default awsConfig
12 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import {App} from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/routes.js:
--------------------------------------------------------------------------------
1 | import {Link, Route, Switch} from "react-router-dom";
2 | import {ProtectedRoute} from "./screens/auth/auth-screen";
3 | import React from "react";
4 | import {UploadNewImage} from "./screens/alttext/upload-new-image";
5 | import {Dashboard} from "./screens/dashboard/dashboard";
6 | import {Documents} from "./screens/documents/documents";
7 |
8 | export function Routes() {
9 |
10 | return
11 | {/* Home */}
12 |
13 |
17 |
18 |
22 |
23 |
27 |
28 |
29 |
30 |
31 | Couldn't find {window.location.pathname}
32 |
33 | Maybe you want to go home?
34 |
35 |
36 |
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/alttext/upload-new-image.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import makeStyles from '@mui/styles/makeStyles';
3 | import Typography from '@mui/material/Typography';
4 | import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
5 | import Tooltip from "@mui/material/Tooltip";
6 | import Button from '@mui/material/Button';
7 | import Divider from '@mui/material/Divider';
8 |
9 | import {Alert, CircularProgress, Grid} from "@mui/material";
10 | import {UploadService} from "../../services/dao/uploader-service";
11 | import {Logger} from "@aws-amplify/core";
12 | import {green} from '@mui/material/colors';
13 |
14 | const logger = new Logger("UploadFileAccordion");
15 |
16 | const useStyles = makeStyles((theme) => ({
17 | root: {
18 | flexGrow: 1,
19 | width: "100%",
20 | padding: theme.spacing(3),
21 |
22 | }, accordion: {
23 | width: "100%",
24 | }, selectEmpty: {
25 | marginTop: theme.spacing(2),
26 | }, heading: {
27 | fontSize: theme.typography.pxToRem(15),
28 | }, secondaryHeading: {
29 | fontSize: theme.typography.pxToRem(15), color: theme.palette.text.secondary,
30 | }, icon: {
31 | verticalAlign: 'bottom', height: 20, width: 20,
32 | }, details: {
33 | alignItems: 'center',
34 | }, column: {
35 | flexBasis: '100%',
36 | }, helper: {
37 | borderLeft: `2px solid ${theme.palette.divider}`, padding: theme.spacing(1, 2),
38 | }, link: {
39 | color: theme.palette.primary.main, textDecoration: 'none', '&:hover': {
40 | textDecoration: 'underline',
41 | },
42 | }, wrapper: {
43 | margin: theme.spacing(1), position: 'relative',
44 | }, buttonSuccess: {
45 | backgroundColor: green[500], '&:hover': {
46 | backgroundColor: green[700],
47 | },
48 | }, buttonProgress: {
49 | color: green[500], position: 'absolute', top: '50%', left: '50%', marginTop: -12, marginLeft: -12,
50 | },
51 | }));
52 |
53 | const uploaderService = new UploadService();
54 |
55 | export const UploadNewImage = ({fileUploadedCallback}) => {
56 | const classes = useStyles();
57 | const [file, setFile] = useState(null);
58 |
59 | const [fileUploadError, setFileUploadError] = React.useState("");
60 | const [fileUploadSuccess, setFileUploadSuccess] = React.useState("");
61 | const [fileUploadAttempted, setFileUploadAttempted] = React.useState(false);
62 |
63 | const [loading, setLoading] = React.useState(false);
64 |
65 |
66 | const onFileSave = async () => {
67 | setLoading(true);
68 |
69 | setFileUploadError("");
70 | setFileUploadSuccess("");
71 |
72 | setFileUploadAttempted(true);
73 |
74 | const response = await uploaderService.uploadFile(file.selectedFile);
75 | setLoading(false);
76 |
77 | if (response && response.error) {
78 | setFileUploadError(response?.errorMessage);
79 | }
80 | if (response && !response.error) {
81 | setFileUploadSuccess("File was successfully uploaded");
82 | if (fileUploadedCallback) fileUploadedCallback(file.selectedFile.name);
83 | setFile(null);
84 |
85 | }
86 | logger.info("response: " + response);
87 | }
88 |
89 | const onMailFileSelectionChangeEvent = (event) => {
90 | setFile({
91 | file: window.URL.createObjectURL(event.target.files[0]), selectedFile: event.target.files[0], loaded: 0,
92 | })
93 | }
94 |
95 | useEffect(() => {
96 | if (file === null) {
97 |
98 | }
99 | }, [file]); // Only re-run the effect if count changes
100 |
101 | return (
102 |
103 |
113 |
114 | {fileUploadAttempted &&
115 |
116 | {fileUploadError &&
117 |
{fileUploadError}}
118 | {fileUploadSuccess &&
119 |
Your file was uploaded and is processing}
120 |
}
121 |
122 |
123 | Select a file
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | {!file &&
134 |
135 |
143 |
148 | }
149 |
150 | {file &&
151 |
153 |
157 |
161 | {loading &&
162 | }
163 | }
164 |
165 | {file &&
166 |
172 |
173 |
174 | {file.selectedFile.name}
175 |
176 |
177 |
184 | }
185 |
186 |
187 |
)
188 | ;
189 | }
190 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/auth/auth-screen.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {Redirect, Route} from "react-router";
3 | import {RegisterForm} from "./register-form";
4 | import {ResetPasswordStep1} from "./reset-password-step-1";
5 | import {ResetPasswordStep2} from "./reset-password-step-2";
6 | import {RegisterConfirmForm} from "./register-confirm-form";
7 | import {LoginForm} from "./login-form";
8 | import {Auth} from "aws-amplify";
9 | import CssBaseline from '@mui/material/CssBaseline';
10 | import Grid from '@mui/material/Grid';
11 | import makeStyles from '@mui/styles/makeStyles';
12 |
13 |
14 | const useStyles = makeStyles((theme) => ({
15 | root: {
16 | height: '100vh',
17 | },
18 | image: {
19 | backgroundImage: "url('images/auth/login.webp')",
20 | backgroundRepeat: 'no-repeat',
21 | backgroundColor:
22 | theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.grey[900],
23 | backgroundSize: 'cover',
24 | backgroundPosition: 'center',
25 | },
26 | paper: {
27 | margin: theme.spacing(8, 4),
28 | display: 'flex',
29 | flexDirection: 'column',
30 | alignItems: 'center',
31 | },
32 | avatar: {
33 | margin: theme.spacing(1),
34 | backgroundColor: theme.palette.secondary.main,
35 | },
36 | form: {
37 | width: '100%', // Fix IE 11 issue.
38 | marginTop: theme.spacing(1),
39 | },
40 | submit: {
41 | margin: theme.spacing(3, 0, 2),
42 | },
43 | }));
44 |
45 |
46 | // import { Link, BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
47 |
48 |
49 | export function AuthScreen() {
50 | const classes = useStyles();
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | }
69 |
70 | export function ProtectedRoute({component: Component, ...rest}) {
71 |
72 | const [isAuthenticated, setLoggedIn] = useState(true);
73 | // Similar to componentDidMount and componentDidUpdate:
74 | useEffect(() => {
75 | (async () => {
76 | let user = null;
77 |
78 | try {
79 | user = await Auth.currentAuthenticatedUser()
80 | if (user) {
81 | setLoggedIn(true);
82 | } else {
83 | setLoggedIn(false);
84 | }
85 | } catch (e) {
86 | setLoggedIn(false);
87 | }
88 | })();
89 | });
90 |
91 | return (
92 |
95 | isAuthenticated ? :
96 | }
97 | />
98 | );
99 |
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/auth/quotes.js:
--------------------------------------------------------------------------------
1 | export const LINCOLN_QUOTE = {
2 | text: "In the end, it's not the years in your life that count. It's the life in your years.",
3 | author: "Abraham Lincoln"
4 | };
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/auth/reset-password-step-1.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {Link, useHistory} from 'react-router-dom';
3 | import {Hub, Logger} from "@aws-amplify/core";
4 | import {AuthService} from "./service/auth-service";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | import Avatar from "@mui/material/Avatar";
7 | import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
8 | import Typography from "@mui/material/Typography";
9 | import Grid from "@mui/material/Grid";
10 | import TextField from "@mui/material/TextField";
11 | import makeStyles from '@mui/styles/makeStyles';
12 | import {Alert, Box, Button, Container} from '@mui/material';
13 | import Paper from "@mui/material/Paper";
14 |
15 | const useStyles = makeStyles((theme) => ({
16 | root: {
17 | minHeight: '100vh',
18 | minWidth: '100vw',
19 | display: 'flex',
20 | alignItems: 'center',
21 | justifyContent: 'center',
22 | background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
23 | margin: 0,
24 | padding: 0,
25 | overflowY: 'auto'
26 | },
27 | image: {
28 | backgroundImage: "url('/images/auth/login.webp')",
29 | backgroundRepeat: 'no-repeat',
30 | backgroundColor: 'transparent',
31 | backgroundSize: 'cover',
32 | backgroundPosition: 'center',
33 | borderRadius: '20px 0 0 20px',
34 | boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
35 | minHeight: '600px',
36 | [theme.breakpoints.down('sm')]: {
37 | display: 'none',
38 | },
39 | },
40 | paper: {
41 | margin: theme.spacing(4),
42 | display: 'flex',
43 | flexDirection: 'column',
44 | alignItems: 'center',
45 | padding: theme.spacing(2),
46 | [theme.breakpoints.up('sm')]: {
47 | margin: theme.spacing(4),
48 | padding: theme.spacing(4),
49 | },
50 | },
51 | avatar: {
52 | margin: theme.spacing(1),
53 | backgroundColor: '#FF8E53',
54 | width: theme.spacing(7),
55 | height: theme.spacing(7),
56 | boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
57 | },
58 | form: {
59 | width: '100%',
60 | marginTop: theme.spacing(1),
61 | },
62 | submit: {
63 | margin: theme.spacing(3, 0, 2),
64 | padding: theme.spacing(1.5),
65 | background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
66 | borderRadius: 25,
67 | color: 'white',
68 | boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
69 | '&:hover': {
70 | background: 'linear-gradient(45deg, #FE6B8B 60%, #FF8E53 90%)',
71 | transform: 'scale(1.02)',
72 | transition: 'all 0.2s ease-in-out'
73 | }
74 | },
75 | textField: {
76 | '& .MuiOutlinedInput-root': {
77 | borderRadius: '15px',
78 | '&.Mui-focused fieldset': {
79 | borderColor: '#FE6B8B',
80 | },
81 | },
82 | }, loginContainer: {
83 | borderRadius: '20px',
84 | boxShadow: '0 3px 15px 2px rgba(255, 105, 135, .3)',
85 | overflow: 'hidden',
86 | backgroundColor: 'rgba(255, 255, 255, 0.9)',
87 | height: 'auto',
88 | minHeight: '600px',
89 | maxHeight: '90vh',
90 | margin: theme.spacing(2),
91 | [theme.breakpoints.up('sm')]: {
92 | margin: theme.spacing(2),
93 | },
94 | },
95 | link: {
96 | color: '#FE6B8B',
97 | textDecoration: 'none',
98 | '&:hover': {
99 | color: '#FF8E53',
100 | textDecoration: 'underline'
101 | }
102 | },
103 | }));
104 |
105 | export function ResetPasswordStep1() {
106 | const classes = useStyles();
107 |
108 | const logger = new Logger("ResetPasswordStep1");
109 | const history = useHistory();
110 |
111 | const styles = {
112 | loginForm: {
113 | "max-width": "300px"
114 | },
115 | loginFormForgot: {
116 | "float": "right"
117 | },
118 | loginFormButton: {
119 | "width": "100%"
120 | }
121 | };
122 | const [errorMessage, setErrorMessage] = useState("");
123 | const [userNotConfirmed, setUserNotConfirmed] = useState(false);
124 |
125 | useEffect(() => {
126 | Hub.listen(AuthService.CHANNEL, onHubCapsule, 'MyListener');
127 |
128 | return function cleanup() {
129 | Hub.remove(AuthService.CHANNEL, onHubCapsule);
130 | }
131 | })
132 |
133 | const onHubCapsule = (capsule) => {
134 | const {channel, payload} = capsule;
135 | if (channel === AuthService.CHANNEL && payload.event === AuthService.AUTH_EVENTS.PASSWORD_RESET) {
136 | logger.info(payload.message);
137 | if (!payload.success) {
138 | setErrorMessage(payload.message)
139 | } else {
140 | setErrorMessage(null)
141 | history.push("/forgotpassword2")
142 | }
143 | }
144 | };
145 | const onFinish = (e) => {
146 |
147 | e.preventDefault();
148 |
149 | AuthService.forgotPassword(email);
150 | };
151 |
152 | const onFinishFailed = errorInfo => {
153 | console.log('Failed:', errorInfo);
154 | };
155 | const [email, setEmail] = useState('');
156 |
157 |
158 | return (
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | Reset Password
172 |
173 |
174 | Enter your email to receive a reset code
175 |
176 | {errorMessage &&
177 |
178 | {errorMessage}
179 | }
180 |
212 |
213 |
214 |
215 |
216 |
217 | );
218 |
219 | }
220 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/auth/service/constants.js:
--------------------------------------------------------------------------------
1 | const prod = {
2 | endpointAfterRegistration: "/registerconfirm"
3 | };
4 |
5 | const dev = {
6 | endpointAfterRegistration: "/registerconfirm"
7 | };
8 |
9 | export const config = process.env.NODE_ENV === "development" ? dev : prod;
10 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/auth/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles';
2 |
3 | export const useAuthStyles = makeStyles((theme) => ({
4 | formContainer: {
5 | display: 'flex',
6 | flexDirection: 'column',
7 | minHeight: '100vh',
8 | padding: theme.spacing(4),
9 | },
10 | formWrapper: {
11 | width: '100%',
12 | height: '100%',
13 | display: 'flex',
14 | flexDirection: 'column',
15 | justifyContent: 'center',
16 | },
17 | quoteContainer: {
18 | display: 'flex',
19 | alignItems: 'center',
20 | justifyContent: 'center',
21 | padding: theme.spacing(4),
22 | backgroundColor: theme.palette.grey[100],
23 | borderRadius: theme.shape.borderRadius,
24 | margin: theme.spacing(2),
25 | },
26 | quote: {
27 | color: theme.palette.text.primary,
28 | fontSize: '1.2rem',
29 | fontStyle: 'italic',
30 | marginBottom: theme.spacing(1),
31 | },
32 | author: {
33 | color: theme.palette.text.secondary,
34 | marginTop: theme.spacing(1),
35 | },
36 | root: {
37 | minHeight: '100vh',
38 | opacity: 0,
39 | animation: '$fadeIn 0.4s ease-out forwards',
40 | },
41 | paper: {
42 | margin: theme.spacing(8, 4),
43 | display: 'flex',
44 | flexDirection: 'column',
45 | alignItems: 'center',
46 | animation: '$slideUp 0.4s ease-out forwards',
47 | },
48 | avatar: {
49 | margin: theme.spacing(1),
50 | backgroundColor: theme.palette.primary.main,
51 | animation: '$scaleIn 0.3s ease-out',
52 | },
53 | form: {
54 | width: '100%',
55 | marginTop: theme.spacing(1),
56 | },
57 | submit: {
58 | margin: theme.spacing(3, 0, 2),
59 | padding: theme.spacing(1.5),
60 | transition: 'transform 0.2s ease',
61 | '&:hover': {
62 | transform: 'translateY(-2px)',
63 | },
64 | '&:active': {
65 | transform: 'translateY(0)',
66 | },
67 | },
68 | formField: {
69 | opacity: 0,
70 | animation: '$slideUp 0.4s ease-out forwards',
71 | '&:nth-child(1)': {
72 | animationDelay: '0.1s',
73 | },
74 | '&:nth-child(2)': {
75 | animationDelay: '0.2s',
76 | },
77 | '&:nth-child(3)': {
78 | animationDelay: '0.3s',
79 | },
80 | },
81 | errorAlert: {
82 | animation: '$shake 0.4s ease-in-out',
83 | marginBottom: theme.spacing(2),
84 | },
85 | '@keyframes fadeIn': {
86 | from: {
87 | opacity: 0,
88 | },
89 | to: {
90 | opacity: 1,
91 | },
92 | },
93 | '@keyframes slideUp': {
94 | from: {
95 | opacity: 0,
96 | transform: 'translateY(20px)',
97 | },
98 | to: {
99 | opacity: 1,
100 | transform: 'translateY(0)',
101 | },
102 | },
103 | '@keyframes scaleIn': {
104 | from: {
105 | transform: 'scale(0.8)',
106 | opacity: 0,
107 | },
108 | to: {
109 | transform: 'scale(1)',
110 | opacity: 1,
111 | },
112 | },
113 | '@keyframes shake': {
114 | '0%, 100%': {
115 | transform: 'translateX(0)',
116 | },
117 | '25%': {
118 | transform: 'translateX(-5px)',
119 | },
120 | '75%': {
121 | transform: 'translateX(5px)',
122 | },
123 | },
124 | }));
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/dashboard/dashboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import makeStyles from '@mui/styles/makeStyles';
3 |
4 | import {Container} from "@mui/material";
5 | import {Logger} from "@aws-amplify/core";
6 | import Typography from "@mui/material/Typography";
7 | import {createTheme, ThemeProvider} from "@mui/material/styles";
8 | import Box from "@mui/material/Box";
9 | import Button from "@mui/material/Button";
10 | import {Link} from "react-router-dom";
11 |
12 | const logger = new Logger("UploadFileAccordion");
13 | const theme = createTheme();
14 |
15 | theme.typography.h3 = {
16 | fontSize: '1.2rem',
17 | '@media (min-width:600px)': {
18 | fontSize: '1.5rem',
19 | },
20 | [theme.breakpoints.up('md')]: {
21 | fontSize: '2rem',
22 | },
23 | };
24 |
25 | const useStyles = makeStyles((theme) => ({
26 | root: {
27 | flexGrow: 1,
28 | width: "100%",
29 | padding: theme.spacing(3),
30 |
31 | },box: {
32 | width: '300px',
33 | height: '300px',
34 | backgroundColor: theme.palette.primary.main,
35 | color: theme.palette.primary.contrastText,
36 | display: 'flex',
37 | justifyContent: 'center',
38 | alignItems: 'center',
39 | borderRadius: '10px',
40 | boxShadow: '0px 10px 20px rgba(0, 0, 0, 0.15)',
41 | margin: '0 auto'
42 | }
43 | }));
44 |
45 | export const Dashboard = () => {
46 | const classes = useStyles();
47 |
48 | return (
49 |
50 |
60 |
61 | Sample AWS Solutions...
62 |
63 |
64 |
73 |
74 |
75 |
76 |
)
77 | ;
78 | }
79 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/components/AppLogo/index.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {Box} from '@mui/material';
3 | import makeStyles from '@mui/styles/makeStyles';
4 | import AppContext from '../../../../@crema/utility/AppContext';
5 | import {ThemeMode} from '../../constants/AppEnums';
6 |
7 | const AppLogo = () => {
8 | const {themeMode} = useContext(AppContext);
9 | const useStyles = makeStyles(() => ({
10 | logoRoot: {
11 | display: 'flex',
12 | flexDirection: 'row',
13 | cursor: 'pointer',
14 | alignItems: 'center',
15 | },
16 | logo: {
17 | height: 36,
18 | marginRight: 10,
19 | },
20 | }));
21 | const classes = useStyles();
22 | return (
23 |
24 |
33 |
34 | );
35 | };
36 |
37 | export default AppLogo;
38 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/components/AppLogoWhite/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Box} from '@mui/material';
3 | import makeStyles from '@mui/styles/makeStyles';
4 |
5 | const AppLogoWhite = () => {
6 | const useStyles = makeStyles(() => ({
7 | logoRoot: {
8 | display: 'flex',
9 | flexDirection: 'row',
10 | cursor: 'pointer',
11 | alignItems: 'center',
12 | },
13 | logo: {
14 | height: 36,
15 | marginRight: 10,
16 | },
17 | }));
18 | const classes = useStyles();
19 | return (
20 |
21 |
26 |
27 | );
28 | };
29 |
30 | export default AppLogoWhite;
31 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/components/HeaderUser/index.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import Avatar from '@mui/material/Avatar';
3 | import {useDispatch} from 'react-redux';
4 | import {
5 | onCognitoUserSignOut,
6 | onJWTAuthSignout,
7 | onSignOutAuth0User,
8 | onSignOutFirebaseUser,
9 | } from '../../../../redux/actions';
10 | import {useAuthUser} from '../../../../@crema/utility/AppHooks';
11 | import AppContext from '../../../../@crema/utility/AppContext';
12 | import clsx from 'clsx';
13 | import makeStyles from '@mui/styles/makeStyles';
14 | import MenuItem from '@mui/material/MenuItem';
15 | import Menu from '@mui/material/Menu';
16 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
17 | import Box from '@mui/material/Box';
18 | import {grey, orange} from '@mui/material/colors';
19 | import {AuthType, Fonts, ThemeMode} from '../../constants/AppEnums';
20 | import Hidden from '@mui/material/Hidden';
21 |
22 | const HeaderUser = (props) => {
23 | const {themeMode} = useContext(AppContext);
24 | const dispatch = useDispatch();
25 | const [user, authType] = useAuthUser();
26 |
27 | const [anchorEl, setAnchorEl] = React.useState(null);
28 |
29 | const handleClick = (event) => {
30 | setAnchorEl(event.currentTarget);
31 | };
32 |
33 | const handleClose = () => {
34 | setAnchorEl(null);
35 | };
36 |
37 | const getUserAvatar = () => {
38 | if (user.displayName) {
39 | return user.displayName.charAt(0).toUpperCase();
40 | }
41 | if (user.email) {
42 | return user.email.charAt(0).toUpperCase();
43 | }
44 | };
45 |
46 | const useStyles = makeStyles((theme) => {
47 | return {
48 | crHeaderUser: {
49 | backgroundColor: props.header ? 'transparent' : 'rgba(0,0,0,.08)',
50 | paddingTop: 9,
51 | paddingBottom: 9,
52 | minHeight: 56,
53 | display: 'flex',
54 | flexDirection: 'column',
55 | justifyContent: 'center',
56 | [theme.breakpoints.up('sm')]: {
57 | paddingTop: 10,
58 | paddingBottom: 10,
59 | minHeight: 70,
60 | },
61 | },
62 | profilePic: {
63 | height: 40,
64 | width: 40,
65 | fontSize: 24,
66 | backgroundColor: orange[500],
67 | [theme.breakpoints.up('xl')]: {
68 | height: 45,
69 | width: 45,
70 | },
71 | },
72 | userInfo: {
73 | width: !props.header ? 'calc(100% - 75px)' : '100%',
74 | },
75 | userName: {
76 | overflow: 'hidden',
77 | textOverflow: 'ellipsis',
78 | cursor: 'pointer',
79 | whiteSpace: 'nowrap',
80 | fontSize: 16,
81 | fontFamily: Fonts.MEDIUM,
82 | [theme.breakpoints.up('xl')]: {
83 | fontSize: 18,
84 | },
85 | color:
86 | themeMode === ThemeMode.DARK || !props.header ? 'white' : '#313541',
87 | },
88 | pointer: {
89 | cursor: 'pointer',
90 | },
91 | adminRoot: {
92 | color: grey[500],
93 | fontSize: 16,
94 | overflow: 'hidden',
95 | textOverflow: 'ellipsis',
96 | whiteSpace: 'nowrap',
97 | },
98 | };
99 | });
100 |
101 | const classes = useStyles(props);
102 |
103 | return (
104 |
107 |
108 | {user.photoURL ? (
109 |
110 | ) : (
111 | {getUserAvatar()}
112 | )}
113 |
114 |
118 |
119 |
120 | {user.displayName ? user.displayName : user.email}
121 |
122 | Admin
123 |
124 |
125 |
126 |
134 |
135 |
157 |
158 |
159 |
160 |
161 |
162 | );
163 | };
164 |
165 | export default HeaderUser;
166 | HeaderUser.defaultProps = {
167 | header: true,
168 | };
169 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/components/UserInfo/index.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import Avatar from '@mui/material/Avatar';
3 | import {useDispatch} from 'react-redux';
4 | import {
5 | onCognitoUserSignOut,
6 | onJWTAuthSignout,
7 | onSignOutAuth0User,
8 | onSignOutFirebaseUser,
9 | } from '../../../../redux/actions';
10 | import {useAuthUser} from '../../../../@crema/utility/AppHooks';
11 | import AppContext from '../../../../@crema/utility/AppContext';
12 | import clsx from 'clsx';
13 | import makeStyles from '@mui/styles/makeStyles';
14 | import MenuItem from '@mui/material/MenuItem';
15 | import Menu from '@mui/material/Menu';
16 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
17 | import Box from '@mui/material/Box';
18 | import {grey, orange} from '@mui/material/colors';
19 | import {AuthType, Fonts} from '../../constants/AppEnums';
20 |
21 | const UserInfo = (props) => {
22 | const {themeMode} = useContext(AppContext);
23 | const dispatch = useDispatch();
24 | const [user, authType] = useAuthUser();
25 |
26 | const [anchorEl, setAnchorEl] = React.useState(null);
27 |
28 | const handleClick = (event) => {
29 | setAnchorEl(event.currentTarget);
30 | };
31 |
32 | const handleClose = () => {
33 | setAnchorEl(null);
34 | };
35 |
36 | const getUserAvatar = () => {
37 | if (user.displayName) {
38 | return user.displayName.charAt(0).toUpperCase();
39 | }
40 | if (user.email) {
41 | return user.email.charAt(0).toUpperCase();
42 | }
43 | };
44 |
45 | const useStyles = makeStyles((theme) => {
46 | return {
47 | crUserInfo: {
48 | backgroundColor: 'rgba(0,0,0,.08)',
49 | paddingTop: 9,
50 | paddingBottom: 9,
51 | minHeight: 56,
52 | display: 'flex',
53 | flexDirection: 'column',
54 | justifyContent: 'center',
55 | [theme.breakpoints.up('sm')]: {
56 | paddingTop: 10,
57 | paddingBottom: 10,
58 | minHeight: 70,
59 | },
60 | },
61 | profilePic: {
62 | height: 40,
63 | width: 40,
64 | fontSize: 24,
65 | backgroundColor: orange[500],
66 | [theme.breakpoints.up('xl')]: {
67 | height: 45,
68 | width: 45,
69 | },
70 | },
71 | userInfo: {
72 | width: 'calc(100% - 75px)',
73 | },
74 | userName: {
75 | overflow: 'hidden',
76 | textOverflow: 'ellipsis',
77 | whiteSpace: 'nowrap',
78 | fontSize: 18,
79 | fontFamily: Fonts.MEDIUM,
80 | [theme.breakpoints.up('xl')]: {
81 | fontSize: 20,
82 | },
83 | color: themeMode === 'light' ? '#313541' : 'white',
84 | },
85 | designation: {
86 | textOverflow: 'ellipsis',
87 | whiteSpace: 'nowrap',
88 | },
89 | pointer: {
90 | cursor: 'pointer',
91 | },
92 | adminRoot: {
93 | color: grey[500],
94 | fontSize: 16,
95 | overflow: 'hidden',
96 | textOverflow: 'ellipsis',
97 | whiteSpace: 'nowrap',
98 | },
99 | };
100 | });
101 |
102 | const classes = useStyles(props);
103 |
104 | return (
105 |
108 |
109 | {user.photoURL ? (
110 |
111 | ) : (
112 | {getUserAvatar()}
113 | )}
114 |
115 |
119 |
120 | {user.displayName ? user.displayName : 'Admin User '}
121 |
122 |
126 |
127 |
149 |
150 |
151 |
152 | System Manager
153 |
154 |
155 |
156 |
157 | );
158 | };
159 |
160 | export default UserInfo;
161 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | //API
2 | export const FETCH_START = 'fetch_start';
3 | export const FETCH_SUCCESS = 'fetch_success';
4 | export const FETCH_ERROR = 'fetch_error';
5 | export const SHOW_MESSAGE = 'show_message';
6 | export const HIDE_MESSAGE = 'hide_message';
7 | export const TOGGLE_APP_DRAWER = 'toggle_app_drawer';
8 | export const UPDATING_CONTENT = 'updating_content';
9 |
10 | //APP SETTING
11 | export const TOGGLE_NAV_COLLAPSED = 'toggle_nav_collapsed';
12 | export const SET_INITIAL_PATH = 'set_initial_path';
13 |
14 | //JWT_AUTH
15 | export const UPDATE_JWT_AUTH_USER = 'update_jwt_auth_user';
16 | export const SET_JWT_USER_DATA = 'set_jwt_user_data';
17 | export const SIGNOUT_JWT_USER_SUCCESS = 'signout_jwt_user_success';
18 | export const SET_JWT_TOKEN_SET = 'set_jwt_token_set';
19 |
20 | //FIREBASE_AUTH
21 | export const UPDATE_FIREBASE_USER = 'update_firebase_user';
22 |
23 | //AWS_COGNITO_AUTH
24 | export const UPDATE_COGNITO_USER = 'update_cognito_user';
25 |
26 | //AUTH0
27 | export const UPDATE_AUTH0_USER = 'update_auth0_user';
28 | export const SET_AUTH0_TOKEN_SET = 'set_auth0_token_set';
29 | export const SIGNOUT_AUTH0_SUCCESS = 'signout_auth0_success';
30 |
31 | //ANALYTICS-DASHBOARD
32 | export const GET_ANALYTICS_DATA = 'get_analytics_data';
33 |
34 | //ECOMMERCE-DASHBOARD
35 | export const GET_ECOMMERCE_DATA = 'get_ecommerce_data';
36 |
37 | //CRM-DASHBOARD
38 | export const GET_CRM_DATA = 'get_crm_data';
39 |
40 | //CRYPTO-DASHBOARD
41 | export const GET_CRYPTO_DATA = 'get_crypto_data';
42 |
43 | //METRICS-DASHBOARD
44 | export const GET_METRICS_DATA = 'get_metrics_data';
45 |
46 | //WIDGETS_DASHBOARD
47 | export const GET_WIDGETS_DATA = 'get_widgets_data';
48 |
49 | //MAIL-APP
50 | export const GET_MAIL_LIST = 'get_mail_list';
51 | export const GET_FOLDER_LIST = 'get_folder_list';
52 | export const GET_LABEL_LIST = 'get_label_list';
53 | export const TOGGLE_MAIL_DRAWER = 'toggle_mail_drawer';
54 | export const COMPOSE_MAIL = 'compose_mail';
55 | export const GET_MAIL_DETAIL = 'get_mail_detail';
56 | export const UPDATE_MAIL_FOLDER = 'update_mail_folders';
57 | export const UPDATE_MAIL_LABEL = 'update_mail_label';
58 | export const UPDATE_STARRED_STATUS = 'update_starred_status';
59 | export const UPDATED_MAIL_DETAIL = 'updated_mail_detail';
60 | export const CHANGE_READ_STATUS = 'change_read_status';
61 | export const GET_CONNECTION_LIST = 'get_connection_list';
62 | export const NULLIFY_MAIL = 'nullify_mail';
63 |
64 | //TODO-APP
65 | export const GET_TASK_LIST = 'get_task_list';
66 | export const CREATE_NEW_TASK = 'create_new_task';
67 | export const TOGGLE_TODO_DRAWER = 'toggle_todo_drawer';
68 | export const GET_TODO_FOLDER_LIST = 'GET_TODO_FOLDER_LIST';
69 | export const GET_TODO_LABEL_LIST = 'GET_TODO_LABEL_LIST';
70 | export const GET_TODO_STATUS_LIST = 'GET_TODO_STATUS_LIST';
71 | export const GET_TODO_PRIORITY_LIST = 'GET_TODO_PRIORITY_LIST';
72 | export const UPDATE_TASK_FOLDER = 'UPDATE_TASK_FOLDER';
73 | export const UPDATE_TASK_LABEL = 'UPDATE_TASK_LABEL';
74 | export const UPDATE_TASK_STARRED_STATUS = 'UPDATE_TASK_STARRED_STATUS';
75 | export const GET_TASK_DETAIL = 'GET_TASK_DETAIL';
76 | export const UPDATE_TASK_DETAIL = 'UPDATE_TASK_DETAIL';
77 | export const GET_TODO_STAFF_LIST = 'GET_TODO_STAFF_LIST';
78 |
79 | //CONTACT_APP
80 | export const GET_CONTACT_LIST = 'GET_CONTACT_LIST';
81 | export const GET_CONTACT_FOLDER_LIST = 'GET_CONTACT_FOLDER_LIST';
82 | export const GET_CONTACT_LABEL_LIST = 'GET_CONTACT_LABEL_LIST';
83 | export const DELETE_CONTACT = 'DELETE_CONTACT';
84 | export const UPDATE_CONTACT_LABEL = 'UPDATE_CONTACT_LABEL';
85 | export const UPDATE_CONTACT_STARRED_STATUS = 'UPDATE_CONTACT_STARRED_STATUS';
86 | export const GET_CONTACT_DETAIL = 'GET_CONTACT_DETAIL';
87 | export const TOGGLE_CONTACT_DRAWER = 'TOGGLE_CONTACT_DRAWER';
88 | export const UPDATE_CONTACT_DETAIL = 'UPDATE_CONTACT_DETAIL';
89 | export const CREATE_NEW_CONTACT = 'CREATE_NEW_CONTACT';
90 |
91 | //SCRUMBOARD_APP
92 | export const GET_MEMBER_LIST = 'GET_MEMBER_LIST';
93 | export const GET_SCRUM_LABEL_LIST = 'GET_SCRUM_LABEL_LIST';
94 | export const GET_BOARDS = 'GET_BOARDS';
95 | export const GET_BOARD_DETAIL = 'GET_BOARD_DETAIL';
96 | export const ADD_BOARD_LIST = 'ADD_BOARD_LIST';
97 | export const ADD_LIST_CARD = 'ADD_LIST_CARD';
98 | export const EDIT_LIST_CARD = 'EDIT_LIST_CARD';
99 | export const DELETE_LIST_CARD = 'DELETE_LIST_CARD';
100 | export const DELETE_BOARD = 'DELETE_BOARD';
101 | export const ADD_NEW_BOARD = 'ADD_NEW_BOARD';
102 | export const DELETE_LIST = 'DELETE_LIST';
103 | export const EDIT_BOARD_DETAIL = 'EDIT_BOARD_DETAIL';
104 | export const EDIT_BOARD_LIST = 'EDIT_BOARD_LIST';
105 |
106 | //CHAT_APP
107 | export const GET_CONNECTIONS_LIST = 'get_connections_list';
108 | export const GET_USER_MESSAGES = 'get_user_messages';
109 | export const ADD_NEW_MESSAGE = 'add_new_message';
110 | export const EDIT_MESSAGE = 'edit_message';
111 | export const DELETE_MESSAGE = 'delete_message';
112 | export const DELETE_USER_MESSAGES = 'delete_user_messages';
113 | export const TOGGLE_CHAT_DRAWER = 'toggle_chat_drawer';
114 | export const SELECT_USER = 'select_user';
115 |
116 | //USER_LIST
117 | export const GET_USER_LIST = 'GET_USER_LIST';
118 |
119 | //ECOMMERCE_LIST
120 | export const GET_ECOMMERCE_LIST = 'get_ecommerce_list';
121 | export const SET_PRODUCT_VIEW_TYPE = 'set_product_view_type';
122 | export const SET_FILTER_DATA = 'set_filter_data';
123 | export const SET_PRODUCT_DATA = 'set_product_data';
124 | export const GET_RECENT_ORDER = 'get_recent_order';
125 | export const GET_CUSTOMERS = 'get_customers';
126 | export const ADD_CART_ITEM = 'add_cart_item';
127 | export const REMOVE_CART_ITEM = 'remove_cart_item';
128 | export const UPDATE_CART_ITEM = 'update_cart_item';
129 | export const SET_CART_ITEMS = 'set_cart_items';
130 |
131 | //CK-EDITOR
132 | export const GET_BALLOON_BLOCK_DATA = 'get_balloon_block_data';
133 | export const UPDATE_BALLOON_BLOCK_DATA = 'update_balloon_block_data';
134 | export const GET_BALLOON_DATA = 'get_balloon_data';
135 | export const UPDATE_BALLOON_DATA = 'update_balloon_data';
136 | export const GET_CLASSIC_DATA = 'get_classic_data';
137 | export const UPDATE_CLASSIC_DATA = 'update_classic_data';
138 | export const GET_INLINE_DATA = 'get_inline_data';
139 | export const UPDATE_INLINE_DATA = 'update_inline_data';
140 | export const GET_DOCUMENT_DATA = 'get_document_data';
141 | export const UPDATE_DOCUMENT_DATA = 'update_document_data';
142 | export const GET_CUSTOM_DATA = 'get_custom_data';
143 | export const UPDATE_CUSTOM_DATA = 'update_custom_data';
144 |
145 | //GALLERY
146 | export const GET_GALLERY_PHOTO = 'get_gallery_photo';
147 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/constants/AppConst.js:
--------------------------------------------------------------------------------
1 | export const defaultUser = {
2 | displayName: 'John Alex',
3 | email: 'demo@example.com',
4 | token: 'access-token',
5 | photoURL: 'https://via.placeholder.com/150',
6 | };
7 | export const initialUrl = '/dashboards/analytics'; // this serviceUrl will open after login
8 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/constants/AppEnums.js:
--------------------------------------------------------------------------------
1 | export const ThemeStyle = {
2 | MODERN: 'modern',
3 | STANDARD: 'standard',
4 | };
5 | export const ThemeStyleRadius = {
6 | MODERN: 30,
7 | STANDARD: 4,
8 | };
9 | export const ThemeMode = {
10 | LIGHT: 'light',
11 | SEMI_DARK: 'semi-dark',
12 | DARK: 'dark',
13 | };
14 | export const LayoutType = {
15 | FULL_WIDTH: 'full-width',
16 | BOXED: 'boxed',
17 | };
18 | export const NavStyle = {
19 | DEFAULT: 'default',
20 | MINI: 'mini',
21 | MINI_SIDEBAR_TOGGLE: 'mini_sidebar_toggle',
22 | STANDARD: 'standard',
23 | HEADER_USER: 'user-header',
24 | HEADER_USER_MINI: 'user-mini-header',
25 | DRAWER: 'drawer',
26 | BIT_BUCKET: 'bit-bucket',
27 | H_DEFAULT: 'h-default',
28 | HOR_LIGHT_NAV: 'hor-light-nav',
29 | HOR_DARK_LAYOUT: 'hor-dark-layout',
30 | };
31 | export const FooterType = {
32 | FIXED: 'fixed',
33 | FLUID: 'fluid',
34 | };
35 | export const HeaderType = {
36 | DARK: 'dark',
37 | LIGHT: 'light',
38 | };
39 | export const RouteTransition = {
40 | NONE: 'none',
41 | FADE: 'fade',
42 | SLIDE_LEFT: 'slideLeft',
43 | SLIDE_RIGHT: 'slideRight',
44 | SLIDE_UP: 'slideUp',
45 | SLIDE_DOWN: 'slideDown',
46 | };
47 | export const Fonts = {
48 | LIGHT: 'Gilroy-Light',
49 | REGULAR: 'Gilroy-Regular',
50 | MEDIUM: 'Gilroy-Medium',
51 | BOLD: 'Gilroy-Bold',
52 | EXTRA_BOLD: 'Gilroy-ExtraBold',
53 | };
54 |
55 | export const AuthType = {
56 | FIREBASE: 'firebase',
57 | AWS_COGNITO: 'aws_cognito',
58 | AUTH0: 'auth0',
59 | JWT_AUTH: 'jwt_auth',
60 | };
61 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/constants/ColorSets.js:
--------------------------------------------------------------------------------
1 | import MonochromeColors from './MonochromeTheme';
2 |
3 | export default [
4 | {
5 | PrimaryColor: MonochromeColors.BLACK,
6 | SecondaryColor: MonochromeColors.DARK_GRAY,
7 | SidebarColor: MonochromeColors.ALMOST_BLACK,
8 | },
9 | {
10 | PrimaryColor: MonochromeColors.DARK_BLUE,
11 | SecondaryColor: MonochromeColors.MEDIUM_GRAY,
12 | SidebarColor: MonochromeColors.ALMOST_BLACK,
13 | },
14 | {
15 | PrimaryColor: MonochromeColors.BLACK,
16 | SecondaryColor: MonochromeColors.LIGHT_GRAY,
17 | SidebarColor: MonochromeColors.ALMOST_BLACK,
18 | },
19 | {
20 | PrimaryColor: MonochromeColors.DARK_GRAY,
21 | SecondaryColor: MonochromeColors.MEDIUM_GRAY,
22 | SidebarColor: MonochromeColors.ALMOST_BLACK,
23 | },
24 | {
25 | PrimaryColor: MonochromeColors.ALMOST_BLACK,
26 | SecondaryColor: MonochromeColors.DARK_GRAY,
27 | SidebarColor: MonochromeColors.ALMOST_BLACK,
28 | },
29 | {
30 | PrimaryColor: MonochromeColors.BLACK,
31 | SecondaryColor: MonochromeColors.DARK_BLUE,
32 | SidebarColor: MonochromeColors.ALMOST_BLACK,
33 | },
34 |
35 | {
36 | PrimaryColor: '#FD933A',
37 | SecondaryColor: '#5A63C8',
38 | SidebarColor: '#313541',
39 | },
40 | {
41 | PrimaryColor: '#03A9F4',
42 | SecondaryColor: '#FFC107',
43 | SidebarColor: '#313541',
44 | },
45 | {
46 | PrimaryColor: '#03A9F4',
47 | SecondaryColor: '#FF80AB',
48 | SidebarColor: '#313541',
49 | },
50 |
51 | {
52 | PrimaryColor: '#3F51B5',
53 | SecondaryColor: '#2196F3',
54 | SidebarColor: '#FFC107',
55 | },
56 |
57 | {
58 | PrimaryColor: '#9C27B0',
59 | SecondaryColor: '#FFCA28',
60 | SidebarColor: '#313541',
61 | },
62 |
63 | {
64 | PrimaryColor: '#673AB7',
65 | SecondaryColor: '#2196F3',
66 | SidebarColor: '#313541',
67 | },
68 | ];
69 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/constants/MonochromeTheme.js:
--------------------------------------------------------------------------------
1 | export const MonochromeColors = {
2 | // Primary Colors
3 | WHITE: '#FFFFFF',
4 | BLACK: '#1A1A2E',
5 |
6 | // Base Colors
7 | PRIMARY_BLUE: '#2A4365',
8 | SECONDARY_BLUE: '#3B82F6',
9 |
10 | // Secondary Colors
11 | LIGHT_GRAY: '#F3F4F6',
12 | MEDIUM_GRAY: '#E2E8F0',
13 | DARK_GRAY: '#64748B',
14 | ALMOST_BLACK: '#0F172A',
15 |
16 | // Accent Colors
17 | ACCENT_TEAL: '#0D9488',
18 | ACCENT_PURPLE: '#7C3AED',
19 | ACCENT_ROSE: '#E11D48',
20 |
21 | // Semantic Colors
22 | PRIMARY_BUTTON: '#2A4365',
23 | DISABLED_BUTTON: '#94A3B8',
24 |
25 | // Interactive States
26 | HOVER_BLUE: '#1E40AF',
27 | ACTIVE_BLUE: '#1E3A8A',
28 | };
29 |
30 | export const ChartColors = {
31 | BARS: MonochromeColors.BLACK,
32 | GRID_LINES: MonochromeColors.MEDIUM_GRAY,
33 | };
34 |
35 | export default MonochromeColors;
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/jss/common/common.style.js:
--------------------------------------------------------------------------------
1 | import makeStyles from '@mui/styles/makeStyles';
2 | import {Fonts} from '../../constants/AppEnums';
3 | import MonochromeColors from '../../constants/MonochromeTheme';
4 |
5 | const useStyles = makeStyles((theme) => ({
6 | '@global': {
7 | // for global styles
8 | '.MuiLink-root': {
9 | fontFamily: Fonts.REGULAR,
10 | color: MonochromeColors.DARK_BLUE,
11 | '&:hover': {
12 | color: MonochromeColors.BLACK,
13 | },
14 | },
15 | '.pointer': {
16 | cursor: 'pointer',
17 | },
18 | '.MuiTableCell-stickyHeader': {
19 | backgroundColor: MonochromeColors.WHITE,
20 | },
21 | // Chart styles
22 | '.recharts-cartesian-grid-horizontal line, .recharts-cartesian-grid-vertical line': {
23 | stroke: MonochromeColors.MEDIUM_GRAY,
24 | },
25 | '.recharts-bar-rectangle path': {
26 | fill: MonochromeColors.BLACK,
27 | },
28 | },
29 | }));
30 |
31 | export default useStyles;
32 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/jss/common/theme.js:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material/styles';
2 | import MonochromeColors from '../../constants/MonochromeTheme';
3 |
4 | const theme = createTheme({
5 | palette: {
6 | mode: 'light',
7 | primary: {
8 | main: MonochromeColors.PRIMARY_BLUE,
9 | light: MonochromeColors.SECONDARY_BLUE,
10 | dark: MonochromeColors.ACTIVE_BLUE,
11 | },
12 | secondary: {
13 | main: MonochromeColors.ACCENT_TEAL,
14 | light: MonochromeColors.ACCENT_PURPLE,
15 | dark: MonochromeColors.ACCENT_ROSE,
16 | },
17 | text: {
18 | primary: MonochromeColors.BLACK,
19 | secondary: MonochromeColors.DARK_GRAY,
20 | disabled: MonochromeColors.DISABLED_BUTTON,
21 | },
22 | background: {
23 | default: MonochromeColors.LIGHT_GRAY,
24 | paper: MonochromeColors.WHITE,
25 | },
26 | divider: MonochromeColors.MEDIUM_GRAY,
27 | action: {
28 | active: MonochromeColors.BLACK,
29 | hover: MonochromeColors.DARK_BLUE,
30 | disabled: MonochromeColors.DISABLED_BUTTON,
31 | disabledBackground: MonochromeColors.MEDIUM_GRAY,
32 | },
33 | },
34 | components: {
35 | MuiButton: {
36 | styleOverrides: {
37 | root: {
38 | '&.Mui-disabled': {
39 | backgroundColor: MonochromeColors.DISABLED_BUTTON,
40 | color: MonochromeColors.WHITE,
41 | },
42 | },
43 | contained: {
44 | backgroundColor: MonochromeColors.PRIMARY_BUTTON,
45 | color: MonochromeColors.WHITE,
46 | '&:hover': {
47 | backgroundColor: MonochromeColors.HOVER_BLUE,
48 | },
49 | '&:active': {
50 | backgroundColor: MonochromeColors.ACTIVE_BLUE,
51 | },
52 | },
53 | },
54 | },
55 | MuiTableCell: {
56 | styleOverrides: {
57 | root: {
58 | borderBottom: `1px solid ${MonochromeColors.MEDIUM_GRAY}`,
59 | },
60 | head: {
61 | color: MonochromeColors.BLACK,
62 | fontWeight: 'bold',
63 | },
64 | },
65 | },
66 | MuiTooltip: {
67 | styleOverrides: {
68 | tooltip: {
69 | backgroundColor: MonochromeColors.LIGHT_GRAY_ACCENT,
70 | color: MonochromeColors.WHITE,
71 | },
72 | },
73 | },
74 | },
75 | typography: {
76 | allVariants: {
77 | color: MonochromeColors.BLACK,
78 | },
79 | h1: {
80 | color: MonochromeColors.BLACK,
81 | fontWeight: 700,
82 | },
83 | h2: {
84 | color: MonochromeColors.BLACK,
85 | fontWeight: 700,
86 | },
87 | h3: {
88 | color: MonochromeColors.BLACK,
89 | fontWeight: 600,
90 | },
91 | body1: {
92 | color: MonochromeColors.DARK_GRAY,
93 | },
94 | body2: {
95 | color: MonochromeColors.DARK_GRAY,
96 | },
97 | },
98 | });
99 |
100 | export default theme;
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/entries/ar_SA.js:
--------------------------------------------------------------------------------
1 | import saMessages from '../locales/ar_SA.json';
2 |
3 | const saLang = {
4 | messages: {
5 | ...saMessages,
6 | },
7 | locale: 'ar-SA',
8 | };
9 | export default saLang;
10 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/entries/en-US.js:
--------------------------------------------------------------------------------
1 | import enMessages from '../locales/en_US.json';
2 | import {enUS} from '@mui/material/locale';
3 |
4 | const EnLang = {
5 | messages: {
6 | ...enMessages,
7 | },
8 | muiLocale: enUS,
9 | locale: 'en-US',
10 | };
11 | export default EnLang;
12 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/entries/es_ES.js:
--------------------------------------------------------------------------------
1 | import saMessages from '../locales/es_ES.json';
2 | import {esES} from '@mui/material/locale';
3 |
4 | const saLang = {
5 | messages: {
6 | ...saMessages,
7 | },
8 | muiLocale: esES,
9 | locale: 'es',
10 | };
11 | export default saLang;
12 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/entries/fr_FR.js:
--------------------------------------------------------------------------------
1 | import saMessages from '../locales/fr_FR.json';
2 | import {frFR} from '@mui/material/locale';
3 |
4 | const saLang = {
5 | messages: {
6 | ...saMessages,
7 | },
8 | muiLocale: frFR,
9 | locale: 'fr-FR',
10 | };
11 | export default saLang;
12 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/entries/it_IT.js:
--------------------------------------------------------------------------------
1 | import saMessages from '../locales/it_IT.json';
2 | import {itIT} from '@mui/material/locale';
3 |
4 | const saLang = {
5 | messages: {
6 | ...saMessages,
7 | },
8 | muiLocale: itIT,
9 | locale: 'it-IT',
10 | };
11 | export default saLang;
12 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/entries/zh-Hans-CN.js:
--------------------------------------------------------------------------------
1 | import zhMessages from '../locales/zh-Hans.json';
2 | import {zhCN} from '@mui/material/locale';
3 |
4 | const ZhLan = {
5 | messages: {
6 | ...zhMessages,
7 | },
8 | muiLocale: zhCN,
9 | locale: 'zh-Hans-CN',
10 | };
11 | export default ZhLan;
12 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/localization/index.js:
--------------------------------------------------------------------------------
1 | import enLang from './entries/en-US';
2 | import zhLang from './entries/zh-Hans-CN';
3 | import arLang from './entries/ar_SA';
4 | import itLang from './entries/it_IT';
5 | import esLang from './entries/es_ES';
6 | import frLang from './entries/fr_FR';
7 |
8 | const AppLocale = {
9 | en: enLang,
10 | zh: zhLang,
11 | ar: arLang,
12 | it: itLang,
13 | es: esLang,
14 | fr: frLang,
15 | };
16 |
17 | export default AppLocale;
18 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/base.css:
--------------------------------------------------------------------------------
1 | @import '../../assets/vendors/material-design-icon/css/material-design-iconic-font.min.css';
2 |
3 | :root {
4 | --light: Gilroy-Light;
5 | --regular: Gilroy-Regular;
6 | --medium: Gilroy-Medium;
7 | --bold: Gilroy-Bold;
8 | --extra-bold: Gilroy-ExtraBold;
9 | font-weight: normal;
10 | font-style: normal;
11 | }
12 |
13 | body {
14 | line-height: 1.35 !important;
15 | }
16 |
17 | * {
18 | margin: 0;
19 | padding: 0;
20 | }
21 |
22 | *,
23 | *:before,
24 | *:after {
25 | box-sizing: border-box;
26 | }
27 |
28 | img {
29 | max-width: 100%;
30 | height: auto;
31 | }
32 |
33 | h1 {
34 | font-size: 36px;
35 | }
36 |
37 | h2 {
38 | font-size: 30px;
39 | }
40 |
41 | h3 {
42 | font-size: 24px;
43 | }
44 |
45 | h4 {
46 | font-size: 22px;
47 | }
48 |
49 | h5 {
50 | font-size: 18px;
51 | }
52 |
53 | h6 {
54 | font-size: 16px;
55 | }
56 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/index.css:
--------------------------------------------------------------------------------
1 | @import 'base.css';
2 | @import 'vendors/index.css';
3 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/calendar.css:
--------------------------------------------------------------------------------
1 | /* Calendar Module Style */
2 | .app-calendar,
3 | .rbc-calendar {
4 | width: 100%;
5 | }
6 |
7 | .app-calendar .rbc-calendar {
8 | display: -webkit-flex;
9 | display: -moz-flex;
10 | display: -ms-flex;
11 | display: -o-flex;
12 | display: flex;
13 | -webkit-flex-direction: column;
14 | -ms-flex-direction: column;
15 | flex-direction: column;
16 | -webkit-flex-wrap: nowrap;
17 | -ms-flex-wrap: nowrap;
18 | flex-wrap: nowrap;
19 | -webkit-flex: 1 1 auto;
20 | -ms-flex: 1 1 auto;
21 | flex: 1 1 auto;
22 | width: 100%;
23 | min-height: calc(100vh - 360px);
24 | }
25 |
26 | .app-cul-calendar .rbc-calendar {
27 | min-height: calc(100vh - 400px);
28 | }
29 |
30 | .rbc-toolbar button:active,
31 | .rbc-toolbar button.rbc-active {
32 | background-image: none;
33 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
34 | -moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
35 | -o-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
36 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
37 | }
38 |
39 | .rbc-month-row {
40 | min-height: 100px;
41 | }
42 |
43 | .rbc-event {
44 | min-height: 50px;
45 | }
46 |
47 | .rbc-event:focus {
48 | outline: none;
49 | }
50 |
51 | .cr-calendar .react-daypicker-root {
52 | border: solid 1px #e5e9ec;
53 | border-radius: 30px;
54 | padding: 30px;
55 | width: 100%;
56 | height: 100%;
57 | }
58 |
59 | .cr-calendar table {
60 | width: 100%;
61 | height: calc(100% - 30px);
62 | }
63 |
64 | .cr-calendar table td {
65 | position: relative;
66 | }
67 |
68 | .cr-calendar table th {
69 | text-transform: uppercase;
70 | font-family: var(--light);
71 | font-size: 15px;
72 | }
73 |
74 | .cr-calendar .header .month-year {
75 | font-family: var(--extra-bold);
76 | padding-top: 3px;
77 | }
78 |
79 | .cr-calendar-color .react-daypicker-root {
80 | border: 0 none;
81 | font-size: 20px;
82 | }
83 |
84 | .cr-calendar-color .header {
85 | padding: 30px;
86 | margin: -30px -30px 0;
87 | }
88 |
89 | .cr-calendar-color .header .month-year {
90 | font-size: 22px;
91 | }
92 |
93 | .cr-calendar-color table {
94 | font-family: var(--extra-bold);
95 | height: calc(100% - 60px);
96 | }
97 |
98 | .cr-calendar-color table thead th {
99 | font-family: var(--extra-bold);
100 | font-size: 20px;
101 | }
102 |
103 | @media screen and (max-width: 1919px) {
104 | .cr-calendar .react-daypicker-root {
105 | padding: 20px;
106 | }
107 |
108 | .cr-calendar .react-daypicker-root th,
109 | .cr-calendar .react-daypicker-root td {
110 | width: 30px;
111 | }
112 |
113 | .cr-calendar table th,
114 | .cr-calendar table td {
115 | font-size: 12px;
116 | }
117 |
118 | .cr-calendar-color .react-daypicker-root {
119 | font-size: 16px;
120 | }
121 |
122 | .cr-calendar-color .header {
123 | padding: 20px;
124 | margin: -20px -20px 0;
125 | }
126 |
127 | .cr-calendar-color .header .month-year {
128 | font-size: 18px;
129 | }
130 |
131 | .cr-calendar-color table thead th {
132 | font-size: 15px;
133 | }
134 |
135 | .cr-calendar-color table {
136 | height: calc(100% - 70px);
137 | }
138 | }
139 |
140 | @media screen and (max-width: 1367px) {
141 | .cr-calendar-color table thead th {
142 | font-size: 13px;
143 | }
144 | }
145 |
146 | @media screen and (max-width: 959px) {
147 | .rbc-toolbar {
148 | display: -webkit-flex;
149 | display: -moz-flex;
150 | display: -ms-flex;
151 | display: -o-flex;
152 | display: flex;
153 | -webkit-flex-direction: column;
154 | -ms-flex-direction: column;
155 | flex-direction: column;
156 | -webkit-flex-wrap: nowrap;
157 | -ms-flex-wrap: nowrap;
158 | flex-wrap: nowrap;
159 | -webkit-align-items: flex-start;
160 | align-items: flex-start;
161 | }
162 |
163 | .rbc-toolbar .rbc-toolbar-label {
164 | margin: 8px 0;
165 | }
166 |
167 | .cr-calendar table {
168 | height: auto;
169 | }
170 | }
171 |
172 | @media screen and (max-width: 599px) {
173 | .cr-calendar .react-daypicker-root {
174 | padding: 10px;
175 | }
176 |
177 | .cr-calendar .react-daypicker-root th,
178 | .cr-calendar .react-daypicker-root td {
179 | width: 20px;
180 | }
181 |
182 | .app-calendar .rbc-calendar {
183 | min-height: 400px;
184 | }
185 |
186 | .app-cul-calendar .rbc-calendar {
187 | min-height: 400px;
188 | }
189 | }
190 |
191 | @media screen and (max-width: 467px) {
192 | .rbc-toolbar {
193 | font-size: 12px;
194 | }
195 |
196 | .rbc-toolbar button {
197 | padding: 5px 10px;
198 | }
199 |
200 | .cr-calendar table th,
201 | .cr-calendar table td {
202 | font-size: 12px;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/chat-window.css:
--------------------------------------------------------------------------------
1 | /*Apps style*/
2 |
3 | .sc-launcher {
4 | cursor: pointer;
5 | z-index: 999;
6 | display: none;
7 | }
8 |
9 | .sc-header--img {
10 | width: 50px;
11 | height: 50px;
12 | padding: 0 !important;
13 | margin-right: 5px;
14 | }
15 |
16 | .sc-chat-window {
17 | bottom: 25px !important;
18 | z-index: 999;
19 | max-height: 380px !important;
20 | }
21 |
22 | .sc-header {
23 | padding: 12px 16px !important;
24 | }
25 |
26 | .sc-header--team-name {
27 | font-family: var(--extra-bold) !important;
28 | font-size: 18px;
29 | }
30 |
31 | @media (max-width: 1367px) {
32 | .sc-header--img {
33 | width: 40px;
34 | height: 40px;
35 | }
36 |
37 | .sc-header {
38 | min-height: 65px;
39 | }
40 |
41 | .sc-header--team-name {
42 | font-size: 16px;
43 | }
44 |
45 | .sc-message--text {
46 | padding: 12px 16px !important;
47 | }
48 | }
49 |
50 | @media (max-width: 599px) {
51 | .sc-chat-window {
52 | width: 300px !important;
53 | height: 80% !important;
54 | border-radius: 10px !important;
55 | right: 10px !important;
56 | bottom: 10px !important;
57 | overflow: hidden;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/ck-editor.css:
--------------------------------------------------------------------------------
1 | .document-editor {
2 | border: 1px solid var(--ck-color-base-border);
3 | border-radius: var(--ck-border-radius);
4 | /* Set vertical boundaries for the document editor. */
5 | max-height: 700px;
6 | /* This element is a flex container for easier rendering. */
7 | display: flex;
8 | flex-flow: column nowrap;
9 | }
10 |
11 | .document-editor__toolbar {
12 | /* Make sure the toolbar container is always above the editable. */
13 | z-index: 1;
14 | /* Create the illusion of the toolbar floating over the editable. */
15 | box-shadow: 0 0 5px hsla(0, 0%, 0%, 0.2);
16 | /* Use the CKEditor CSS variables to keep the UI consistent. */
17 | border-bottom: 1px solid var(--ck-color-toolbar-border);
18 | }
19 |
20 | /* Adjust the look of the toolbar inside of the container. */
21 | .document-editor__toolbar .ck-toolbar {
22 | border: 0;
23 | border-radius: 0;
24 | }
25 |
26 | /* Make the editable container look like the inside of a native word processor app. */
27 | .document-editor__editable-container {
28 | padding: calc(2 * var(--ck-spacing-large));
29 | background: var(--ck-color-base-foreground);
30 | /* Make it possible to scroll the "page" of the edited content. */
31 | overflow-y: scroll;
32 | }
33 |
34 | .document-editor__editable-container
35 | .document-editor__editable.ck-editor__editable {
36 | /* Set the dimensions of the "page". */
37 | width: 15.8cm;
38 | min-height: 21cm;
39 | /* Keep the "page" off the boundaries of the container. */
40 | padding: 1cm 2cm 2cm;
41 | border: 1px hsl(0, 0%, 82.7%) solid;
42 | border-radius: var(--ck-border-radius);
43 | background: white;
44 | /* The "page" should cast a slight shadow (3D illusion). */
45 | box-shadow: 0 0 5px hsla(0, 0%, 0%, 0.1);
46 | /* Center the "page". */
47 | margin: 0 auto;
48 | }
49 |
50 | /* Override the page's width in the "Examples" section which is wider. */
51 | .main__content-wide
52 | .document-editor__editable-container
53 | .document-editor__editable.ck-editor__editable {
54 | width: 18cm;
55 | }
56 |
57 | /* Set the default font for the "page" of the content. */
58 | .document-editor .ck-content,
59 | .document-editor .ck-heading-dropdown .ck-list .ck-button__label {
60 | font: 16px/1.6 'Helvetica Neue', Helvetica, Arial, sans-serif;
61 | }
62 |
63 | /* Adjust the headings dropdown to host some larger heading styles. */
64 | .document-editor .ck-heading-dropdown .ck-list .ck-button__label {
65 | line-height: calc(
66 | 1.7 * var(--ck-line-height-base) * var(--ck-font-size-base)
67 | );
68 | min-width: 6em;
69 | }
70 |
71 | /* Scale down all heading previews because they are way too big to be presented in the UI.
72 | Preserve the relative scale, though. */
73 | .document-editor
74 | .ck-heading-dropdown
75 | .ck-list
76 | .ck-heading_heading1
77 | .ck-button__label,
78 | .document-editor
79 | .ck-heading-dropdown
80 | .ck-list
81 | .ck-heading_heading2
82 | .ck-button__label {
83 | transform: scale(0.8);
84 | transform-origin: left;
85 | }
86 |
87 | /* Set the styles for "Heading 1". */
88 | .document-editor .ck-content h2,
89 | .document-editor .ck-heading-dropdown .ck-heading_heading1 .ck-button__label {
90 | font-size: 2.18em;
91 | font-weight: normal;
92 | }
93 |
94 | .document-editor .ck-content h2 {
95 | line-height: 1.37em;
96 | padding-top: 0.342em;
97 | margin-bottom: 0.142em;
98 | }
99 |
100 | /* Set the styles for "Heading 2". */
101 | .document-editor .ck-content h3,
102 | .document-editor .ck-heading-dropdown .ck-heading_heading2 .ck-button__label {
103 | font-size: 1.75em;
104 | font-weight: normal;
105 | color: hsl(203, 100%, 50%);
106 | }
107 |
108 | .document-editor
109 | .ck-heading-dropdown
110 | .ck-heading_heading2.ck-on
111 | .ck-button__label {
112 | color: var(--ck-color-list-button-on-text);
113 | }
114 |
115 | /* Set the styles for "Heading 2". */
116 | .document-editor .ck-content h3 {
117 | line-height: 1.86em;
118 | padding-top: 0.171em;
119 | margin-bottom: 0.357em;
120 | }
121 |
122 | /* Set the styles for "Heading 3". */
123 | .document-editor .ck-content h4,
124 | .document-editor .ck-heading-dropdown .ck-heading_heading3 .ck-button__label {
125 | font-size: 1.31em;
126 | font-weight: bold;
127 | }
128 |
129 | .document-editor .ck-content h4 {
130 | line-height: 1.24em;
131 | padding-top: 0.286em;
132 | margin-bottom: 0.952em;
133 | }
134 |
135 | /* Make the block quoted text serif with some additional spacing. */
136 | .document-editor .ck-content blockquote {
137 | font-family: var(--regular) !important;
138 | margin-left: calc(2 * var(--ck-spacing-large));
139 | margin-right: calc(2 * var(--ck-spacing-large));
140 | }
141 |
142 | /* Some table cells have a lot content and some not. Align them vertically
143 | to make reading easier. */
144 | .document-editor .ck-content table td {
145 | vertical-align: middle;
146 | }
147 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/index.css:
--------------------------------------------------------------------------------
1 | @import 'calendar.css';
2 | @import 'react-notification.css';
3 | @import 'react-table.css';
4 | @import 'ck-editor.css';
5 | @import 'maps.css';
6 | @import 'timeline.css';
7 | @import 'slider.css';
8 | @import 'chat-window.css';
9 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/maps.css:
--------------------------------------------------------------------------------
1 | /*Embed Responsive Classes*/
2 | .cr-embed-responsive {
3 | position: relative;
4 | display: block;
5 | width: 100%;
6 | padding: 0;
7 | overflow: hidden;
8 | }
9 |
10 | .cr-embed-responsive:before {
11 | display: block;
12 | content: '';
13 | }
14 |
15 | .cr-embed-responsive .cr-embed-responsive-item,
16 | .cr-embed-responsive iframe,
17 | .cr-embed-responsive embed,
18 | .cr-embed-responsive object,
19 | .cr-embed-responsive video {
20 | position: absolute;
21 | top: 0;
22 | bottom: 0;
23 | left: 0;
24 | width: 100%;
25 | height: 100%;
26 | border: 0;
27 | }
28 |
29 | .cr-embed-responsive-21by9:before {
30 | padding-top: 42.85714286%;
31 | }
32 |
33 | .cr-embed-responsive-16by9:before {
34 | padding-top: 56.25%;
35 | }
36 |
37 | .cr-embed-responsive-4by3:before {
38 | padding-top: 75%;
39 | }
40 |
41 | .cr-embed-responsive-1by1:before {
42 | padding-top: 100%;
43 | }
44 |
45 | @media screen and (max-width: 499px) {
46 | .cr-embed-responsive-21by9:before,
47 | .cr-embed-responsive-16by9:before {
48 | padding-top: 100%;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/react-notification.css:
--------------------------------------------------------------------------------
1 | .notification-custom-icon {
2 | flex-basis: 20%;
3 | position: relative;
4 | padding: 8px 8px 8px 12px;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | .notification-custom-icon .fa {
11 | color: #fff;
12 | font-size: 28px;
13 | }
14 |
15 | .notification-custom-content {
16 | flex-basis: 80%;
17 | padding: 12px 12px 12px 8px;
18 | display: inline-block;
19 | }
20 |
21 | .notification-custom-success {
22 | width: 100%;
23 | display: flex;
24 | background-color: #28a745;
25 | }
26 |
27 | .notification-custom-success .notification-custom-icon {
28 | border-left: 8px solid #19692c;
29 | }
30 |
31 | .notification-custom-default {
32 | width: 100%;
33 | display: flex;
34 | background-color: #007bff;
35 | }
36 |
37 | .notification-custom-default .notification-custom-icon {
38 | border-left: 8px solid #0056b3;
39 | }
40 |
41 | .notification-custom-danger {
42 | width: 100%;
43 | display: flex;
44 | background-color: #dc3545;
45 | }
46 |
47 | .notification-custom-danger .notification-custom-icon {
48 | border-left: 8px solid #a71d2a;
49 | }
50 |
51 | .notification-custom-info {
52 | width: 100%;
53 | display: flex;
54 | background-color: #17a2b8;
55 | }
56 |
57 | .notification-custom-info .notification-custom-icon {
58 | border-left: 8px solid #0f6674;
59 | }
60 |
61 | .notification-custom-warning {
62 | width: 100%;
63 | display: flex;
64 | background-color: #eab000;
65 | }
66 |
67 | .notification-custom-warning .notification-custom-icon {
68 | border-left: 8px solid #9e7600;
69 | }
70 |
71 | .custom-image-content {
72 | background-color: white;
73 | padding: 10px;
74 | }
75 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/react-table.css:
--------------------------------------------------------------------------------
1 | .ReactTable {
2 | width: 100%;
3 | }
4 |
5 | .ReactTable .rt-th,
6 | .ReactTable .rt-td {
7 | padding: 12px 15px;
8 | }
9 |
10 | .ReactTable .rt-th.rt-expandable,
11 | .ReactTable .rt-td.rt-expandable {
12 | padding: 0;
13 | }
14 |
15 | .ReactTable .rt-thead .rt-th,
16 | .ReactTable .rt-thead .rt-td {
17 | padding: 12px 15px;
18 | }
19 |
20 | .ReactTable .rt-thead .rt-th.rt-expandable,
21 | .ReactTable .rt-thead .rt-td.rt-expandable {
22 | padding: 0;
23 | }
24 |
25 | .ReactTable .rt-td > div {
26 | padding: 3px 10px;
27 | }
28 |
29 | @media screen and (max-width: 399px) {
30 | .ReactTable .-pagination {
31 | flex-direction: column;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/slider.css:
--------------------------------------------------------------------------------
1 | /*Slider Style*/
2 | .slick-slider {
3 | padding-bottom: 20px;
4 | }
5 |
6 | .slick-slide img {
7 | display: inline-block;
8 | height: 190px;
9 | object-fit: contain;
10 | }
11 |
12 | .slick-dots {
13 | bottom: 0;
14 | }
15 |
16 | .slick-dots li {
17 | width: 12px;
18 | height: 12px;
19 | margin-right: 2px;
20 | margin-left: 2px;
21 | }
22 |
23 | .slick-dots li button {
24 | width: 12px;
25 | height: 12px;
26 | padding: 0;
27 | }
28 |
29 | .slick-dots li button:before {
30 | width: 12px;
31 | height: 12px;
32 | line-height: 12px;
33 | font-size: 10px;
34 | color: #be8658;
35 | opacity: 0.55;
36 | }
37 |
38 | .slick-dots li.slick-active button::before {
39 | color: #825a44;
40 | opacity: 0.75;
41 | }
42 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/shared/styles/vendors/timeline.css:
--------------------------------------------------------------------------------
1 | .rt-timeline-container {
2 | padding: 1em;
3 | justify-content: center;
4 | align-items: center;
5 | display: flex;
6 | }
7 |
8 | .rt-timeline {
9 | max-width: 1600px;
10 | width: 100%;
11 | padding: 0;
12 | list-style-type: none;
13 | position: relative;
14 | }
15 |
16 | .rt-timeline:after {
17 | left: calc(50% - 0.5px);
18 | top: 1px;
19 | z-index: 1;
20 | width: 1px;
21 | height: 100%;
22 | content: '';
23 | position: absolute;
24 | background-color: #d5d6d8;
25 | }
26 |
27 | .rt-label-container {
28 | clear: both;
29 | margin: 1em auto 1em auto;
30 | display: flex;
31 | justify-content: center;
32 | }
33 |
34 | .rt-label-container:first-of-type {
35 | margin-top: 0 !important;
36 | }
37 |
38 | .rt-label-container:last-of-type {
39 | margin-bottom: 0;
40 | }
41 |
42 | .rt-label {
43 | display: flex;
44 | justify-content: center;
45 | align-items: center;
46 | color: white;
47 | border-radius: 50%;
48 | font-size: 24px;
49 | font-family: var(--medium) !important;
50 | width: 3em;
51 | height: 3em;
52 | position: relative;
53 | z-index: 3;
54 | }
55 |
56 | .rt-clear {
57 | clear: both;
58 | }
59 |
60 | .rt-backing {
61 | padding: 20px;
62 | }
63 |
64 | .rt-event {
65 | position: relative;
66 | float: right;
67 | clear: right;
68 | width: 50%;
69 | margin-top: 2em;
70 | margin-left: 0;
71 | margin-right: 0;
72 | padding-left: 65px;
73 | list-style-type: none;
74 | display: block;
75 | min-height: 700px;
76 | }
77 |
78 | .rt-event .rt-dot {
79 | position: absolute;
80 | margin-top: 20px;
81 | left: -101px;
82 | right: auto;
83 | fill: currentcolor;
84 | font-size: 14px;
85 | width: 30px;
86 | height: 30px;
87 | border-radius: 50%;
88 | z-index: 100;
89 | }
90 |
91 | .rt-event .rt-svg-container {
92 | position: relative;
93 | }
94 |
95 | .rt-event .rt-svg-container svg {
96 | transform: scale(-1, 1);
97 | }
98 |
99 | .rt-event .rt-svg-container .rt-arrow {
100 | z-index: 100;
101 | position: absolute;
102 | margin-top: 20px;
103 | left: -42px;
104 | right: auto;
105 | fill: currentcolor;
106 | width: 23px;
107 | }
108 |
109 | .rt-event:nth-of-type(2n) {
110 | float: left;
111 | clear: left;
112 | text-align: right;
113 | padding-left: 0;
114 | padding-right: 65px;
115 | }
116 |
117 | .rt-event:nth-of-type(2n) svg {
118 | transform: scale(1, 1);
119 | }
120 |
121 | .rt-event:nth-of-type(2n) .rt-arrow {
122 | left: auto;
123 | right: -42px;
124 | }
125 |
126 | .rt-event:nth-of-type(2n) .rt-dot {
127 | left: auto;
128 | right: -101px;
129 | }
130 |
131 | .rt-timeline li:nth-child(3) {
132 | margin-top: 400px;
133 | }
134 |
135 | .rt-title {
136 | margin-bottom: 8px;
137 | font-size: 18px;
138 | font-family: var(--medium) !important;
139 | }
140 |
141 | .rt-date {
142 | margin: 0;
143 | color: #a8a8a8;
144 | font-family: var(--medium) !important;
145 | font-size: 14px;
146 | }
147 |
148 | .rt-header-container {
149 | margin-bottom: 30px;
150 | }
151 |
152 | .rt-image-container {
153 | margin-bottom: 15px;
154 | }
155 |
156 | .rt-image {
157 | vertical-align: top;
158 | display: flex;
159 | align-items: center;
160 | justify-content: center;
161 | margin: 0;
162 | width: 100%;
163 | height: auto;
164 | max-width: 100vw;
165 | max-height: 250px;
166 | object-fit: cover;
167 | }
168 |
169 | .rt-footer-container {
170 | margin: 15px -20px -20px;
171 | padding: 20px;
172 | text-align: center;
173 | }
174 |
175 | .rt-btn {
176 | text-align: center;
177 | font-size: 16px;
178 | font-family: var(--medium) !important;
179 | cursor: pointer;
180 | white-space: nowrap;
181 | overflow: hidden;
182 | text-overflow: ellipsis;
183 | text-decoration: none;
184 | text-transform: capitalize;
185 | }
186 |
187 | .rt-text-container {
188 | max-height: 200px;
189 | font-weight: lighter;
190 | overflow: hidden;
191 | text-overflow: ellipsis;
192 | content: '';
193 | position: relative;
194 | text-align: left;
195 | }
196 |
197 | .rt-text-container:before {
198 | content: '';
199 | font-weight: lighter;
200 | width: 100%;
201 | height: 80px;
202 | position: absolute;
203 | left: 0;
204 | top: 120px;
205 | background: transparent;
206 | background: -webkit-linear-gradient(rgba(255, 255, 255, 0), #f0f0f0);
207 | /* For Safari 5.1 to 6.0 */
208 | background: -o-linear-gradient(rgba(255, 255, 255, 0), #f0f0f0);
209 | /* For Opera 11.1 to 12.0 */
210 | background: -moz-linear-gradient(rgba(255, 255, 255, 0), #f0f0f0);
211 | /* For Firefox 3.6 to 15 */
212 | background: linear-gradient(rgba(255, 255, 255, 0), #f0f0f0);
213 | /* Standard syntax */
214 | }
215 |
216 | @media all and (max-width: 1919px) {
217 | .rt-label {
218 | font-size: 20px;
219 | }
220 |
221 | .rt-event {
222 | padding-left: 45px;
223 | }
224 |
225 | .rt-event:nth-of-type(2n) {
226 | padding-right: 45px;
227 | }
228 |
229 | .rt-event .rt-dot {
230 | left: -81px;
231 | }
232 |
233 | .rt-event:nth-of-type(2n) .rt-dot {
234 | right: -81px;
235 | }
236 |
237 | .rt-header-container {
238 | margin-bottom: 20px;
239 | }
240 |
241 | .rt-title {
242 | font-size: 16px;
243 | }
244 | }
245 |
246 | @media all and (max-width: 959px) {
247 | .rt-timeline-container {
248 | padding: 0;
249 | }
250 |
251 | .rt-event {
252 | width: 100%;
253 | padding-left: 90px;
254 | min-height: 100px;
255 | margin-bottom: 30px;
256 | margin-top: 0;
257 | }
258 |
259 | .rt-event:nth-of-type(2n) {
260 | padding-right: 0;
261 | padding-left: 90px;
262 | text-align: left;
263 | }
264 |
265 | .rt-backing {
266 | padding: 20px;
267 | }
268 |
269 | .rt-event .rt-svg-container .rt-arrow {
270 | left: -43px;
271 | }
272 |
273 | .rt-event:nth-of-type(2n) .rt-arrow {
274 | left: -43px;
275 | right: auto;
276 | -ms-transform: rotate(180deg); /* IE 9 */
277 | -webkit-transform: rotate(180deg); /* Safari prior 9.0 */
278 | transform: rotate(180deg); /* Standard syntax */
279 | }
280 |
281 | .rt-event .rt-dot {
282 | left: -95px;
283 | }
284 |
285 | .rt-event:nth-of-type(2n) .rt-dot {
286 | right: auto;
287 | left: -95px;
288 | }
289 |
290 | .rt-label-container {
291 | justify-content: flex-start;
292 | }
293 |
294 | .rt-timeline:after {
295 | left: 30px;
296 | }
297 |
298 | .rt-timeline li:nth-child(3) {
299 | margin-top: 0;
300 | }
301 |
302 | .rt-footer-container {
303 | margin: 15px -20px -20px;
304 | padding: 15px 20px;
305 | }
306 |
307 | .rt-label-container:last-of-type {
308 | margin-top: 0;
309 | }
310 | }
311 |
312 | @media all and (max-width: 499px) {
313 | .rt-timeline-container {
314 | padding: 12px;
315 | min-width: 400px;
316 | margin-left: -16px;
317 | }
318 |
319 | .rt-label {
320 | font-size: 16px;
321 | }
322 |
323 | .rt-timeline::after {
324 | left: 25px;
325 | }
326 |
327 | .rt-event,
328 | .rt-event:nth-of-type(2n) {
329 | padding-left: 70px;
330 | }
331 |
332 | .rt-event .rt-dot,
333 | .rt-event:nth-of-type(2n) .rt-dot {
334 | left: -80px;
335 | }
336 |
337 | .rt-event .rt-svg-container .rt-arrow,
338 | .rt-event:nth-of-type(2n) .rt-arrow {
339 | left: -40px;
340 | }
341 |
342 | .rt-title {
343 | font-size: 18px;
344 | }
345 |
346 | .rt-header-container {
347 | margin-bottom: 10px;
348 | }
349 | }
350 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/uploads/camera-screen.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Webcam from "react-webcam";
3 |
4 | const WebcamComponent = () => ;
5 |
6 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/screens/uploads/upload-floating-button.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Fab from '@mui/material/Fab';
3 | import AddIcon from '@mui/icons-material/Add';
4 | import Zoom from '@mui/material/Zoom';
5 | import Box from '@mui/material/Box';
6 | import Typography from '@mui/material/Typography';
7 | import {Link} from "react-router-dom";
8 |
9 | const fabStyle = {
10 | position: 'fixed',
11 | right: 20,
12 | bottom: 20,
13 | };
14 |
15 | const actionButtonStyle = (index) => ({
16 | position: 'fixed',
17 | right: 20,
18 | bottom: 80 + index * 60, // Spacing between action buttons
19 | });
20 |
21 | export default function FloatingActionButtonZoom() {
22 | const [expanded, setExpanded] = React.useState(false);
23 |
24 | const handleExpandClick = () => {
25 | setExpanded(!expanded);
26 | };
27 |
28 | const actionButtons = [
29 | {icon: , name: 'Alt Text', linkTo: '/alttext'}
30 | ];
31 |
32 | return (
33 |
34 |
35 |
36 |
37 | {expanded && actionButtons.map((button, index) => (
38 |
39 |
40 | {/* Adjust this `sx` prop to position the label to the left or right as desired */}
41 |
48 | {button.name}
49 |
50 |
51 | setExpanded(false)}>
52 | {button.icon}
53 |
54 |
55 |
56 |
57 | ))}
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/services/constants.js:
--------------------------------------------------------------------------------
1 | const prod = {
2 | uploaderService: "https://demos.budilov.com/generic/api/v1",
3 | genericService: "https://demos.budilov.com/generic/api/v1",
4 | logLevel: 'INFO',
5 | };
6 |
7 | const dev = {
8 | uploaderService: "http://localhost:8080",
9 | genericService: "http://localhost:8080",
10 | logLevel: 'INFO',
11 | };
12 |
13 | export const config = process.env.NODE_ENV === "development" ? prod : prod;
14 |
15 | // export const config = process.env.NODE_ENV === "development" ? prod : prod;
16 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/services/dao/uploader-service.js:
--------------------------------------------------------------------------------
1 | import {Logger} from "@aws-amplify/core";
2 | import {config} from "../constants";
3 | import {AuthService} from "../../screens/auth/service/auth-service";
4 |
5 | const logger = new Logger("UploadService");
6 |
7 | export class UploadService {
8 |
9 | getUploads = async (offset, limit) => {
10 |
11 | let queryParams = "";
12 | let response = null;
13 |
14 | if (offset)
15 | queryParams = "?offset=" + offset;
16 | // if (limit)
17 | // queryParams = "limit=" + limit;
18 | let endpoint = config.uploaderService + "/uploads"
19 |
20 | logger.debug("getUploads() URL: " + endpoint);
21 | let idJwtToken = await AuthService.getIdTokenOfCurrentUser();
22 | if (idJwtToken)
23 | return await fetch(endpoint, {
24 | crossDomain: true,
25 | headers: {
26 | 'Authorization': "Bearer " + idJwtToken,
27 | },
28 | mode: "cors"
29 | }).then(response =>
30 | response.json())
31 | .then(json => {
32 | logger.debug('got uploads', JSON.stringify(json)) // access json.body here
33 | return json;
34 | }).catch(function (ex) {
35 | logger.error("Couldn't get files", ex)
36 | })
37 | logger.debug("Final response: " + JSON.stringify(response));
38 | return response;
39 | }
40 |
41 | uploadFile = async (file, type) => {
42 | if (type === null)
43 | type = "alttext";
44 |
45 | logger.debug("type: " + type);
46 |
47 | logger.debug("enter uploadFile")
48 | let formData = new FormData();
49 | formData.append('file', file);
50 |
51 | let response = null;
52 |
53 | let endpoint = config.uploaderService + "/" + type + "/upload"
54 |
55 | let idJwtToken = await AuthService.getIdTokenOfCurrentUser();
56 | if (idJwtToken)
57 | try {
58 | let rawResponse = await fetch(endpoint, {
59 | method: 'POST',
60 | headers: {
61 | 'Authorization': "Bearer " + idJwtToken,
62 | "Content-Length": file.size
63 | },
64 | body: formData
65 | }).then(response => response.json())
66 | .then(json => {
67 | logger.debug('parsed json: ' + JSON.stringify(json)) // access json.body here
68 | response = json;
69 | });
70 | // logger.debug("rawResponse : " + JSON.stringify(rawResponse));
71 |
72 | // if (rawResponse)
73 | // response = rawResponse.json();
74 |
75 | } catch (e) {
76 | logger.error("couldn't upload: " + e);
77 | }
78 | else {
79 | response = {"error": true, "errorMessage": "You're not authenticated"};
80 | }
81 |
82 | logger.debug("response : " + JSON.stringify(response));
83 | return response;
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/services/http-service.js:
--------------------------------------------------------------------------------
1 | import {Logger} from "@aws-amplify/core";
2 | import {Auth} from "aws-amplify";
3 |
4 | const log = new Logger("HttpService");
5 |
6 | export class HttpService {
7 |
8 | constructor(chatUrl) {
9 | this.url = chatUrl;
10 | }
11 |
12 | /**
13 | * Returns the user if he's logged in, returns null if he's not.
14 | *
15 | *
16 | * @returns {Promise}
17 | */
18 | getIdTokenOfCurrentUser = async () => {
19 | let session = await Auth.currentSession();
20 |
21 | if (session) {
22 | let token = await session.getIdToken().getJwtToken()
23 | log.debug("token: " + token)
24 | return token;
25 | } else
26 | return null;
27 | }
28 |
29 | makeCall = async (endpoint, method, queryParameterMap, headersMapParam, body, addAuthToken) => {
30 |
31 | let response = null;
32 | let headersMap = {};
33 |
34 | if (headersMapParam)
35 | headersMap = headersMapParam;
36 |
37 | headersMap["Content-Type"] = "application/json";
38 |
39 | // Add auth token if needed
40 | if (addAuthToken) {
41 | log.debug("addAuthToken: " + addAuthToken);
42 |
43 | let session = await Auth.currentSession();
44 |
45 | let jwtToken = session.getIdToken().getJwtToken();
46 |
47 | log.debug("jwtToken: " + jwtToken);
48 | headersMap["authorization"] = jwtToken;
49 | }
50 |
51 | let queryString = "";
52 |
53 | if (queryParameterMap) {
54 | queryString = "?";
55 |
56 | for (let key in queryParameterMap) {
57 | if (queryString !== "?")
58 | queryString = queryString + "&"
59 | queryString = queryString + key + "=" + queryParameterMap[key];
60 | }
61 | }
62 |
63 | log.debug("headersMap: " + JSON.stringify(headersMap))
64 | let requestData = {
65 | method: method,
66 | headers: headersMap
67 | };
68 |
69 | if (body)
70 | requestData['body'] = body;
71 |
72 | try {
73 | response = (await fetch(endpoint + queryString, requestData)).json();
74 |
75 | log.debug("Response: " + JSON.stringify(response));
76 | } catch (e) {
77 | log.error("couldn't fetch: " + e);
78 | }
79 |
80 | return response;
81 |
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/services/local-storage.js:
--------------------------------------------------------------------------------
1 | import {Logger} from "@aws-amplify/core";
2 |
3 | const logger = new Logger("LocalStorage");
4 |
5 | export class LocalStorage {
6 |
7 |
8 | static removeStorage(key) {
9 | try {
10 | localStorage.removeItem(key);
11 | localStorage.removeItem(key + '_expiresIn');
12 | } catch (e) {
13 | console.log('removeStorage: Error removing key [' + key + '] from localStorage: ' + JSON.stringify(e));
14 | return false;
15 | }
16 | return true;
17 | }
18 |
19 | /* getItem: retrieves a key from localStorage previously set with setItem().
20 | params:
21 | key : localStorage key
22 | returns:
23 | : value of localStorage key
24 | null : in case of expired key or failure
25 | */
26 | static getItem(key) {
27 |
28 | var now = Date.now(); //epoch time, lets deal only with integer
29 | // set expiration for storage
30 | var expiresIn = localStorage.getItem(key + '_expiresIn');
31 | if (expiresIn === undefined || expiresIn === null) {
32 | logger.debug("Couldn't find " + key)
33 |
34 | expiresIn = 0;
35 | }
36 |
37 | if (expiresIn < now) {// Expired
38 | LocalStorage.removeStorage(key);
39 | logger.debug(key + " expired")
40 | return null;
41 | } else {
42 | logger.debug("Searching for " + key)
43 |
44 | try {
45 | let value = localStorage.getItem(key);
46 | return value;
47 | } catch (e) {
48 | console.log('getItem: Error reading key [' + key + '] from localStorage: ' + JSON.stringify(e));
49 | return null;
50 | }
51 | }
52 | }
53 |
54 | /* setItem: writes a key into localStorage setting a expire time
55 | params:
56 | key : localStorage key
57 | value : localStorage value
58 | expires : number of seconds from now to expire the key
59 | returns:
60 | : telling if operation succeeded
61 | */
62 | static setItem(key, value, expires) {
63 |
64 | if (expires === undefined || expires === null) {
65 | expires = (24 * 60 * 60); // default: seconds for 1 day
66 | } else {
67 | expires = Math.abs(expires); //make sure it's positive
68 | }
69 |
70 | var now = Date.now(); //millisecs since epoch time, lets deal only with integer
71 | var schedule = now + expires * 1000;
72 | try {
73 | localStorage.setItem(key, value);
74 | localStorage.setItem(key + '_expiresIn', schedule);
75 | } catch (e) {
76 | console.log('setItem: Error setting key [' + key + '] in localStorage: ' + JSON.stringify(e));
77 | return false;
78 | }
79 | return true;
80 | }
81 |
82 | /* setItem: writes a key into localStorage setting a expire time
83 | params:
84 | key : localStorage key
85 | value : localStorage value
86 | expires : number of seconds from now to expire the key
87 | returns:
88 | : telling if operation succeeded
89 | */
90 | static setObject(key, value, expires) {
91 |
92 | LocalStorage.setItem(key, JSON.stringify(value), expires);
93 | }
94 |
95 | static getObject(key) {
96 | let object = LocalStorage.getItem(key);
97 |
98 | if (object)
99 | return JSON.parse(object);
100 | else
101 | return null;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/reactjs-webapp/src/shared/styles/animations.css:
--------------------------------------------------------------------------------
1 | /* Smooth transitions */
2 | .smooth-transition {
3 | transition: all 0.3s ease-in-out;
4 | }
5 |
6 | /* Button hover effect */
7 | .btn {
8 | position: relative;
9 | overflow: hidden;
10 | transition: background-color 0.3s ease, transform 0.2s ease;
11 | }
12 |
13 | .btn:hover {
14 | transform: translateY(-2px);
15 | }
16 |
17 | .btn:active {
18 | transform: translateY(0);
19 | }
20 |
21 | /* Fade in animation */
22 | .fade-in {
23 | animation: fadeIn 0.5s ease-in;
24 | }
25 |
26 | @keyframes fadeIn {
27 | from {
28 | opacity: 0;
29 | transform: translateY(10px);
30 | }
31 | to {
32 | opacity: 1;
33 | transform: translateY(0);
34 | }
35 | }
36 |
37 | /* Smooth scroll behavior */
38 | html {
39 | scroll-behavior: smooth;
40 | }
41 |
42 | /* Mobile responsive grid */
43 | .grid-container {
44 | display: grid;
45 | grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
46 | gap: 1rem;
47 | padding: 1rem;
48 | }
49 |
50 | /* Collapsible sidebar for mobile */
51 | @media (max-width: 768px) {
52 | .sidebar {
53 | position: fixed;
54 | left: -250px;
55 | transition: left 0.3s ease-in-out;
56 | }
57 |
58 | .sidebar.open {
59 | left: 0;
60 | }
61 |
62 | .main-content {
63 | margin-left: 0;
64 | width: 100%;
65 | }
66 | }
--------------------------------------------------------------------------------
/reactjs-webapp/src/shared/styles/index.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
2 | @import './animations.css';
--------------------------------------------------------------------------------
/reactjs-webapp/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: {
8 | port: 3000,
9 | open: true
10 | },
11 | build: {
12 | outDir: 'build',
13 | sourcemap: true
14 | }
15 | })
--------------------------------------------------------------------------------