diff --git a/amplify/backend/auth/hpiotwebapp78e5977f/hpiotwebapp78e5977f-cloudformation-template.yml b/amplify/backend/auth/hpiotwebapp78e5977f/hpiotwebapp78e5977f-cloudformation-template.yml new file mode 100644 index 0000000..ca15766 --- /dev/null +++ b/amplify/backend/auth/hpiotwebapp78e5977f/hpiotwebapp78e5977f-cloudformation-template.yml @@ -0,0 +1,366 @@ +AWSTemplateFormatVersion: 2010-09-09 + +Parameters: + env: + Type: String + authRoleArn: + Type: String + unauthRoleArn: + Type: String + + + + + identityPoolName: + Type: String + + allowUnauthenticatedIdentities: + Type: String + + resourceNameTruncated: + Type: String + + userPoolName: + Type: String + + autoVerifiedAttributes: + Type: CommaDelimitedList + + mfaConfiguration: + Type: String + + mfaTypes: + Type: CommaDelimitedList + + smsAuthenticationMessage: + Type: String + + smsVerificationMessage: + Type: String + + emailVerificationSubject: + Type: String + + emailVerificationMessage: + Type: String + + defaultPasswordPolicy: + Type: String + + passwordPolicyMinLength: + Type: Number + + passwordPolicyCharacters: + Type: CommaDelimitedList + + requiredAttributes: + Type: CommaDelimitedList + + userpoolClientGenerateSecret: + Type: String + + userpoolClientRefreshTokenValidity: + Type: Number + + userpoolClientWriteAttributes: + Type: CommaDelimitedList + + userpoolClientReadAttributes: + Type: CommaDelimitedList + + userpoolClientLambdaRole: + Type: String + + userpoolClientSetAttributes: + Type: String + + resourceName: + Type: String + + authSelections: + Type: String + + useDefault: + Type: String + + usernameAttributes: + Type: CommaDelimitedList + + dependsOn: + Type: CommaDelimitedList + +Conditions: + ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ] + +Resources: + + + # BEGIN SNS ROLE RESOURCE + SNSRole: + # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process + Type: AWS::IAM::Role + Properties: + RoleName: !If [ShouldNotCreateEnvResources, 'hpiotw78e5977f_sns-role', !Join ['',['hpiotw78e5977f_sns-role', '-', !Ref env]]] + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: "" + Effect: "Allow" + Principal: + Service: "cognito-idp.amazonaws.com" + Action: + - "sts:AssumeRole" + Condition: + StringEquals: + sts:ExternalId: hpiotw78e5977f_role_external_id + Policies: + - + PolicyName: hpiotw78e5977f-sns-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Action: + - "sns:Publish" + Resource: "*" + # BEGIN USER POOL RESOURCES + UserPool: + # Created upon user selection + # Depends on SNS Role for Arn if MFA is enabled + Type: AWS::Cognito::UserPool + UpdateReplacePolicy: Retain + Properties: + UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]] + + Schema: + + - + Name: email + Required: true + Mutable: true + + + + + AutoVerifiedAttributes: !Ref autoVerifiedAttributes + + + EmailVerificationMessage: !Ref emailVerificationMessage + EmailVerificationSubject: !Ref emailVerificationSubject + + Policies: + PasswordPolicy: + MinimumLength: !Ref passwordPolicyMinLength + RequireLowercase: false + RequireNumbers: false + RequireSymbols: false + RequireUppercase: false + + UsernameAttributes: !Ref usernameAttributes + + MfaConfiguration: !Ref mfaConfiguration + SmsVerificationMessage: !Ref smsVerificationMessage + SmsConfiguration: + SnsCallerArn: !GetAtt SNSRole.Arn + ExternalId: hpiotw78e5977f_role_external_id + + + UserPoolClientWeb: + # Created provide application access to user pool + # Depends on UserPool for ID reference + Type: "AWS::Cognito::UserPoolClient" + Properties: + ClientName: hpiotw78e5977f_app_clientWeb + + RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity + UserPoolId: !Ref UserPool + DependsOn: UserPool + UserPoolClient: + # Created provide application access to user pool + # Depends on UserPool for ID reference + Type: "AWS::Cognito::UserPoolClient" + Properties: + ClientName: hpiotw78e5977f_app_client + + GenerateSecret: !Ref userpoolClientGenerateSecret + RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity + UserPoolId: !Ref UserPool + DependsOn: UserPool + # BEGIN USER POOL LAMBDA RESOURCES + UserPoolClientRole: + # Created to execute Lambda which gets userpool app client config values + Type: 'AWS::IAM::Role' + Properties: + RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + DependsOn: UserPoolClient + UserPoolClientLambda: + # Lambda which gets userpool app client config values + # Depends on UserPool for id + # Depends on UserPoolClientRole for role ARN + Type: 'AWS::Lambda::Function' + Properties: + Code: + ZipFile: !Join + - |+ + - - 'const response = require(''cfn-response'');' + - 'const aws = require(''aws-sdk'');' + - 'const identity = new aws.CognitoIdentityServiceProvider();' + - 'exports.handler = (event, context, callback) => {' + - ' if (event.RequestType == ''Delete'') { ' + - ' response.send(event, context, response.SUCCESS, {})' + - ' }' + - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {' + - ' const params = {' + - ' ClientId: event.ResourceProperties.clientId,' + - ' UserPoolId: event.ResourceProperties.userpoolId' + - ' };' + - ' identity.describeUserPoolClient(params).promise()' + - ' .then((res) => {' + - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});' + - ' })' + - ' .catch((err) => {' + - ' response.send(event, context, response.FAILED, {err});' + - ' });' + - ' }' + - '};' + Handler: index.handler + Runtime: nodejs8.10 + Timeout: '300' + Role: !GetAtt + - UserPoolClientRole + - Arn + DependsOn: UserPoolClientRole + UserPoolClientLambdaPolicy: + # Sets userpool policy for the role that executes the Userpool Client Lambda + # Depends on UserPool for Arn + # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: hpiotw78e5977f_userpoolclient_lambda_iam_policy + Roles: + - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'cognito-idp:DescribeUserPoolClient' + Resource: !GetAtt UserPool.Arn + DependsOn: UserPoolClientLambda + UserPoolClientLogPolicy: + # Sets log policy for the role that executes the Userpool Client Lambda + # Depends on UserPool for Arn + # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: hpiotw78e5977f_userpoolclient_lambda_log_policy + Roles: + - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]] + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + Resource: !Sub + - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:* + - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda} + DependsOn: UserPoolClientLambdaPolicy + UserPoolClientInputs: + # Values passed to Userpool client Lambda + # Depends on UserPool for Id + # Depends on UserPoolClient for Id + # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing + Type: 'Custom::LambdaCallout' + Properties: + ServiceToken: !GetAtt UserPoolClientLambda.Arn + clientId: !Ref UserPoolClient + userpoolId: !Ref UserPool + DependsOn: UserPoolClientLogPolicy + + + + + + + + # BEGIN IDENTITY POOL RESOURCES + + + IdentityPool: + # Always created + Type: AWS::Cognito::IdentityPool + Properties: + IdentityPoolName: !If [ShouldNotCreateEnvResources, 'hpiotwebapp78e5977f_identitypool_78e5977f', !Join ['',['hpiotwebapp78e5977f_identitypool_78e5977f', '__', !Ref env]]] + + CognitoIdentityProviders: + - ClientId: !Ref UserPoolClient + ProviderName: !Sub + - cognito-idp.${region}.amazonaws.com/${client} + - { region: !Ref "AWS::Region", client: !Ref UserPool} + - ClientId: !Ref UserPoolClientWeb + ProviderName: !Sub + - cognito-idp.${region}.amazonaws.com/${client} + - { region: !Ref "AWS::Region", client: !Ref UserPool} + + AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities + + + DependsOn: UserPoolClientInputs + + + IdentityPoolRoleMap: + # Created to map Auth and Unauth roles to the identity pool + # Depends on Identity Pool for ID ref + Type: AWS::Cognito::IdentityPoolRoleAttachment + Properties: + IdentityPoolId: !Ref IdentityPool + Roles: + unauthenticated: !Ref unauthRoleArn + authenticated: !Ref authRoleArn + DependsOn: IdentityPool + + +Outputs : + + IdentityPoolId: + Value: !Ref 'IdentityPool' + Description: Id for the identity pool + IdentityPoolName: + Value: !GetAtt IdentityPool.Name + + + + + UserPoolId: + Value: !Ref 'UserPool' + Description: Id for the user pool + UserPoolName: + Value: !Ref userPoolName + AppClientIDWeb: + Value: !Ref 'UserPoolClientWeb' + Description: The user pool app client id for web + AppClientID: + Value: !Ref 'UserPoolClient' + Description: The user pool app client id + AppClientSecret: + Value: !GetAtt UserPoolClientInputs.appSecret + + + + + + + \ No newline at end of file diff --git a/amplify/backend/auth/hpiotwebapp78e5977f/parameters.json b/amplify/backend/auth/hpiotwebapp78e5977f/parameters.json new file mode 100644 index 0000000..4f56103 --- /dev/null +++ b/amplify/backend/auth/hpiotwebapp78e5977f/parameters.json @@ -0,0 +1,52 @@ +{ + "identityPoolName": "hpiotwebapp78e5977f_identitypool_78e5977f", + "allowUnauthenticatedIdentities": false, + "resourceNameTruncated": "hpiotw78e5977f", + "userPoolName": "hpiotwebapp78e5977f_userpool_78e5977f", + "autoVerifiedAttributes": [ + "email" + ], + "mfaConfiguration": "OFF", + "mfaTypes": [ + "SMS Text Message" + ], + "smsAuthenticationMessage": "Your authentication code is {####}", + "smsVerificationMessage": "Your verification code is {####}", + "emailVerificationSubject": "Your verification code", + "emailVerificationMessage": "Your verification code is {####}", + "defaultPasswordPolicy": false, + "passwordPolicyMinLength": 8, + "passwordPolicyCharacters": [], + "requiredAttributes": [ + "email" + ], + "userpoolClientGenerateSecret": true, + "userpoolClientRefreshTokenValidity": 30, + "userpoolClientWriteAttributes": [ + "email" + ], + "userpoolClientReadAttributes": [ + "email" + ], + "userpoolClientLambdaRole": "hpiotw78e5977f_userpoolclient_lambda_role", + "userpoolClientSetAttributes": false, + "resourceName": "hpiotwebapp78e5977f", + "authSelections": "identityPoolAndUserPool", + "authRoleArn": { + "Fn::GetAtt": [ + "AuthRole", + "Arn" + ] + }, + "unauthRoleArn": { + "Fn::GetAtt": [ + "UnauthRole", + "Arn" + ] + }, + "useDefault": "default", + "usernameAttributes": [ + "email, phone_number" + ], + "dependsOn": [] +} \ No newline at end of file diff --git a/amplify/backend/backend-config.json b/amplify/backend/backend-config.json index 0a1e195..2a3bc71 100644 --- a/amplify/backend/backend-config.json +++ b/amplify/backend/backend-config.json @@ -4,5 +4,12 @@ "service": "S3AndCloudFront", "providerPlugin": "awscloudformation" } + }, + "auth": { + "hpiotwebapp78e5977f": { + "service": "Cognito", + "providerPlugin": "awscloudformation", + "dependsOn": [] + } } } \ No newline at end of file diff --git a/amplify/team-provider-info.json b/amplify/team-provider-info.json index e41c0fd..e27b93a 100644 --- a/amplify/team-provider-info.json +++ b/amplify/team-provider-info.json @@ -10,5 +10,22 @@ "StackName": "hpiotwebapp-dev-20191011145158", "StackId": "arn:aws:cloudformation:us-east-1:860246592755:stack/hpiotwebapp-dev-20191011145158/95829790-ec60-11e9-a33f-1207b4ca758c" } + }, + "devs": { + "awscloudformation": { + "AuthRoleName": "hpiotwebapp-devs-20191011153744-authRole", + "UnauthRoleArn": "arn:aws:iam::860246592755:role/hpiotwebapp-devs-20191011153744-unauthRole", + "AuthRoleArn": "arn:aws:iam::860246592755:role/hpiotwebapp-devs-20191011153744-authRole", + "Region": "us-east-1", + "DeploymentBucketName": "hpiotwebapp-devs-20191011153744-deployment", + "UnauthRoleName": "hpiotwebapp-devs-20191011153744-unauthRole", + "StackName": "hpiotwebapp-devs-20191011153744", + "StackId": "arn:aws:cloudformation:us-east-1:860246592755:stack/hpiotwebapp-devs-20191011153744/fa4c3b30-ec66-11e9-8123-0e639cbb91d4" + }, + "categories": { + "auth": { + "hpiotwebapp78e5977f": {} + } + } } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f6049fe..d7708fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1096,6 +1096,167 @@ "tslib": "^1.9.0" } }, + "@aws-amplify/analytics": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/analytics/-/analytics-1.3.2.tgz", + "integrity": "sha512-RzSBZ0lIHkOnP88pRc0hjh3z6A+kSfHCTyloUSXrpMgiRMi+XvaWNqD+uH0Au5x9X3xKIZb3AOrsPX9pyCTXeA==", + "requires": { + "@aws-amplify/cache": "^1.1.2", + "@aws-amplify/core": "^1.2.2", + "uuid": "^3.2.1" + } + }, + "@aws-amplify/api": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/api/-/api-1.2.2.tgz", + "integrity": "sha512-jpMf6HQO36oyZ0WVi6wIDK3EtQCjMLDSwHrLKzFGM83mtcDdC8XBIaqElWG6nKgFE8MKOmG8I9YSinTl3HpPqg==", + "requires": { + "@aws-amplify/auth": "^1.4.2", + "@aws-amplify/cache": "^1.1.2", + "@aws-amplify/core": "^1.2.2", + "axios": "^0.19.0", + "graphql": "0.13.0", + "uuid": "^3.2.1", + "zen-observable": "^0.8.6" + } + }, + "@aws-amplify/auth": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/auth/-/auth-1.4.2.tgz", + "integrity": "sha512-X82t3zxAMjRQjwZ8M4xT+96DhRrb6H8FoZZDp1Pxr4p2KSKKMkEkS2AJCenLNQ37g6CUBLlltQ++iGXDLabskw==", + "requires": { + "@aws-amplify/cache": "^1.1.2", + "@aws-amplify/core": "^1.2.2", + "amazon-cognito-identity-js": "^3.1.2", + "crypto-js": "^3.1.9-1" + } + }, + "@aws-amplify/cache": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/cache/-/cache-1.1.2.tgz", + "integrity": "sha512-ZK/IANuqQ0rrgI2soN6yR6YhwvftHsnmovMXzeQG+tHdnK2n3RxUciku6+Ey9k4usCD67ZFbXbb+V4KWqBf9Yw==", + "requires": { + "@aws-amplify/core": "^1.2.2" + } + }, + "@aws-amplify/core": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-1.2.2.tgz", + "integrity": "sha512-mczpQr+wBP6WxL0EK9yue4p+5mQVm+u8IGiyB9HN5ibMLs1t4hPcbS/D/QzjeWSfZ16oMPvZ3Sej3Z4J9zjv9Q==", + "requires": { + "aws-sdk": "2.518.0", + "url": "^0.11.0" + } + }, + "@aws-amplify/interactions": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/interactions/-/interactions-1.1.2.tgz", + "integrity": "sha512-07kHROAHk2IF87+3+yWjbagPiQLgDbas3NgqQXg0gxwd/Vt/jsUGHiJop64g4prO7NZ2ut+nx27bf0n3cxPNGA==", + "requires": { + "@aws-amplify/core": "^1.2.2" + } + }, + "@aws-amplify/predictions": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/predictions/-/predictions-1.1.2.tgz", + "integrity": "sha512-vCWy8U4cWbL47C2BxqkeX2JUfH7i9XyVPgFpVorkDKpR4yH4Rt4zyHXzNXT2rP4iFAZwJCVXa1L5QA/u3AxPJg==", + "requires": { + "@aws-amplify/core": "^1.2.2", + "@aws-amplify/storage": "^1.2.2", + "@aws-sdk/eventstream-marshaller": "^0.1.0-preview.2", + "@aws-sdk/util-utf8-node": "^0.1.0-preview.1", + "uuid": "^3.2.1" + } + }, + "@aws-amplify/pubsub": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/pubsub/-/pubsub-1.2.2.tgz", + "integrity": "sha512-nyWnf26ppu2VELwtTF6vF8UaDB/SiWau7atwu4MrGBSuLi3GBd5bhvLTPgDWV91YWA8rcc4Q17Cd18LE8guw3Q==", + "requires": { + "@aws-amplify/core": "^1.2.2", + "uuid": "^3.2.1", + "zen-observable": "^0.8.6" + } + }, + "@aws-amplify/storage": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/storage/-/storage-1.2.2.tgz", + "integrity": "sha512-ZZgLRA2yKIfvFfOi2EKBSHE3P2kSsvRu9OX/9uLiHzNfZjF/LNebhpadh3+jMfLvF7obS/AWZYK0wzhZtcgw1Q==", + "requires": { + "@aws-amplify/core": "^1.2.2" + } + }, + "@aws-amplify/ui": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/ui/-/ui-1.1.2.tgz", + "integrity": "sha512-K9wX+yYY0jp+GEDnhT2hVEB5JuLm0a5u134qC+Neg3rD5u71oOUd+jCJq48iuE2hjaNxMMOA0eGwFmsltr7h6w==" + }, + "@aws-amplify/xr": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws-amplify/xr/-/xr-0.2.2.tgz", + "integrity": "sha512-DSyD12o0rcbvWj0DMEss50HRS3X8xja2lNpNrO8MvjdN6Y8zS+a3GBTM9vx+/kCgp2zRRmC4Ei/ZeD/Rt5N5PA==", + "requires": { + "@aws-amplify/core": "^1.2.2" + } + }, + "@aws-crypto/crc32": { + "version": "0.1.0-preview.1", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-0.1.0-preview.1.tgz", + "integrity": "sha512-GPxlpx1ezlWAYygSfyGIsQ2/imDJgOYnpqwAFlU3H4KacIqX3LHpE5/Ps+s7nxiYtEFR9GvQhsN7VNDF5/FbHg==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@aws-sdk/eventstream-marshaller": { + "version": "0.1.0-preview.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-marshaller/-/eventstream-marshaller-0.1.0-preview.2.tgz", + "integrity": "sha512-StNivqLMGk+6Blp7eBYgLvidD9HEhthzNz7dBBAQPELx3Nd3imodzSvckDw5ZkuWt6ViP+aAl8HgQvJmD71M5Q==", + "requires": { + "@aws-crypto/crc32": "^0.1.0-preview.1", + "@aws-sdk/types": "^0.1.0-preview.1", + "@aws-sdk/util-hex-encoding": "^0.1.0-preview.1", + "tslib": "^1.8.0" + } + }, + "@aws-sdk/is-array-buffer": { + "version": "0.1.0-preview.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-0.1.0-preview.1.tgz", + "integrity": "sha512-9Qrr9w6sNX19N0eO7JBYjp86OPcOyjDPe580L5ISDKo7XfuzK20IC2TeGTZ77okhRTsm8rF5UgP9scBu59jwoA==", + "requires": { + "tslib": "^1.8.0" + } + }, + "@aws-sdk/types": { + "version": "0.1.0-preview.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-0.1.0-preview.1.tgz", + "integrity": "sha512-CcZpxyN2G0I7+Jyj0om3LafYX7d30JWJAAQ+53Ysjau7jyL/xLMMkLZgniQPV8BMV7uKLXyf4hwu8JSs0Ejb+w==" + }, + "@aws-sdk/util-buffer-from": { + "version": "0.1.0-preview.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-0.1.0-preview.1.tgz", + "integrity": "sha512-i46iuFQA05+92L/epK7befgoxw6DM38LnaHjHNxRoJeIYUllZvpJst74FRCJ5UvVOaMDvHO3woWG+dY8/KVABw==", + "requires": { + "@aws-sdk/is-array-buffer": "^0.1.0-preview.1", + "tslib": "^1.8.0" + } + }, + "@aws-sdk/util-hex-encoding": { + "version": "0.1.0-preview.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-0.1.0-preview.1.tgz", + "integrity": "sha512-97ZMVcJpIXwOQN2RntPimav6G5iod/QHEbqGrmaECXyjXzSrexKHRYjpQAtmJ7geZTMsofoRSdj3qZCymjn7Uw==", + "requires": { + "tslib": "^1.8.0" + } + }, + "@aws-sdk/util-utf8-node": { + "version": "0.1.0-preview.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-0.1.0-preview.1.tgz", + "integrity": "sha512-PSUsSJ0nnMPS389f0R3kIVR0BElnEb22Ofj40iO5HCtw9gZ1ot+enFdbOmW4m1e5+ED9U/Hqxqc7QhFWWF4NUQ==", + "requires": { + "@aws-sdk/util-buffer-from": "^0.1.0-preview.1", + "tslib": "^1.8.0" + } + }, "@babel/code-frame": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", @@ -1648,6 +1809,16 @@ "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", "dev": true }, + "amazon-cognito-identity-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-3.1.2.tgz", + "integrity": "sha512-NQ/MxGvzYAauHNWjWu/OMB0LtH5w3D/BKeYP9royQwT6+pR4UJifqFaETh2GQUefROKkv0rqEuvkvT/Wx2RA+Q==", + "requires": { + "buffer": "4.9.1", + "crypto-js": "^3.1.9-1", + "js-cookie": "^2.1.4" + } + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -1913,6 +2084,90 @@ "postcss-value-parser": "^4.0.0" } }, + "aws-amplify": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/aws-amplify/-/aws-amplify-1.2.2.tgz", + "integrity": "sha512-HJ+TRwO/zow+F5OBiNaPo4xgCSgR7S4LFJTn9pMB7SqDRm0l8hKFGnlJ2nU7cBxFUf2ot1ZpdIS4l3d6RBKesw==", + "requires": { + "@aws-amplify/analytics": "^1.3.2", + "@aws-amplify/api": "^1.2.2", + "@aws-amplify/auth": "^1.4.2", + "@aws-amplify/cache": "^1.1.2", + "@aws-amplify/core": "^1.2.2", + "@aws-amplify/interactions": "^1.1.2", + "@aws-amplify/predictions": "^1.1.2", + "@aws-amplify/pubsub": "^1.2.2", + "@aws-amplify/storage": "^1.2.2", + "@aws-amplify/ui": "^1.1.2", + "@aws-amplify/xr": "^0.2.2" + } + }, + "aws-sdk": { + "version": "2.518.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.518.0.tgz", + "integrity": "sha512-hwtKKf93TFyd3qugDW54ElpkUXhPe+ArPIHadre6IAFjCJiv08L8DaZKLRyclDnKfTavKe+f/PhdSEYo1QUHiA==", + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1925,6 +2180,38 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, "axobject-query": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", @@ -2147,8 +2434,7 @@ "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "base64id": { "version": "1.0.0", @@ -2385,7 +2671,6 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -3096,6 +3381,11 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" + }, "css-parse": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", @@ -4388,6 +4678,14 @@ "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, + "graphql": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.0.tgz", + "integrity": "sha512-WlO+ZJT9aY3YrBT+H5Kk+eVb3OVVehB9iRD/xqeHdmrrn4AFl5FIcOpfHz/vnBr6Y6JthGMlnFqU8XRnDjSR7A==", + "requires": { + "iterall": "1.1.x" + } + }, "hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", @@ -4743,8 +5041,7 @@ "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "iferr": { "version": "0.1.5", @@ -5175,8 +5472,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "3.0.3", @@ -5413,6 +5709,11 @@ "handlebars": "^4.1.2" } }, + "iterall": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.4.tgz", + "integrity": "sha512-eaDsM/PY8D/X5mYQhecVc5/9xvSHED7yPON+ffQroBeTuqUVm7dfphMkK8NksXuImqZlVRoKtrNfMIVCYIqaUQ==" + }, "jasmine": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", @@ -5453,6 +5754,16 @@ "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", "dev": true }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -6962,8 +7273,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multicast-dns": { "version": "6.2.3", @@ -8244,8 +8554,7 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", @@ -10245,7 +10554,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -10254,8 +10562,7 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" } } }, @@ -10354,8 +10661,7 @@ "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "validate-npm-package-license": { "version": "3.0.4", @@ -12192,6 +12498,11 @@ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", "dev": true }, + "zen-observable": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.14.tgz", + "integrity": "sha512-kQz39uonEjEESwh+qCi83kcC3rZJGh4mrZW7xjkSQYXkq//JZHTtKo+6yuVloTgMtzsIWOJrjIrKvk/dqm0L5g==" + }, "zone.js": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz", diff --git a/package.json b/package.json index c63f69d..308108f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@angular/pwa": "^0.803.9", "@angular/router": "~8.2.0", "@angular/service-worker": "~8.2.0", + "aws-amplify": "^1.2.2", "hammerjs": "^2.0.8", "rxjs": "~6.4.0", "tslib": "^1.10.0", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index be5efb9..3d67b08 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,11 +1,38 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home/home.component'; - +import { AuthComponent } from './auth/auth.component'; +import { SignInComponent } from './auth/sign-in/sign-in.component'; +import { SignUpComponent } from './auth/sign-up/sign-up.component'; +import { UnauthGuard } from './auth/unauth.guard'; +import { AuthGuard } from './auth/auth.guard'; +import { ConfirmCodeComponent } from './auth/confirm-code/confirm-code.component'; +import { ProfileComponent } from './auth/profile/profile.component'; const routes: Routes = [ - { path: '', component: HomeComponent} -]; + { path: 'auth', component: AuthComponent, children: [ + { + path: 'signin', + component: SignInComponent, + canActivate: [UnauthGuard] + }, + { + path: 'signup', + component: SignUpComponent, + canActivate: [UnauthGuard] + }, + { + path: 'confirm', + component: ConfirmCodeComponent, + canActivate: [UnauthGuard] + }, + { + path: 'profile', + component: ProfileComponent, + canActivate: [AuthGuard] + } + ]}, + { path: '', component: HomeComponent, canActivate: [AuthGuard] }]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8819f45..7d35562 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -8,16 +8,16 @@ import { MatSidenav } from '@angular/material'; styleUrls: ['./app.component.scss'] }) export class AppComponent { - title = 'Material PWA'; + title = 'Henry Pump SCADA'; mobileQuery: MediaQueryList; nav = [ { - 'title': 'Home', - 'path': '/' + title: 'Home', + path: '/' }, { - 'title': 'This is a second link', - 'path': '/auth' + title: 'Account Settings', + path: '/auth' } ]; private mobileQueryListener: () => void; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a519d65..b8b8fb1 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,20 +8,42 @@ import { MaterialModule } from './material/material.module'; import { HomeComponent } from './home/home.component'; import { ServiceWorkerModule } from '@angular/service-worker'; import { environment } from '../environments/environment'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { AuthComponent } from './auth/auth.component'; +import { LoaderComponent } from './loader/loader.component'; +import { CountryCodeSelectComponent } from './auth/country-code-select/country-code-select.component'; +import { FilterPipe } from './auth/country-code-select/filter.pipe'; +import { SignInComponent } from './auth/sign-in/sign-in.component'; +import { SignUpComponent } from './auth/sign-up/sign-up.component'; +import { ConfirmCodeComponent } from './auth/confirm-code/confirm-code.component'; +import { ProfileComponent } from './auth/profile/profile.component'; +import { AvatarComponent } from './auth/profile/avatar/avatar.component'; @NgModule({ declarations: [ AppComponent, - HomeComponent + HomeComponent, + AuthComponent, + LoaderComponent, + CountryCodeSelectComponent, + FilterPipe, + SignInComponent, + SignUpComponent, + ConfirmCodeComponent, + ProfileComponent, + AvatarComponent ], imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, MaterialModule, - ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }) + ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), + ReactiveFormsModule, + FormsModule ], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], + entryComponents: [LoaderComponent, CountryCodeSelectComponent] }) export class AppModule { } diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html new file mode 100644 index 0000000..6a164d6 --- /dev/null +++ b/src/app/auth/auth.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/auth/auth.component.scss b/src/app/auth/auth.component.scss new file mode 100644 index 0000000..5a978da --- /dev/null +++ b/src/app/auth/auth.component.scss @@ -0,0 +1,9 @@ +:host { + height: 100%; +} + +.auth-btn-signup { + position: absolute; + bottom: 1em; + right: 2em; +} \ No newline at end of file diff --git a/src/app/auth/auth.component.spec.ts b/src/app/auth/auth.component.spec.ts new file mode 100644 index 0000000..884576c --- /dev/null +++ b/src/app/auth/auth.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AuthComponent } from './auth.component'; + +describe('AuthComponent', () => { + let component: AuthComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AuthComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AuthComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts new file mode 100644 index 0000000..c5b83ab --- /dev/null +++ b/src/app/auth/auth.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, NgZone } from '@angular/core'; +import Auth from '@aws-amplify/auth'; +import { Router } from '@angular/router'; +import { Hub } from '@aws-amplify/core'; + +@Component({ + selector: 'app-auth', + templateUrl: './auth.component.html', + styleUrls: ['./auth.component.scss'] +}) +export class AuthComponent implements OnInit { + + constructor( private _router: Router, private _zone: NgZone ) { } + + ngOnInit() { + Hub.listen('auth', ({ payload: { event, data } }) => { + switch (event) { + case 'signIn': + this._zone.run(() => { + this._router.navigate(['/']); + }); + break; + case 'signOut': + this._zone.run(() => { + this._router.navigate(['/auth/signin']); + }); + break; + } + }); + Auth.currentAuthenticatedUser() + .then(() => { + this._router.navigate(['auth/profile']); + }) + .catch(() => {}); + } + +} \ No newline at end of file diff --git a/src/app/auth/auth.guard.spec.ts b/src/app/auth/auth.guard.spec.ts new file mode 100644 index 0000000..7ed05ee --- /dev/null +++ b/src/app/auth/auth.guard.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, async, inject } from '@angular/core/testing'; + +import { AuthGuard } from './auth.guard'; + +describe('AuthGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthGuard] + }); + }); + + it('should ...', inject([AuthGuard], (guard: AuthGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/src/app/auth/auth.guard.ts b/src/app/auth/auth.guard.ts new file mode 100644 index 0000000..2e99123 --- /dev/null +++ b/src/app/auth/auth.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import Auth from '@aws-amplify/auth'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor( private _router: Router ) { } + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean { + return Auth.currentAuthenticatedUser().then(() => { return true; }) + .catch(() => { + this._router.navigate(['auth/signin']); + return false; + }); + } +} diff --git a/src/app/auth/auth.service.spec.ts b/src/app/auth/auth.service.spec.ts new file mode 100644 index 0000000..f3d964d --- /dev/null +++ b/src/app/auth/auth.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: AuthService = TestBed.get(AuthService); + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts new file mode 100644 index 0000000..4c03d54 --- /dev/null +++ b/src/app/auth/auth.service.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@angular/core'; +import Auth, { CognitoHostedUIIdentityProvider } from '@aws-amplify/auth'; +import { Hub, ICredentials } from '@aws-amplify/core'; +import { Subject, Observable } from 'rxjs'; +import { CognitoUser } from 'amazon-cognito-identity-js'; + +export interface NewUser { + email: string; + phone: string; + password: string; + firstName: string; + lastName: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + public loggedIn: boolean; + private _authState: Subject = new Subject(); + authState: Observable = this._authState.asObservable(); + + public static SIGN_IN = 'signIn'; + public static SIGN_OUT = 'signOut'; + public static FACEBOOK = CognitoHostedUIIdentityProvider.Facebook; + public static GOOGLE = CognitoHostedUIIdentityProvider.Google; + + constructor() { + Hub.listen('auth', (data) => { + const { channel, payload } = data; + if (channel === 'auth') { + this._authState.next(payload.event); + } + }); + } + + signUp(user: NewUser): Promise { + return Auth.signUp({ + "username": user.email, + "password": user.password, + "attributes": { + "email": user.email, + "given_name": user.firstName, + "family_name": user.lastName, + "phone_number": user.phone + } + }); + } + + signIn(username: string, password: string):Promise { + return new Promise((resolve, reject) => { + Auth.signIn(username, password) + .then((user: CognitoUser|any) => { + this.loggedIn = true; + resolve(user); + }).catch((error: any) => reject(error)); + }); + } + + signOut(): Promise { + return Auth.signOut() + .then(() => this.loggedIn = false); + } + + socialSignIn(provider:CognitoHostedUIIdentityProvider): Promise { + return Auth.federatedSignIn({ + 'provider': provider + }); + } + +} \ No newline at end of file diff --git a/src/app/auth/confirm-code/confirm-code.component.html b/src/app/auth/confirm-code/confirm-code.component.html new file mode 100644 index 0000000..a364c27 --- /dev/null +++ b/src/app/auth/confirm-code/confirm-code.component.html @@ -0,0 +1,12 @@ +
+

