diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..53930641 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/devcontainers/typescript-node:22 + +ENV PNPM_HOME="/home/node/.pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +WORKDIR /app + +# This is needed so pnpm-store volume is created with correct permissions (see docker-compose.yml) +RUN mkdir -p /app/.pnpm-store +RUN chown -R node:node /app/.pnpm-store + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile +RUN chown -R node:node /home/node/.pnpm diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..4ea768e0 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +{ + "name": "Saleor app template", + "dockerComposeFile": "docker-compose.yml", + "service": "saleor-app-template", + "workspaceFolder": "/app", + "forwardPorts": [3000], + "portsAttributes": { + "3000": { + "label": "Saleor app" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "GraphQL.vscode-graphql-syntax", + "GraphQL.vscode-graphql", + "streetsidesoftware.code-spell-checker" + ] + } + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000..518d0731 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,14 @@ +services: + saleor-app-template: + image: saleor-app-template + command: sleep infinity # keeps docker container running + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - "..:/app" + - "pnpm-store:/app/.pnpm-store" + +volumes: + pnpm-store: + driver: local diff --git a/.eslintrc b/.eslintrc index b77f54a4..1367bbb5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,12 @@ { "extends": ["next", "plugin:@saleor/saleor-app/recommended", "prettier"], + "plugins": ["simple-import-sort"], "parserOptions": { "project": "tsconfig.json" + }, + "rules": { + "import/order": "off", // to avoid conflicts with simple-import-sort + "simple-import-sort/imports": "warn", + "simple-import-sort/exports": "warn" } } diff --git a/README.md b/README.md index 3454901a..9c3a9911 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,17 @@ You can use any preferred technology to create Saleor Apps, but Next.js is among ## Development +#### Running app locally in development containers + +The easiest way of running app for local development is to use [development containers](https://containers.dev/). +If you have Visual Studio Code follow their [guide](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container) on how to open existing folder in container. + +Development container only creates container, you still need to start the server. + +Development container will have port opened: + +1. `3000` - were app dev server will listen to requests + ### Requirements Before you start, make sure you have installed: diff --git a/generated/graphql.ts b/generated/graphql.ts index 3c0d8ca1..2dde90f0 100644 --- a/generated/graphql.ts +++ b/generated/graphql.ts @@ -33162,23 +33162,18 @@ export type _Service = { sdl?: Maybe; }; -export type FetchAppDetailsQueryVariables = Exact<{ [key: string]: never; }>; +export type OrderCreatedWebhookPayloadFragment = { __typename?: 'OrderCreated', order?: { __typename?: 'Order', userEmail?: string | null, id: string, number: string, user?: { __typename?: 'User', email: string, firstName: string, lastName: string } | null } | null }; + +export type OrderCreatedSubscriptionSubscriptionVariables = Exact<{ [key: string]: never; }>; -export type FetchAppDetailsQuery = { __typename?: 'Query', app?: { __typename?: 'App', id: string, privateMetadata: Array<{ __typename?: 'MetadataItem', key: string, value: string }> } | null }; +export type OrderCreatedSubscriptionSubscription = { __typename?: 'Subscription', event?: { __typename?: 'AccountChangeEmailRequested' } | { __typename?: 'AccountConfirmationRequested' } | { __typename?: 'AccountConfirmed' } | { __typename?: 'AccountDeleteRequested' } | { __typename?: 'AccountDeleted' } | { __typename?: 'AccountEmailChanged' } | { __typename?: 'AccountSetPasswordRequested' } | { __typename?: 'AddressCreated' } | { __typename?: 'AddressDeleted' } | { __typename?: 'AddressUpdated' } | { __typename?: 'AppDeleted' } | { __typename?: 'AppInstalled' } | { __typename?: 'AppStatusChanged' } | { __typename?: 'AppUpdated' } | { __typename?: 'AttributeCreated' } | { __typename?: 'AttributeDeleted' } | { __typename?: 'AttributeUpdated' } | { __typename?: 'AttributeValueCreated' } | { __typename?: 'AttributeValueDeleted' } | { __typename?: 'AttributeValueUpdated' } | { __typename?: 'CalculateTaxes' } | { __typename?: 'CategoryCreated' } | { __typename?: 'CategoryDeleted' } | { __typename?: 'CategoryUpdated' } | { __typename?: 'ChannelCreated' } | { __typename?: 'ChannelDeleted' } | { __typename?: 'ChannelMetadataUpdated' } | { __typename?: 'ChannelStatusChanged' } | { __typename?: 'ChannelUpdated' } | { __typename?: 'CheckoutCreated' } | { __typename?: 'CheckoutFilterShippingMethods' } | { __typename?: 'CheckoutFullyPaid' } | { __typename?: 'CheckoutMetadataUpdated' } | { __typename?: 'CheckoutUpdated' } | { __typename?: 'CollectionCreated' } | { __typename?: 'CollectionDeleted' } | { __typename?: 'CollectionMetadataUpdated' } | { __typename?: 'CollectionUpdated' } | { __typename?: 'CustomerCreated' } | { __typename?: 'CustomerMetadataUpdated' } | { __typename?: 'CustomerUpdated' } | { __typename?: 'DraftOrderCreated' } | { __typename?: 'DraftOrderDeleted' } | { __typename?: 'DraftOrderUpdated' } | { __typename?: 'FulfillmentApproved' } | { __typename?: 'FulfillmentCanceled' } | { __typename?: 'FulfillmentCreated' } | { __typename?: 'FulfillmentMetadataUpdated' } | { __typename?: 'FulfillmentTrackingNumberUpdated' } | { __typename?: 'GiftCardCreated' } | { __typename?: 'GiftCardDeleted' } | { __typename?: 'GiftCardExportCompleted' } | { __typename?: 'GiftCardMetadataUpdated' } | { __typename?: 'GiftCardSent' } | { __typename?: 'GiftCardStatusChanged' } | { __typename?: 'GiftCardUpdated' } | { __typename?: 'InvoiceDeleted' } | { __typename?: 'InvoiceRequested' } | { __typename?: 'InvoiceSent' } | { __typename?: 'ListStoredPaymentMethods' } | { __typename?: 'MenuCreated' } | { __typename?: 'MenuDeleted' } | { __typename?: 'MenuItemCreated' } | { __typename?: 'MenuItemDeleted' } | { __typename?: 'MenuItemUpdated' } | { __typename?: 'MenuUpdated' } | { __typename?: 'OrderBulkCreated' } | { __typename?: 'OrderCancelled' } | { __typename?: 'OrderConfirmed' } | { __typename?: 'OrderCreated', order?: { __typename?: 'Order', userEmail?: string | null, id: string, number: string, user?: { __typename?: 'User', email: string, firstName: string, lastName: string } | null } | null } | { __typename?: 'OrderExpired' } | { __typename?: 'OrderFilterShippingMethods' } | { __typename?: 'OrderFulfilled' } | { __typename?: 'OrderFullyPaid' } | { __typename?: 'OrderFullyRefunded' } | { __typename?: 'OrderMetadataUpdated' } | { __typename?: 'OrderPaid' } | { __typename?: 'OrderRefunded' } | { __typename?: 'OrderUpdated' } | { __typename?: 'PageCreated' } | { __typename?: 'PageDeleted' } | { __typename?: 'PageTypeCreated' } | { __typename?: 'PageTypeDeleted' } | { __typename?: 'PageTypeUpdated' } | { __typename?: 'PageUpdated' } | { __typename?: 'PaymentAuthorize' } | { __typename?: 'PaymentCaptureEvent' } | { __typename?: 'PaymentConfirmEvent' } | { __typename?: 'PaymentGatewayInitializeSession' } | { __typename?: 'PaymentGatewayInitializeTokenizationSession' } | { __typename?: 'PaymentListGateways' } | { __typename?: 'PaymentMethodInitializeTokenizationSession' } | { __typename?: 'PaymentMethodProcessTokenizationSession' } | { __typename?: 'PaymentProcessEvent' } | { __typename?: 'PaymentRefundEvent' } | { __typename?: 'PaymentVoidEvent' } | { __typename?: 'PermissionGroupCreated' } | { __typename?: 'PermissionGroupDeleted' } | { __typename?: 'PermissionGroupUpdated' } | { __typename?: 'ProductCreated' } | { __typename?: 'ProductDeleted' } | { __typename?: 'ProductExportCompleted' } | { __typename?: 'ProductMediaCreated' } | { __typename?: 'ProductMediaDeleted' } | { __typename?: 'ProductMediaUpdated' } | { __typename?: 'ProductMetadataUpdated' } | { __typename?: 'ProductUpdated' } | { __typename?: 'ProductVariantBackInStock' } | { __typename?: 'ProductVariantCreated' } | { __typename?: 'ProductVariantDeleted' } | { __typename?: 'ProductVariantMetadataUpdated' } | { __typename?: 'ProductVariantOutOfStock' } | { __typename?: 'ProductVariantStockUpdated' } | { __typename?: 'ProductVariantUpdated' } | { __typename?: 'PromotionCreated' } | { __typename?: 'PromotionDeleted' } | { __typename?: 'PromotionEnded' } | { __typename?: 'PromotionRuleCreated' } | { __typename?: 'PromotionRuleDeleted' } | { __typename?: 'PromotionRuleUpdated' } | { __typename?: 'PromotionStarted' } | { __typename?: 'PromotionUpdated' } | { __typename?: 'SaleCreated' } | { __typename?: 'SaleDeleted' } | { __typename?: 'SaleToggle' } | { __typename?: 'SaleUpdated' } | { __typename?: 'ShippingListMethodsForCheckout' } | { __typename?: 'ShippingPriceCreated' } | { __typename?: 'ShippingPriceDeleted' } | { __typename?: 'ShippingPriceUpdated' } | { __typename?: 'ShippingZoneCreated' } | { __typename?: 'ShippingZoneDeleted' } | { __typename?: 'ShippingZoneMetadataUpdated' } | { __typename?: 'ShippingZoneUpdated' } | { __typename?: 'ShopMetadataUpdated' } | { __typename?: 'StaffCreated' } | { __typename?: 'StaffDeleted' } | { __typename?: 'StaffSetPasswordRequested' } | { __typename?: 'StaffUpdated' } | { __typename?: 'StoredPaymentMethodDeleteRequested' } | { __typename?: 'ThumbnailCreated' } | { __typename?: 'TransactionCancelationRequested' } | { __typename?: 'TransactionChargeRequested' } | { __typename?: 'TransactionInitializeSession' } | { __typename?: 'TransactionItemMetadataUpdated' } | { __typename?: 'TransactionProcessSession' } | { __typename?: 'TransactionRefundRequested' } | { __typename?: 'TranslationCreated' } | { __typename?: 'TranslationUpdated' } | { __typename?: 'VoucherCodeExportCompleted' } | { __typename?: 'VoucherCodesCreated' } | { __typename?: 'VoucherCodesDeleted' } | { __typename?: 'VoucherCreated' } | { __typename?: 'VoucherDeleted' } | { __typename?: 'VoucherMetadataUpdated' } | { __typename?: 'VoucherUpdated' } | { __typename?: 'WarehouseCreated' } | { __typename?: 'WarehouseDeleted' } | { __typename?: 'WarehouseMetadataUpdated' } | { __typename?: 'WarehouseUpdated' } | null }; export type LastOrderQueryVariables = Exact<{ [key: string]: never; }>; export type LastOrderQuery = { __typename?: 'Query', orders?: { __typename?: 'OrderCountableConnection', edges: Array<{ __typename?: 'OrderCountableEdge', node: { __typename?: 'Order', id: string, number: string, created: any, user?: { __typename?: 'User', firstName: string, lastName: string } | null, shippingAddress?: { __typename?: 'Address', country: { __typename?: 'CountryDisplay', country: string } } | null, total: { __typename?: 'TaxedMoney', gross: { __typename?: 'Money', amount: number, currency: string } }, lines: Array<{ __typename?: 'OrderLine', id: string }> } }> } | null }; -export type OrderCreatedWebhookPayloadFragment = { __typename?: 'OrderCreated', order?: { __typename?: 'Order', userEmail?: string | null, id: string, number: string, user?: { __typename?: 'User', email: string, firstName: string, lastName: string } | null } | null }; - -export type OrderCreatedSubscriptionVariables = Exact<{ [key: string]: never; }>; - - -export type OrderCreatedSubscription = { __typename?: 'Subscription', event?: { __typename?: 'AccountChangeEmailRequested' } | { __typename?: 'AccountConfirmationRequested' } | { __typename?: 'AccountConfirmed' } | { __typename?: 'AccountDeleteRequested' } | { __typename?: 'AccountDeleted' } | { __typename?: 'AccountEmailChanged' } | { __typename?: 'AccountSetPasswordRequested' } | { __typename?: 'AddressCreated' } | { __typename?: 'AddressDeleted' } | { __typename?: 'AddressUpdated' } | { __typename?: 'AppDeleted' } | { __typename?: 'AppInstalled' } | { __typename?: 'AppStatusChanged' } | { __typename?: 'AppUpdated' } | { __typename?: 'AttributeCreated' } | { __typename?: 'AttributeDeleted' } | { __typename?: 'AttributeUpdated' } | { __typename?: 'AttributeValueCreated' } | { __typename?: 'AttributeValueDeleted' } | { __typename?: 'AttributeValueUpdated' } | { __typename?: 'CalculateTaxes' } | { __typename?: 'CategoryCreated' } | { __typename?: 'CategoryDeleted' } | { __typename?: 'CategoryUpdated' } | { __typename?: 'ChannelCreated' } | { __typename?: 'ChannelDeleted' } | { __typename?: 'ChannelMetadataUpdated' } | { __typename?: 'ChannelStatusChanged' } | { __typename?: 'ChannelUpdated' } | { __typename?: 'CheckoutCreated' } | { __typename?: 'CheckoutFilterShippingMethods' } | { __typename?: 'CheckoutFullyPaid' } | { __typename?: 'CheckoutMetadataUpdated' } | { __typename?: 'CheckoutUpdated' } | { __typename?: 'CollectionCreated' } | { __typename?: 'CollectionDeleted' } | { __typename?: 'CollectionMetadataUpdated' } | { __typename?: 'CollectionUpdated' } | { __typename?: 'CustomerCreated' } | { __typename?: 'CustomerMetadataUpdated' } | { __typename?: 'CustomerUpdated' } | { __typename?: 'DraftOrderCreated' } | { __typename?: 'DraftOrderDeleted' } | { __typename?: 'DraftOrderUpdated' } | { __typename?: 'FulfillmentApproved' } | { __typename?: 'FulfillmentCanceled' } | { __typename?: 'FulfillmentCreated' } | { __typename?: 'FulfillmentMetadataUpdated' } | { __typename?: 'FulfillmentTrackingNumberUpdated' } | { __typename?: 'GiftCardCreated' } | { __typename?: 'GiftCardDeleted' } | { __typename?: 'GiftCardExportCompleted' } | { __typename?: 'GiftCardMetadataUpdated' } | { __typename?: 'GiftCardSent' } | { __typename?: 'GiftCardStatusChanged' } | { __typename?: 'GiftCardUpdated' } | { __typename?: 'InvoiceDeleted' } | { __typename?: 'InvoiceRequested' } | { __typename?: 'InvoiceSent' } | { __typename?: 'ListStoredPaymentMethods' } | { __typename?: 'MenuCreated' } | { __typename?: 'MenuDeleted' } | { __typename?: 'MenuItemCreated' } | { __typename?: 'MenuItemDeleted' } | { __typename?: 'MenuItemUpdated' } | { __typename?: 'MenuUpdated' } | { __typename?: 'OrderBulkCreated' } | { __typename?: 'OrderCancelled' } | { __typename?: 'OrderConfirmed' } | { __typename?: 'OrderCreated', order?: { __typename?: 'Order', userEmail?: string | null, id: string, number: string, user?: { __typename?: 'User', email: string, firstName: string, lastName: string } | null } | null } | { __typename?: 'OrderExpired' } | { __typename?: 'OrderFilterShippingMethods' } | { __typename?: 'OrderFulfilled' } | { __typename?: 'OrderFullyPaid' } | { __typename?: 'OrderFullyRefunded' } | { __typename?: 'OrderMetadataUpdated' } | { __typename?: 'OrderPaid' } | { __typename?: 'OrderRefunded' } | { __typename?: 'OrderUpdated' } | { __typename?: 'PageCreated' } | { __typename?: 'PageDeleted' } | { __typename?: 'PageTypeCreated' } | { __typename?: 'PageTypeDeleted' } | { __typename?: 'PageTypeUpdated' } | { __typename?: 'PageUpdated' } | { __typename?: 'PaymentAuthorize' } | { __typename?: 'PaymentCaptureEvent' } | { __typename?: 'PaymentConfirmEvent' } | { __typename?: 'PaymentGatewayInitializeSession' } | { __typename?: 'PaymentGatewayInitializeTokenizationSession' } | { __typename?: 'PaymentListGateways' } | { __typename?: 'PaymentMethodInitializeTokenizationSession' } | { __typename?: 'PaymentMethodProcessTokenizationSession' } | { __typename?: 'PaymentProcessEvent' } | { __typename?: 'PaymentRefundEvent' } | { __typename?: 'PaymentVoidEvent' } | { __typename?: 'PermissionGroupCreated' } | { __typename?: 'PermissionGroupDeleted' } | { __typename?: 'PermissionGroupUpdated' } | { __typename?: 'ProductCreated' } | { __typename?: 'ProductDeleted' } | { __typename?: 'ProductExportCompleted' } | { __typename?: 'ProductMediaCreated' } | { __typename?: 'ProductMediaDeleted' } | { __typename?: 'ProductMediaUpdated' } | { __typename?: 'ProductMetadataUpdated' } | { __typename?: 'ProductUpdated' } | { __typename?: 'ProductVariantBackInStock' } | { __typename?: 'ProductVariantCreated' } | { __typename?: 'ProductVariantDeleted' } | { __typename?: 'ProductVariantMetadataUpdated' } | { __typename?: 'ProductVariantOutOfStock' } | { __typename?: 'ProductVariantStockUpdated' } | { __typename?: 'ProductVariantUpdated' } | { __typename?: 'PromotionCreated' } | { __typename?: 'PromotionDeleted' } | { __typename?: 'PromotionEnded' } | { __typename?: 'PromotionRuleCreated' } | { __typename?: 'PromotionRuleDeleted' } | { __typename?: 'PromotionRuleUpdated' } | { __typename?: 'PromotionStarted' } | { __typename?: 'PromotionUpdated' } | { __typename?: 'SaleCreated' } | { __typename?: 'SaleDeleted' } | { __typename?: 'SaleToggle' } | { __typename?: 'SaleUpdated' } | { __typename?: 'ShippingListMethodsForCheckout' } | { __typename?: 'ShippingPriceCreated' } | { __typename?: 'ShippingPriceDeleted' } | { __typename?: 'ShippingPriceUpdated' } | { __typename?: 'ShippingZoneCreated' } | { __typename?: 'ShippingZoneDeleted' } | { __typename?: 'ShippingZoneMetadataUpdated' } | { __typename?: 'ShippingZoneUpdated' } | { __typename?: 'ShopMetadataUpdated' } | { __typename?: 'StaffCreated' } | { __typename?: 'StaffDeleted' } | { __typename?: 'StaffSetPasswordRequested' } | { __typename?: 'StaffUpdated' } | { __typename?: 'StoredPaymentMethodDeleteRequested' } | { __typename?: 'ThumbnailCreated' } | { __typename?: 'TransactionCancelationRequested' } | { __typename?: 'TransactionChargeRequested' } | { __typename?: 'TransactionInitializeSession' } | { __typename?: 'TransactionItemMetadataUpdated' } | { __typename?: 'TransactionProcessSession' } | { __typename?: 'TransactionRefundRequested' } | { __typename?: 'TranslationCreated' } | { __typename?: 'TranslationUpdated' } | { __typename?: 'VoucherCodeExportCompleted' } | { __typename?: 'VoucherCodesCreated' } | { __typename?: 'VoucherCodesDeleted' } | { __typename?: 'VoucherCreated' } | { __typename?: 'VoucherDeleted' } | { __typename?: 'VoucherMetadataUpdated' } | { __typename?: 'VoucherUpdated' } | { __typename?: 'WarehouseCreated' } | { __typename?: 'WarehouseDeleted' } | { __typename?: 'WarehouseMetadataUpdated' } | { __typename?: 'WarehouseUpdated' } | null }; - import { IntrospectionQuery } from 'graphql'; export default { "__schema": { @@ -103455,20 +103450,16 @@ export const UntypedOrderCreatedWebhookPayloadFragmentDoc = gql` } } `; -export const UntypedFetchAppDetailsDocument = gql` - query FetchAppDetails { - app { - id - privateMetadata { - key - value - } +export const UntypedOrderCreatedSubscriptionDocument = gql` + subscription OrderCreatedSubscription { + event { + ...OrderCreatedWebhookPayload } } - `; + ${UntypedOrderCreatedWebhookPayloadFragmentDoc}`; -export function useFetchAppDetailsQuery(options?: Omit, 'query'>) { - return Urql.useQuery({ query: UntypedFetchAppDetailsDocument, ...options }); +export function useOrderCreatedSubscriptionSubscription(options: Omit, 'query'> = {}, handler?: Urql.SubscriptionHandler) { + return Urql.useSubscription({ query: UntypedOrderCreatedSubscriptionDocument, ...options }, handler); }; export const UntypedLastOrderDocument = gql` query LastOrder { @@ -103505,18 +103496,6 @@ export const UntypedLastOrderDocument = gql` export function useLastOrderQuery(options?: Omit, 'query'>) { return Urql.useQuery({ query: UntypedLastOrderDocument, ...options }); }; -export const UntypedOrderCreatedDocument = gql` - subscription OrderCreated { - event { - ...OrderCreatedWebhookPayload - } -} - ${UntypedOrderCreatedWebhookPayloadFragmentDoc}`; - -export function useOrderCreatedSubscription(options: Omit, 'query'> = {}, handler?: Urql.SubscriptionHandler) { - return Urql.useSubscription({ query: UntypedOrderCreatedDocument, ...options }, handler); -}; export const OrderCreatedWebhookPayloadFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OrderCreatedWebhookPayload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"OrderCreated"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}}]}}]}}]} as unknown as DocumentNode; -export const FetchAppDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FetchAppDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"app"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"privateMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]} as unknown as DocumentNode; -export const LastOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LastOrder"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"shippingAddress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"country"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"country"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"total"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"gross"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"currency"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"lines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const OrderCreatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"OrderCreated"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"OrderCreatedWebhookPayload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OrderCreatedWebhookPayload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"OrderCreated"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const OrderCreatedSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"OrderCreatedSubscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"event"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"OrderCreatedWebhookPayload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OrderCreatedWebhookPayload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"OrderCreated"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userEmail"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}}]}}]}}]} as unknown as DocumentNode; +export const LastOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LastOrder"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"shippingAddress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"country"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"country"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"total"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"gross"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"currency"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"lines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/graphql/fragments/.gitkeep b/graphql/fragments/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/graphql/fragments/order-created.graphql b/graphql/fragments/order-created.graphql new file mode 100644 index 00000000..bdf09b2d --- /dev/null +++ b/graphql/fragments/order-created.graphql @@ -0,0 +1,12 @@ +fragment OrderCreatedWebhookPayload on OrderCreated { + order { + userEmail + id + number + user { + email + firstName + lastName + } + } +} diff --git a/graphql/queries/FetchAppDetails.graphql b/graphql/queries/FetchAppDetails.graphql deleted file mode 100644 index b4bad820..00000000 --- a/graphql/queries/FetchAppDetails.graphql +++ /dev/null @@ -1,9 +0,0 @@ -query FetchAppDetails { - app { - id - privateMetadata { - key - value - } - } -} diff --git a/graphql/subscriptions/.gitkeep b/graphql/subscriptions/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/graphql/subscriptions/order-created.graphql b/graphql/subscriptions/order-created.graphql new file mode 100644 index 00000000..5072a499 --- /dev/null +++ b/graphql/subscriptions/order-created.graphql @@ -0,0 +1,5 @@ +subscription OrderCreatedSubscription { + event { + ...OrderCreatedWebhookPayload + } +} diff --git a/package.json b/package.json index 17bc570e..2a1c326f 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "eslint": "8.31.0", "eslint-config-next": "13.1.2", "eslint-config-prettier": "^8.6.0", + "eslint-plugin-simple-import-sort": "12.1.1", "prettier": "^2.8.2", "typescript": "5.0.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c86a71e1..cd0d7ab3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: eslint-config-prettier: specifier: ^8.6.0 version: 8.10.0(eslint@8.31.0) + eslint-plugin-simple-import-sort: + specifier: 12.1.1 + version: 12.1.1(eslint@8.31.0) prettier: specifier: ^2.8.2 version: 2.8.8 @@ -2420,6 +2423,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + eslint-plugin-simple-import-sort@12.1.1: + resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + peerDependencies: + eslint: '>=5.0.0' + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -7218,6 +7226,10 @@ snapshots: semver: 6.3.1 string.prototype.matchall: 4.0.10 + eslint-plugin-simple-import-sort@12.1.1(eslint@8.31.0): + dependencies: + eslint: 8.31.0 + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 diff --git a/src/lib/no-ssr-wrapper.tsx b/src/lib/no-ssr-wrapper.tsx index f1954399..bf4e4027 100644 --- a/src/lib/no-ssr-wrapper.tsx +++ b/src/lib/no-ssr-wrapper.tsx @@ -1,5 +1,5 @@ -import React, { PropsWithChildren } from "react"; import dynamic from "next/dynamic"; +import React, { PropsWithChildren } from "react"; const Wrapper = (props: PropsWithChildren<{}>) => {props.children}; diff --git a/src/order-example.tsx b/src/order-example.tsx index e8aaa914..dfe1b2ba 100644 --- a/src/order-example.tsx +++ b/src/order-example.tsx @@ -2,6 +2,7 @@ import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge"; import { Box, Text } from "@saleor/macaw-ui"; import gql from "graphql-tag"; import Link from "next/link"; + import { useLastOrderQuery } from "../generated/graphql"; /** diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f33e4b15..72f633ce 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,13 +3,13 @@ import "../styles/globals.css"; import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge"; import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next"; +import { ThemeProvider } from "@saleor/macaw-ui"; import { AppProps } from "next/app"; import { useEffect } from "react"; -import { ThemeProvider } from "@saleor/macaw-ui"; -import { NoSSRWrapper } from "../lib/no-ssr-wrapper"; -import { ThemeSynchronizer } from "../lib/theme-synchronizer"; -import { GraphQLProvider } from "../providers/GraphQLProvider"; +import { NoSSRWrapper } from "@/lib/no-ssr-wrapper"; +import { ThemeSynchronizer } from "@/lib/theme-synchronizer"; +import { GraphQLProvider } from "@/providers/GraphQLProvider"; /** * Ensure instance is a singleton. @@ -32,7 +32,7 @@ function NextApp({ Component, pageProps }: AppProps) { - + diff --git a/src/pages/actions.tsx b/src/pages/actions.tsx index 9dbd7f66..d51a51ab 100644 --- a/src/pages/actions.tsx +++ b/src/pages/actions.tsx @@ -1,5 +1,6 @@ import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge"; import { Box, Button, Text } from "@saleor/macaw-ui"; + import { OrderExample } from "../order-example"; /** diff --git a/src/pages/api/manifest.ts b/src/pages/api/manifest.ts index 4a9b5e26..9b379423 100644 --- a/src/pages/api/manifest.ts +++ b/src/pages/api/manifest.ts @@ -19,7 +19,7 @@ export default createManifestHandler({ const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl; const manifest: AppManifest = { - name: 'Saleor App Template', + name: "Saleor App Template", tokenTargetUrl: `${apiBaseURL}/api/register`, appUrl: iframeBaseUrl, /** diff --git a/src/pages/api/register.ts b/src/pages/api/register.ts index 796ec03d..e3795c64 100644 --- a/src/pages/api/register.ts +++ b/src/pages/api/register.ts @@ -1,6 +1,6 @@ import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next"; -import { saleorApp } from "../../saleor-app"; +import { saleorApp } from "@/saleor-app"; /** * Required endpoint, called by Saleor to install app. diff --git a/src/pages/api/webhooks/order-created.ts b/src/pages/api/webhooks/order-created.ts index d6e9c4ee..e74c65b2 100644 --- a/src/pages/api/webhooks/order-created.ts +++ b/src/pages/api/webhooks/order-created.ts @@ -1,40 +1,12 @@ -import { gql } from "urql"; import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next"; -import { OrderCreatedWebhookPayloadFragment } from "../../../../generated/graphql"; -import { saleorApp } from "../../../saleor-app"; -import { createClient } from "../../../lib/create-graphq-client"; -/** - * Example payload of the webhook. It will be transformed with graphql-codegen to Typescript type: OrderCreatedWebhookPayloadFragment - */ -const OrderCreatedWebhookPayload = gql` - fragment OrderCreatedWebhookPayload on OrderCreated { - order { - userEmail - id - number - user { - email - firstName - lastName - } - } - } -`; +import { createClient } from "@/lib/create-graphq-client"; +import { saleorApp } from "@/saleor-app"; -/** - * Top-level webhook subscription query, that will be attached to the Manifest. - * Saleor will use it to register webhook. - */ -const OrderCreatedGraphqlSubscription = gql` - # Payload fragment must be included in the root query - ${OrderCreatedWebhookPayload} - subscription OrderCreated { - event { - ...OrderCreatedWebhookPayload - } - } -`; +import { + OrderCreatedSubscriptionDocument, + OrderCreatedWebhookPayloadFragment, +} from "../../../../generated/graphql"; /** * Create abstract Webhook. It decorates handler and performs security checks under the hood. @@ -46,7 +18,7 @@ export const orderCreatedWebhook = new SaleorAsyncWebhook { * Create GraphQL client to interact with Saleor API. */ const client = createClient(authData.saleorApiUrl, async () => ({ token: authData.token })); - + /** * Now you can fetch additional data using urql. * https://formidable.com/open-source/urql/docs/api/core/#clientquery */ - + // const data = await client.query().toPromise() /** diff --git a/src/providers/GraphQLProvider.tsx b/src/providers/GraphQLProvider.tsx index 862875ba..79bf11b4 100644 --- a/src/providers/GraphQLProvider.tsx +++ b/src/providers/GraphQLProvider.tsx @@ -1,7 +1,8 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge"; import { PropsWithChildren } from "react"; import { Provider } from "urql"; -import { createClient } from "../lib/create-graphq-client"; + +import { createClient } from "@/lib/create-graphq-client"; export function GraphQLProvider(props: PropsWithChildren<{}>) { const { appBridgeState } = useAppBridge(); diff --git a/src/setup-tests.ts b/src/setup-tests.ts index 50462786..5f1e1984 100644 --- a/src/setup-tests.ts +++ b/src/setup-tests.ts @@ -3,4 +3,4 @@ * * https://vitest.dev/config/#setupfiles */ -export {} +export {}; diff --git a/tsconfig.json b/tsconfig.json index 99710e85..e626cf50 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,11 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true + "incremental": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/vitest.config.ts b/vitest.config.ts index 1dde5bc4..af5af05a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,5 +9,8 @@ export default defineConfig({ environment: "jsdom", setupFiles: "./src/setup-tests.ts", css: false, + alias: { + "@/": new URL("./src/", import.meta.url).pathname, + }, }, });