Skip to content

Commit

Permalink
feat: sitemap (#71)
Browse files Browse the repository at this point in the history

---------

Co-authored-by: Maciej Daniłowicz <[email protected]>
  • Loading branch information
patzick and mdanilowicz authored Mar 27, 2023
1 parent e13d3d9 commit e71cc78
Show file tree
Hide file tree
Showing 24 changed files with 333 additions and 61 deletions.
35 changes: 35 additions & 0 deletions .changeset/neat-starfishes-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@shopware-pwa/nuxt3-module": minor
---

**BREAKING**: Remove default config and use Nuxt runtime config

change your `nuxt.config.js` from:

```ts
export default defineNuxtConfig({
///...
shopware: {
shopwareEndpoint: "http://localhost:8000",
shopwareAccessToken: "your-access-token",
},
///...
});
```

to

```ts
export default defineNuxtConfig({
///...
runtimeConfig: {
public: {
shopware: {
shopwareEndpoint: "http://localhost:8000",
shopwareAccessToken: "your-access-token",
},
},
},
///...
});
```
5 changes: 5 additions & 0 deletions .changeset/rare-donkeys-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vue-demo-store": minor
---

Add merged sitemap
5 changes: 5 additions & 0 deletions .changeset/yellow-plants-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/api-client": minor
---

Add sitemap service
4 changes: 3 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
BASE_E2E_URL="https://frontends-demo.vercel.app/"
BASE_E2E_URL="https://frontends-demo.vercel.app/"
NUXT_PUBLIC_SHOPWARE_SHOPWARE_ENDPOINT="https://demo-frontends.shopware.store"
NUXT_PUBLIC_SHOPWARE_SHOPWARE_ACCESS_TOKEN="SWSCBHFSNTVMAWNZDNFKSHLAYW"
6 changes: 5 additions & 1 deletion apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const sidebar = [
{ text: "Content Pages", link: "/getting-started/content-pages" },
{ text: "Cart", link: "/getting-started/cart" },
{ text: "Checkout ", link: "/getting-started/checkout" },
{ text: "Payments", link: "/getting-started/payments"},
{ text: "Payments", link: "/getting-started/payments" },
{ text: "Custom Payment", link: "/getting-started/custom-payment" },
{ text: "Login Form", link: "/getting-started/login-form" },
{ text: "Prices", link: "/getting-started/prices" },
Expand All @@ -37,6 +37,10 @@ export const sidebar = [
text: "Overwriting CMS blocks",
link: "/getting-started/overwriting-cms",
},
{
text: "Sitemap",
link: "/getting-started/sitemap",
},
],
},
{
Expand Down
85 changes: 41 additions & 44 deletions apps/docs/src/getting-started/payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ In order to follow this guide properly, we recommend that you get familiar with

- [Payments Concept](https://developer.shopware.com/docs/concepts/commerce/checkout-concept/payments) - especially `asynchronous` and `synchronous` chapters.
- [Payment API](https://shopware.stoplight.io/docs/store-api/8218801e50fe5-handling-the-payment)
:::
:::

## Synchronous Payment

Expand All @@ -35,7 +35,7 @@ In this case, the flow looks as follows:
const { createOrder } = useCheckout();

// create an order from the current Cart
const order = await createOrder(/** optional params omitted */)
const order = await createOrder(/** optional params omitted */);
// order object on success, unhandled rejection otherwise
```

Expand All @@ -48,75 +48,72 @@ In general, the client side does not have any direct control on the sync payment

## Asynchronous Payment

Contrary to the sync flow, the asynchronous payment has more options and thus, more control of the payment process.
Contrary to the sync flow, the asynchronous payment has more options and thus, more control of the payment process.

This is a better option for those payment providers that would need to pass additional data (like credentials, one time tokens) to complete the payment process.


### External gateway

To give an example, let's say we need to implement a payment method which redirects a customer to the external payment gateway. Depending on success or failure, we need to be redirected to success page in case of payment was done properly, otherwise display an error page to the user in our shop page.
To give an example, let's say we need to implement a payment method which redirects a customer to the external payment gateway. Depending on success or failure, we need to be redirected to success page in case of payment was done properly, otherwise display an error page to the user in our shop page.

1. Create an order

```js{3}
const { createOrder } = useCheckout();
// create an order
const order = await createOrder();
```
```js{3}
const { createOrder } = useCheckout();
// create an order
const order = await createOrder();
```

2. Utilize `useOrderPayment` composable to proceed the payment process once order is placed

```js
// utilize useOrderPayment to proceed on the provided order
const { paymentUrl, handlePayment, isAsynchronous, state, paymentMethod } =
useOrderPayment(ref(order));
```
```js
// utilize useOrderPayment to proceed on the provided order
const { paymentUrl, handlePayment, isAsynchronous, state, paymentMethod } =
useOrderPayment(ref(order));
```

3. Initialize a payment handler

This is the moment, when any additional information can be passed (if a payment extension allows to do so). Payment handler can communicate with an external service to init some additional process, like preparation of external gateway session to process the payment for specific order.
This is the moment, when any additional information can be passed (if a payment extension allows to do so). Payment handler can communicate with an external service to init some additional process, like preparation of external gateway session to process the payment for specific order.

```js{6-15}
// where to redirect an user when payment is done correctly
const SUCCESS_PAYMENT_URL: string = `${window?.location?.origin}/checkout/success/${orderId}/paid`;
// go to this page otherwise
const FAILURE_PAYMENT_URL: string = `${window?.location?.origin}/checkout/success/${orderId}/unpaid`;
```js{6-15}
// where to redirect an user when payment is done correctly
const SUCCESS_PAYMENT_URL: string = `${window?.location?.origin}/checkout/success/${orderId}/paid`;
// go to this page otherwise
const FAILURE_PAYMENT_URL: string = `${window?.location?.origin}/checkout/success/${orderId}/unpaid`;
const handlePaymentResponse = await handlePayment(
SUCCESS_PAYMENT_URL,
FAILURE_PAYMENT_URL,
{
/**
* here goes additional information required by payment provider
* can be payment intent token
*/
}
)
```
const handlePaymentResponse = await handlePayment(
SUCCESS_PAYMENT_URL,
FAILURE_PAYMENT_URL,
{
/**
* here goes additional information required by payment provider
* can be payment intent token
*/
}
)
```

Note that, this is an example, does not show how to create success/failure pages.
Note that, this is an example, does not show how to create success/failure pages.

4. Do the action on processed payment handler

If payment provider (shipped via app/plugin/extension) has external payment gateway, you will probably get the URL to go to.
```js
const handlePaymentResponse = await handlePayment(
/* parameters omitted, see previous point */
)
If payment provider (shipped via app/plugin/extension) has external payment gateway, you will probably get the URL to go to.

const redirectUrl = handlePaymentResponse?.redirectUrl; // URL or undefined
```
```js
const handlePaymentResponse = await handlePayment();
/* parameters omitted, see previous point */

Then you are ready to perform a redirection of an user to the URL in order to finish the payment.
If succeed, the customer will be redirected back to `SUCCESS_PAYMENT_URL` defined before. Otherwise, `FAILURE_PAYMENT_URL` will be displayed.
const redirectUrl = handlePaymentResponse?.redirectUrl; // URL or undefined
```
Then you are ready to perform a redirection of an user to the URL in order to finish the payment.
If succeed, the customer will be redirected back to `SUCCESS_PAYMENT_URL` defined before. Otherwise, `FAILURE_PAYMENT_URL` will be displayed.
### Credit cards
Flow for the credit cards may vary between providers, nevertheless there is a general rule: asynchronous payment flow applies also in this case. Because there is always additional data to be sent, like one time tokens, hash and other security solutions.
Sometimes the external authorization is needed and the external gateway can be used, or a popup to interact with payment provider.
However, if there are no plugin-specific endpoints to interact with, the `handlePayment` method (or `/store-api/handle-payment` endpoint) is always a good choice.
Expand Down
46 changes: 46 additions & 0 deletions apps/docs/src/getting-started/sitemap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
head:
- - meta
- name: og:title
content: "Sitemap"
- - meta
- name: og:description
content: "Understand sitemap generated by Shopware Frontends"
- - meta
- name: og:image
content: "https://frontends-og-image.vercel.app/Sitemap.png?fontSize=150px"
---

# Sitemap

Sitemap is generated by combining two sitemaps, Frontends app and Shopware backend

Link:

```
http://<your_domain>/sitemap.xml
```

## Backend sitemap

```
/server/routes/sitemap.xml.ts
```

This sitemap contains links to pages like:

- Product pages
- Category pages
- CMS pages

More about backend sitemap can be found [here](https://docs.shopware.com/en/shopware-6-en/settings/sitemap)

## Frontends sitemap

```
/server/routes/sitemap-local.xml.ts
```

This sitemap contains static page that are declared in the Frontends app

Each static page that comes from the Frontneds app, should be added manually to the `/server/sitemap.ts` file.
16 changes: 11 additions & 5 deletions apps/docs/src/getting-started/templates/demo-store-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,25 @@ One exception to the rule are CMS components. CMS components are handled as a se

The blank template is pre-configured to connect to a public Shopware backend, so you start building right away.

In order to connect it to your own store backend, you need to edit the `nuxt.config.ts` file and add a configuration object with `shopware` as a key:
In order to connect it to your own store backend, you need to edit the `nuxt.config.ts` file and edit a configuration object with `shopware` as a key:

```ts{4-7}
```ts{7-8}
/* ... */
export default defineNuxtConfig({
/* ... */
shopware: {
endpoint: "https://your-business.shopware.store",
accessToken: "access-token-from-settings",
runtimeConfig: {
public: {
shopware: {
endpoint: "https://your-business.shopware.store",
accessToken: "access-token-from-settings",
}
}
}
});
```

You can also use `.env` file to override this configuration. More about this you can find [here](https://nuxt.com/docs/guide/going-further/runtime-config#environment-variables)

## Limitations

The **Demo Store Template** suggests how to build a store UI with Shopware Frontends. It does not make any assumptions about custom implementations and hence does not contain every feature of Shopware.
Expand Down
8 changes: 8 additions & 0 deletions packages/api-client/src/endpoints.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
getNewsletterRecipientEndpoint,
getDocumentDownloadEndpoint,
getOrderDownloadsEndpoint,
getSitemapEndpoint,
} from "../src/endpoints";

const sampleProductId = "eea0f69ec02d44f7a4224272b3d99478";
Expand Down Expand Up @@ -277,4 +278,11 @@ describe("endpoints", () => {
expect(result).toBe(`/store-api/order/download/123/345`);
});
});

describe("getSitemapEndpoint", () => {
it("should return Shopware sitemap endpoint", async () => {
const result = getSitemapEndpoint();
expect(result).toEqual("/store-api/sitemap");
});
});
});
4 changes: 4 additions & 0 deletions packages/api-client/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,7 @@ export const getOrderDownloadsEndpoint = (
orderId: string,
downloadId: string
) => `/store-api/order/download/${orderId}/${downloadId}`;
/**
* @public
*/
export const getSitemapEndpoint = () => `/store-api/sitemap`;
1 change: 1 addition & 0 deletions packages/api-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from "./services/formsService";
export * from "./services/wishlistService";
export * from "./services/documentService";
export * from "./services/orderService";
export * from "./services/sitemapService";
export * from "./services/newsletterService";
export * from "./endpoints";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getSitemap } from "../sitemapService";
import { defaultInstance } from "../../apiService";
import { describe, expect, it, beforeEach, vi } from "vitest";

