Skip to content

Commit

Permalink
feat: digital product (SWF-276)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdanilowicz authored and patzick committed Mar 6, 2023
1 parent 63b3466 commit dab0f83
Show file tree
Hide file tree
Showing 17 changed files with 251 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-gifts-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/api-client": minor
---

Add document and product media service
5 changes: 5 additions & 0 deletions .changeset/little-falcons-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vue-demo-store": minor
---

Add digital product to the order history list
16 changes: 16 additions & 0 deletions packages/api-client/src/endpoints.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
getLandingPageDetailsEndpoint,
getProductListingEndpoint,
getNewsletterRecipientEndpoint,
getDocumentDownloadEndpoint,
getOrderDownloadsEndpoint,
} from "../src/endpoints";

const sampleProductId = "eea0f69ec02d44f7a4224272b3d99478";
Expand Down Expand Up @@ -261,4 +263,18 @@ describe("endpoints", () => {
expect(result).toBe(`/store-api/product-listing/${categoryId}`);
});
});

describe("getDocumentDownloadEndpoint", () => {
it("should return document download endpoint", () => {
const result = getDocumentDownloadEndpoint("123", "345");
expect(result).toBe(`/store-api/document/download/123/345`);
});
});

describe("getOrderDownloadsEndpoint", () => {
it("should return order download endpoint", () => {
const result = getOrderDownloadsEndpoint("123", "345");
expect(result).toBe(`/store-api/order/download/123/345`);
});
});
});
14 changes: 14 additions & 0 deletions packages/api-client/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,17 @@ export const getRemoveWishlistProductEndpoint = (productId: string) =>
*/
export const getMergeWishlistProductsEndpoint = () =>
`/store-api/customer/wishlist/merge`;
/**
* @public
*/
export const getDocumentDownloadEndpoint = (
documentId: string,
deepLinkCode: string
) => `/store-api/document/download/${documentId}/${deepLinkCode}`;
/**
* @public
*/
export const getOrderDownloadsEndpoint = (
orderId: string,
downloadId: string
) => `/store-api/order/download/${orderId}/${downloadId}`;
2 changes: 2 additions & 0 deletions packages/api-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export * from "./services/pluginService";
export * from "./services/searchService";
export * from "./services/formsService";
export * from "./services/wishlistService";
export * from "./services/documentService";
export * from "./services/orderService";
export * from "./endpoints";

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

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

describe("DocumentService - getDocumentDownload", () => {
const mockedPost = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
mockedApiInstance.invoke = {
post: mockedPost,
} as any;
});

it("should return document file", async () => {
mockedPost.mockResolvedValueOnce({ data: { data: {} } });
const result = await getDocumentDownload({
documentId: "123",
deepLinkCode: "456",
});
expect(mockedPost).toBeCalledTimes(1);
expect(mockedPost).toBeCalledWith(`/store-api/document/download/123/456`);
expect(result).toMatchObject({});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getOrderDownloads } from "../orderService";
import { defaultInstance } from "../../apiService";
import { describe, expect, it, beforeEach, vi } from "vitest";

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

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

it("should return order file", async () => {
mockedGet.mockResolvedValueOnce({ data: { data: {} } });
const result = await getOrderDownloads({
orderId: "123",
downloadId: "456",
});
expect(mockedGet).toBeCalledTimes(1);
expect(mockedGet).toBeCalledWith(`/store-api/order/download/123/456`, {
responseType: "blob",
});
expect(result).toMatchObject({});
});
});
23 changes: 23 additions & 0 deletions packages/api-client/src/services/documentService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getDocumentDownloadEndpoint } from "../endpoints";
import { defaultInstance, ShopwareApiInstance } from "../apiService";

type DocumentDownloadParams = {
documentId: string;
deepLinkCode: string;
};

/**
* Download selected document
*
* @throws ClientApiError
* @public
*/
export async function getDocumentDownload(
params: DocumentDownloadParams,
contextInstance: ShopwareApiInstance = defaultInstance
) {
const resp = await contextInstance.invoke.post(
getDocumentDownloadEndpoint(params.documentId, params.deepLinkCode)
);
return resp.data;
}
24 changes: 24 additions & 0 deletions packages/api-client/src/services/orderService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defaultInstance, ShopwareApiInstance } from "../apiService";
import { getOrderDownloadsEndpoint } from "../endpoints";

type GetUserCountryParams = {
orderId: string;
downloadId: string;
};

/**
* @throws ClientApiError
* @public
*/
export async function getOrderDownloads(
data: GetUserCountryParams,
contextInstance: ShopwareApiInstance = defaultInstance
): Promise<Blob> {
const resp = await contextInstance.invoke.get(
getOrderDownloadsEndpoint(data.orderId, data.downloadId),
{
responseType: "blob",
}
);
return resp.data;
}
26 changes: 26 additions & 0 deletions packages/composables/src/useOrderDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
changeOrderPaymentMethod,
getOrderDetails,
handlePayment as apiHandlePayment,
getOrderDownloads,
} from "@shopware-pwa/api-client";
import { useShopwareContext } from "./useShopwareContext";

