diff --git a/deco.ts b/deco.ts index 48947b7b6..4d50e9fcd 100644 --- a/deco.ts +++ b/deco.ts @@ -38,6 +38,7 @@ const config = { app("linx-impulse"), app("shopify"), app("nuvemshop"), + app("streamshop"), app("website"), app("commerce"), app("workflows"), diff --git a/decohub/apps/streamshop.ts b/decohub/apps/streamshop.ts new file mode 100644 index 000000000..8bb34bfaa --- /dev/null +++ b/decohub/apps/streamshop.ts @@ -0,0 +1 @@ +export { default, preview } from "../../streamshop/mod.ts"; diff --git a/shopify/utils/storefront/storefront.graphql.gen.ts b/shopify/utils/storefront/storefront.graphql.gen.ts index 29eee4c5c..86f5fad6b 100644 --- a/shopify/utils/storefront/storefront.graphql.gen.ts +++ b/shopify/utils/storefront/storefront.graphql.gen.ts @@ -7789,6 +7789,17 @@ export type AddItemToCartMutationVariables = Exact<{ export type AddItemToCartMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null } | null }; +export type RegisterAccountMutationVariables = Exact<{ + email: Scalars['String']['input']; + password: Scalars['String']['input']; + firstName?: InputMaybe; + lastName?: InputMaybe; + acceptsMarketing?: InputMaybe; +}>; + + +export type RegisterAccountMutation = { customerCreate?: { customer?: { id: string } | null, customerUserErrors: Array<{ code?: CustomerErrorCode | null, message: string }> } | null }; + export type AddCouponMutationVariables = Exact<{ cartId: Scalars['ID']['input']; discountCodes: Array | Scalars['String']['input']; diff --git a/streamshop/1ex.png b/streamshop/1ex.png new file mode 100644 index 000000000..d4ba30ecd Binary files /dev/null and b/streamshop/1ex.png differ diff --git a/streamshop/2ex.png b/streamshop/2ex.png new file mode 100644 index 000000000..a6353772f Binary files /dev/null and b/streamshop/2ex.png differ diff --git a/streamshop/3ex.png b/streamshop/3ex.png new file mode 100644 index 000000000..6a0c44073 Binary files /dev/null and b/streamshop/3ex.png differ diff --git a/streamshop/README.md b/streamshop/README.md new file mode 100644 index 000000000..5925c7a80 --- /dev/null +++ b/streamshop/README.md @@ -0,0 +1 @@ +# Stream Shop Readme diff --git a/streamshop/actions/myAction.ts b/streamshop/actions/myAction.ts new file mode 100644 index 000000000..c1fade26a --- /dev/null +++ b/streamshop/actions/myAction.ts @@ -0,0 +1,25 @@ +import { AppContext } from "../mod.ts"; +import { GithubUser } from "../utils/types.ts"; + +interface Props { + username: string; +} + +/** + * @title This name will appear on the admin + */ +const action = async ( + props: Props, + _req: Request, + ctx: AppContext, +): Promise => { + const response = await ctx.api[`POST /users/:username`]({ + username: props.username, + }, { body: { filter: "filter" } }); + + const result = await response.json(); + + return result; +}; + +export default action; diff --git a/streamshop/banner_streamshop.png b/streamshop/banner_streamshop.png new file mode 100644 index 000000000..be245990e Binary files /dev/null and b/streamshop/banner_streamshop.png differ diff --git a/streamshop/components/ui/Avatar.tsx b/streamshop/components/ui/Avatar.tsx new file mode 100644 index 000000000..c5b43f432 --- /dev/null +++ b/streamshop/components/ui/Avatar.tsx @@ -0,0 +1,51 @@ +import { clx } from "../../sdk/clx.ts"; + +/** + * This component renders the filter and selectors for skus. + * TODO: Figure out a better name for this component. + */ +interface Props { + variant?: "active" | "disabled" | "default"; + content: string; +} + +const colors: Record> = { + "azul-clara": { backgroundColor: "#87CEFA" }, + "azul-marinho": { backgroundColor: "#000080" }, + "branca": { backgroundColor: "#FFFFFF" }, + "cinza": { backgroundColor: "#808080" }, + "cinza-escura": { backgroundColor: "#A9A9A9" }, + "laranja": { backgroundColor: "#FFA500" }, + "marrom": { backgroundColor: "#A52A2A" }, + "preta": { backgroundColor: "#161616" }, + "verde-clara": { backgroundColor: "#90EE90" }, + "vermelha": { backgroundColor: "#FF0000" }, +}; + +const variants = { + active: "ring-base-content", + disabled: "line-through", + default: "ring-base-400", +}; + +function Avatar({ content, variant = "default" }: Props) { + return ( +
+
+ + {colors[content] ? "" : content.substring(0, 2)} + +
+
+ ); +} + +export default Avatar; diff --git a/streamshop/components/ui/Breadcrumb.tsx b/streamshop/components/ui/Breadcrumb.tsx new file mode 100644 index 000000000..4637943da --- /dev/null +++ b/streamshop/components/ui/Breadcrumb.tsx @@ -0,0 +1,26 @@ +import type { BreadcrumbList } from "apps/commerce/types.ts"; +import { relative } from "../../sdk/url.ts"; + +interface Props { + itemListElement: BreadcrumbList["itemListElement"]; +} + +function Breadcrumb({ itemListElement = [] }: Props) { + const items = [{ name: "Home", item: "/" }, ...itemListElement]; + + return ( + + ); +} + +export default Breadcrumb; diff --git a/streamshop/components/ui/CategoryBanner.tsx b/streamshop/components/ui/CategoryBanner.tsx new file mode 100644 index 000000000..a69ab3243 --- /dev/null +++ b/streamshop/components/ui/CategoryBanner.tsx @@ -0,0 +1,88 @@ +import type { ImageWidget } from "apps/admin/widgets.ts"; +import { Picture, Source } from "apps/website/components/Picture.tsx"; +import { type SectionProps } from "@deco/deco"; +/** + * @titleBy matcher + */ +export interface Banner { + /** @description RegExp to enable this banner on the current URL. Use /feminino/* to display this banner on feminino category */ + matcher: string; + /** @description text to be rendered on top of the image */ + title?: string; + /** @description text to be rendered on top of the image */ + subtitle?: string; + image: { + /** @description Image for big screens */ + desktop: ImageWidget; + /** @description Image for small screens */ + mobile: ImageWidget; + /** @description image alt text */ + alt?: string; + }; +} +const DEFAULT_PROPS = { + banners: [ + { + image: { + mobile: + "https://ozksgdmyrqcxcwhnbepg.supabase.co/storage/v1/object/public/assets/239/91102b71-4832-486a-b683-5f7b06f649af", + desktop: + "https://ozksgdmyrqcxcwhnbepg.supabase.co/storage/v1/object/public/assets/239/ec597b6a-dcf1-48ca-a99d-95b3c6304f96", + alt: "a", + }, + title: "Woman", + matcher: "/*", + subtitle: "As", + }, + ], +}; +function Banner(props: SectionProps>) { + const { banner } = props; + if (!banner) { + return null; + } + const { title, subtitle, image } = banner; + return ( +
+ + + + {image.alt + + +
+

+ + {title} + +

+

+ + {subtitle} + +

+
+
+ ); +} +export interface Props { + banners?: Banner[]; +} +export const loader = (props: Props, req: Request) => { + const { banners } = { ...DEFAULT_PROPS, ...props }; + const banner = banners.find(({ matcher }) => + new URLPattern({ pathname: matcher }).test(req.url) + ); + return { banner }; +}; +export default Banner; diff --git a/streamshop/components/ui/Drawer.tsx b/streamshop/components/ui/Drawer.tsx new file mode 100644 index 000000000..cca5e4e75 --- /dev/null +++ b/streamshop/components/ui/Drawer.tsx @@ -0,0 +1,87 @@ +import { type ComponentChildren } from "preact"; +import { clx } from "../../sdk/clx.ts"; +import { useId } from "../../sdk/useId.ts"; +import Icon from "./Icon.tsx"; +import { useScript } from "@deco/deco/hooks"; +export interface Props { + open?: boolean; + class?: string; + children?: ComponentChildren; + aside: ComponentChildren; + id?: string; +} +const script = (id: string) => { + const handler = (e: KeyboardEvent) => { + if (e.key !== "Escape" && e.keyCode !== 27) { + return; + } + const input = document.getElementById(id) as HTMLInputElement | null; + if (!input) { + return; + } + input.checked = false; + }; + addEventListener("keydown", handler); +}; +function Drawer( + { children, aside, open, class: _class = "", id = useId() }: Props, +) { + return ( + <> +
+ + +
+ {children} +
+ + +
+ + + + + + { + /* Reels e Info + */ + } +
+
+
+
+
+ Video Commerce StreamShop +
+

+ + Incorpore Reels interativos no seu ecommerce ou distribua por whatsapp 🚀 + +

+

+ + + O Video Commerce da StreamShop é uma solução inovadora de simples implementação que eleva a experiência dos seus clientes com a sua marca. + +Incorpore os vídeos que você já produz para as redes sociais às páginas do seu site ou distribua por whatsapp, oferecendo muito mais conteúdo e interatividade para seus consumidores. + +
‍ +
+
+ + Eleve a conversão de suas vendas. Abra a sua conta! + +
+
+
‍ +
+ + Clique nos videos e experimente! + +

+
+
+ +
+
+
+
+ + + + ); +} + +const ArrowSvg = () => ( + + + +); diff --git a/streamshop/components/ui/QuantitySelector.tsx b/streamshop/components/ui/QuantitySelector.tsx new file mode 100644 index 000000000..d6f13e62f --- /dev/null +++ b/streamshop/components/ui/QuantitySelector.tsx @@ -0,0 +1,60 @@ +import { type JSX } from "preact"; +import { clx } from "../../sdk/clx.ts"; +import { useId } from "../../sdk/useId.ts"; +import { useScript } from "@deco/deco/hooks"; +const onClick = (delta: number) => { + // doidera! + event!.stopPropagation(); + const button = event!.currentTarget as HTMLButtonElement; + const input = button.parentElement + ?.querySelector('input[type="number"]')!; + const min = Number(input.min) || -Infinity; + const max = Number(input.max) || Infinity; + input.value = `${Math.min(Math.max(input.valueAsNumber + delta, min), max)}`; + input.dispatchEvent(new Event("change", { bubbles: true })); +}; +function QuantitySelector( + { id = useId(), disabled, ...props }: JSX.IntrinsicElements["input"], +) { + return ( +
+ +
+ +
+ +
+ ); +} +export default QuantitySelector; diff --git a/streamshop/components/ui/Section.tsx b/streamshop/components/ui/Section.tsx new file mode 100644 index 000000000..818a7a189 --- /dev/null +++ b/streamshop/components/ui/Section.tsx @@ -0,0 +1,86 @@ +import { JSX } from "preact"; +import { clx } from "../../sdk/clx.ts"; + +export interface Props { + /** @description Section title */ + title?: string; + + /** @description See all link */ + cta?: string; +} + +function Header({ title, cta }: Props) { + if (!title) { + return null; + } + + return ( +
+ {title} + {cta && ( + + See all + + )} +
+ ); +} + +interface Tab { + title: string; +} + +function Tabbed( + { children }: { + children: JSX.Element; + }, +) { + return ( + <> + {children} + + ); +} + +function Container({ class: _class, ...props }: JSX.IntrinsicElements["div"]) { + return ( +
+ ); +} + +function Placeholder( + { height, class: _class }: { height: string; class?: string }, +) { + return ( +
+ +
+ ); +} + +function Section() {} + +Section.Container = Container; +Section.Header = Header; +Section.Tabbed = Tabbed; +Section.Placeholder = Placeholder; + +export default Section; diff --git a/streamshop/components/ui/Slider.tsx b/streamshop/components/ui/Slider.tsx new file mode 100644 index 000000000..71f9ac1ea --- /dev/null +++ b/streamshop/components/ui/Slider.tsx @@ -0,0 +1,191 @@ +import type { JSX } from "preact"; +import { clx } from "../../sdk/clx.ts"; +import { useScript } from "@deco/deco/hooks"; +function Dot({ index, ...props }: { + index: number; +} & JSX.IntrinsicElements["button"]) { + return ( +