Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ NEXT_PUBLIC_STOREFRONT_URL=http://localhost:3000

# Token used for fetching channels
SALEOR_APP_TOKEN=

# Channel which will be used as default (e.g. for homepage redirection when no channel is selected)
# You can use it as alternative to providing app token
NEXT_PUBLIC_DEFAULT_CHANNEL=default-channel
3 changes: 2 additions & 1 deletion src/app/[channel]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type ReactNode } from "react";
import { executeGraphQL } from "@/lib/graphql";
import { ChannelsListDocument } from "@/gql/graphql";
import { DEFAULT_CHANNEL } from "@/lib/utils";

export const generateStaticParams = async () => {
// the `channels` query is protected
Expand All @@ -20,7 +21,7 @@ export const generateStaticParams = async () => {
.map((channel) => ({ channel: channel.slug })) ?? []
);
} else {
return [{ channel: "default-channel" }];
return [{ channel: DEFAULT_CHANNEL }];
}
};

Expand Down
15 changes: 15 additions & 0 deletions src/app/api/openStorefrontApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Open Storefront App

This directory provides an example of integrating the Saleor Dashboard with the Storefront. After installing this app, the product editing view in the dashboard will display a convenient link that opens the corresponding product page in the Storefront.

## Requirements

- Saleor version >=3.22.0-a.0
- `NEXT_PUBLIC_STOREFRONT_URL` and `NEXT_PUBLIC_DEFAULT_CHANNEL` environment variables configured

## Installation

- deploy Storefront or [tunnel it](https://docs.saleor.io/developer/extending/apps/developing-with-tunnels)
- go to the Saleor Dashboard, open section `Extensions`
- click on `Add Extension` and choose `Install from manifest`
- enter the manifest address: `https://[public address of the storefront]/api/openStorefrontApp/manifest` and install it
34 changes: 34 additions & 0 deletions src/app/api/openStorefrontApp/appUrl/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";

export default function Page() {
return (
<section className="p-8">
<h1 className="mb-4 text-2xl">Open Storefront App</h1>
<p>
Once you install this app, a new option will appear that lets you preview the product you’re currently
editing directly in the storefront.
</p>
<p>
<a
className="cursor-pointer text-blue-600 underline hover:text-blue-800"
onClick={(e) => {
e.preventDefault();
window.parent.postMessage(
{
type: "redirect",
payload: {
to: "https://docs.saleor.io/developer/extending/apps/extending-dashboard-with-apps",
newContext: true,
},
},
"*",
);
}}
>
Read Dashboard Extensions documentation
</a>{" "}
to learn more about integrating with Dashboard UI.
</p>
</section>
);
}
35 changes: 35 additions & 0 deletions src/app/api/openStorefrontApp/manifest/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export async function GET() {
const baseUrl = process.env.NEXT_PUBLIC_STOREFRONT_URL;
if (!baseUrl) {
return new Response("Base URL is not configured. Check the .env file", { status: 500 });
}

return Response.json({
name: "Open Storefront App",
id: "example.app.openStorefrontApp",
version: "0.0.1",
requiredSaleorVersion: ">=3.22.0-a.0",
author: "Saleor Community Resources",
about: "Adds storefront redirection button to the Product Details page.",

permissions: ["MANAGE_PRODUCTS"],
appUrl: new URL("/api/openStorefrontApp/appUrl", baseUrl).toString(),
tokenTargetUrl: new URL("/api/openStorefrontApp/register", baseUrl).toString(),

webhooks: [],
extensions: [
{
label: "Open this product in the storefront",
mount: "PRODUCT_DETAILS_WIDGETS",
target: "NEW_TAB",
permissions: ["MANAGE_PRODUCTS"],
url: new URL("/api/openStorefrontApp/redirect", baseUrl).toString(),
options: {
newTabTarget: {
method: "GET",
},
},
},
],
});
}
35 changes: 35 additions & 0 deletions src/app/api/openStorefrontApp/redirect/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type NextRequest } from "next/server";
import { ProductSlugDocument } from "@/gql/graphql";
import { executeGraphQL } from "@/lib/graphql";
import { DEFAULT_CHANNEL } from "@/lib/utils";

export async function GET(request: NextRequest) {
const baseUrl = process.env.NEXT_PUBLIC_STOREFRONT_URL;

if (!baseUrl) {
return new Response("Base URL is not configured. Check the .env file", { status: 500 });
}

const searchParams = request.nextUrl.searchParams;
const productId = searchParams.get("productId");

if (!productId) {
return new Response("Product ID is required", { status: 400 });
}

const { product } = await executeGraphQL(ProductSlugDocument, {
variables: {
productId,
channel: DEFAULT_CHANNEL,
},
revalidate: 60,
});

const slug = product?.slug;

if (!slug) {
return new Response("Product not found", { status: 404 });
}

return Response.redirect(new URL(`/${DEFAULT_CHANNEL}/products/${product.slug}`, baseUrl).toString(), 302);
}
8 changes: 8 additions & 0 deletions src/app/api/openStorefrontApp/register/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Since this mini app does not communicate with the Saleor API,
// we do not need to keep app token

export async function POST() {
return new Response("", {
status: 200,
});
}
3 changes: 2 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { redirect } from "next/navigation";
import { DEFAULT_CHANNEL } from "@/lib/utils";

export default function EmptyPage() {
redirect("/default-channel");
redirect(`/${DEFAULT_CHANNEL}`);
}
5 changes: 5 additions & 0 deletions src/graphql/ProductSlug.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query ProductSlug($productId: ID!, $channel: String!) {
product(id: $productId, channel: $channel) {
slug
}
}
2 changes: 2 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ export function getHrefForVariant({
const query = new URLSearchParams({ variant: variantId });
return `${pathname}?${query.toString()}`;
}

export const DEFAULT_CHANNEL = process.env.NEXT_PUBLIC_DEFAULT_CHANNEL || "default-channel";
Loading