Confirm your email address

+ + + + + + Enter the confirmation code that was emailed to you + + +

Didn't receive a code? Send again

+
diff --git a/src/app/auth/confirm-code/confirm-code.component.scss b/src/app/auth/confirm-code/confirm-code.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/auth/confirm-code/confirm-code.component.spec.ts b/src/app/auth/confirm-code/confirm-code.component.spec.ts new file mode 100644 index 0000000..f52f788 --- /dev/null +++ b/src/app/auth/confirm-code/confirm-code.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmCodeComponent } from './confirm-code.component'; + +describe('ConfirmCodeComponent', () => { + let component: ConfirmCodeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ConfirmCodeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmCodeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/confirm-code/confirm-code.component.ts b/src/app/auth/confirm-code/confirm-code.component.ts new file mode 100644 index 0000000..7e2da80 --- /dev/null +++ b/src/app/auth/confirm-code/confirm-code.component.ts @@ -0,0 +1,60 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, Validators, FormControl } from '@angular/forms'; +import { environment } from 'src/environments/environment'; +import { Router } from '@angular/router'; +import Auth from '@aws-amplify/auth'; +import { NotificationService } from 'src/app/services/notification.service'; + +@Component({ + selector: 'app-confirm-code', + templateUrl: './confirm-code.component.html', + styleUrls: ['./confirm-code.component.scss'] +}) +export class ConfirmCodeComponent implements OnInit { + + email = environment.confirm.email; + confirmForm: FormGroup = new FormGroup({ + email: new FormControl({value: this.email, disabled: true}), + code: new FormControl('', [ Validators.required, Validators.min(3) ]) + }); + + get codeInput() { return this.confirmForm.get('code'); } + + constructor( private _router: Router, private _notification: NotificationService ) { } + + ngOnInit() { + if (!this.email) { + this._router.navigate(['auth/signup']); + } else { + Auth.resendSignUp(this.email); + } + } + + sendAgain() { + Auth.resendSignUp(this.email) + .then(() => this._notification.show('A code has been emailed to you')) + .catch(() => this._notification.show('An error occurred')); + } + + confirmCode() { + Auth.confirmSignUp(this.email, this.codeInput.value) + .then((data: any) => { + console.log(data); + if (data === 'SUCCESS' && + environment.confirm.email && + environment.confirm.password) { + Auth.signIn(this.email, environment.confirm.password) + .then(() => { + this._router.navigate(['']); + }).catch((error: any) => { + this._router.navigate(['auth/signin']); + }) + } + }) + .catch((error: any) => { + console.log(error); + this._notification.show(error.message); + }) + } + +} \ No newline at end of file diff --git a/src/app/auth/country-code-select/country-code-select.component.html b/src/app/auth/country-code-select/country-code-select.component.html new file mode 100644 index 0000000..205b36f --- /dev/null +++ b/src/app/auth/country-code-select/country-code-select.component.html @@ -0,0 +1,10 @@ + + + + + + {{ code.name }} + {{ code.dial_code }} + + + \ No newline at end of file diff --git a/src/app/auth/country-code-select/country-code-select.component.scss b/src/app/auth/country-code-select/country-code-select.component.scss new file mode 100644 index 0000000..be4f704 --- /dev/null +++ b/src/app/auth/country-code-select/country-code-select.component.scss @@ -0,0 +1,10 @@ +.country-list { + margin-top: 5px; + height: 300px; + overflow-y: scroll; +} +.country-filter { + position: sticky; + top: 0em; + margin-top:0.5em; +} diff --git a/src/app/auth/country-code-select/country-code-select.component.spec.ts b/src/app/auth/country-code-select/country-code-select.component.spec.ts new file mode 100644 index 0000000..c9b907d --- /dev/null +++ b/src/app/auth/country-code-select/country-code-select.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CountryCodeSelectComponent } from './country-code-select.component'; + +describe('CountryCodeSelectComponent', () => { + let component: CountryCodeSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CountryCodeSelectComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CountryCodeSelectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/country-code-select/country-code-select.component.ts b/src/app/auth/country-code-select/country-code-select.component.ts new file mode 100644 index 0000000..7de0f93 --- /dev/null +++ b/src/app/auth/country-code-select/country-code-select.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { CountryCode, CountryCodes } from './country-codes'; +import { MatBottomSheetRef } from '@angular/material'; + +@Component({ + selector: 'app-country-code-select', + templateUrl: './country-code-select.component.html', + styleUrls: ['./country-code-select.component.scss'] +}) +export class CountryCodeSelectComponent implements OnInit { + countryCodes: Array = CountryCodes; + constructor(private bottomSheetRef: MatBottomSheetRef) { } + + ngOnInit() { + } + + selectCountry(code: CountryCode) { + this.bottomSheetRef.dismiss(code); + } + +} diff --git a/src/app/auth/country-code-select/country-codes.spec.ts b/src/app/auth/country-code-select/country-codes.spec.ts new file mode 100644 index 0000000..a7a4429 --- /dev/null +++ b/src/app/auth/country-code-select/country-codes.spec.ts @@ -0,0 +1,7 @@ +import { CountryCodes } from './country-codes'; + +describe('CountryCodes', () => { + it('should create an instance', () => { + expect(new CountryCodes()).toBeTruthy(); + }); +}); diff --git a/src/app/auth/country-code-select/country-codes.ts b/src/app/auth/country-code-select/country-codes.ts new file mode 100644 index 0000000..02a6d71 --- /dev/null +++ b/src/app/auth/country-code-select/country-codes.ts @@ -0,0 +1,1213 @@ +export interface CountryCode { + name: string; + dial_code: string; + code: string; +} + +export const CountryCodes: Array = [ +{ +name: 'Afghanistan', +dial_code: '+93', +code: 'AF' +}, +{ +name: 'Aland Islands', +dial_code: '+358', +code: 'AX' +}, +{ +name: 'Albania', +dial_code: '+355', +code: 'AL' +}, +{ +name: 'Algeria', +dial_code: '+213', +code: 'DZ' +}, +{ +name: 'AmericanSamoa', +dial_code: '+1 684', +code: 'AS' +}, +{ +name: 'Andorra', +dial_code: '+376', +code: 'AD' +}, +{ +name: 'Angola', +dial_code: '+244', +code: 'AO' +}, +{ +name: 'Anguilla', +dial_code: '+1 264', +code: 'AI' +}, +{ +name: 'Antarctica', +dial_code: '+672', +code: 'AQ' +}, +{ +name: 'Antigua and Barbuda', +dial_code: '+1268', +code: 'AG' +}, +{ +name: 'Argentina', +dial_code: '+54', +code: 'AR' +}, +{ +name: 'Armenia', +dial_code: '+374', +code: 'AM' +}, +{ +name: 'Aruba', +dial_code: '+297', +code: 'AW' +}, +{ +name: 'Australia', +dial_code: '+61', +code: 'AU' +}, +{ +name: 'Austria', +dial_code: '+43', +code: 'AT' +}, +{ +name: 'Azerbaijan', +dial_code: '+994', +code: 'AZ' +}, +{ +name: 'Bahamas', +dial_code: '+1 242', +code: 'BS' +}, +{ +name: 'Bahrain', +dial_code: '+973', +code: 'BH' +}, +{ +name: 'Bangladesh', +dial_code: '+880', +code: 'BD' +}, +{ +name: 'Barbados', +dial_code: '+1 246', +code: 'BB' +}, +{ +name: 'Belarus', +dial_code: '+375', +code: 'BY' +}, +{ +name: 'Belgium', +dial_code: '+32', +code: 'BE' +}, +{ +name: 'Belize', +dial_code: '+501', +code: 'BZ' +}, +{ +name: 'Benin', +dial_code: '+229', +code: 'BJ' +}, +{ +name: 'Bermuda', +dial_code: '+1 441', +code: 'BM' +}, +{ +name: 'Bhutan', +dial_code: '+975', +code: 'BT' +}, +{ +name: 'Bolivia, Plurinational State of', +dial_code: '+591', +code: 'BO' +}, +{ +name: 'Bosnia and Herzegovina', +dial_code: '+387', +code: 'BA' +}, +{ +name: 'Botswana', +dial_code: '+267', +code: 'BW' +}, +{ +name: 'Brazil', +dial_code: '+55', +code: 'BR' +}, +{ +name: 'British Indian Ocean Territory', +dial_code: '+246', +code: 'IO' +}, +{ +name: 'Brunei Darussalam', +dial_code: '+673', +code: 'BN' +}, +{ +name: 'Bulgaria', +dial_code: '+359', +code: 'BG' +}, +{ +name: 'Burkina Faso', +dial_code: '+226', +code: 'BF' +}, +{ +name: 'Burundi', +dial_code: '+257', +code: 'BI' +}, +{ +name: 'Cambodia', +dial_code: '+855', +code: 'KH' +}, +{ +name: 'Cameroon', +dial_code: '+237', +code: 'CM' +}, +{ +name: 'Canada', +dial_code: '+1', +code: 'CA' +}, +{ +name: 'Cape Verde', +dial_code: '+238', +code: 'CV' +}, +{ +name: 'Cayman Islands', +dial_code: '+ 345', +code: 'KY' +}, +{ +name: 'Central African Republic', +dial_code: '+236', +code: 'CF' +}, +{ +name: 'Chad', +dial_code: '+235', +code: 'TD' +}, +{ +name: 'Chile', +dial_code: '+56', +code: 'CL' +}, +{ +name: 'China', +dial_code: '+86', +code: 'CN' +}, +{ +name: 'Christmas Island', +dial_code: '+61', +code: 'CX' +}, +{ +name: 'Cocos (Keeling) Islands', +dial_code: '+61', +code: 'CC' +}, +{ +name: 'Colombia', +dial_code: '+57', +code: 'CO' +}, +{ +name: 'Comoros', +dial_code: '+269', +code: 'KM' +}, +{ +name: 'Congo', +dial_code: '+242', +code: 'CG' +}, +{ +name: 'Congo, The Democratic Republic of the Congo', +dial_code: '+243', +code: 'CD' +}, +{ +name: 'Cook Islands', +dial_code: '+682', +code: 'CK' +}, +{ +name: 'Costa Rica', +dial_code: '+506', +code: 'CR' +}, +{ +name: 'Cote d\'Ivoire', +dial_code: '+225', +code: 'CI' +}, +{ +name: 'Croatia', +dial_code: '+385', +code: 'HR' +}, +{ +name: 'Cuba', +dial_code: '+53', +code: 'CU' +}, +{ +name: 'Cyprus', +dial_code: '+357', +code: 'CY' +}, +{ +name: 'Czech Republic', +dial_code: '+420', +code: 'CZ' +}, +{ +name: 'Denmark', +dial_code: '+45', +code: 'DK' +}, +{ +name: 'Djibouti', +dial_code: '+253', +code: 'DJ' +}, +{ +name: 'Dominica', +dial_code: '+1 767', +code: 'DM' +}, +{ +name: 'Dominican Republic', +dial_code: '+1 849', +code: 'DO' +}, +{ +name: 'Ecuador', +dial_code: '+593', +code: 'EC' +}, +{ +name: 'Egypt', +dial_code: '+20', +code: 'EG' +}, +{ +name: 'El Salvador', +dial_code: '+503', +code: 'SV' +}, +{ +name: 'Equatorial Guinea', +dial_code: '+240', +code: 'GQ' +}, +{ +name: 'Eritrea', +dial_code: '+291', +code: 'ER' +}, +{ +name: 'Estonia', +dial_code: '+372', +code: 'EE' +}, +{ +name: 'Ethiopia', +dial_code: '+251', +code: 'ET' +}, +{ +name: 'Falkland Islands (Malvinas)', +dial_code: '+500', +code: 'FK' +}, +{ +name: 'Faroe Islands', +dial_code: '+298', +code: 'FO' +}, +{ +name: 'Fiji', +dial_code: '+679', +code: 'FJ' +}, +{ +name: 'Finland', +dial_code: '+358', +code: 'FI' +}, +{ +name: 'France', +dial_code: '+33', +code: 'FR' +}, +{ +name: 'French Guiana', +dial_code: '+594', +code: 'GF' +}, +{ +name: 'French Polynesia', +dial_code: '+689', +code: 'PF' +}, +{ +name: 'Gabon', +dial_code: '+241', +code: 'GA' +}, +{ +name: 'Gambia', +dial_code: '+220', +code: 'GM' +}, +{ +name: 'Georgia', +dial_code: '+995', +code: 'GE' +}, +{ +name: 'Germany', +dial_code: '+49', +code: 'DE' +}, +{ +name: 'Ghana', +dial_code: '+233', +code: 'GH' +}, +{ +name: 'Gibraltar', +dial_code: '+350', +code: 'GI' +}, +{ +name: 'Greece', +dial_code: '+30', +code: 'GR' +}, +{ +name: 'Greenland', +dial_code: '+299', +code: 'GL' +}, +{ +name: 'Grenada', +dial_code: '+1 473', +code: 'GD' +}, +{ +name: 'Guadeloupe', +dial_code: '+590', +code: 'GP' +}, +{ +name: 'Guam', +dial_code: '+1 671', +code: 'GU' +}, +{ +name: 'Guatemala', +dial_code: '+502', +code: 'GT' +}, +{ +name: 'Guernsey', +dial_code: '+44', +code: 'GG' +}, +{ +name: 'Guinea', +dial_code: '+224', +code: 'GN' +}, +{ +name: 'Guinea-Bissau', +dial_code: '+245', +code: 'GW' +}, +{ +name: 'Guyana', +dial_code: '+595', +code: 'GY' +}, +{ +name: 'Haiti', +dial_code: '+509', +code: 'HT' +}, +{ +name: 'Holy See (Vatican City State)', +dial_code: '+379', +code: 'VA' +}, +{ +name: 'Honduras', +dial_code: '+504', +code: 'HN' +}, +{ +name: 'Hong Kong', +dial_code: '+852', +code: 'HK' +}, +{ +name: 'Hungary', +dial_code: '+36', +code: 'HU' +}, +{ +name: 'Iceland', +dial_code: '+354', +code: 'IS' +}, +{ +name: 'India', +dial_code: '+91', +code: 'IN' +}, +{ +name: 'Indonesia', +dial_code: '+62', +code: 'ID' +}, +{ +name: 'Iran, Islamic Republic of Persian Gulf', +dial_code: '+98', +code: 'IR' +}, +{ +name: 'Iraq', +dial_code: '+964', +code: 'IQ' +}, +{ +name: 'Ireland', +dial_code: '+353', +code: 'IE' +}, +{ +name: 'Isle of Man', +dial_code: '+44', +code: 'IM' +}, +{ +name: 'Israel', +dial_code: '+972', +code: 'IL' +}, +{ +name: 'Italy', +dial_code: '+39', +code: 'IT' +}, +{ +name: 'Jamaica', +dial_code: '+1 876', +code: 'JM' +}, +{ +name: 'Japan', +dial_code: '+81', +code: 'JP' +}, +{ +name: 'Jersey', +dial_code: '+44', +code: 'JE' +}, +{ +name: 'Jordan', +dial_code: '+962', +code: 'JO' +}, +{ +name: 'Kazakhstan', +dial_code: '+7 7', +code: 'KZ' +}, +{ +name: 'Kenya', +dial_code: '+254', +code: 'KE' +}, +{ +name: 'Kiribati', +dial_code: '+686', +code: 'KI' +}, +{ +name: 'Korea, Democratic People\'s Republic of Korea', +dial_code: '+850', +code: 'KP' +}, +{ +name: 'Korea, Republic of South Korea', +dial_code: '+82', +code: 'KR' +}, +{ +name: 'Kuwait', +dial_code: '+965', +code: 'KW' +}, +{ +name: 'Kyrgyzstan', +dial_code: '+996', +code: 'KG' +}, +{ +name: 'Laos', +dial_code: '+856', +code: 'LA' +}, +{ +name: 'Latvia', +dial_code: '+371', +code: 'LV' +}, +{ +name: 'Lebanon', +dial_code: '+961', +code: 'LB' +}, +{ +name: 'Lesotho', +dial_code: '+266', +code: 'LS' +}, +{ +name: 'Liberia', +dial_code: '+231', +code: 'LR' +}, +{ +name: 'Libyan Arab Jamahiriya', +dial_code: '+218', +code: 'LY' +}, +{ +name: 'Liechtenstein', +dial_code: '+423', +code: 'LI' +}, +{ +name: 'Lithuania', +dial_code: '+370', +code: 'LT' +}, +{ +name: 'Luxembourg', +dial_code: '+352', +code: 'LU' +}, +{ +name: 'Macao', +dial_code: '+853', +code: 'MO' +}, +{ +name: 'Macedonia', +dial_code: '+389', +code: 'MK' +}, +{ +name: 'Madagascar', +dial_code: '+261', +code: 'MG' +}, +{ +name: 'Malawi', +dial_code: '+265', +code: 'MW' +}, +{ +name: 'Malaysia', +dial_code: '+60', +code: 'MY' +}, +{ +name: 'Maldives', +dial_code: '+960', +code: 'MV' +}, +{ +name: 'Mali', +dial_code: '+223', +code: 'ML' +}, +{ +name: 'Malta', +dial_code: '+356', +code: 'MT' +}, +{ +name: 'Marshall Islands', +dial_code: '+692', +code: 'MH' +}, +{ +name: 'Martinique', +dial_code: '+596', +code: 'MQ' +}, +{ +name: 'Mauritania', +dial_code: '+222', +code: 'MR' +}, +{ +name: 'Mauritius', +dial_code: '+230', +code: 'MU' +}, +{ +name: 'Mayotte', +dial_code: '+262', +code: 'YT' +}, +{ +name: 'Mexico', +dial_code: '+52', +code: 'MX' +}, +{ +name: 'Micronesia, Federated States of Micronesia', +dial_code: '+691', +code: 'FM' +}, +{ +name: 'Moldova', +dial_code: '+373', +code: 'MD' +}, +{ +name: 'Monaco', +dial_code: '+377', +code: 'MC' +}, +{ +name: 'Mongolia', +dial_code: '+976', +code: 'MN' +}, +{ +name: 'Montenegro', +dial_code: '+382', +code: 'ME' +}, +{ +name: 'Montserrat', +dial_code: '+1664', +code: 'MS' +}, +{ +name: 'Morocco', +dial_code: '+212', +code: 'MA' +}, +{ +name: 'Mozambique', +dial_code: '+258', +code: 'MZ' +}, +{ +name: 'Myanmar', +dial_code: '+95', +code: 'MM' +}, +{ +name: 'Namibia', +dial_code: '+264', +code: 'NA' +}, +{ +name: 'Nauru', +dial_code: '+674', +code: 'NR' +}, +{ +name: 'Nepal', +dial_code: '+977', +code: 'NP' +}, +{ +name: 'Netherlands', +dial_code: '+31', +code: 'NL' +}, +{ +name: 'Netherlands Antilles', +dial_code: '+599', +code: 'AN' +}, +{ +name: 'New Caledonia', +dial_code: '+687', +code: 'NC' +}, +{ +name: 'New Zealand', +dial_code: '+64', +code: 'NZ' +}, +{ +name: 'Nicaragua', +dial_code: '+505', +code: 'NI' +}, +{ +name: 'Niger', +dial_code: '+227', +code: 'NE' +}, +{ +name: 'Nigeria', +dial_code: '+234', +code: 'NG' +}, +{ +name: 'Niue', +dial_code: '+683', +code: 'NU' +}, +{ +name: 'Norfolk Island', +dial_code: '+672', +code: 'NF' +}, +{ +name: 'Northern Mariana Islands', +dial_code: '+1 670', +code: 'MP' +}, +{ +name: 'Norway', +dial_code: '+47', +code: 'NO' +}, +{ +name: 'Oman', +dial_code: '+968', +code: 'OM' +}, +{ +name: 'Pakistan', +dial_code: '+92', +code: 'PK' +}, +{ +name: 'Palau', +dial_code: '+680', +code: 'PW' +}, +{ +name: 'Palestinian Territory, Occupied', +dial_code: '+970', +code: 'PS' +}, +{ +name: 'Panama', +dial_code: '+507', +code: 'PA' +}, +{ +name: 'Papua New Guinea', +dial_code: '+675', +code: 'PG' +}, +{ +name: 'Paraguay', +dial_code: '+595', +code: 'PY' +}, +{ +name: 'Peru', +dial_code: '+51', +code: 'PE' +}, +{ +name: 'Philippines', +dial_code: '+63', +code: 'PH' +}, +{ +name: 'Pitcairn', +dial_code: '+872', +code: 'PN' +}, +{ +name: 'Poland', +dial_code: '+48', +code: 'PL' +}, +{ +name: 'Portugal', +dial_code: '+351', +code: 'PT' +}, +{ +name: 'Puerto Rico', +dial_code: '+1 939', +code: 'PR' +}, +{ +name: 'Qatar', +dial_code: '+974', +code: 'QA' +}, +{ +name: 'Romania', +dial_code: '+40', +code: 'RO' +}, +{ +name: 'Russia', +dial_code: '+7', +code: 'RU' +}, +{ +name: 'Rwanda', +dial_code: '+250', +code: 'RW' +}, +{ +name: 'Reunion', +dial_code: '+262', +code: 'RE' +}, +{ +name: 'Saint Barthelemy', +dial_code: '+590', +code: 'BL' +}, +{ +name: 'Saint Helena, Ascension and Tristan Da Cunha', +dial_code: '+290', +code: 'SH' +}, +{ +name: 'Saint Kitts and Nevis', +dial_code: '+1 869', +code: 'KN' +}, +{ +name: 'Saint Lucia', +dial_code: '+1 758', +code: 'LC' +}, +{ +name: 'Saint Martin', +dial_code: '+590', +code: 'MF' +}, +{ +name: 'Saint Pierre and Miquelon', +dial_code: '+508', +code: 'PM' +}, +{ +name: 'Saint Vincent and the Grenadines', +dial_code: '+1 784', +code: 'VC' +}, +{ +name: 'Samoa', +dial_code: '+685', +code: 'WS' +}, +{ +name: 'San Marino', +dial_code: '+378', +code: 'SM' +}, +{ +name: 'Sao Tome and Principe', +dial_code: '+239', +code: 'ST' +}, +{ +name: 'Saudi Arabia', +dial_code: '+966', +code: 'SA' +}, +{ +name: 'Senegal', +dial_code: '+221', +code: 'SN' +}, +{ +name: 'Serbia', +dial_code: '+381', +code: 'RS' +}, +{ +name: 'Seychelles', +dial_code: '+248', +code: 'SC' +}, +{ +name: 'Sierra Leone', +dial_code: '+232', +code: 'SL' +}, +{ +name: 'Singapore', +dial_code: '+65', +code: 'SG' +}, +{ +name: 'Slovakia', +dial_code: '+421', +code: 'SK' +}, +{ +name: 'Slovenia', +dial_code: '+386', +code: 'SI' +}, +{ +name: 'Solomon Islands', +dial_code: '+677', +code: 'SB' +}, +{ +name: 'Somalia', +dial_code: '+252', +code: 'SO' +}, +{ +name: 'South Africa', +dial_code: '+27', +code: 'ZA' +}, +{ +name: 'South Georgia and the South Sandwich Islands', +dial_code: '+500', +code: 'GS' +}, +{ +name: 'Spain', +dial_code: '+34', +code: 'ES' +}, +{ +name: 'Sri Lanka', +dial_code: '+94', +code: 'LK' +}, +{ +name: 'Sudan', +dial_code: '+249', +code: 'SD' +}, +{ +name: 'Suriname', +dial_code: '+597', +code: 'SR' +}, +{ +name: 'Svalbard and Jan Mayen', +dial_code: '+47', +code: 'SJ' +}, +{ +name: 'Swaziland', +dial_code: '+268', +code: 'SZ' +}, +{ +name: 'Sweden', +dial_code: '+46', +code: 'SE' +}, +{ +name: 'Switzerland', +dial_code: '+41', +code: 'CH' +}, +{ +name: 'Syrian Arab Republic', +dial_code: '+963', +code: 'SY' +}, +{ +name: 'Taiwan', +dial_code: '+886', +code: 'TW' +}, +{ +name: 'Tajikistan', +dial_code: '+992', +code: 'TJ' +}, +{ +name: 'Tanzania, United Republic of Tanzania', +dial_code: '+255', +code: 'TZ' +}, +{ +name: 'Thailand', +dial_code: '+66', +code: 'TH' +}, +{ +name: 'Timor-Leste', +dial_code: '+670', +code: 'TL' +}, +{ +name: 'Togo', +dial_code: '+228', +code: 'TG' +}, +{ +name: 'Tokelau', +dial_code: '+690', +code: 'TK' +}, +{ +name: 'Tonga', +dial_code: '+676', +code: 'TO' +}, +{ +name: 'Trinidad and Tobago', +dial_code: '+1 868', +code: 'TT' +}, +{ +name: 'Tunisia', +dial_code: '+216', +code: 'TN' +}, +{ +name: 'Turkey', +dial_code: '+90', +code: 'TR' +}, +{ +name: 'Turkmenistan', +dial_code: '+993', +code: 'TM' +}, +{ +name: 'Turks and Caicos Islands', +dial_code: '+1 649', +code: 'TC' +}, +{ +name: 'Tuvalu', +dial_code: '+688', +code: 'TV' +}, +{ +name: 'Uganda', +dial_code: '+256', +code: 'UG' +}, +{ +name: 'Ukraine', +dial_code: '+380', +code: 'UA' +}, +{ +name: 'United Arab Emirates', +dial_code: '+971', +code: 'AE' +}, +{ +name: 'United Kingdom', +dial_code: '+44', +code: 'GB' +}, +{ +name: 'United States', +dial_code: '+1', +code: 'US' +}, +{ +name: 'Uruguay', +dial_code: '+598', +code: 'UY' +}, +{ +name: 'Uzbekistan', +dial_code: '+998', +code: 'UZ' +}, +{ +name: 'Vanuatu', +dial_code: '+678', +code: 'VU' +}, +{ +name: 'Venezuela, Bolivarian Republic of Venezuela', +dial_code: '+58', +code: 'VE' +}, +{ +name: 'Vietnam', +dial_code: '+84', +code: 'VN' +}, +{ +name: 'Virgin Islands, British', +dial_code: '+1 284', +code: 'VG' +}, +{ +name: 'Virgin Islands, U.S.', +dial_code: '+1 340', +code: 'VI' +}, +{ +name: 'Wallis and Futuna', +dial_code: '+681', +code: 'WF' +}, +{ +name: 'Yemen', +dial_code: '+967', +code: 'YE' +}, +{ +name: 'Zambia', +dial_code: '+260', +code: 'ZM' +}, +{ +name: 'Zimbabwe', +dial_code: '+263', +code: 'ZW' +} +]; diff --git a/src/app/auth/country-code-select/filter.pipe.spec.ts b/src/app/auth/country-code-select/filter.pipe.spec.ts new file mode 100644 index 0000000..1427de3 --- /dev/null +++ b/src/app/auth/country-code-select/filter.pipe.spec.ts @@ -0,0 +1,8 @@ +import { FilterPipe } from './filter.pipe'; + +describe('FilterPipe', () => { + it('create an instance', () => { + const pipe = new FilterPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/auth/country-code-select/filter.pipe.ts b/src/app/auth/country-code-select/filter.pipe.ts new file mode 100644 index 0000000..a74c418 --- /dev/null +++ b/src/app/auth/country-code-select/filter.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { CountryCode } from './country-codes'; +@Pipe({ + name: 'filter' +}) +export class FilterPipe implements PipeTransform { + transform(items: CountryCode[], searchText: string): CountryCode[] { + if (!items) { + return []; + } + if (!searchText) { + return items; + } + searchText = searchText.toLowerCase(); + return items.filter( code => { + return code.name.toLowerCase().includes(searchText); + }); + } +} \ No newline at end of file diff --git a/src/app/auth/profile/avatar/avatar.component.html b/src/app/auth/profile/avatar/avatar.component.html new file mode 100644 index 0000000..72b4c4d --- /dev/null +++ b/src/app/auth/profile/avatar/avatar.component.html @@ -0,0 +1,33 @@ +
+
+ +
+
+ Avatar Preview + +
Processing Avatar...
+
+
+ +
+
+
+
+ +
{{ errorMessage }}
+ × +
+
+
\ No newline at end of file diff --git a/src/app/auth/profile/avatar/avatar.component.scss b/src/app/auth/profile/avatar/avatar.component.scss new file mode 100644 index 0000000..9ca0ec2 --- /dev/null +++ b/src/app/auth/profile/avatar/avatar.component.scss @@ -0,0 +1,71 @@ +.app-avatar { + width: 95%; + border-radius: 6px; +} + +.avatar-remove { + position: relative; + left: -2em; +} + +.app-avatar-container { + width: 100%; + margin: 0 auto; + padding: 1em; +} + +.app-avatar-upload { + height: 212px; + border: 2px dotted #979797; + border-radius: 4px; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAABBCAYAAACUyynLAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAASASURBVHic7ZtZe7I6FEbfEMIMbe3p//+BfRyKzBk4F2pFvyJDMdiWda2bZD1JSHY2hHNeY2EQxtwN+Iks0kawSBvBIm0Ei7QRmLofyIXEerOHVOpbcahh4HUVgpl0opb1R/tI28Xpt4UBgFQKuzidoEXD0SqtLDmqSkwWr6oEypJPFq8vWqXFSf4jYnahTVpZclR8ulF2ouL6R5s2afccEbpHGxly9uRcoKg4pFSoB5xY67pGXlRj2tcb17FACOn9e0IASg04FgNjwzYRvaRVlcBun4HfYXo9AoyZeA49WFY/eZ3S0qyc7dWum+fIh+/Znb+7uablRfVnhAGHPWSfZaRVmlL1nxJ2YhenUOr2itUqLc2Lzj//RpSqkebFzd+0Sitm2Gk/Cl19b5UmhJy8MT+Frr7fXNP+KqPXtIV2tOfTvsJiJlzHgmWZYCb93NnXdQ0uJKpKIC+qu5xdxzCrNMsyEQUe7JadOCEEFjNhMROB76CsBOIkmzS9NIbZpD1HHnzPGfQf2zLxtoqQZgV2cXanlnWjXRohBK8v4cXoklIhy0sUJYeQEkrVMAwCk1I4NoPn2qD0vPz6ngPTNLHe7lEPyRxMhHZpq+fgU1hd14iTHEn672ZSqRqVEqi4QJzkCHwHUeB+rne2ZWL1HGC93WttP6D57RmFHhybATjk+N838ZfCviJJC7xv4ov7BcdmiELvLm29hTZplBoIjhmEuq6x3u7B+bANNOfynykZeJdTVwfanhaF3ufUipN8sLATnMuLTC0hRPto0yLNMAjc07SUqveUbCNJC0h5nqauzWAY/bO230WLNMc+p6KzvJwkZjMOIQSObU0Stw9apFnsfAs+VfbkOk7zGfdGizRKzx0ScprsyXWc5jPujRZppLHeTJU9uY5Dftuahkb/plqw/4mj8WCgRVrzTWdONI2u4zSfcW+0SOPinJU4nQi+y3Wc5jPujRZpzVoLz+2+V+zDdZyy0nenoWmkSfBj3p1SA4E/LCV0TeA7F0cnLuToE8YYZimAiQIXbOS+ijGKKHAvY+/15ta0SSuKCtVxCp1yakPFMUbx+hJeFLqUFdd+3ag1PbD5SKGOqR1qGHhbRb2nauA7eFtFoMa5yVIpbD/0VwFoTUJKqbDeJvhvdRgthBA8hR4CzxmUuQUO6aXNNtG61TihPXNbcYH3dYzVSwjzKIJSA2HgIrxaq9oQUmGz3X++XHQzy70nFxLv6w+kWTm4ODDNS7yvP2YTBsx4G3WqSkqyAoHnwHWs1iOWUodKyiQrHqJcYvbLYiEkdnGKXZzCNCmYSWEc347qeFn8CKKazC6tiXhAQV/RuqYNqPn9dXT1vVWazqTeo9HV91ZpjjVNNuIn0tX3VmlTZSN+Il19b5XGGB1coPIb8D2n80x8c3P7FLqtZVC/Edsy8RR2n0o6P76o6xof+xxp9r0L3kfH9xw8hW6vT4V6fxvFhUSWHQ7VUslBx59HhBCAGseEgGcP+kJ50AdlCweWQuURLNJGsEgbwSJtBIu0ESzSRrBIG8H/BsMLCwR4M6kAAAAASUVORK5CYII=') center no-repeat #FBFBFB; +} + +.app-avatar-upload-dragover { + opacity: 0.5; + height: 212px; + border: 2px solid #333; + border-radius: 4px; + background: url('data:image/png;base64,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') center no-repeat #cccccc; +} + +.app-upload-input { + margin-top: 0.5em; +} + +.app-upload-button { + margin: 0.5em auto !important; + width: 100% !important; +} + +.app-avatar-preview { + width: auto; + max-height: 200px; +} + +.app-image-container { + display: inline; +} + +.app-image { + width: 30%; + margin: 0.2em; + border-radius: 6px; + border: 2px solid white; + cursor: pointer; +} + +@media (max-width: 600px) { +.app-avatar { + width: 100%; +} +.app-image { + width: 100%; +} +.app-avatar-container { + width: auto; +} +.app-avatar-upload { + width: auto; +} +} \ No newline at end of file diff --git a/src/app/auth/profile/avatar/avatar.component.spec.ts b/src/app/auth/profile/avatar/avatar.component.spec.ts new file mode 100644 index 0000000..562f867 --- /dev/null +++ b/src/app/auth/profile/avatar/avatar.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AvatarComponent } from './avatar.component'; + +describe('AvatarComponent', () => { + let component: AvatarComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AvatarComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AvatarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/profile/avatar/avatar.component.ts b/src/app/auth/profile/avatar/avatar.component.ts new file mode 100644 index 0000000..a2d6679 --- /dev/null +++ b/src/app/auth/profile/avatar/avatar.component.ts @@ -0,0 +1,160 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import Storage from '@aws-amplify/storage'; +import { NotificationService } from 'src/app/services/notification.service'; +import { CompressorService } from 'src/app/services/compressor.service'; + +@Component({ + selector: 'app-avatar', + templateUrl: './avatar.component.html', + styleUrls: ['./avatar.component.scss'] +}) +export class AvatarComponent { + + photoUrl: string; + hasPhoto: boolean = false; + uploading: boolean = false; + s3ImageFile: any = null; + s3ImagePath: string = "avatar"; + errorMessage: string; + previewClass = "app-avatar-upload"; + + private _storageOptions: any = { 'level': 'private' }; + private _previewClassIdle = "app-avatar-upload"; + private _previewClassOver = "app-avatar-upload-dragover" + + @Input() + set url(url: string) { + this.photoUrl = url; + this.hasPhoto = true; + } + + @Input() + set storageOptions(storageOptions: any){ + this._storageOptions = Object.assign(this._storageOptions, storageOptions); + } + + @Input() + set path(path: string){ + this.s3ImagePath = path; + } + + @Input() + set data(data: any) { + this.photoUrl = data.url; + this.s3ImagePath = data.path; + this._storageOptions = Object.assign(this._storageOptions, data.storageOptions); + this.hasPhoto = true; + } + + @Output() + picked: EventEmitter = new EventEmitter(); + + @Output() + loaded: EventEmitter = new EventEmitter(); + + @Output() + uploaded: EventEmitter = new EventEmitter(); + + @Output() + removed: EventEmitter = new EventEmitter(); + + constructor( private notification: NotificationService, + private compressor: CompressorService ) {} + + pick(evt) { + let file = null; + if (evt.target.files) { + file = evt.target.files[0]; + } + if (!file && evt.dataTransfer.files) { + file = evt.dataTransfer.files[0]; + } + if (!file) { return; } + const isImage = file.type.split('/')[0] === 'image'; + if (!isImage) { + this.previewClass = this._previewClassIdle; + return this.notification.show('Only images are allowed.'); + } + if (!this._storageOptions.contentType) { + this._storageOptions.contentType = file.type; + } + // console.log('file size: ', file.size); + this.picked.emit(file); + this.compressor.compress(file).subscribe( + (file: File) => { + const { name, size, type } = file; + // console.log('compressed size: ', size); + const fileName = file.name.split('.'); + const fileExt = fileName[fileName.length - 1]; + this.s3ImagePath = `${this.s3ImagePath}/picture.${fileExt}` + this.s3ImageFile = file; + const that = this; + const reader = new FileReader(); + reader.onload = function(e) { + const target: any = e.target; + const url = target.result; + that.photoUrl = url; + that.hasPhoto = true; + that.loaded.emit(url); + that.uploadFile(); + }; + reader.readAsDataURL(file); + } + ); + } + + uploadFile() { + this.uploading = true; + Storage.put( + this.s3ImagePath, + this.s3ImageFile, this._storageOptions) + .then ( (result:any) => { + this.uploaded.emit(result); + this.completeFileUpload(); + }) + .catch( error => { + this.completeFileUpload(error); + }); + } + + completeFileUpload(error?:any) { + if (error) { + return this._setError(error); + } + this.uploading = false; + } + + onPhotoError() { + this.hasPhoto = false; + } + + onAlertClose() { + this._setError(null); + } + + onDrop(event) { + event.preventDefault(); + this.pick(event); + } + + onDragover(event: DragEvent) { + event.stopPropagation(); + event.preventDefault(); + this.previewClass = this._previewClassOver; + } + + onDragout(event: DragEvent) { + event.stopPropagation(); + event.preventDefault(); + this.previewClass = this._previewClassIdle; + } + + _setError(err) { + if (!err) { + this.errorMessage = null; + return; + } + this.errorMessage = err.message || err; + } + +} \ No newline at end of file diff --git a/src/app/auth/profile/profile.component.html b/src/app/auth/profile/profile.component.html new file mode 100644 index 0000000..d91b81a --- /dev/null +++ b/src/app/auth/profile/profile.component.html @@ -0,0 +1,31 @@ +
+

My Account

+
+ + + Enter your first name + + + + + Enter your last name + + + + + + +
+ + + +
+ + + +
+
\ No newline at end of file diff --git a/src/app/auth/profile/profile.component.scss b/src/app/auth/profile/profile.component.scss new file mode 100644 index 0000000..63119af --- /dev/null +++ b/src/app/auth/profile/profile.component.scss @@ -0,0 +1,26 @@ +.container { + width: 100%; +} +form { + width: 100%; +} + +form > * { + margin-bottom: 1.5em; + width: 100%; +} + +.cursor-pointer { + cursor: pointer; +} + +.signout { + float: right; +} + +@media (min-width: 900px) { + .container { + margin: 0 auto; + width: 75%; + } +} \ No newline at end of file diff --git a/src/app/auth/profile/profile.component.spec.ts b/src/app/auth/profile/profile.component.spec.ts new file mode 100644 index 0000000..692b234 --- /dev/null +++ b/src/app/auth/profile/profile.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProfileComponent } from './profile.component'; + +describe('ProfileComponent', () => { + let component: ProfileComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ProfileComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/profile/profile.component.ts b/src/app/auth/profile/profile.component.ts new file mode 100644 index 0000000..680725e --- /dev/null +++ b/src/app/auth/profile/profile.component.ts @@ -0,0 +1,106 @@ +import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../auth.service'; +import { Router } from '@angular/router'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; +import Auth, { CognitoUser } from '@aws-amplify/auth'; +import Storage from '@aws-amplify/storage'; +import { NotificationService } from 'src/app/services/notification.service'; +import { LoaderService } from 'src/app/loader/loader.service'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.scss'] +}) +export class ProfileComponent implements OnInit { + profileForm: FormGroup = new FormGroup({ + email: new FormControl('',[ Validators.email ]), + phone: new FormControl('', [ Validators.min(10) ]), + fname: new FormControl('', [ Validators.min(2) ]), + lname: new FormControl('', [ Validators.min(2) ]) + }); + currentAvatarUrl: string; + avatar: string; + deleteAvatar = false; + profile:any = {}; + user: CognitoUser; + + get emailInput() { return this.profileForm.get('email'); } + get fnameInput() { return this.profileForm.get('fname'); } + get lnameInput() { return this.profileForm.get('lname'); } + get phoneInput() { return this.profileForm.get('phone'); } + + constructor( + private _authService: AuthService, + private _router: Router, + private _notification: NotificationService, + public loading: LoaderService ) { } + + ngOnInit() { + this.loading.show(); + this.getUserInfo(); + } + + async getUserInfo() { + this.profile = await Auth.currentUserInfo(); + this.user = await Auth.currentAuthenticatedUser(); + if ( this.profile.attributes['profile'] ) { + this.avatar = this.profile.attributes['profile']; + this.currentAvatarUrl = await Storage.vault.get(this.avatar) as string; + } + this.fnameInput.setValue(this.profile.attributes['given_name']); + this.lnameInput.setValue(this.profile.attributes['family_name']); + this.phoneInput.setValue(this.profile.attributes['phone_number']); + this.loading.hide(); + } + + getEmailInputError() { + if (this.emailInput.hasError('email')) { + return 'Please enter a valid email address.'; + } + if (this.emailInput.hasError('required')) { + return 'An Email is required.'; + } + } + + signOut() { + this._authService.signOut() + .then(() => this._router.navigate(['auth/signin'])) + } + + onAvatarUploadComplete(data: any) { + this.avatar = data.key; + this.loading.hide(); + } + + onAvatarRemove() { + this.avatar = undefined; + this.currentAvatarUrl = undefined; + this.deleteAvatar = true; + } + + async editProfile() { + try { + let attributes = { + 'given_name': this.fnameInput.value, + 'family_name': this.lnameInput.value, + 'phone_number': this.phoneInput.value + }; + if (this.avatar) { + attributes['profile'] = this.avatar; + } + await Auth.updateUserAttributes(this.user,attributes); + if (!this.avatar && this.deleteAvatar) { + this.user.deleteAttributes(["profile"],(error) => { + if (error) console.log(error); + this._notification.show('Your profile information has been updated.'); + }); + } else { + this._notification.show('Your profile information has been updated.'); + } + } catch (error) { + console.log(error); + } + } + +} diff --git a/src/app/auth/sign-in/sign-in.component.html b/src/app/auth/sign-in/sign-in.component.html new file mode 100644 index 0000000..78ee610 --- /dev/null +++ b/src/app/auth/sign-in/sign-in.component.html @@ -0,0 +1,23 @@ +
+

Sign in to your account

+ + + Enter your email address + + {{getEmailInputError()}} + + + + + {{hide ? 'visibility_off' : 'visibility'}} + Enter your password + + {{getPasswordInputError()}} + + + +

or

+

+ +

Don't have an account? Sign up

+
diff --git a/src/app/auth/sign-in/sign-in.component.scss b/src/app/auth/sign-in/sign-in.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/auth/sign-in/sign-in.component.spec.ts b/src/app/auth/sign-in/sign-in.component.spec.ts new file mode 100644 index 0000000..e5fe8d3 --- /dev/null +++ b/src/app/auth/sign-in/sign-in.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignInComponent } from './sign-in.component'; + +describe('SignInComponent', () => { + let component: SignInComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SignInComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SignInComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/sign-in/sign-in.component.ts b/src/app/auth/sign-in/sign-in.component.ts new file mode 100644 index 0000000..43b6707 --- /dev/null +++ b/src/app/auth/sign-in/sign-in.component.ts @@ -0,0 +1,80 @@ +import { Component } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; +import { AuthService } from '../auth.service'; +import { CognitoUser } from '@aws-amplify/auth'; +import { NotificationService } from 'src/app/services/notification.service'; +import { Router } from '@angular/router'; +import { environment } from 'src/environments/environment'; +import { LoaderService } from 'src/app/loader/loader.service'; + +@Component({ + selector: 'app-sign-in', + templateUrl: './sign-in.component.html', + styleUrls: ['./sign-in.component.scss'] +}) +export class SignInComponent { + + signinForm: FormGroup = new FormGroup({ + email: new FormControl('',[ Validators.email, Validators.required ]), + password: new FormControl('', [ Validators.required, Validators.min(6) ]) + }); + + hide = true; + + get emailInput() { return this.signinForm.get('email'); } + get passwordInput() { return this.signinForm.get('password'); } + + constructor( + public auth: AuthService, + private _notification: NotificationService, + private _router: Router, + private _loader: LoaderService ) { } + + getEmailInputError() { + if (this.emailInput.hasError('email')) { + return 'Please enter a valid email address.'; + } + if (this.emailInput.hasError('required')) { + return 'An Email is required.'; + } + } + + getPasswordInputError() { + if (this.passwordInput.hasError('required')) { + return 'A password is required.'; + } + } + + signIn() { + this._loader.show(); + this.auth.signIn(this.emailInput.value, this.passwordInput.value) + .then((user: CognitoUser|any) => { + this._loader.hide(); + this._router.navigate(['']); + }) + .catch((error: any) => { + this._loader.hide(); + this._notification.show(error.message); + switch (error.code) { + case "UserNotConfirmedException": + environment.confirm.email = this.emailInput.value; + environment.confirm.password = this.passwordInput.value; + this._router.navigate(['auth/confirm']); + break; + case "UsernameExistsException": + this._router.navigate(['auth/signin']); + break; + } + }) + } + + async signInWithFacebook() { + const socialResult = await this.auth.socialSignIn(AuthService.FACEBOOK); + console.log('fb Result:', socialResult); + } + + async signInWithGoogle() { + const socialResult = await this.auth.socialSignIn(AuthService.GOOGLE); + console.log('google Result:', socialResult); + } +} \ No newline at end of file diff --git a/src/app/auth/sign-up/sign-up.component.html b/src/app/auth/sign-up/sign-up.component.html new file mode 100644 index 0000000..8b22e09 --- /dev/null +++ b/src/app/auth/sign-up/sign-up.component.html @@ -0,0 +1,27 @@ +
+

Create a new account

+ + + Enter your email address + + + {{ countryCode }}   + + mode_edit + + + + Enter your first name + + + + Enter your last name + + + + {{hide ? 'visibility_off' : 'visibility'}} + Enter your password + + +

Already have an account? Sign In

+
\ No newline at end of file diff --git a/src/app/auth/sign-up/sign-up.component.scss b/src/app/auth/sign-up/sign-up.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/auth/sign-up/sign-up.component.spec.ts b/src/app/auth/sign-up/sign-up.component.spec.ts new file mode 100644 index 0000000..24c0a1c --- /dev/null +++ b/src/app/auth/sign-up/sign-up.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignUpComponent } from './sign-up.component'; + +describe('SignUpComponent', () => { + let component: SignUpComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SignUpComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SignUpComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/sign-up/sign-up.component.ts b/src/app/auth/sign-up/sign-up.component.ts new file mode 100644 index 0000000..17beef2 --- /dev/null +++ b/src/app/auth/sign-up/sign-up.component.ts @@ -0,0 +1,92 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; +import { MatBottomSheet } from '@angular/material'; +import { CountryCodeSelectComponent } from '../country-code-select/country-code-select.component'; +import { CountryCode } from '../country-code-select/country-codes'; +import { AuthService } from '../auth.service'; +import { environment } from 'src/environments/environment'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-sign-up', + templateUrl: './sign-up.component.html', + styleUrls: ['./sign-up.component.scss'] +}) +export class SignUpComponent implements OnInit { + hide = true; + signupForm: FormGroup = new FormGroup({ + email: new FormControl('', [ Validators.email, Validators.required ]), + password: new FormControl('', [ Validators.required ]), + phone: new FormControl('', [ Validators.min(10) ]), + fname: new FormControl('', [ Validators.min(2) ]), + lname: new FormControl('', [ Validators.min(2) ]) + }); + + countryCode = '+1'; + + get emailInput() { return this.signupForm.get('email'); } + get passwordInput() { return this.signupForm.get('password'); } + get fnameInput() { return this.signupForm.get('fname'); } + get lnameInput() { return this.signupForm.get('lname'); } + get phoneInput() { return this.signupForm.get('phone'); } + + constructor( + private _bottomSheet: MatBottomSheet, + private _authService: AuthService, + private _router: Router ) { + + } + + ngOnInit() { + } + + selectCountryCode() { + this._bottomSheet.open(CountryCodeSelectComponent) + .afterDismissed() + .subscribe( + (data: CountryCode) => { + this.countryCode = (data) ? data.dial_code : this.countryCode; + } + ); + } + + getEmailInputError() { + if (this.emailInput.hasError('email')) { + return 'Please enter a valid email address.'; + } + if (this.emailInput.hasError('required')) { + return 'An Email is required.'; + } + } + + getPasswordInputError() { + if (this.passwordInput.hasError('required')) { + return 'A password is required.'; + } + } + + shouldEnableSubmit() { + return !this.emailInput.valid || + !this.passwordInput.valid || + !this.fnameInput.valid || + !this.lnameInput.valid || + !this.phoneInput.valid; + } + + signUp() { + this._authService.signUp({ + "email": this.emailInput.value, + "password": this.passwordInput.value, + "firstName": this.fnameInput.value, + "lastName": this.lnameInput.value, + "phone": this.countryCode + this.phoneInput.value + }) + .then((data) => { + environment.confirm.email = this.emailInput.value; + environment.confirm.password = this.passwordInput.value; + this._router.navigate(['auth/confirm']); + }) + .catch((error) => console.log(error)); + } + +} \ No newline at end of file diff --git a/src/app/auth/unauth.guard.spec.ts b/src/app/auth/unauth.guard.spec.ts new file mode 100644 index 0000000..202c0f4 --- /dev/null +++ b/src/app/auth/unauth.guard.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, async, inject } from '@angular/core/testing'; + +import { UnauthGuard } from './unauth.guard'; + +describe('UnauthGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [UnauthGuard] + }); + }); + + it('should ...', inject([UnauthGuard], (guard: UnauthGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/src/app/auth/unauth.guard.ts b/src/app/auth/unauth.guard.ts new file mode 100644 index 0000000..57faf6b --- /dev/null +++ b/src/app/auth/unauth.guard.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import Auth from '@aws-amplify/auth'; + +@Injectable({ + providedIn: 'root' +}) +export class UnauthGuard implements CanActivate { + constructor( private _router: Router ) { } + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean { + return Auth.currentAuthenticatedUser() + .then(() => { + this._router.navigate(['auth/profile']); + return false; + }) + .catch(() => { + return true; + }); + } +} \ No newline at end of file diff --git a/src/app/loader/loader.component.html b/src/app/loader/loader.component.html new file mode 100644 index 0000000..420fbd6 --- /dev/null +++ b/src/app/loader/loader.component.html @@ -0,0 +1,4 @@ +
+ +

{{message}}

+
diff --git a/src/app/loader/loader.component.scss b/src/app/loader/loader.component.scss new file mode 100644 index 0000000..f70bfb6 --- /dev/null +++ b/src/app/loader/loader.component.scss @@ -0,0 +1,7 @@ +.loader > * { + text-align: center; + margin: 0 auto; +} +.loader p { + margin-top: 1em; +} diff --git a/src/app/loader/loader.component.spec.ts b/src/app/loader/loader.component.spec.ts new file mode 100644 index 0000000..35adf49 --- /dev/null +++ b/src/app/loader/loader.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoaderComponent } from './loader.component'; + +describe('LoaderComponent', () => { + let component: LoaderComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoaderComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loader/loader.component.ts b/src/app/loader/loader.component.ts new file mode 100644 index 0000000..9b5b725 --- /dev/null +++ b/src/app/loader/loader.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material'; + +@Component({ + selector: 'app-loader', + templateUrl: './loader.component.html', + styleUrls: ['./loader.component.scss'] +}) +export class LoaderComponent implements OnInit { + + message = 'Please wait...'; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: any + ) { + if (data.message) { + this.message = data.message; + } + } + + ngOnInit() { + } + +} diff --git a/src/app/loader/loader.service.spec.ts b/src/app/loader/loader.service.spec.ts new file mode 100644 index 0000000..ff3167a --- /dev/null +++ b/src/app/loader/loader.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoaderService } from './loader.service'; + +describe('LoaderService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: LoaderService = TestBed.get(LoaderService); + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/loader/loader.service.ts b/src/app/loader/loader.service.ts new file mode 100644 index 0000000..ce78909 --- /dev/null +++ b/src/app/loader/loader.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material'; +import { LoaderComponent } from './loader.component'; + +@Injectable({ + providedIn: 'root' +}) +export class LoaderService { + loading: boolean; + dialogRef: MatDialogRef; + constructor( private _dialog: MatDialog ) { } + + show(message: string = "Please wait..."): void { + setTimeout(() => { + this.loading = true; + this.dialogRef = this._dialog.open(LoaderComponent, { + width: '80%', + data: { 'message': message }, + closeOnNavigation: false + }); + }); + } + + hide() { + if (this.dialogRef) { + this.dialogRef.close(); + this.loading = false; + } + } +} diff --git a/src/app/services/compressor.service.spec.ts b/src/app/services/compressor.service.spec.ts new file mode 100644 index 0000000..c0572fb --- /dev/null +++ b/src/app/services/compressor.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { CompressorService } from './compressor.service'; + +describe('CompressorService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: CompressorService = TestBed.get(CompressorService); + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/compressor.service.ts b/src/app/services/compressor.service.ts new file mode 100644 index 0000000..0bb43c9 --- /dev/null +++ b/src/app/services/compressor.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { Observable} from 'rxjs'; +@Injectable({ + providedIn: 'root' +}) +export class CompressorService { + + constructor() { } + compress(file: File): Observable { + const width = 128; // For scaling relative to width + const reader = new FileReader(); + reader.readAsDataURL(file); + return new Observable(observer => { + reader.onload = ev => { + const img = new Image(); + img.src = (ev.target as any).result; + (img.onload = () => { + const elem = document.createElement('canvas'); // Use Angular's Renderer2 method + const scaleFactor = width / img.width; + elem.width = width; + elem.height = img.height * scaleFactor; + const ctx = elem.getContext('2d') as CanvasRenderingContext2D; + ctx.drawImage(img, 0, 0, width, img.height * scaleFactor); + ctx.canvas.toBlob( + blob => { + observer.next( + new File([blob], file.name, { + type: file.type, + lastModified: Date.now(), + }), + ); + }, + file.type, + 1, + ); + }), + (reader.onerror = error => observer.error(error)); + }; + }); + } +} diff --git a/src/app/services/notification.service.spec.ts b/src/app/services/notification.service.spec.ts new file mode 100644 index 0000000..698c421 --- /dev/null +++ b/src/app/services/notification.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { NotificationService } from './notification.service'; + +describe('NotificationService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: NotificationService = TestBed.get(NotificationService); + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/notification.service.ts b/src/app/services/notification.service.ts new file mode 100644 index 0000000..fd6ee69 --- /dev/null +++ b/src/app/services/notification.service.ts @@ -0,0 +1,57 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { MatSnackBar, MatSnackBarRef } from '@angular/material'; +import { Subscription } from 'rxjs'; + +/** + * Provides an abstract wrapper around showing a MatSnackbar + * notification based on global environment or API provided + * configuration. + * + * This class Listens for the authentication state to change. + * Once the state becomes authenticated, retrieve the startup + * configuration from the API service. Once de-authenticated + * set the _params to undefined and unsubscribe. + */ +@Injectable({ + providedIn: 'root' +}) +export class NotificationService implements OnDestroy { + + // Configuration api subscription + private _configState: Subscription; + /** + * Constructor + * @param toast {MatSnackBar} + * @param configService {ConfigurationService} + */ + constructor( + private toast: MatSnackBar) {} + + /** + * Unsubscribe from the config state + * when the component is destroyed, and remove + * the in-memory parameters. + */ + ngOnDestroy() { + this._configState.unsubscribe(); + } + + /** + * Display a MatSnackbar notification and return the reference. + * Will set the duration to the global configuration if present. + * @param message {string} + * @param buttonLabel {string} + * @returns {MatSnackBarRef} + */ + show(message: string, buttonLabel: string = 'OK'): MatSnackBarRef { + const toastTimeout = 8000; + if (toastTimeout > 0) { + return this.toast.open(message, buttonLabel, { + duration: toastTimeout + }); + } else { + return this.toast.open(message, buttonLabel, { + }); + } + } +} diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7b4f817..959da50 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,11 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + confirm: { + email: '', + password: '' + } }; /* diff --git a/src/index.html b/src/index.html index 282377b..7943813 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - HPIoTWebApp + Henry Pump SCADA diff --git a/src/main.ts b/src/main.ts index 3b2b7d0..4424adb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,13 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +// Amplify Config +import Auth from '@aws-amplify/auth'; +import AWSConfig from './aws-exports'; + +Auth.configure(AWSConfig); + + if (environment.production) { enableProdMode(); } diff --git a/src/polyfills.ts b/src/polyfills.ts index aa665d6..c303cf5 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -55,6 +55,7 @@ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ +(window as any).global = window; import 'zone.js/dist/zone'; // Included with Angular CLI. diff --git a/tsconfig.app.json b/tsconfig.app.json index 565a11a..45f325a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["node"] }, "files": [ "src/main.ts",