Skip to content

Commit

Permalink
Merge pull request #193 from twilio-labs/fix/forSignal
Browse files Browse the repository at this point in the history
Fix/for signal
  • Loading branch information
nokenwa authored Nov 8, 2024
2 parents 2908840 + a801e20 commit 71eea1b
Show file tree
Hide file tree
Showing 81 changed files with 2,887 additions and 2,268 deletions.
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
MIXOLOGIST_LOGIN=someuser:somepassword
ADMIN_LOGIN=someadmin:otherpassword
KIOSK_LOGIN=somekiosk:somepassword
SERVICE_INSTANCE_PREFIX=Mixologist
UNLIMITED_ORDERS=CommaSeparatedNumbersToWhichTheLimitDoesNotApply

Expand All @@ -13,4 +14,9 @@ TWILIO_VERIFY_SERVICE_SID=
TWILIO_CONVERSATIONS_SERVICE_SID=

# NGROK URL GOES HERE
PUBLIC_BASE_URL=
PUBLIC_BASE_URL=

#Optional
SEGMENT_SPACE_ID="your_segment_space_id"
SEGMENT_PROFILE_KEY="your_segment_profile_key"
SEGMENT_TRAIT_CHECK="your_segment_trait_check"
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
TWILIO_CONVERSATIONS_SERVICE_SID: ${{ vars.TWILIO_CONVERSATIONS_SERVICE_SID }}
MIXOLOGIST_LOGIN: ${{ vars.MIXOLOGIST_LOGIN }}
ADMIN_LOGIN: ${{ vars.ADMIN_LOGIN }}
KIOSK_LOGIN: ${{ vars.KIOSK_LOGIN }}
- uses: actions/upload-artifact@v4
if: always()
with:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ package-lock.json
/playwright/.cache/


attendees.csv

attendees-*.csv
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ members of the project's leadership.
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org
[homepage]: https://www.contributor-covenant.org
91 changes: 59 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,49 @@
Twilio Mixologist is an application that allows you to solve the problem of long queues at stands at events. Attendees can order their coffee, smoothie or whatever you serve via Twilio-powered channels, Mixologists get all orders on a website that can be accessed via a tablet and once an order is done the attendee will be notified via the system to come and pick it up. No more queueing and efficient coffee ☕️ ordering! 🎉

