added login
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
52
amplify/backend/auth/hpiotwebapp78e5977f/parameters.json
Normal file
52
amplify/backend/auth/hpiotwebapp78e5977f/parameters.json
Normal file
@@ -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": []
|
||||
}
|
||||
@@ -4,5 +4,12 @@
|
||||
"service": "S3AndCloudFront",
|
||||
"providerPlugin": "awscloudformation"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"hpiotwebapp78e5977f": {
|
||||
"service": "Cognito",
|
||||
"providerPlugin": "awscloudformation",
|
||||
"dependsOn": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
343
package-lock.json
generated
343
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)],
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
2
src/app/auth/auth.component.html
Normal file
2
src/app/auth/auth.component.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
9
src/app/auth/auth.component.scss
Normal file
9
src/app/auth/auth.component.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
:host {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.auth-btn-signup {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
right: 2em;
|
||||
}
|
||||
25
src/app/auth/auth.component.spec.ts
Normal file
25
src/app/auth/auth.component.spec.ts
Normal file
@@ -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<AuthComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AuthComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AuthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
37
src/app/auth/auth.component.ts
Normal file
37
src/app/auth/auth.component.ts
Normal file
@@ -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(() => {});
|
||||
}
|
||||
|
||||
}
|
||||
15
src/app/auth/auth.guard.spec.ts
Normal file
15
src/app/auth/auth.guard.spec.ts
Normal file
@@ -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();
|
||||
}));
|
||||
});
|
||||
20
src/app/auth/auth.guard.ts
Normal file
20
src/app/auth/auth.guard.ts
Normal file
@@ -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<boolean> | Promise<boolean> | boolean {
|
||||
return Auth.currentAuthenticatedUser().then(() => { return true; })
|
||||
.catch(() => {
|
||||
this._router.navigate(['auth/signin']);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
12
src/app/auth/auth.service.spec.ts
Normal file
12
src/app/auth/auth.service.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
72
src/app/auth/auth.service.ts
Normal file
72
src/app/auth/auth.service.ts
Normal file
@@ -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<CognitoUser|any> = new Subject<CognitoUser|any>();
|
||||
authState: Observable<CognitoUser|any> = 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<CognitoUser|any> {
|
||||
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<CognitoUser|any> {
|
||||
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<any> {
|
||||
return Auth.signOut()
|
||||
.then(() => this.loggedIn = false);
|
||||
}
|
||||
|
||||
socialSignIn(provider:CognitoHostedUIIdentityProvider): Promise<ICredentials> {
|
||||
return Auth.federatedSignIn({
|
||||
'provider': provider
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
12
src/app/auth/confirm-code/confirm-code.component.html
Normal file
12
src/app/auth/confirm-code/confirm-code.component.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<form [formGroup]="confirmForm">
|
||||
<h2>Confirm your email address</h2>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Email" type="email" formControlName="email">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Confirmation Code" type="text" formControlName="code" required>
|
||||
<mat-hint *ngIf="!codeInput.value">Enter the confirmation code that was emailed to you</mat-hint>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="confirmCode()">Confirm</button>
|
||||
<p>Didn't receive a code? <a href="javascript:void(null)" (click)="sendAgain()">Send again</a></p>
|
||||
</form>
|
||||
25
src/app/auth/confirm-code/confirm-code.component.spec.ts
Normal file
25
src/app/auth/confirm-code/confirm-code.component.spec.ts
Normal file
@@ -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<ConfirmCodeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ConfirmCodeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfirmCodeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
60
src/app/auth/confirm-code/confirm-code.component.ts
Normal file
60
src/app/auth/confirm-code/confirm-code.component.ts
Normal file
@@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<mat-form-field class="full-width country-filter">
|
||||
<input matInput [(ngModel)]="searchText" placeholder="Filter Countries">
|
||||
</mat-form-field>
|
||||
<mat-nav-list class="country-list">
|
||||
<a mat-list-item *ngFor="let code of countryCodes | filter : searchText" (click)="selectCountry(code)">
|
||||
<span mat-line>{{ code.name }}</span>
|
||||
<span mat-line>{{ code.dial_code }}</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.country-list {
|
||||
margin-top: 5px;
|
||||
height: 300px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.country-filter {
|
||||
position: sticky;
|
||||
top: 0em;
|
||||
margin-top:0.5em;
|
||||
}
|
||||
@@ -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<CountryCodeSelectComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CountryCodeSelectComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CountryCodeSelectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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<CountryCode> = CountryCodes;
|
||||
constructor(private bottomSheetRef: MatBottomSheetRef<CountryCodeSelectComponent>) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
selectCountry(code: CountryCode) {
|
||||
this.bottomSheetRef.dismiss(code);
|
||||
}
|
||||
|
||||
}
|
||||
7
src/app/auth/country-code-select/country-codes.spec.ts
Normal file
7
src/app/auth/country-code-select/country-codes.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { CountryCodes } from './country-codes';
|
||||
|
||||
describe('CountryCodes', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new CountryCodes()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
1213
src/app/auth/country-code-select/country-codes.ts
Normal file
1213
src/app/auth/country-code-select/country-codes.ts
Normal file
File diff suppressed because it is too large
Load Diff
8
src/app/auth/country-code-select/filter.pipe.spec.ts
Normal file
8
src/app/auth/country-code-select/filter.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { FilterPipe } from './filter.pipe';
|
||||
|
||||
describe('FilterPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new FilterPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
19
src/app/auth/country-code-select/filter.pipe.ts
Normal file
19
src/app/auth/country-code-select/filter.pipe.ts
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
33
src/app/auth/profile/avatar/avatar.component.html
Normal file
33
src/app/auth/profile/avatar/avatar.component.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="app-avatar">
|
||||
<div class="app-avatar-container">
|
||||
|
||||
<div
|
||||
[class]="previewClass"
|
||||
*ngIf="!hasPhoto"
|
||||
(drop)="onDrop($event)"
|
||||
(dragleave)="onDragout($event)"
|
||||
(dragover)="onDragover($event)"></div>
|
||||
<div class="app-avatar-preview">
|
||||
<img class="app-avatar-preview"
|
||||
src="{{photoUrl}}"
|
||||
*ngIf="hasPhoto"
|
||||
alt="Avatar Preview"
|
||||
(error)="onPhotoError()"
|
||||
/>
|
||||
<button mat-mini-fab (click)="this.removed.emit()" *ngIf="hasPhoto && !uploading" class="avatar-remove" title="Delete avatar" aria-label="Delete avatar"><mat-icon>close</mat-icon></button>
|
||||
<small *ngIf="uploading"><br/>Processing Avatar...</small>
|
||||
</div>
|
||||
<div class="app-upload-input" *ngIf="!uploading && !hasPhoto">
|
||||
<input type="file"
|
||||
accept="image/*"
|
||||
(change)="pick($event)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-alert" *ngIf="errorMessage">
|
||||
<div class="app-alert-body">
|
||||
<span class="app-alert-icon">⚠</span>
|
||||
<div class="app-alert-message">{{ errorMessage }}</div>
|
||||
<a class="app-alert-close" (click)="onAlertClose()">×</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
71
src/app/auth/profile/avatar/avatar.component.scss
Normal file
71
src/app/auth/profile/avatar/avatar.component.scss
Normal file
File diff suppressed because one or more lines are too long
25
src/app/auth/profile/avatar/avatar.component.spec.ts
Normal file
25
src/app/auth/profile/avatar/avatar.component.spec.ts
Normal file
@@ -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<AvatarComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AvatarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvatarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
160
src/app/auth/profile/avatar/avatar.component.ts
Normal file
160
src/app/auth/profile/avatar/avatar.component.ts
Normal file
@@ -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<string> = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
loaded: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
uploaded: EventEmitter<Object> = new EventEmitter<Object>();
|
||||
|
||||
@Output()
|
||||
removed: EventEmitter<null> = new EventEmitter<null>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
31
src/app/auth/profile/profile.component.html
Normal file
31
src/app/auth/profile/profile.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="container">
|
||||
<h2>My Account</h2>
|
||||
<form [formGroup]="profileForm" autocomplete="on">
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="First Name" type="text" formControlName="fname" autocomplete="family-name" required>
|
||||
<mat-hint *ngIf="!fnameInput.value">Enter your first name</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Last Name" type="text" formControlName="lname" autocomplete="given-name" required>
|
||||
<mat-hint *ngIf="!lnameInput.value">Enter your last name</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<input type="tel" matInput placeholder="Telephone" formControlName="phone" autocomplete="tel">
|
||||
</mat-form-field>
|
||||
|
||||
<div class="profile-picture">
|
||||
<label>Profile Picture</label>
|
||||
<app-avatar
|
||||
[url]="currentAvatarUrl"
|
||||
(loaded)="this.loading.show()"
|
||||
(uploaded)="onAvatarUploadComplete($event)"
|
||||
(removed)="onAvatarRemove()">
|
||||
</app-avatar>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="editProfile()" [disabled]="loading.loading || !profileForm.valid">Save</button>
|
||||
<button mat-raised-button (click)="signOut()" class="signout">Sign Out</button>
|
||||
</form>
|
||||
</div>
|
||||
26
src/app/auth/profile/profile.component.scss
Normal file
26
src/app/auth/profile/profile.component.scss
Normal file
@@ -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%;
|
||||
}
|
||||
}
|
||||
25
src/app/auth/profile/profile.component.spec.ts
Normal file
25
src/app/auth/profile/profile.component.spec.ts
Normal file
@@ -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<ProfileComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ProfileComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
106
src/app/auth/profile/profile.component.ts
Normal file
106
src/app/auth/profile/profile.component.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
src/app/auth/sign-in/sign-in.component.html
Normal file
23
src/app/auth/sign-in/sign-in.component.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<form [formGroup]="signinForm">
|
||||
<h2>Sign in to your account</h2>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Email" type="email" formControlName="email" autocomplete="email" required>
|
||||
<mat-hint *ngIf="!emailInput.value">Enter your email address</mat-hint>
|
||||
<mat-error *ngIf="!emailInput.valid">
|
||||
{{getEmailInputError()}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Password" [type]="hide ? 'password' : 'text'" formControlName="password" required>
|
||||
<mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
<mat-hint *ngIf="!passwordInput.value">Enter your password</mat-hint>
|
||||
<mat-error *ngIf="!passwordInput.valid">
|
||||
{{getPasswordInputError()}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="signIn()" [disabled]="!signinForm.valid">Sign In</button>
|
||||
<p style="text-align:center;">or</p>
|
||||
<button mat-raised-button color="primary" (click)="signInWithFacebook()">Sign In with Facebook</button><br/><br/>
|
||||
<button mat-raised-button color="primary" (click)="signInWithGoogle()">Sign In with Google</button>
|
||||
<p>Don't have an account? <a routerLink="/auth/signup">Sign up</a></p>
|
||||
</form>
|
||||
0
src/app/auth/sign-in/sign-in.component.scss
Normal file
0
src/app/auth/sign-in/sign-in.component.scss
Normal file
25
src/app/auth/sign-in/sign-in.component.spec.ts
Normal file
25
src/app/auth/sign-in/sign-in.component.spec.ts
Normal file
@@ -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<SignInComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SignInComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SignInComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
80
src/app/auth/sign-in/sign-in.component.ts
Normal file
80
src/app/auth/sign-in/sign-in.component.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
27
src/app/auth/sign-up/sign-up.component.html
Normal file
27
src/app/auth/sign-up/sign-up.component.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<form [formGroup]="signupForm">
|
||||
<h2>Create a new account</h2>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Email" type="email" formControlName="email" autocomplete="email" required>
|
||||
<mat-hint *ngIf="!emailInput.value">Enter your email address</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<span matPrefix>{{ countryCode }} </span>
|
||||
<input type="tel" matInput placeholder="Telephone" formControlName="phone" autocomplete="tel">
|
||||
<mat-icon matSuffix class="cursor-pointer" (click)="selectCountryCode()">mode_edit</mat-icon>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="First Name" type="text" formControlName="fname" autocomplete="given-name" required>
|
||||
<mat-hint *ngIf="!fnameInput.value">Enter your first name</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Last Name" type="text" formControlName="lname" autocomplete="family-name" required>
|
||||
<mat-hint *ngIf="!lnameInput.value">Enter your last name</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Password" [type]="hide ? 'password' : 'text'" formControlName="password" required>
|
||||
<mat-icon matSuffix class="cursor-pointer" (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
<mat-hint *ngIf="!passwordInput.value">Enter your password</mat-hint>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="signUp()" [disabled]="shouldEnableSubmit()">Sign up</button>
|
||||
<p class="center">Already have an account? <a routerLink="/auth/signin">Sign In</a></p>
|
||||
</form>
|
||||
0
src/app/auth/sign-up/sign-up.component.scss
Normal file
0
src/app/auth/sign-up/sign-up.component.scss
Normal file
25
src/app/auth/sign-up/sign-up.component.spec.ts
Normal file
25
src/app/auth/sign-up/sign-up.component.spec.ts
Normal file
@@ -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<SignUpComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SignUpComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SignUpComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
92
src/app/auth/sign-up/sign-up.component.ts
Normal file
92
src/app/auth/sign-up/sign-up.component.ts
Normal file
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
15
src/app/auth/unauth.guard.spec.ts
Normal file
15
src/app/auth/unauth.guard.spec.ts
Normal file
@@ -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();
|
||||
}));
|
||||
});
|
||||
27
src/app/auth/unauth.guard.ts
Normal file
27
src/app/auth/unauth.guard.ts
Normal file
@@ -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<boolean> | Promise<boolean> | boolean {
|
||||
return Auth.currentAuthenticatedUser()
|
||||
.then(() => {
|
||||
this._router.navigate(['auth/profile']);
|
||||
return false;
|
||||
})
|
||||
.catch(() => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
4
src/app/loader/loader.component.html
Normal file
4
src/app/loader/loader.component.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div class="loader">
|
||||
<mat-spinner></mat-spinner>
|
||||
<p> {{message}} </p>
|
||||
</div>
|
||||
7
src/app/loader/loader.component.scss
Normal file
7
src/app/loader/loader.component.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.loader > * {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.loader p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
25
src/app/loader/loader.component.spec.ts
Normal file
25
src/app/loader/loader.component.spec.ts
Normal file
@@ -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<LoaderComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
24
src/app/loader/loader.component.ts
Normal file
24
src/app/loader/loader.component.ts
Normal file
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
12
src/app/loader/loader.service.spec.ts
Normal file
12
src/app/loader/loader.service.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
30
src/app/loader/loader.service.ts
Normal file
30
src/app/loader/loader.service.ts
Normal file
@@ -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<LoaderComponent>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/app/services/compressor.service.spec.ts
Normal file
12
src/app/services/compressor.service.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
41
src/app/services/compressor.service.ts
Normal file
41
src/app/services/compressor.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable} from 'rxjs';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CompressorService {
|
||||
|
||||
constructor() { }
|
||||
compress(file: File): Observable<any> {
|
||||
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));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
12
src/app/services/notification.service.spec.ts
Normal file
12
src/app/services/notification.service.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
57
src/app/services/notification.service.ts
Normal file
57
src/app/services/notification.service.ts
Normal file
@@ -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<any> {
|
||||
const toastTimeout = 8000;
|
||||
if (toastTimeout > 0) {
|
||||
return this.toast.open(message, buttonLabel, {
|
||||
duration: toastTimeout
|
||||
});
|
||||
} else {
|
||||
return this.toast.open(message, buttonLabel, {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: ''
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>HPIoTWebApp</title>
|
||||
<title>Henry Pump SCADA</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
|
||||
Reference in New Issue
Block a user