Skip to content

Commit

Permalink
feat: reset password using Accounts and Integration Hub
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardo-larosa committed Dec 19, 2024
1 parent 78bf5ed commit dd6787c
Show file tree
Hide file tree
Showing 256 changed files with 29,140 additions and 0 deletions.
6 changes: 6 additions & 0 deletions examples/password-reset/.composablerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": 1,
"cli": {
"packageManager": "yarn"
}
}
12 changes: 12 additions & 0 deletions examples/password-reset/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["react"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"react/jsx-curly-brace-presence": "error"
}
}
45 changes: 45 additions & 0 deletions examples/password-reset/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.idea/

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.*

# vercel
.vercel

# typescript
*.tsbuildinfo

# Being generated by the moltin js-sdk during dev server from server side requests
/localStorage

test-results
32 changes: 32 additions & 0 deletions examples/password-reset/.lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const path = require("path");

/**
* Using next lint with lint-staged requires this setup
* https://nextjs.org/docs/basic-features/eslint#lint-staged
*/

const buildEslintCommand = (filenames) =>
`next lint --fix --file ${filenames
.map((f) => path.relative(process.cwd(), f))
.join(" --file ")}`;

/**
* () => "npm run type:check"
* needs to be a function because arguments are getting passed from lint-staged
* when those arguments get through to the "tsc" command that "npm run type:check"
* is calling the args cause "tsc" to ignore the tsconfig.json in our root directory.
* https://github.com/microsoft/TypeScript/issues/27379
*/
module.exports = {
"*.{js,jsx}": [
"npm run format:fix",
buildEslintCommand,
"npm run format:check",
],
"*.{ts,tsx}": [
"npm run format:fix",
() => "npm run type:check",
buildEslintCommand,
"npm run format:check",
],
};
1 change: 1 addition & 0 deletions examples/password-reset/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/.next/**
1 change: 1 addition & 0 deletions examples/password-reset/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
91 changes: 91 additions & 0 deletions examples/password-reset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# password-reset Elastic Path storefront starter

This project was generated with [Composable CLI](https://www.npmjs.com/package/composable-cli).

This storefront accelerates the development of a direct-to-consumer ecommerce experience using Elastic Path's modular products.

## Tech Stack

- [Elastic Path](https://www.elasticpath.com/products): A family of composable products for businesses that need to quickly & easily create unique experiences and next-level customer engagements that drive revenue.

- [Next.js](https://nextjs.org/): a React framework for building static and server-side rendered applications

- [Tailwind CSS](https://tailwindcss.com/): enabling you to get started with a range of out the box components that are
easy to customize

- [Headless UI](https://headlessui.com/): completely unstyled, fully accessible UI components, designed to integrate
beautifully with Tailwind CSS.

- [Radix UI Primitives](https://www.radix-ui.com/primitives): Unstyled, accessible, open source React primitives for high-quality web apps and design systems.

- [Typescript](https://www.typescriptlang.org/): a typed superset of JavaScript that compiles to plain JavaScript

## Getting Started

Run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

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

You can start editing the page by modifying `app/page.tsx`. The page will hot reload as you edit the file.

## Deployment

Deployment is typical for a Next.js site. We recommend using a provider
like [Netlify](https://www.netlify.com/blog/2020/11/30/how-to-deploy-next.js-sites-to-netlify/)
or [Vercel](https://vercel.com/docs/frameworks/nextjs) to get full Next.js feature support.

## Current feature set reference

| **Feature** | **Notes** |
|------------------------------------------|-----------------------------------------------------------------------------------------------|
| PDP | Product Display Pages |
| PLP | Product Listing Pages. |
| EPCC PXM product variations | [Learn more](https://elasticpath.dev/docs/pxm/products/pxm-product-variations/pxm-variations) |
| EPCC PXM bundles | [Learn more](https://elasticpath.dev/docs/pxm/products/pxm-bundles/pxm-bundles) |
| EPCC PXM hierarchy-based navigation menu | Main site nav driven directly from your store's hierarchy and node structure |
| Prebuilt helper components | Some basic building blocks for typical ecommerce store features |
| Checkout | [Learn more](https://elasticpath.dev/docs/commerce-cloud/checkout/checkout-workflow) |
| Cart | [Learn more](https://elasticpath.dev/docs/commerce-cloud/carts/carts) |

## Notes on this starter

This starter is using the simple store implementation and add the password reset flow.
You'll need to ensure your email service is configured to send the password reset emails.

Here is how to do it using Composer and Postmark.
- Make sure you have a Postmark account and API key
- In Postmark, create a new template for the password reset email and note the template ID
- Go to Composer in Commerce Manager and select Postmark Email
- Add your Postmark API key
- In Event Mapping, add key `one-time-password-token-request.created` and value:

```json
{
"messagingProvider": {
"from": "noreply@<your-domain>.com",
"templateId": 38364654,
"to": "$.payload.user_authentication_info.email"
},
"dynamicFieldMapping": {
"username": "$.payload.user_authentication_info.email",
"token": "$.payload.one_time_password_token",
"password_profile_id": "$.payload.password_profile_id",
"user_authentication_info_id": "$.payload.user_authentication_info.id",
"user_authentication_password_profile_info_id": "$.payload.user_authentication_password_profile_info.id"
},
"metadata": {
"user_id": "$.payload.user_authentication_info.id"
}
}
```
- To learn more about One Time Password tokens, see [here](https://elasticpath.dev/guides/How-To/Authentication/how-to-utilize-one-time-password-tokens)
41 changes: 41 additions & 0 deletions examples/password-reset/e2e/checkout-flow.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { test } from "@playwright/test";
import { createD2CProductDetailPage } from "./models/d2c-product-detail-page";
import { client } from "./util/epcc-client";
import { createD2CCartPage } from "./models/d2c-cart-page";
import { createD2CCheckoutPage } from "./models/d2c-checkout-page";

test.describe("Checkout flow", async () => {
test("should perform product checkout", async ({ page }) => {
const productDetailPage = createD2CProductDetailPage(page, client);
const cartPage = createD2CCartPage(page);
const checkoutPage = createD2CCheckoutPage(page);

/* Go to simple product page */
await productDetailPage.gotoSimpleProduct();

