├── .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 |
--------------------------------------------------------------------------------