If you want to learn more about how this project was started, check out the this blog post:
> [Serving Coffee with Twilio Programmable SMS and React](https://www.twilio.com/en-us/blog/serving-coffee-with-sms-and-react-html)

Different versions of this system have been used at events such as:

* [NDC Oslo](https://ndcoslo.com) 2016, 2017
* [CSSConf EU](https://2017.cssconf.eu/) && [JSConf EU](https://2017.jsconf.eu/) 2017
* [WeAreDevelopers World Congress](https://www.wearedevelopers.com/world-congress) 2023, 2024
* [Mobile World Congress Barcelona](https://www.mwcbarcelona.com/) 2023, 2024
* [Money 20/20](https://www.money2020.com/) 2023
* [Twilio SIGNAL](https://signal.twilio.com/) 2023, 2024
> [Serving Coffee with Twilio Programmable SMS and React](https://www.twilio.com/en-us/blog/serving-coffee-with-sms-and-react-html)
Different versions of this system have been used at events such as:

- [NDC Oslo](https://ndcoslo.com) 2016, 2017
- [CSSConf EU](https://2017.cssconf.eu/) && [JSConf EU](https://2017.jsconf.eu/) 2017
- [WeAreDevelopers World Congress](https://www.wearedevelopers.com/world-congress) 2023, 2024
- [Mobile World Congress Barcelona](https://www.mwcbarcelona.com/) 2023, 2024
- [Money 20/20](https://www.money2020.com/) 2023
- [Twilio SIGNAL](https://signal.twilio.com/) 2023, 2024

## Features

* Receive orders using [Twilio Messaging]
* Store orders and real-time synchronization them between back-end and front-end using [Twilio Sync]
* Easy dynamic application configuration using [Twilio Sync]
* Managing message threads using [Twilio Conversations]
* Permission management based on [Twilio Sync]
* Easy way to reset the application from the admin interface
* Support multiple events that happen in parallel
* Query for location in the queue as well as canceling the order as a user
* All combined into a single [NextJS](https://nextjs.org/) web application
- Receive orders using [Twilio Messaging]
- Store orders and real-time synchronization them between back-end and front-end using [Twilio Sync]
- Easy dynamic application configuration using [Twilio Sync]
- Managing message threads using [Twilio Conversations]
- Permission management based on [Twilio Sync]
- Easy way to reset the application from the admin interface
- Support multiple events that happen in parallel
- Query for location in the queue as well as canceling the order as a user
- All combined into a single [NextJS](https://nextjs.org/) web application

### Pending Features

- [ ] Integration with Segment
- [ ] Your suggestions

### Channels

The current [Twilio Channels] are:

* [WhatsApp][twilio whatsapp]
* [SMS][twilio messaging]

- [WhatsApp][twilio whatsapp]
- [SMS][twilio messaging]

## Setup

### Requirements

* [Node.js] version 20 or higher
* [pnpm]
* A Twilio account - [Sign up here](https://www.twilio.com/try-twilio)
- [Node.js] version 20 or higher
- [pnpm]
- A Twilio account - [Sign up here](https://www.twilio.com/try-twilio)

## Setup

Expand All @@ -65,6 +64,7 @@ The current [Twilio Channels] are:
# Application related values
MIXOLOGIST_LOGIN=someuser:assword
ADMIN_LOGIN=someadmin:password
KIOSK_LOGIN=somekiosk:somepassword
SERVICE_INSTANCE_PREFIX=Mixologist
ACTIVE_CUSTOMERS_MAP=ActiveCustomers
UNLIMITED_ORDERS=CommaSeparatedNumbersToWhichTheLimitDoesNotApply
Expand All @@ -76,6 +76,10 @@ The current [Twilio Channels] are:
TWILIO_ACCOUNT_SID=
TWILIO_API_KEY=
TWILIO_API_SECRET=
SEGMENT_SPACE_ID="your_segment_space_id"
SEGMENT_PROFILE_KEY="your_segment_profile_key"
SEGMENT_TRAIT_CHECK="your_segment_trait_check"
```

Go into the [Twilio Console] and [generate an API Key and Secret](https://www.twilio.com/console/dev-tools/api-keys). Make sure to store the information safely.
Expand Down Expand Up @@ -109,6 +113,10 @@ pnpm dev

9. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## How To Use

[Here's a diagram of what happens when the user sends a message to the application](resources/user-flow-diagram.png)

## Testing

This projects comes with a test suit that runs on every push to `main` and `feat/` branches. Unit tests cover basic capabilities (access control checks, template generator). And e2e tests cover the main functionality of the website since the data is loaded async and RSC's are currently not supported by unit test frameworks.
Expand All @@ -118,13 +126,33 @@ pnpm test # run unit tests
pnpm test:e2e # run e2e tests
```

## Deploy
## Optional Setup

TBD
### Tips for production

## How To Use
Here are a few helpful notes:

[Here's a diagram of what happens when the user sends a message to the application](resources/user-flow-diagram.png)
- If you are using the SMS channel, make sure to [set the SMS Geo Permissions](https://www.twilio.com/docs/messaging/guides/sms-geo-permissions)to make sure senders from the entire world can interact with the Mixologist.
- Edit the [opt-out management settings](https://help.twilio.com/articles/360034798533-Getting-Started-with-Advanced-Opt-Out-for-Messaging-Services) of the messaging service to avoid that users accidentally unsubscribe from the list.
- Regularly run `pnpm check-for-errors` and see if unforeseen errors occurred when the users tried to order.
- The Kiosk interface is a self-service interface that you can make available to attendees via a table or phone. The page allows the manual entry of an order without the need to put a phone number down. This form can be accessed via `https://<mixologist.server>/<event-slug>/kiosk` and the credentials are defined in the environment variable `KIOSK_LOGIN`.
- Users can send the command "forget me" to remove all data stored about this user. It cancels pending orders, removes the user from the Sync data store and removes the Conversation resource. This can be used for debugging as well as to be GDPR-compliant.

### Segment Integration

This project includes an optional integration with Segment's Profiles API. If you provide the `SEGMENT_SPACE_ID` and `SEGMENT_PROFILE_KEY` environment variables, the application will fetch user traits from Segment using the provided email address once the verification step is completed. The `SEGMENT_TRAIT_CHECK` environment variable allows you to specify a specific trait to check for in the user's profile.

To set up Segment integration:

1. **Create a Segment account** if you don't have one. Sign up [here](https://segment.com/).

2. **Create a Segment Space** and obtain your `SEGMENT_SPACE_ID`.

3. **Generate a Segment Profile API Key** and obtain your `SEGMENT_PROFILE_KEY`.

4. **Specify a Trait to Check** by setting the `SEGMENT_TRAIT_CHECK` environment variable to the desired trait key.

For more details on Segment and how to use the Profiles API, refer to the [Segment documentation](https://segment.com/docs/).

## Code of Conduct

Expand All @@ -136,13 +164,12 @@ All third party contributors acknowledge that any contributions they provide wil

## Icons Used

* [Mixologist Icons by Oliver Pitsch](https://www.smashingmagazine.com/2016/03/freebie-Mixologist-iconset-50-icons-eps-png-svg/)
* [Bar by BirVa Mehta from Noun Project](https://thenounproject.com/term/bar/1323725/)
- [Mixologist Icons by Oliver Pitsch](https://www.smashingmagazine.com/2016/03/freebie-Mixologist-iconset-50-icons-eps-png-svg/)
- [Bar by BirVa Mehta from Noun Project](https://thenounproject.com/term/bar/1323725/)

## License

MIT

MIT

[twilio console]: https://www.twilio.com/console
[twilio rest api]: https://www.twilio.com/docs/api/rest
Expand Down
7 changes: 2 additions & 5 deletions __tests__/e2e/browse-events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ test.describe("[admin]", () => {
await expect(page.getByText("Barista")).toBeVisible();

await expect(
page.getByRole("heading", { name: "Latte Macchiato", exact: true }),
page.getByRole("heading", { name: "Caffè Latte", exact: true }),
).toBeVisible();
await expect(
page.locator(".space-y-2 > div:nth-child(2) > .peer").first(),
Expand All @@ -142,7 +142,7 @@ test.describe("[admin]", () => {

await page.goto("http://localhost:3000/event/test-event");

await page.waitForTimeout(1000);
await page.waitForTimeout(2000);

// select the first 10 items
for (let i = 1; i <= 10; i++) {
Expand Down Expand Up @@ -248,9 +248,6 @@ test.describe("[admin]", () => {
await expect(
page.getByRole("heading", { name: "Colombia (Red like Twilio!)" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Short title: Colombia" }),
).toBeVisible();
await expect(page.getByText("Strawberry, Pineapple, Apple")).toBeVisible();

const createButton = page
Expand Down
31 changes: 13 additions & 18 deletions __tests__/e2e/browse-orders.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ test.describe("[no login]", () => {
page.getByText('Original Message - "A Delivered Order"'),
).toBeVisible();

await expect(page.getByTestId("pause-orders")).toBeDisabled();
await expect(page.getByTestId("pause-orders")).toBeHidden();
});

test("Scroll through extra orders", async ({ page }) => {
Expand All @@ -66,15 +66,15 @@ test.describe("[no login]", () => {

await expect(
page.getByRole("button", { name: "Send Message to all open" }),
).toBeDisabled();
).toBeHidden();
});

test("Custom Order usable", async ({ page, context }) => {
await page.goto("/event/test-event/orders");

await expect(
page.getByRole("button", { name: "Create a Manual Order" }),
).toBeDisabled();
).toBeHidden();
});
});

Expand Down Expand Up @@ -143,12 +143,10 @@ test.describe("[mixologist]", () => {
.getByRole("button", { name: "Send Message to all open" })
.click();
await page.getByPlaceholder("Type your message here...").fill("Hello test");

await page
.getByRole("button", { name: "Send Message", exact: true })
.click();
await expect(
page.getByRole("button", { name: "Sending...", exact: true }),
).toBeVisible();
.isEnabled();
});

test("Custom Order usable", async ({ page, context }) => {
Expand All @@ -165,18 +163,15 @@ test.describe("[mixologist]", () => {

await page.goto("/event/test-event/orders");

await page
.getByRole("button", { name: "Create a Manual Order" })
.click();
await page.getByPlaceholder("Attendee name").fill("Test Name");
await page.getByRole("button", { name: "Create a Manual Order" }).click();
await page.getByPlaceholder("Customer name").fill("Test Name");
await page.getByLabel("Order Item").click();
await page.getByLabel('Espresso', { exact: true }).click();
await page.getByPlaceholder("Without regular milk or similar...").fill("Test Notes");
await page.getByLabel("Espresso", { exact: true }).click();
await page
.getByPlaceholder("Without regular milk or similar...")
.fill("Test Notes");
await page
.getByRole("button", { name: "Create Order", exact: true })
.click();
await expect(
page.getByRole("button", { name: "Creating...", exact: true }),
).toBeVisible();
.isEnabled();
});
});
});
37 changes: 37 additions & 0 deletions __tests__/e2e/kiosk-render.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { test, expect, type Locator } from "@playwright/test";
import { Privilege } from "@/middleware";

test.describe("[no login]", () => {
test("Should not see the page", async ({ page }) => {
await page.goto("/event/test-event/kiosk");

await expect(page.getByText("Unauthorized")).toBeVisible();
});
});

test.describe("[kiosk]", () => {
test("Form should be visible", async ({ page, context }) => {
await context.addCookies([
{
name: "privilege",
value: Privilege.KIOSK,
url: "http://localhost:3000",
},
]);
context.setExtraHTTPHeaders({
Authorization: `Basic ${btoa(process.env.KIOSK_LOGIN || ":")}`,
});

await page.goto("/event/test-event/kiosk");

await page.getByPlaceholder("Customer name").fill("Test Name");
await page.getByLabel("Order Item").click();
await page.getByLabel("Espresso", { exact: true }).click();
await page
.getByPlaceholder("Without regular milk or similar...")
.fill("Test Notes");
await page
.getByRole("button", { name: "Create Order", exact: true })
.isEnabled();
});
});
5 changes: 2 additions & 3 deletions __tests__/e2e/order-terminal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test, expect, type Locator } from "@playwright/test";
import { Privilege } from "@/middleware";
import exp from "constants";

test.describe("[no login] ", () => {
test("Only elements with permissions should be visible / page 1/2", async ({
Expand Down Expand Up @@ -47,7 +46,7 @@ test.describe("[no login] ", () => {
page.getByText('Original Message - "A Delivered Order"'),
).toBeVisible();

await expect(page.getByTestId("pause-orders")).toBeDisabled();
await expect(page.getByTestId("pause-orders")).toBeHidden();
});

test("Only elements with permissions should be visible / page 2/2", async ({
Expand Down Expand Up @@ -94,7 +93,7 @@ test.describe("[no login] ", () => {
page.getByText('Original Message - "A Delivered Order"'),
).toBeVisible();

await expect(page.getByTestId("pause-orders")).toBeDisabled();
await expect(page.getByTestId("pause-orders")).toBeHidden();
});

test("Should show right header for page 2/4", async ({ page }) => {
Expand Down
18 changes: 11 additions & 7 deletions __tests__/global-tear-down.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import Axios from "axios";
async function globalTeardown(config: FullConfig) {
const baseURL = config?.webServer?.url || "http://localhost:3000";

const response = await Axios.delete(`${baseURL}/api/event/test-event`, {
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa(process.env.ADMIN_LOGIN || ":")}`,
},
});
try {
const response = await Axios.delete(`${baseURL}/api/event/test-event`, {
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa(process.env.ADMIN_LOGIN || ":")}`,
},
});

expect(response.status).toBe(204);
expect(response.status).toBe(204);
} catch (error) {
console.error("Error deleting event: ", error);
}
}

export default globalTeardown;
Loading

0 comments on commit 71eea1b

Please sign in to comment.