/* Add the product to cart */
await productDetailPage.addProductToCart();

/* Go to cart page and checkout */
await cartPage.goto();
await cartPage.checkoutCart();

/* Enter information */
await checkoutPage.enterInformation({
"Email Address": { value: "[email protected]", fieldType: "input" },
"First Name": { value: "Jim", fieldType: "input" },
"Last Name": { value: "Brown", fieldType: "input" },
Address: { value: "Main Street", fieldType: "input" },
City: { value: "Brownsville", fieldType: "input" },
Region: { value: "Browns", fieldType: "input" },
Postcode: { value: "ABC 123", fieldType: "input" },
Country: { value: "Algeria", fieldType: "combobox" },
"Phone Number": { value: "01234567891", fieldType: "input" },
});

await checkoutPage.checkout();

/* Continue Shopping */
await checkoutPage.continueShopping();
});
});
10 changes: 10 additions & 0 deletions examples/password-reset/e2e/home-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { test } from "@playwright/test";
import { createD2CHomePage } from "./models/d2c-home-page";
import { skipIfMissingCatalog } from "./util/missing-published-catalog";

test.describe("Home Page", async () => {
test("should load home page", async ({ page }) => {
const d2cHomePage = createD2CHomePage(page);
await d2cHomePage.goto();
});
});
23 changes: 23 additions & 0 deletions examples/password-reset/e2e/models/d2c-cart-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Locator, Page } from "@playwright/test";

export interface D2CCartPage {
readonly page: Page;
readonly checkoutBtn: Locator;
readonly goto: () => Promise<void>;
readonly checkoutCart: () => Promise<void>;
}

export function createD2CCartPage(page: Page): D2CCartPage {
const checkoutBtn = page.getByRole("link", { name: "Checkout" });

return {
page,
checkoutBtn,
async goto() {
await page.goto(`/cart`);
},
async checkoutCart() {
await checkoutBtn.click();
},
};
}
48 changes: 48 additions & 0 deletions examples/password-reset/e2e/models/d2c-checkout-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Locator, Page } from "@playwright/test";
import { fillAllFormFields, FormInput } from "../util/fill-form-field";
import { enterPaymentInformation as _enterPaymentInformation } from "../util/enter-payment-information";

export interface D2CCheckoutPage {
readonly page: Page;
readonly payNowBtn: Locator;
readonly checkoutBtn: Locator;
readonly goto: () => Promise<void>;
readonly enterInformation: (values: FormInput) => Promise<void>;
readonly checkout: () => Promise<void>;
readonly enterPaymentInformation: (values: FormInput) => Promise<void>;
readonly submitPayment: () => Promise<void>;
readonly continueShopping: () => Promise<void>;
}

export function createD2CCheckoutPage(page: Page): D2CCheckoutPage {
const payNowBtn = page.getByRole("button", { name: "Pay $" });
const checkoutBtn = page.getByRole("button", { name: "Pay $" });
const continueShoppingBtn = page.getByRole("link", {
name: "Continue shopping",
});

return {
page,
payNowBtn,
checkoutBtn,
async goto() {
await page.goto(`/cart`);
},
async enterPaymentInformation(values: FormInput) {
await _enterPaymentInformation(page, values);
},
async enterInformation(values: FormInput) {
await fillAllFormFields(page, values);
},
async submitPayment() {
await payNowBtn.click();
},
async checkout() {
await checkoutBtn.click();
},
async continueShopping() {
await continueShoppingBtn.click();
await page.waitForURL("/");
},
};
}
15 changes: 15 additions & 0 deletions examples/password-reset/e2e/models/d2c-home-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Page } from "@playwright/test";

export interface D2CHomePage {
readonly page: Page;
readonly goto: () => Promise<void>;
}

export function createD2CHomePage(page: Page): D2CHomePage {
return {
page,
async goto() {
await page.goto("/");
},
};
}
Loading

0 comments on commit dd6787c

Please sign in to comment.