├── .github └── dependabot.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── amplify ├── backend.ts ├── custom │ └── customResources │ │ └── resource.ts ├── data │ └── resource.ts ├── package.json └── tsconfig.json ├── assets ├── diagram.jpg ├── web_ui.jpg └── workflow.jpg ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.jsx └── main.jsx └── vite.config.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "weekly" 10 | day: "tuesday" 11 | time: "09:00" 12 | timezone: "Europe/Berlin" 13 | versioning-strategy: "increase" 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | *.iml 26 | 27 | #amplify-do-not-edit-begin 28 | amplify/\#current-cloud-backend 29 | amplify/.config/local-* 30 | amplify/logs 31 | amplify/mock-data 32 | amplify/mock-api-resources 33 | amplify/backend/amplify-meta.json 34 | amplify/backend/.temp 35 | build/ 36 | dist/ 37 | node_modules/ 38 | aws-exports.js 39 | awsconfiguration.json 40 | amplifyconfiguration.json 41 | amplifyconfiguration.dart 42 | amplify-build-config.json 43 | amplify-gradle-config.json 44 | amplifytools.xcconfig 45 | .secret-* 46 | **.sample 47 | #amplify-do-not-edit-end 48 | 49 | # amplify 50 | .amplify 51 | amplify_outputs* 52 | amplifyconfiguration* 53 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-amplify-stepfunctions-example 2 | 3 | > [!Note] 4 | > This sample was originally published on the AWS Front-End Web & Mobile blog in the post [Integrate AWS Step Functions with AWS Amplify using Amplify custom resources](https://aws.amazon.com/blogs/mobile/integrate-aws-step-functions-with-aws-amplify-using-amplify-custom-resources/). The original version used the AWS Amplify CLI to create the custom resource. The current version uses Amplify Gen 2 instead. 5 | 6 | In this sample, we'll demonstrate how to integrate custom resources with AWS Amplify Gen 2 using the AWS Cloud Development Kit (CDK). We'll create a Step Functions workflow as an Amplify custom resource and connect it to an existing Amplify-managed GraphQL API. 7 | 8 | ## What you will learn 9 | 10 | - How to create a Step Functions workflow as an Amplify custom resource using the AWS CDK. 11 | - How to connect our custom resource to an existing Amplify-managed GraphQL API. 12 | 13 | ## What you will build 14 | 15 |

16 | 17 |

