diff --git a/README.md b/README.md index d9b84011..c38c1b5b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # starter.store -A starter powered by FastStore. +A starter powered by FastStore . diff --git a/cms/faststore/sections.json b/cms/faststore/sections.json new file mode 100644 index 00000000..9c6a5e76 --- /dev/null +++ b/cms/faststore/sections.json @@ -0,0 +1,83 @@ +[ + { + "name": "FixedImageHero", + "schema": { + "title": "Hero with a Fixed Image", + "description": "Add a quick promotion with an image/action pair", + "type": "object", + "required": ["title"], + "properties": { + "title": { + "title": "Title", + "type": "string" + }, + "subtitle": { + "title": "Subtitle", + "type": "string" + }, + "link": { + "title": "Call to Action", + "type": "object", + "properties": { + "text": { + "type": "string", + "title": "Text" + }, + "url": { + "type": "string", + "title": "URL" + }, + "linkTargetBlank": { + "type": "boolean", + "title": "Open link in new window?", + "default": false + } + } + }, + "colorVariant": { + "type": "string", + "title": "Color variant", + "enumNames": ["Main", "Light", "Accent"], + "enum": ["main", "light", "accent"] + }, + "variant": { + "type": "string", + "title": "Variant", + "enumNames": ["Primary", "Secondary"], + "enum": ["primary", "secondary"] + } + } + } + }, + { + "name": "CallToAction", + "requiredScopes": [], + "schema": { + "title": "Call To Action", + "description": "Get your 20% off on the first purchase!", + "type": "object", + "required": ["title", "link"], + "properties": { + "title": { + "title": "Title", + "type": "string" + }, + "link": { + "title": "Link Path", + "type": "object", + "required": ["text", "url"], + "properties": { + "text": { + "title": "Text", + "type": "string" + }, + "url": { + "title": "URL", + "type": "string" + } + } + } + } + } + } +] diff --git a/package.json b/package.json index 4e4186b3..a24be2cf 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "faststore test" }, "dependencies": { - "@faststore/core": "3.0.54", + "@faststore/core": "3.0.55", "next": "^13.5.6", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/src/components/index.tsx b/src/components/index.tsx new file mode 100644 index 00000000..ae4b0419 --- /dev/null +++ b/src/components/index.tsx @@ -0,0 +1,4 @@ +import CallToAction from "./sections/CallToAction"; +import FixedImageHero from "./sections/FixedImageHero"; + +export default { CallToAction, FixedImageHero }; diff --git a/src/components/sections/CallToAction.tsx b/src/components/sections/CallToAction.tsx new file mode 100644 index 00000000..ff953086 --- /dev/null +++ b/src/components/sections/CallToAction.tsx @@ -0,0 +1,21 @@ +import { useDynamicContent } from "@faststore/core"; +import { ServerDynamicContentQuery } from "@faststore/core/api"; + +export interface CallToActionProps { + title: string; + link: { + text: string; + url: string; + }; +} + +export default function CallToAction(props: CallToActionProps) { + const context = useDynamicContent(); + console.log("🚀 ~ CallToAction context:", context); + return ( +
+

{`${props.title} ${context?.data?.namedExtraData?.data}`}

