├── .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 | 70 | 71 | {loggedInUser && loggedInUser.attributes && ( 72 | {loggedInUser.attributes.email} 73 | )} 74 | 75 | 76 | {/**/} 77 | {/* */} 78 | {/* */} 79 | {/* File Uploads*/} 80 | {/* */} 81 | {/**/} 82 | 83 | 84 | 85 | Settings 86 | 87 | 88 | 89 | 90 | {loggedIn ? ( 91 | 92 | 93 | Logout 94 | 95 | ) : ( 96 | 97 | 98 | 99 | Login 100 | 101 | 102 | )} 103 | 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 | Logo 110 | 111 | 112 | 113 | {/**/} 114 | {/* */} 115 | {/* }*/} 117 | {/* className={classes.searchButton}*/} 118 | {/* />*/} 119 | {/* */} 120 | {/**/} 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 | 2 | 3 | 4 | 5 | 7 | 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 | {"image"} 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 |
181 | 182 | setEmail(e.target.value)} 193 | /> 194 | 195 | 196 | 204 | 205 | 206 | 207 | Try signing in again 208 | 209 | 210 | 211 |
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 | crema-logo 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 | crema-logo 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 | 141 | My account 142 | { 144 | if (user && authType === AuthType.AWS_COGNITO) { 145 | dispatch(onCognitoUserSignOut()); 146 | } else if (user && authType === AuthType.FIREBASE) { 147 | dispatch(onSignOutFirebaseUser()); 148 | } else if (user && authType === AuthType.AUTH0) { 149 | dispatch(onSignOutAuth0User()); 150 | } else if (user && authType === AuthType.JWT_AUTH) { 151 | dispatch(onJWTAuthSignout()); 152 | } 153 | }}> 154 | Logout 155 | 156 | 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 | 133 | My account 134 | { 136 | if (user && authType === AuthType.AWS_COGNITO) { 137 | dispatch(onCognitoUserSignOut()); 138 | } else if (user && authType === AuthType.FIREBASE) { 139 | dispatch(onSignOutFirebaseUser()); 140 | } else if (user && authType === AuthType.AUTH0) { 141 | dispatch(onSignOutAuth0User()); 142 | } else if (user && authType === AuthType.JWT_AUTH) { 143 | dispatch(onJWTAuthSignout()); 144 | } 145 | }}> 146 | Logout 147 | 148 | 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 | }) --------------------------------------------------------------------------------