18 | 19 | The proposed solution consists of the following elements: 20 | 21 | - Our sample web application is a customer feedback form built using [Vite](https://vitejs.dev/) and [Amplify UI](https://ui.docs.amplify.aws/). 22 | - Submitting the feedback form will trigger a Step Functions [express workflow](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html) created as an Amplify custom resource via an [AWS AppSync](https://aws.amazon.com/appsync/) API managed by Amplify. 23 | - The Step Function workflow will detect the sentiment of the submitted feedback using [Amazon Comprehend](https://aws.amazon.com/comprehend/)’s `DetectSentiment` API. 24 | - Next, the workflow will store the feedback and detected sentiment in an Amplify-managed [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) table. 25 | - If a non-positive sentiment is detected, the workflow will trigger a notification to a customer support email address using the [Amazon Simple Notification Service (Amazon SNS)](https://aws.amazon.com/sns/). 26 | - Depending on the result of the sentiment analysis, our web application will display different confirmation messages to the customer. 27 | 28 | The Step Functions workflow looks like this: 29 | 30 |

31 | 32 |

33 | 34 | From the perspective of the user of our web application, the result will look like this: 35 | 36 |

37 | 38 |

39 | 40 | ## Deploy the sample 41 | 42 | To deploy the sample, you need to have Node.js 18.x or later installed on your machine. 43 | 44 | Clone the repository and navigate to the `aws-amplify-stepfunctions-example` directory: 45 | 46 | ```bash 47 | git clone https://github.com/aws-samples/aws-amplify-stepfunctions-example.git 48 | cd aws-amplify-stepfunctions-example 49 | ``` 50 | 51 | Install the dependencies: 52 | 53 | ```bash 54 | npm ci 55 | ``` 56 | 57 | Fill in your email address in the `amplify/backent.ts` file to receive the notification emails: 58 | 59 | ```diff 60 | new CustomResources( 61 | backend.createStack('customResources'), 62 | 'customResources', 63 | { 64 | data: { 65 | apiId: backend.data.apiId, 66 | }, 67 | notification: { 68 | - emailAddress: "hello@email.com" // Fill in your email address 69 | } 70 | } 71 | ); 72 | ``` 73 | 74 | Deploy the sample: 75 | 76 | ```bash 77 | npx ampx sandbox 78 | ``` 79 | 80 | After the deployment is complete, you can start the development server: 81 | 82 | ```bash 83 | npm run dev 84 | ``` 85 | 86 | ## Security 87 | 88 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 89 | 90 | ## License 91 | 92 | This library is licensed under the MIT-0 License. See the LICENSE file. 93 | -------------------------------------------------------------------------------- /amplify/backend.ts: -------------------------------------------------------------------------------- 1 | import { defineBackend } from '@aws-amplify/backend'; 2 | import { data } from './data/resource'; 3 | import { CustomResources } from "./custom/customResources/resource"; 4 | 5 | /** 6 | * @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more 7 | */ 8 | const backend = defineBackend({ 9 | data, 10 | }); 11 | 12 | new CustomResources( 13 | backend.createStack('customResources'), 14 | 'customResources', 15 | { 16 | data: { 17 | apiId: backend.data.apiId, 18 | }, 19 | notification: { 20 | emailAddress: "hello@email.com" // Fill in your email address 21 | } 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /amplify/custom/customResources/resource.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "aws-cdk-lib"; 2 | import { Construct } from "constructs"; 3 | import * as iam from "aws-cdk-lib/aws-iam"; 4 | import * as sns from "aws-cdk-lib/aws-sns"; 5 | import * as subs from "aws-cdk-lib/aws-sns-subscriptions"; 6 | import * as appsync from "aws-cdk-lib/aws-appsync"; 7 | import * as sfn from "aws-cdk-lib/aws-stepfunctions"; 8 | import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks"; 9 | import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; 10 | 11 | /** 12 | * The GraphoQL API to which the custom resource will be added 13 | **/ 14 | type CustomResourceProps = { 15 | data: { 16 | apiId: string 17 | }, 18 | notification: { 19 | emailAddress: string 20 | } 21 | }; 22 | const START_EXECUTION_REQUEST_TEMPLATE = (stateMachineArn: String) => { 23 | return ` 24 | { 25 | "version": "2018-05-29", 26 | "method": "POST", 27 | "resourcePath": "/", 28 | "params": { 29 | "headers": { 30 | "content-type": "application/x-amz-json-1.0", 31 | "x-amz-target":"AWSStepFunctions.StartSyncExecution" 32 | }, 33 | "body": { 34 | "stateMachineArn": "${stateMachineArn}", 35 | "input": "{ \\\"input\\\": \\\"$context.args.input\\\"}" 36 | } 37 | } 38 | } 39 | `; 40 | }; 41 | 42 | const RESPONSE_TEMPLATE = ` 43 | ## Raise a GraphQL field error in case of a datasource invocation error 44 | #if($ctx.error) 45 | $util.error($ctx.error.message, $ctx.error.type) 46 | #end 47 | ## if the response status code is not 200, then return an error. Else return the body ** 48 | #if($ctx.result.statusCode == 200) 49 | ## If response is 200, return the body. 50 | $ctx.result.body 51 | #else 52 | ## If response is not 200, append the response to error block. 53 | $utils.appendError($ctx.result.body, $ctx.result.statusCode) 54 | #end 55 | `; 56 | 57 | export class CustomResources extends Construct { 58 | constructor( scope: Construct, id: string, props: CustomResourceProps ) 59 | { 60 | super(scope, id); 61 | 62 | const { data, notification} = props; 63 | 64 | // References the existing API via its ID 65 | const api = appsync.GraphqlApi.fromGraphqlApiAttributes(this, "API", { 66 | graphqlApiId: data.apiId, 67 | }); 68 | 69 | // Adds the AWS Step Functions (SFN) service endpoint as a new HTTP data source to the GraphQL API 70 | const httpdatasource = api.addHttpDataSource( 71 | "ds", 72 | "https://sync-states." + cdk.Stack.of(this).region + ".amazonaws.com", 73 | { 74 | name: "HTTPDataSourceWithSFN", 75 | authorizationConfig: { 76 | signingRegion: cdk.Stack.of(this).region, 77 | signingServiceName: "states", 78 | }, 79 | } 80 | ); 81 | 82 | /* 83 | Defines the first task in our SFN workflow. 84 | We call the Amazon Comprehend detectSentiment API with 85 | the input provided with the SFN execution. 86 | */ 87 | const detect_sentiment_task = new tasks.CallAwsService( 88 | this, 89 | "Detect feedback sentiment", 90 | { 91 | service: "comprehend", 92 | action: "detectSentiment", 93 | iamResources: ["*"], 94 | iamAction: "comprehend:DetectSentiment", 95 | parameters: { "Text.$": "$.input", LanguageCode: "en" }, 96 | resultPath: "$.DetectSentiment", 97 | } 98 | ); 99 | 100 | // Import the DynamoDB table created by Amplify as a result of the @model directive in our GraphQL schema 101 | const feedbackTable = dynamodb.Table.fromTableName( 102 | this, 103 | "FeedbackTable", 104 | "Feedback-" + data.apiId + "-" + 'NONE' 105 | ); 106 | 107 | // Save feedback and detected sentiment to DynamoDB table 108 | const save_to_ddb = new tasks.DynamoPutItem( 109 | this, 110 | "Record feedback and sentiment", 111 | { 112 | item: { 113 | id: tasks.DynamoAttributeValue.fromString( 114 | sfn.JsonPath.stringAt("$$.Execution.Id") 115 | ), 116 | __typename: tasks.DynamoAttributeValue.fromString("Feedback"), 117 | createdAt: tasks.DynamoAttributeValue.fromString( 118 | sfn.JsonPath.stringAt("$$.State.EnteredTime") 119 | ), 120 | updatedAt: tasks.DynamoAttributeValue.fromString( 121 | sfn.JsonPath.stringAt("$$.State.EnteredTime") 122 | ), 123 | content: tasks.DynamoAttributeValue.fromString( 124 | sfn.JsonPath.stringAt("$.input") 125 | ), 126 | sentiment: tasks.DynamoAttributeValue.fromString( 127 | sfn.JsonPath.stringAt("$.DetectSentiment.Sentiment") 128 | ), 129 | }, 130 | table: feedbackTable, 131 | resultPath: sfn.JsonPath.DISCARD, 132 | } 133 | ); 134 | 135 | // Creates an Amazon SNS topic to which we'll later publish notifications from our SFN workflow 136 | const customer_support_topic = new sns.Topic( 137 | this, 138 | "Customer support SNS topic" 139 | ); 140 | 141 | /* Creates a subscription to the topic defined above using our own email 142 | address. Make sure to replace this with an actual email address you have 143 | access to. 144 | */ 145 | customer_support_topic.addSubscription( 146 | new subs.EmailSubscription(notification.emailAddress) // <- replace with your email 147 | ); 148 | 149 | /* 150 | Defines a SFN task that publishs a notification 151 | containing the sentiment detected by Amazon Rekognition to 152 | the SNS topic we defined above. 153 | */ 154 | const handleNonPositiveResult = new tasks.SnsPublish( 155 | this, 156 | "Notify customer support", 157 | { 158 | topic: customer_support_topic, 159 | message: sfn.TaskInput.fromObject({ 160 | Message: "Non-positive feedback detected.", 161 | "Detected sentiment": sfn.JsonPath.stringAt( 162 | "$.DetectSentiment.Sentiment" 163 | ), 164 | }), 165 | } 166 | ); 167 | 168 | // Defines a pass state that outputs that a negative sentiment was detected 169 | const nonPositiveResult = new sfn.Pass( 170 | this, 171 | "Non-positive feedback received", 172 | { 173 | result: sfn.Result.fromObject({ Sentiment: "NON-POSITIVE" }), 174 | } 175 | ); 176 | 177 | // Defines what state the workflow moves to after the handleNonPositiveResult state 178 | handleNonPositiveResult.next(nonPositiveResult); 179 | 180 | // Defines a pass state that outputs that a positive sentiment was detected 181 | const positiveResult = new sfn.Pass(this, "Positive feedback received", { 182 | result: sfn.Result.fromObject({ Sentiment: "POSITIVE" }), 183 | }); 184 | 185 | // Defines a Choice state 186 | const sentiment_choice = new sfn.Choice( 187 | this, 188 | "Positive or non-positive sentiment?" 189 | ); 190 | 191 | // Defines what happens if our Choice state receives a positive sentiment 192 | sentiment_choice.when( 193 | sfn.Condition.stringEquals("$.DetectSentiment.Sentiment", "POSITIVE"), 194 | positiveResult 195 | ); 196 | 197 | // Defines what happens if our Choice state receives anything other than a positive sentiment 198 | sentiment_choice.otherwise(handleNonPositiveResult); 199 | 200 | // The state machine definition brings together all our defined tasks 201 | const stateMachineDefinition = detect_sentiment_task 202 | .next(save_to_ddb) 203 | .next(sentiment_choice); 204 | 205 | // Create a service role for SFN to use 206 | const serviceRole = new iam.Role(this, "Role", { 207 | assumedBy: new iam.ServicePrincipal( 208 | "states." + cdk.Stack.of(this).region + ".amazonaws.com" 209 | ), 210 | }); 211 | 212 | /* 213 | Defines the express SFN workflow resource using the state 214 | machine definition as well as the service role defined above. 215 | */ 216 | const stateMachine = new sfn.StateMachine(this, "SyncStateMachine", { 217 | definition: stateMachineDefinition, 218 | stateMachineType: sfn.StateMachineType.EXPRESS, 219 | role: serviceRole, 220 | }); 221 | 222 | // Grant AppSync HTTP data source rights to execute the SFN workflow 223 | stateMachine.grant( 224 | httpdatasource.grantPrincipal, 225 | "states:StartSyncExecution" 226 | ); 227 | 228 | // Creates an IAM role that can be assumed by the AWS AppSync service 229 | const appsyncStepFunctionsRole = new iam.Role( 230 | this, 231 | "SyncStateMachineRole", 232 | { 233 | assumedBy: new iam.ServicePrincipal("appsync.amazonaws.com"), 234 | } 235 | ); 236 | 237 | // Allows the role we defined above to execute express SFN workflows 238 | appsyncStepFunctionsRole.addToPolicy( 239 | new iam.PolicyStatement({ 240 | resources: [stateMachine.stateMachineArn], 241 | actions: ["states:StartSyncExecution"], 242 | }) 243 | ); 244 | 245 | /* 246 | Adds a GraphQL resolver to our HTTP data source that defines how 247 | GraphQL requests and fetches information from our SFN workflow. 248 | */ 249 | httpdatasource.createResolver("execute-state-machine", { 250 | typeName: "Mutation", 251 | fieldName: "executeStateMachine", 252 | requestMappingTemplate: appsync.MappingTemplate.fromString( 253 | START_EXECUTION_REQUEST_TEMPLATE(stateMachine.stateMachineArn) 254 | ), 255 | responseMappingTemplate: 256 | appsync.MappingTemplate.fromString(RESPONSE_TEMPLATE), 257 | }); 258 | 259 | } 260 | } -------------------------------------------------------------------------------- /amplify/data/resource.ts: -------------------------------------------------------------------------------- 1 | import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; 2 | 3 | /*================================================================= 4 | The section below creates a Feedback database table with a "id", "content" 5 | & "sentiment" fields. . The authorization rule below 6 | specifies that any unauthenticated user can "create", "read", "update", 7 | and "delete" any "Feedback" records. 8 | =========================================================================*/ 9 | const schema = a.schema({ 10 | /* 11 | Creates a database table for 'Feedback' to store the feedbacks 12 | submitted through our web application. 13 | */ 14 | Feedback: a 15 | .model({ 16 | id: a.id(), 17 | content: a.string(), 18 | sentiment: a.string() 19 | }) 20 | .authorization((allow) => [allow.publicApiKey()]), 21 | /* 22 | Create a new 'Execution' type that will be returned by our call 23 | to the Step Functions workflow. 24 | */ 25 | Execution: a.customType({ 26 | name: a.string(), 27 | status: a.string(), 28 | input: a.string(), 29 | executionArn: a.string(), 30 | startDate: a.string(), 31 | stopDate: a.string(), 32 | output: a.string(), 33 | }), 34 | /* 35 | Mutation that triggers the synchronous execution of our Step 36 | Functions workflow. 37 | */ 38 | executeStateMachine: a 39 | .mutation() 40 | .arguments({input: a.string()}) 41 | .returns(a.ref('Execution')) 42 | .authorization((allow) => allow.publicApiKey()), 43 | }); 44 | 45 | export type Schema = ClientSchema; 46 | 47 | export const data = defineData({ 48 | schema, 49 | authorizationModes: { 50 | defaultAuthorizationMode: 'apiKey', 51 | apiKeyAuthorizationMode: { expiresInDays: 30 } 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /amplify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } -------------------------------------------------------------------------------- /amplify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "moduleResolution": "bundler", 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "paths": { 12 | "$amplify/*": [ 13 | "../.amplify/generated/*" 14 | ] 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /assets/diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-amplify-stepfunctions-example/7de7d3f4564fe13290927fb8d5679675da8eadd5/assets/diagram.jpg -------------------------------------------------------------------------------- /assets/web_ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-amplify-stepfunctions-example/7de7d3f4564fe13290927fb8d5679675da8eadd5/assets/web_ui.jpg -------------------------------------------------------------------------------- /assets/workflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-amplify-stepfunctions-example/7de7d3f4564fe13290927fb8d5679675da8eadd5/assets/workflow.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Feedback form 7 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-amplify-sfn", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@aws-amplify/ui-react": "^6.11.2", 12 | "aws-amplify": "^6.14.4", 13 | "react": "^19.0.0", 14 | "react-dom": "^19.0.0", 15 | "react-icons": "^5.5.0" 16 | }, 17 | "devDependencies": { 18 | "@aws-amplify/backend": "^1.16.1", 19 | "@aws-amplify/backend-cli": "^1.7.2", 20 | "@types/react": "^19.0.12", 21 | "@types/react-dom": "^19.0.4", 22 | "@vitejs/plugin-react": "^4.5.1", 23 | "aws-cdk": "^2.1017.1", 24 | "aws-cdk-lib": "^2.200.0", 25 | "constructs": "^10.4.2", 26 | "vite": "^6.3.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | Flex, 4 | Heading, 5 | Text, 6 | Icon, 7 | TextAreaField, 8 | Button, 9 | Alert, 10 | Link, 11 | View, 12 | } from "@aws-amplify/ui-react"; 13 | import { RiFeedbackLine } from "react-icons/ri"; 14 | import { Amplify } from "aws-amplify"; 15 | 16 | import outputs from '../amplify_outputs.json'; 17 | Amplify.configure(outputs); 18 | 19 | import { generateClient } from 'aws-amplify/data'; 20 | /** 21 | * @type {import('aws-amplify/data').Client} 22 | */ 23 | const client = generateClient({ authMode: "apiKey"}); 24 | 25 | function App() { 26 | const [feedback, setFeedback] = useState(""); 27 | const [feedbackState, setFeedbackState] = useState("form"); 28 | 29 | async function handleSubmit(event) { 30 | event.preventDefault(); 31 | 32 | console.log("Feedback: ", feedback); 33 | 34 | try { 35 | const { data, errors } = await client.mutations.executeStateMachine({ 36 | input: feedback 37 | }); 38 | 39 | if (errors) { 40 | console.error("Error submitting feedback: ", errors); 41 | throw new Error(errors); 42 | } 43 | 44 | const output = JSON.parse(data.output); 45 | 46 | setFeedbackState(output.Sentiment); 47 | 48 | setFeedback(""); 49 | } catch (error) { 50 | console.error("Error submitting feedback: ", error); 51 | setFeedbackState("ERROR"); 52 | } 53 | } 54 | 55 | return ( 56 | 64 | 65 | 66 | 67 | We value your feedback! 68 | {(() => { 69 | switch (feedbackState) { 70 | case "form": 71 | return ( 72 | <> 73 | 74 | Please share your feedback to help us improve our services. 75 | 76 | 82 | setFeedback(event.target.value)} 86 | value={feedback} 87 | /> 88 | 89 | 90 | 91 | ); 92 | case "POSITIVE": 93 | return ( 94 | 95 | 101 | Your feedback has been recorded. 102 | 103 | 104 | ); 105 | case "ERROR": 106 | return ( 107 | 108 | 114 | Something went wrong. Please try again later. 115 | 116 | 117 | ); 118 | default: 119 | return ( 120 | 121 | 127 | We are always looking to improve. If you felt your experience 128 | was not optimal, we would love to make things right. Follow{" "} 129 | 134 | this link 135 | {" "} 136 | to get in touch with a customer service representative. 137 | 138 | 139 | ); 140 | } 141 | })()} 142 | 143 | ); 144 | } 145 | 146 | export default App; 147 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "@aws-amplify/ui-react/styles.css"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | /** @type {import('vite').UserConfig} */ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { alias: { "./runtimeConfig": "./runtimeConfig.browser" } }, 8 | build: { 9 | outDir: "build", 10 | }, 11 | server: { 12 | host: true, 13 | port: 8080, 14 | }, 15 | }); 16 | --------------------------------------------------------------------------------