+
{`${JSON.stringify(context?.data, null, 2)}`}
+
+ ); +} diff --git a/src/components/sections/FixedImageHero.tsx b/src/components/sections/FixedImageHero.tsx new file mode 100644 index 00000000..e73674bf --- /dev/null +++ b/src/components/sections/FixedImageHero.tsx @@ -0,0 +1,45 @@ +import { + HeroSection, + getOverriddenSection, + useDynamicContent, +} from "@faststore/core"; +import { ServerDynamicContentQuery } from "@faststore/core/api"; + +const OverridenHero = getOverriddenSection({ + Section: HeroSection, + components: { + HeroImage: { + Component: CustomHeroImage, + }, + }, +}); + +function CustomHeroImage() { + const context = useDynamicContent(); + console.log("🚀 ~ CustomHeroImage context:", context); + + return ( + {context.data?.extraData?.customField + ); +} + +export default function FixedImageHero( + props: React.ComponentProps +) { + const context = useDynamicContent(); + console.log("🚀 ~ FixedImageHero context:", context); + return ( + + ); +} diff --git a/src/dynamicContent/index.ts b/src/dynamicContent/index.ts new file mode 100644 index 00000000..630fec47 --- /dev/null +++ b/src/dynamicContent/index.ts @@ -0,0 +1,101 @@ +// starter.store code that will be copied to .faststore (core) + +import { + ServerDynamicContentQuery, + ServerDynamicContentQueryVariables, + gql, +} from "@faststore/core/api"; +import { execute_unstable as execute } from "@faststore/core/experimental/server"; + +async function fetchDataMyLandingPage() { + const response = await fetch("https://fakestoreapi.com/products/1"); + const data = await response.json(); + return { + data, + }; +} + +async function fetchDataOtherLandingPage() { + const response = await fetch("other-api/other-landing-page-endpoint"); + const data = await response.json(); + return { + data, + }; +} + +async function fetchDataUsingPromiseAll() { + try { + const [apiData1, apiData2] = await Promise.all([ + fetch("my-api/endpoint-1").then((response) => response.json()), + fetch("my-api/endpoint-2").then((response) => response.json()), + ]); + + return { + data: { + apiData1, + apiData2, + }, + }; + } catch (error) { + console.error("Error fetching data from APIs:", error); + return { + data: null, + errors: ["Error fetching data from APIs"], + }; + } +} + +const query = gql(` + query ServerDynamicContent($name: String!){ + extraData { + data { + title + rating { + rate + count + } + } + customField + customFieldFromRoot + } + namedExtraData(name: $name) { + data + } + } +`); + +async function fetchDataUsingApiExtension() { + try { + const dynamicContentResult = await execute< + ServerDynamicContentQueryVariables, + ServerDynamicContentQuery + >({ + variables: { + name: "Variables passed to query - Dynamic Content Feature", + }, + operation: query, + }); + return { + data: dynamicContentResult.data, + }; + } catch (error) { + console.error("Error fetching data from APIs:", error); + return { + data: null, + errors: ["Error fetching data from APIs"], + }; + } +} + +// map the slug to correspondent functions +const dynamicContent = { + home: fetchDataUsingApiExtension, + "my-landing-page": fetchDataUsingApiExtension, + // "my-landing-page": fetchDataMyLandingPage, + "other-landing-page": fetchDataOtherLandingPage, + "landing-page-using-promise-all": fetchDataUsingPromiseAll, + "landing-page-using-api-extension": fetchDataUsingApiExtension, + about: fetchDataUsingApiExtension, +}; + +export default dynamicContent; diff --git a/src/graphql/thirdParty/resolvers/extraData.ts b/src/graphql/thirdParty/resolvers/extraData.ts new file mode 100644 index 00000000..64592886 --- /dev/null +++ b/src/graphql/thirdParty/resolvers/extraData.ts @@ -0,0 +1,13 @@ +import { ExtraDataRoot } from "./query"; + +export const ExtraData = { + data: (root: ExtraDataRoot) => root.data, + customFieldFromRoot: (root: ExtraDataRoot) => root?.data?.[0]?.image ?? "", + customField: async (_: ExtraDataRoot) => { + const res = await fetch( + "https://fakestoreapi.com/products/category/jewelery" + ); + const customField = await res.json(); + return (customField?.[0]?.title as string) ?? ""; + }, +}; diff --git a/src/graphql/thirdParty/resolvers/index.ts b/src/graphql/thirdParty/resolvers/index.ts new file mode 100644 index 00000000..a9b66e14 --- /dev/null +++ b/src/graphql/thirdParty/resolvers/index.ts @@ -0,0 +1,9 @@ +import { ExtraData } from "./extraData"; +import { Query } from "./query"; + +const resolvers = { + ExtraData, + Query, +}; + +export default resolvers; diff --git a/src/graphql/thirdParty/resolvers/query.ts b/src/graphql/thirdParty/resolvers/query.ts new file mode 100644 index 00000000..4f11b9b7 --- /dev/null +++ b/src/graphql/thirdParty/resolvers/query.ts @@ -0,0 +1,37 @@ +export type ProductsExtraData = { + id: number; + title: string; + price: number; + description: string; + category: string; + image: string; + rating: { + rate: string; + count: number; + }; +}; + +export type ExtraDataRoot = { + data?: ProductsExtraData[]; +}; + +async function getProductsFromThirdPartyApi() { + const result = await fetch("https://fakestoreapi.com/products"); + const json = result.json(); + return json; +} + +export const Query = { + extraData: async (): Promise<{ data: ProductsExtraData[] }> => { + const products: ProductsExtraData[] = await getProductsFromThirdPartyApi(); + + return { + data: products, + }; + }, + namedExtraData: (_: unknown, { name }: { name: string }) => { + return { + data: `Named extra data: ${name}`, + }; + }, +}; diff --git a/src/graphql/thirdParty/typeDefs/extra.graphql b/src/graphql/thirdParty/typeDefs/extra.graphql new file mode 100644 index 00000000..18c2a07f --- /dev/null +++ b/src/graphql/thirdParty/typeDefs/extra.graphql @@ -0,0 +1,35 @@ +type Rating { + rate: String + count: Int +} + +type ProductsExtraData { + id: Int + title: String + price: Float + description: String + category: String + image: String + rating: Rating +} + +type ExtraData { + """ + Data customizing ExtraData + """ + data: [ProductsExtraData] + customFieldFromRoot: String + customField: String +} + +type NamedExtraData { + """ + Data customizing NamedExtraData + """ + data: String! +} + +type Query { + extraData: ExtraData + namedExtraData(name: String!): NamedExtraData +} diff --git a/yarn.lock b/yarn.lock index 19468947..81af1aa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -989,10 +989,10 @@ resolved "https://registry.yarnpkg.com/@faststore/components/-/components-3.0.54.tgz#d9857d2ab37ec6bea181ce387bfa6cb4f5fcff06" integrity sha512-cCtKmcjdJwtSmcxOq3AaSLxZXPHwNlMCU6pbpNbkqWtMFT9AB6qk4R8kU15URMQNFAWTZvfPH7Wpmq5OnY7QnQ== -"@faststore/core@3.0.54": - version "3.0.54" - resolved "https://registry.yarnpkg.com/@faststore/core/-/core-3.0.54.tgz#5cabbf17af1846a743e5cc5d809c8820d3029780" - integrity sha512-7TJl0vX2Kk2gtds9AgMbMM+Jv5ejrSUSOOnUQqE8z6CyBdVrG8pB5T6qhXhhtJ0vCR51MRwoXhoPrtYcHo99wA== +"@faststore/core@3.0.55": + version "3.0.55" + resolved "https://registry.yarnpkg.com/@faststore/core/-/core-3.0.55.tgz#71c5f1c30166458dac96fce19207aca9d1a8c214" + integrity sha512-dUyKCPJCYlMq3eElGNzlMq1gYCJTm8NPQn6IXmN1FzMJd4gwrwvG10WqeCx95rcuhTZH2PxCLHD5fbNV0CHp7g== dependencies: "@builder.io/partytown" "^0.6.1" "@envelop/core" "^1.2.0"