vi.mock("../../../src/apiService");
const mockedApiInstance = defaultInstance;

describe("SitemapService - getSitemap", () => {
const mockedGet = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
mockedApiInstance.invoke = {
get: mockedGet,
} as any;
});

it("should return sitemap links", async () => {
mockedGet.mockResolvedValueOnce({ data: { data: {} } });
const result = await getSitemap();
expect(mockedGet).toBeCalledTimes(1);
expect(mockedGet).toBeCalledWith(`/store-api/sitemap`);
expect(result).toMatchObject({});
});
});
16 changes: 16 additions & 0 deletions packages/api-client/src/services/sitemapService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { getSitemapEndpoint } from "../endpoints";
import { defaultInstance, ShopwareApiInstance } from "../apiService";
import { SitemapResult } from "@shopware-pwa/types";

/**
* Get sitemap
*
* @throws ClientApiError
* @public
*/
export async function getSitemap(
contextInstance: ShopwareApiInstance = defaultInstance
): Promise<SitemapResult[]> {
const resp = await contextInstance.invoke.get(`${getSitemapEndpoint()}`);
return resp.data;
}
17 changes: 14 additions & 3 deletions packages/nuxt3-module/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,24 @@ import Cookies from "js-cookie";

const ShopwarePlugin = {
install(app, options) {
const runtimeConfig = useRuntimeConfig();

const cookieContextToken = Cookies.get("sw-context-token");
const cookieLanguageId = Cookies.get("sw-language-id");

if (
!runtimeConfig.public.shopware.shopwareEndpoint ||
!runtimeConfig.public.shopware.shopwareAccessToken
) {
throw new Error(
"Make sure that shopwareEndpoint and shopwareAccessToken are settled in the configuration"
);
}

const instance = createInstance({
endpoint: "<%= options.shopwareEndpoint %>",
accessToken: "<%= options.shopwareAccessToken %>",
timeout: "<%= options.shopwareApiClient.timeout %>",
endpoint: runtimeConfig.public.shopware.shopwareEndpoint,
accessToken: runtimeConfig.public.shopware.shopwareAccessToken,
timeout: runtimeConfig.public.shopware.shopwareAccessToken || 10000,
auth: {
username:
"<%= options.shopwareApiClient.auth ? options.shopwareApiClient.auth.username : undefined %>",
Expand Down
Loading

2 comments on commit e71cc78

@vercel
Copy link

@vercel vercel bot commented on e71cc78 Mar 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on e71cc78 Mar 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

frontends-demo – ./templates/vue-demo-store

frontends-demo-shopware-frontends.vercel.app
frontends-demo.vercel.app
frontends-demo-git-main-shopware-frontends.vercel.app

Please sign in to comment.