Expand All @@ -23,6 +24,11 @@ const orderAssociations: ShopwareSearchParams = {
lineItems: {
associations: {
cover: {},
downloads: {
associations: {
media: {},
},
},
},
},
addresses: {},
Expand Down Expand Up @@ -115,6 +121,13 @@ export type UseOrderDetailsReturn = {
* @returns
*/
changePaymentMethod: (paymentMethodId: string) => Promise<void>;
/**
* Get media content
*
* @param {string} downloadId
* @returns {Blob}
*/
getMediaFile: (downloadId: string) => Promise<Blob>;
};

/**
Expand Down Expand Up @@ -191,6 +204,18 @@ export function useOrderDetails(orderId: string): UseOrderDetailsReturn {
await loadOrderDetails();
}

async function getMediaFile(downloadId: string) {
const response = await getOrderDownloads(
{
orderId,
downloadId,
},
apiInstance
);

return response;
}

return {
order: computed(() => _sharedOrder.value),
status,
Expand All @@ -207,5 +232,6 @@ export function useOrderDetails(orderId: string): UseOrderDetailsReturn {
handlePayment,
cancel,
changePaymentMethod,
getMediaFile,
};
}
1 change: 1 addition & 0 deletions packages/helpers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "./getTranslatedProperty";
export * from "./listing";
export * from "./price";
export * from "./media/image";
export * from "./media/getMedia";
25 changes: 25 additions & 0 deletions packages/helpers/src/media/getMedia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { OrderLineItem, Downloads } from "@shopware-pwa/types";

type ProductMedia = {
id: string;
fileName: string;
};

/**
* Prepare media object
*
* @param {OrderLineItem} lineItem - order item
* @returns
*/
export function getMedia(lineItem: OrderLineItem) {
return lineItem.downloads.reduce(
(acc: [ProductMedia], current: Downloads) => {
acc.push({
id: current.id,
fileName: `${current.media.fileName}.${current.media.fileExtension}`,
});
return acc;
},
[]
);
}
1 change: 1 addition & 0 deletions packages/types/shopware-6-client/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from "./models/checkout/order/OrderDelivery";
export * from "./models/checkout/order/OrderDeliveryPosition";
export * from "./models/checkout/order/OrderLineItem";
export * from "./models/checkout/order/OrderState";
export * from "./models/checkout/order/OrderDownloads";
export * from "./models/checkout/order/OrderTransaction";
export * from "./models/checkout/payment/PaymentMethod";
export * from "./models/checkout/payment/PaymentMethodTranslation";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Media } from "../../content/media/Media";
import { CustomField } from "../../common/CustomField";

/**
* @public
*/
export type Downloads = {
versionId: string;
translated: { [key: string]: string };
createdAt: Date;
updatedAt: Date;
orderLineItemId: string;
orderLineItemVersionId: string;
mediaId: string;
position: number;
media: Media;
accessGranted: boolean;
id: string;
customFields: CustomField | null;
apiAlias: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Media } from "../../content/media/Media";
import { CustomField } from "../../common/CustomField";
import { Product } from "../../content/product/Product";
import { Promotion } from "../promotion/Promotion";

import { OrderDownloads } from "./OrderDownloads";
/**
* @public
*/
Expand Down Expand Up @@ -34,4 +34,5 @@ export type OrderLineItem = {
cover: (Media & { url: string }) | null;
children: OrderLineItem[] | null;
apiAlias: "order_item";
downloads: OrderDownloads | null;
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
<script setup lang="ts">
import { OrderLineItem } from "@shopware-pwa/types";
import { getSmallestThumbnailUrl } from "@shopware-pwa/helpers-next";
import { getSmallestThumbnailUrl, getMedia } from "@shopware-pwa/helpers-next";
defineProps<{
const props = defineProps<{
lineItem: OrderLineItem;
}>();
const { getMediaFile } = useOrderDetails(props.lineItem.orderId);
const getMediaFileHandler = async (mediaId: string, fileName: string) => {
const response = await getMediaFile(mediaId);
const media = document.createElement("a");
media.href = URL.createObjectURL(response);
media.download = fileName;
document.body.appendChild(media);
media.click();
document.body.removeChild(media);
};
</script>

<script lang="ts">
Expand Down Expand Up @@ -57,4 +70,17 @@ export default {
/>
</div>
</div>
<div class="pl-5 pb-3">
<div
v-for="media in getMedia(lineItem)"
class="cursor-pointer"
:key="media.id"
@click="getMediaFileHandler(media.id, media.fileName)"
>
<div class="flex gap-2">
<div class="w-5 h-5 i-carbon-result" />
{{ media.fileName }}
</div>
</div>
</div>
</template>
3 changes: 2 additions & 1 deletion templates/vue-demo-store/components/product/ProductUnits.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ const referenceUnitName = computed(
Content: {{ purchaseUnit }} {{ unitName }}
</template>
<template v-if="referencePrice">
( <SharedPrice :value="referencePrice" /> / {{ referenceUnit }} {{ referenceUnitName }} )
( <SharedPrice :value="referencePrice" /> / {{ referenceUnit }}
{{ referenceUnitName }} )
</template>
</div>
</template>

0 comments on commit dab0f83

Please sign in to comment.