87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/app/(client)/api/webhook/route.ts:
--------------------------------------------------------------------------------
1 | import { Metadata } from "@/actions/createCheckoutSession";
2 | import stripe from "@/lib/stripe";
3 | import { backendClient } from "@/sanity/lib/backendClient";
4 | import { headers } from "next/headers";
5 | import { NextRequest, NextResponse } from "next/server";
6 | import Stripe from "stripe";
7 |
8 | export async function POST(req: NextRequest) {
9 | const body = await req.text();
10 | const headersList = await headers();
11 | const sig = headersList.get("stripe-signature");
12 |
13 | if (!sig) {
14 | return NextResponse.json(
15 | {
16 | error: "No signature",
17 | },
18 | { status: 400 }
19 | );
20 | }
21 |
22 | const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
23 | if (!webhookSecret) {
24 | console.log("Stripe webhook secret is not set");
25 | return NextResponse.json(
26 | {
27 | error: "Stripe webhook secret is not set",
28 | },
29 | { status: 400 }
30 | );
31 | }
32 |
33 | let event: Stripe.Event;
34 |
35 | try {
36 | event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
37 | } catch (error) {
38 | console.error("Webhook signature verification failed:", error);
39 | return NextResponse.json(
40 | {
41 | error: `Webhook Error: ${error}`,
42 | },
43 | { status: 400 }
44 | );
45 | }
46 |
47 | if (event.type === "checkout.session.completed") {
48 | const session = event.data.object as Stripe.Checkout.Session;
49 | try {
50 | await createOrderInSanity(session);
51 | // const order = await createOrderInSanity(session);
52 | // console.log("Order created in Sanity:", order);
53 | } catch (error) {
54 | console.error("Error creating order in sanity:", error);
55 | return NextResponse.json(
56 | {
57 | error: `Error creating order: ${error}`,
58 | },
59 | { status: 400 }
60 | );
61 | }
62 | }
63 | return NextResponse.json({ received: true });
64 | }
65 |
66 | async function createOrderInSanity(session: Stripe.Checkout.Session) {
67 | const {
68 | id,
69 | amount_total,
70 | currency,
71 | metadata,
72 | payment_intent,
73 | // customer,
74 | total_details,
75 | } = session;
76 |
77 | const { orderNumber, customerName, customerEmail, clerkUserId } =
78 | metadata as unknown as Metadata;
79 |
80 | const lineItemsWithProduct = await stripe.checkout.sessions.listLineItems(
81 | id,
82 | { expand: ["data.price.product"] }
83 | );
84 |
85 | // Creating sanity product reference
86 | const sanityProducts = lineItemsWithProduct.data.map((item) => ({
87 | _key: crypto.randomUUID(),
88 | product: {
89 | _type: "reference",
90 | _ref: (item.price?.product as Stripe.Product)?.metadata?.id,
91 | },
92 | quantity: item?.quantity || 0,
93 | }));
94 | const order = await backendClient.create({
95 | _type: "order",
96 | orderNumber,
97 | stripeCheckoutSessionId: id,
98 | stripePaymentIntentId: payment_intent,
99 | customerName,
100 | stripeCustomerId: customerEmail,
101 | clerkUserId: clerkUserId,
102 | email: customerEmail,
103 | currency,
104 | amountDiscount: total_details?.amount_discount
105 | ? total_details.amount_discount / 100
106 | : 0,
107 |
108 | products: sanityProducts,
109 | totalPrice: amount_total ? amount_total / 100 : 0,
110 | status: "paid",
111 | orderDate: new Date().toISOString(),
112 | });
113 | return order;
114 | }
115 |
--------------------------------------------------------------------------------
/app/(client)/success/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import useCartStore from "@/store";
4 | import { Check, Home, Package, ShoppingBag } from "lucide-react";
5 | import { useSearchParams } from "next/navigation";
6 | import { useEffect } from "react";
7 | import { motion } from "framer-motion";
8 | import Link from "next/link";
9 |
10 | const SuccessPage = () => {
11 | const searchParams = useSearchParams();
12 | const orderNumber = searchParams.get("orderNumber");
13 | const clearCart = useCartStore((state) => state.resetCart);
14 |
15 | useEffect(() => {
16 | if (orderNumber) {
17 | clearCart();
18 | }
19 | }, [orderNumber, clearCart]);
20 |
21 | return (
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 | Order Confirmed!
40 |
41 |
42 |
43 | Thank you for your purchase. We’re processing your order and
44 | will ship it soon. A confirmation email with your order details will
45 | be sent to your inbox shortly.
46 |
47 |
48 | Order Number:{" "}
49 | {orderNumber}
50 |
51 |
52 |
53 |
54 |
55 | What’s Next?
56 |
57 |
58 | Check your email for order confirmation
59 | We’ll notify you when your order ships
60 | Track your order status anytime
61 |
62 |
63 |
64 |
65 |
69 |
70 | Home
71 |
72 |
76 |
77 | Orders
78 |
79 |
83 |
84 | Shopping
85 |
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | export default SuccessPage;
93 |
--------------------------------------------------------------------------------
/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React from "react";
3 | import Form from "next/form";
4 | import { ClerkLoaded, SignedIn, SignInButton, UserButton } from "@clerk/nextjs";
5 | import { auth, currentUser } from "@clerk/nextjs/server";
6 | import Container from "./Container";
7 | import Image from "next/image";
8 | import logo from "@/images/logo.png";
9 | import CartIcon from "./CartIcon";
10 | import { BsBasket } from "react-icons/bs";
11 | import { FiUser } from "react-icons/fi";
12 | import { getMyOrders } from "@/sanity/helpers";
13 |
14 | const Header = async () => {
15 | const user = await currentUser();
16 | const { userId } = await auth();
17 | let orders = null;
18 | if (userId) {
19 | orders = await getMyOrders(userId);
20 | }
21 |
22 | return (
23 |
86 | );
87 | };
88 |
89 | export default Header;
90 |
--------------------------------------------------------------------------------
/sanity/helpers/index.ts:
--------------------------------------------------------------------------------
1 | import { defineQuery } from "next-sanity";
2 | import { sanityFetch } from "../lib/live";
3 | import { CouponCode } from "./couponCodes";
4 |
5 | export const getAllProducts = async () => {
6 | const PRODUCTS_QUERY = defineQuery(`*[_type=="product"] | order(name asc)`);
7 | try {
8 | const products = await sanityFetch({
9 | query: PRODUCTS_QUERY,
10 | });
11 | return products.data || [];
12 | } catch (error) {
13 | console.log("Error fetching all products:", error);
14 | return [];
15 | }
16 | };
17 |
18 | export const getAllCategories = async () => {
19 | const CATEGORIES_QUERY = defineQuery(
20 | `*[_type=="category"] | order(name asc)`
21 | );
22 |
23 | try {
24 | const categories = await sanityFetch({
25 | query: CATEGORIES_QUERY,
26 | });
27 | return categories.data || [];
28 | } catch (error) {
29 | console.log("Error fetching all categories:", error);
30 | return [];
31 | }
32 | };
33 |
34 | export const getActiveSaleByCouponCode = async (couponCode: CouponCode) => {
35 | const ACTIVE_COUPON_CODE = defineQuery(
36 | `*[_type == 'sale' && isActive == true && couponCode == $couponCode] | order(validFrom desc)[0]`
37 | );
38 |
39 | try {
40 | const activeSale = await sanityFetch({
41 | query: ACTIVE_COUPON_CODE,
42 | params: {
43 | couponCode,
44 | },
45 | });
46 | return activeSale ? activeSale?.data : null;
47 | } catch (error) {
48 | console.error("Error fetching active sale by coupon code:", error);
49 | return null;
50 | }
51 | };
52 |
53 | export const searchProductsByName = async (searchParam: string) => {
54 | const PRODUCT_SEARCH_QUERY = defineQuery(
55 | `*[_type == "product" && name match $searchParam] | order(name asc)`
56 | );
57 |
58 | try {
59 | const products = await sanityFetch({
60 | query: PRODUCT_SEARCH_QUERY,
61 | params: {
62 | searchParam: `${searchParam}`,
63 | },
64 | });
65 | return products?.data || [];
66 | } catch (error) {
67 | console.error("Error fetching products by name:", error);
68 | return [];
69 | }
70 | };
71 |
72 | export const getProductBySlug = async (slug: string) => {
73 | const PRODUCT_BY_ID_QUERY = defineQuery(
74 | `*[_type == "product" && slug.current == $slug] | order(name asc) [0]`
75 | );
76 |
77 | try {
78 | const product = await sanityFetch({
79 | query: PRODUCT_BY_ID_QUERY,
80 | params: {
81 | slug,
82 | },
83 | });
84 | return product?.data || null;
85 | } catch (error) {
86 | console.error("Error fetching product by ID:", error);
87 | return null;
88 | }
89 | };
90 |
91 | export const getProductsByCategory = async (categorySlug: string) => {
92 | const PRODUCT_BY_CATEGORY_QUERY = defineQuery(
93 | `*[_type == 'product' && references(*[_type == "category" && slug.current == $categorySlug]._id)] | order(name asc)`
94 | );
95 | try {
96 | const products = await sanityFetch({
97 | query: PRODUCT_BY_CATEGORY_QUERY,
98 | params: {
99 | categorySlug,
100 | },
101 | });
102 | return products?.data || [];
103 | } catch (error) {
104 | console.error("Erroor fetching products by category:", error);
105 | return [];
106 | }
107 | };
108 |
109 | export const getSale = async () => {
110 | const SALE_QUERY = defineQuery(`*[_type == 'sale'] | order(name asc)`);
111 | try {
112 | const products = await sanityFetch({
113 | query: SALE_QUERY,
114 | });
115 | return products?.data || [];
116 | } catch (error) {
117 | console.error("Error fetching products by category:", error);
118 | return [];
119 | }
120 | };
121 |
122 | export const getMyOrders = async (userId: string) => {
123 | if (!userId) {
124 | throw new Error("User ID is required");
125 | }
126 | const MY_ORDERS_QUERY =
127 | defineQuery(`*[_type == 'order' && clerkUserId == $userId] | order(orderData desc){
128 | ...,products[]{
129 | ...,product->
130 | }
131 | }`);
132 |
133 | try {
134 | const orders = await sanityFetch({
135 | query: MY_ORDERS_QUERY,
136 | params: { userId },
137 | });
138 | return orders?.data || [];
139 | } catch (error) {
140 | console.error("Error fetching orders:", error);
141 | return [];
142 | }
143 | };
144 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogTrigger,
116 | DialogClose,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/sanity/schemaTypes/orderType.ts:
--------------------------------------------------------------------------------
1 | import { BasketIcon } from "@sanity/icons";
2 | import { defineArrayMember, defineField, defineType } from "sanity";
3 |
4 | export const orderType = defineType({
5 | name: "order",
6 | title: "Order",
7 | type: "document",
8 | icon: BasketIcon,
9 | fields: [
10 | defineField({
11 | name: "orderNumber",
12 | title: "Order Number",
13 | type: "string",
14 | validation: (Rule) => Rule.required(),
15 | }),
16 | defineField({
17 | name: "stripeCheckoutSessionId",
18 | title: "Stripe Checkout Session ID",
19 | type: "string",
20 | }),
21 | defineField({
22 | name: "stripeCustomerId",
23 | title: "Stripe Customer ID",
24 | type: "string",
25 | validation: (Rule) => Rule.required(),
26 | }),
27 | defineField({
28 | name: "clerkUserId",
29 | title: "Store User ID",
30 | type: "string",
31 | validation: (Rule) => Rule.required(),
32 | }),
33 | defineField({
34 | name: "customerName",
35 | title: "Customer Name",
36 | type: "string",
37 | validation: (Rule) => Rule.required(),
38 | }),
39 | defineField({
40 | name: "email",
41 | title: "Customer Email",
42 | type: "string",
43 | validation: (Rule) => Rule.required().email(),
44 | }),
45 | defineField({
46 | name: "stripePaymentIntentId",
47 | title: "Stripe Payment Intent ID",
48 | type: "string",
49 | validation: (Rule) => Rule.required(),
50 | }),
51 | defineField({
52 | name: "products",
53 | title: "Products",
54 | type: "array",
55 | of: [
56 | defineArrayMember({
57 | type: "object",
58 | fields: [
59 | defineField({
60 | name: "product",
61 | title: "Product Bought",
62 | type: "reference",
63 | to: [{ type: "product" }],
64 | }),
65 | defineField({
66 | name: "quantity",
67 | title: "Quantity Purchased",
68 | type: "number",
69 | }),
70 | ],
71 | preview: {
72 | select: {
73 | product: "product.name",
74 | quantity: "quantity",
75 | image: "product.image",
76 | price: "product.price",
77 | currency: "product.currency",
78 | },
79 | prepare(select) {
80 | return {
81 | title: `${select.product} x ${select.quantity}`,
82 | subtitle: `${select.price * select.quantity}`,
83 | media: select.image,
84 | };
85 | },
86 | },
87 | }),
88 | ],
89 | }),
90 | defineField({
91 | name: "totalPrice",
92 | title: "Total Price",
93 | type: "number",
94 | validation: (Rule) => Rule.required().min(0),
95 | }),
96 | defineField({
97 | name: "currency",
98 | title: "Currency",
99 | type: "string",
100 | validation: (Rule) => Rule.required(),
101 | }),
102 | defineField({
103 | name: "amountDiscount",
104 | title: "Amount Discount",
105 | type: "number",
106 | validation: (Rule) => Rule.required(),
107 | }),
108 | defineField({
109 | name: "status",
110 | title: "Order Status",
111 | type: "string",
112 | options: {
113 | list: [
114 | {
115 | title: "Pending",
116 | value: "pending",
117 | },
118 | {
119 | title: "Paid",
120 | value: "paid",
121 | },
122 | {
123 | title: "Shipped",
124 | value: "shipped",
125 | },
126 | {
127 | title: "Delivered",
128 | value: "delivered",
129 | },
130 | {
131 | title: "Cancelled",
132 | value: "cancelled",
133 | },
134 | ],
135 | },
136 | }),
137 | defineField({
138 | name: "orderDate",
139 | title: "Order Date",
140 | type: "datetime",
141 | validation: (Rule) => Rule.required(),
142 | }),
143 | ],
144 | preview: {
145 | select: {
146 | name: "customerName",
147 | amount: "totalPrice",
148 | currency: "currency",
149 | orderId: "orderNumber",
150 | email: "email",
151 | },
152 | prepare(select) {
153 | const orderIdSnippet = `${select.orderId.slice(0, 5)}...${select.orderId.slice(-5)}`;
154 | return {
155 | title: `${select.name} (${orderIdSnippet})`,
156 | subtitle: `${select.amount} ${select.currency}, ${select.email}`,
157 | media: BasketIcon,
158 | };
159 | },
160 | },
161 | });
162 |
--------------------------------------------------------------------------------
/app/(client)/product/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import AddToCartButton from "@/components/AddToCartButton";
2 | import Container from "@/components/Container";
3 | import PriceView from "@/components/PriceView";
4 | import { getProductBySlug } from "@/sanity/helpers";
5 | import { urlFor } from "@/sanity/lib/image";
6 | import Image from "next/image";
7 | import { notFound } from "next/navigation";
8 | import React from "react";
9 | import { FaRegQuestionCircle } from "react-icons/fa";
10 | import { FiShare2 } from "react-icons/fi";
11 | import { LuStar } from "react-icons/lu";
12 | import { RxBorderSplit } from "react-icons/rx";
13 | import { TbTruckDelivery } from "react-icons/tb";
14 |
15 | // export const dynamic = "force-static";
16 | // export const revalidate = 60;
17 |
18 | const ProductPage = async ({
19 | params,
20 | }: {
21 | params: Promise<{ slug: string }>;
22 | }) => {
23 | const { slug } = await params;
24 | const product = await getProductBySlug(slug);
25 |
26 | if (!product) {
27 | return notFound();
28 | }
29 |
30 | return (
31 |
32 |
33 | {product?.image && (
34 |
35 |
43 |
44 | )}
45 |
46 |
47 |
{product?.name}
48 |
49 |
50 | {Array.from({ length: 5 }).map((_, index) => {
51 | const isLastStar = index === 4;
52 | return (
53 |
58 | );
59 | })}
60 |
61 |
{`(25 reviews)`}
62 |
63 |
64 |
70 | {product?.stock && (
71 |
72 | In Stock
73 |
74 | )}
75 |
76 |
77 |
78 | 20
79 | {" "}
80 | People are viewing this right now
81 |
82 |
83 |
84 | {product?.description}
85 |
86 |
87 |
88 |
89 |
90 |
Compare color
91 |
92 |
93 |
94 |
Ask a question
95 |
96 |
97 |
98 |
Delivery & Return
99 |
100 |
104 |
105 |
106 |
107 |
108 | Free Shipping
109 |
110 |
111 | Free shipping over order $120
112 |
113 |
114 |
115 |
116 | Flexible Payment
117 |
118 |
119 | Pay with Multiple Credit Cards
120 |
121 |
122 |
123 |
124 |
125 |
126 | );
127 | };
128 |
129 | export default ProductPage;
130 |
--------------------------------------------------------------------------------
/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { type DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { Search } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 | import { Dialog, DialogContent } from "@/components/ui/dialog"
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | const CommandDialog = ({ children, ...props }: DialogProps) => {
27 | return (
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | const CommandInput = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
43 |
44 |
52 |
53 | ))
54 |
55 | CommandInput.displayName = CommandPrimitive.Input.displayName
56 |
57 | const CommandList = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
66 | ))
67 |
68 | CommandList.displayName = CommandPrimitive.List.displayName
69 |
70 | const CommandEmpty = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >((props, ref) => (
74 |
79 | ))
80 |
81 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82 |
83 | const CommandGroup = React.forwardRef<
84 | React.ElementRef,
85 | React.ComponentPropsWithoutRef
86 | >(({ className, ...props }, ref) => (
87 |
95 | ))
96 |
97 | CommandGroup.displayName = CommandPrimitive.Group.displayName
98 |
99 | const CommandSeparator = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110 |
111 | const CommandItem = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
123 | ))
124 |
125 | CommandItem.displayName = CommandPrimitive.Item.displayName
126 |
127 | const CommandShortcut = ({
128 | className,
129 | ...props
130 | }: React.HTMLAttributes) => {
131 | return (
132 |
139 | )
140 | }
141 | CommandShortcut.displayName = "CommandShortcut"
142 |
143 | export {
144 | Command,
145 | CommandDialog,
146 | CommandInput,
147 | CommandList,
148 | CommandEmpty,
149 | CommandGroup,
150 | CommandItem,
151 | CommandShortcut,
152 | CommandSeparator,
153 | }
154 |
--------------------------------------------------------------------------------
/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import useEmblaCarousel, {
5 | type UseEmblaCarouselType,
6 | } from "embla-carousel-react";
7 | import { ArrowLeft, ArrowRight } from "lucide-react";
8 |
9 | import { cn } from "@/lib/utils";
10 | import { Button } from "@/components/ui/button";
11 |
12 | type CarouselApi = UseEmblaCarouselType[1];
13 | type UseCarouselParameters = Parameters;
14 | type CarouselOptions = UseCarouselParameters[0];
15 | type CarouselPlugin = UseCarouselParameters[1];
16 |
17 | type CarouselProps = {
18 | opts?: CarouselOptions;
19 | plugins?: CarouselPlugin;
20 | orientation?: "horizontal" | "vertical";
21 | setApi?: (api: CarouselApi) => void;
22 | };
23 |
24 | type CarouselContextProps = {
25 | carouselRef: ReturnType[0];
26 | api: ReturnType[1];
27 | scrollPrev: () => void;
28 | scrollNext: () => void;
29 | canScrollPrev: boolean;
30 | canScrollNext: boolean;
31 | } & CarouselProps;
32 |
33 | const CarouselContext = React.createContext(null);
34 |
35 | function useCarousel() {
36 | const context = React.useContext(CarouselContext);
37 |
38 | if (!context) {
39 | throw new Error("useCarousel must be used within a ");
40 | }
41 |
42 | return context;
43 | }
44 |
45 | const Carousel = React.forwardRef<
46 | HTMLDivElement,
47 | React.HTMLAttributes & CarouselProps
48 | >(
49 | (
50 | {
51 | orientation = "horizontal",
52 | opts,
53 | setApi,
54 | plugins,
55 | className,
56 | children,
57 | ...props
58 | },
59 | ref
60 | ) => {
61 | const [carouselRef, api] = useEmblaCarousel(
62 | {
63 | ...opts,
64 | axis: orientation === "horizontal" ? "x" : "y",
65 | },
66 | plugins
67 | );
68 | const [canScrollPrev, setCanScrollPrev] = React.useState(false);
69 | const [canScrollNext, setCanScrollNext] = React.useState(false);
70 |
71 | const onSelect = React.useCallback((api: CarouselApi) => {
72 | if (!api) {
73 | return;
74 | }
75 |
76 | setCanScrollPrev(api.canScrollPrev());
77 | setCanScrollNext(api.canScrollNext());
78 | }, []);
79 |
80 | const scrollPrev = React.useCallback(() => {
81 | api?.scrollPrev();
82 | }, [api]);
83 |
84 | const scrollNext = React.useCallback(() => {
85 | api?.scrollNext();
86 | }, [api]);
87 |
88 | const handleKeyDown = React.useCallback(
89 | (event: React.KeyboardEvent) => {
90 | if (event.key === "ArrowLeft") {
91 | event.preventDefault();
92 | scrollPrev();
93 | } else if (event.key === "ArrowRight") {
94 | event.preventDefault();
95 | scrollNext();
96 | }
97 | },
98 | [scrollPrev, scrollNext]
99 | );
100 |
101 | React.useEffect(() => {
102 | if (!api || !setApi) {
103 | return;
104 | }
105 |
106 | setApi(api);
107 | }, [api, setApi]);
108 |
109 | React.useEffect(() => {
110 | if (!api) {
111 | return;
112 | }
113 |
114 | onSelect(api);
115 | api.on("reInit", onSelect);
116 | api.on("select", onSelect);
117 |
118 | return () => {
119 | api?.off("select", onSelect);
120 | };
121 | }, [api, onSelect]);
122 |
123 | return (
124 |
137 |
145 | {children}
146 |
147 |
148 | );
149 | }
150 | );
151 | Carousel.displayName = "Carousel";
152 |
153 | const CarouselContent = React.forwardRef<
154 | HTMLDivElement,
155 | React.HTMLAttributes
156 | >(({ className, ...props }, ref) => {
157 | const { carouselRef, orientation } = useCarousel();
158 |
159 | return (
160 |
171 | );
172 | });
173 | CarouselContent.displayName = "CarouselContent";
174 |
175 | const CarouselItem = React.forwardRef<
176 | HTMLDivElement,
177 | React.HTMLAttributes
178 | >(({ className, ...props }, ref) => {
179 | const { orientation } = useCarousel();
180 |
181 | return (
182 |
193 | );
194 | });
195 | CarouselItem.displayName = "CarouselItem";
196 |
197 | const CarouselPrevious = React.forwardRef<
198 | HTMLButtonElement,
199 | React.ComponentProps
200 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201 | const { orientation, scrollPrev, canScrollPrev } = useCarousel();
202 |
203 | return (
204 |
219 |
220 | Previous slide
221 |
222 | );
223 | });
224 | CarouselPrevious.displayName = "CarouselPrevious";
225 |
226 | const CarouselNext = React.forwardRef<
227 | HTMLButtonElement,
228 | React.ComponentProps
229 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230 | const { orientation, scrollNext, canScrollNext } = useCarousel();
231 |
232 | return (
233 |
248 |
249 | Next slide
250 |
251 | );
252 | });
253 | CarouselNext.displayName = "CarouselNext";
254 |
255 | export {
256 | type CarouselApi,
257 | Carousel,
258 | CarouselContent,
259 | CarouselItem,
260 | CarouselPrevious,
261 | CarouselNext,
262 | };
263 |
--------------------------------------------------------------------------------
/app/(client)/cart/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Container from "@/components/Container";
3 | import PriceFormatter from "@/components/PriceFormatter";
4 | import QuantityButtons from "@/components/QuantityButtons";
5 | import { Button } from "@/components/ui/button";
6 | import { Separator } from "@/components/ui/separator";
7 | import { urlFor } from "@/sanity/lib/image";
8 | import useCartStore from "@/store";
9 | import { useAuth, useUser } from "@clerk/nextjs";
10 | import { ShoppingBag, Trash2 } from "lucide-react";
11 | import Image from "next/image";
12 | import Link from "next/link";
13 | import React, { useEffect, useState } from "react";
14 | import toast from "react-hot-toast";
15 | import EmptyCart from "@/components/EmptyCart";
16 | import NoAccessToCart from "@/components/NoAccessToCart";
17 | import Loader from "@/components/Loader";
18 | import {
19 | createCheckoutSession,
20 | Metadata,
21 | } from "@/actions/createCheckoutSession";
22 |
23 | const CartPage = () => {
24 | const {
25 | deleteCartProduct,
26 | getTotalPrice,
27 | getItemCount,
28 | getSubTotalPrice,
29 | resetCart,
30 | } = useCartStore();
31 | const [isClient, setIsClient] = useState(false);
32 | const [loading, setLoading] = useState(false);
33 | const groupedItems = useCartStore((state) => state.getGroupedItems());
34 | const { isSignedIn } = useAuth();
35 | const { user } = useUser();
36 |
37 | useEffect(() => {
38 | setIsClient(true);
39 | }, []);
40 | if (!isClient) {
41 | return ;
42 | }
43 |
44 | const handleResetCart = () => {
45 | const confirmed = window.confirm("Are you sure to reset your Cart?");
46 | if (confirmed) {
47 | resetCart();
48 | toast.success("Your cart reset successfully!");
49 | }
50 | };
51 |
52 | const handleCheckout = async () => {
53 | setLoading(true);
54 | try {
55 | const metadata: Metadata = {
56 | orderNumber: crypto.randomUUID(),
57 | customerName: user?.fullName ?? "Unknown",
58 | customerEmail: user?.emailAddresses[0]?.emailAddress ?? "Unknown",
59 | clerkUserId: user!.id,
60 | };
61 | const checkoutUrl = await createCheckoutSession(groupedItems, metadata);
62 | if (checkoutUrl) {
63 | window.location.href = checkoutUrl;
64 | }
65 | } catch (error) {
66 | console.error("Error creating checkout session:", error);
67 | } finally {
68 | setLoading(false);
69 | }
70 | };
71 |
72 | const handleDeleteProduct = (id: string) => {
73 | deleteCartProduct(id);
74 | toast.success("Product deleted successfully!");
75 | };
76 | return (
77 |
78 | {isSignedIn ? (
79 |
80 | {groupedItems?.length ? (
81 | <>
82 |
83 |
84 |
Shopping Cart
85 |
86 |
87 |
88 |
89 |
90 | Order Summary
91 |
92 |
93 |
97 |
103 |
104 |
105 |
106 |
Total
107 |
108 |
112 |
113 |
119 | {loading ? "Processing" : "Proceed to Checkout"}
120 |
121 |
125 | Continue Shopping
126 |
127 |
128 |
129 |
130 | {/* Product View start */}
131 |
132 |
133 |
Product
134 | Price
135 | Quantity
136 | Total
137 |
138 |
139 | {groupedItems?.map(({ product }) => {
140 | const itemCount = getItemCount(product?._id);
141 | return (
142 |
146 |
147 |
handleDeleteProduct(product?._id)}
149 | className="w-4 h-4 md:w-5 md:h-5 mr-1 text-gray-500 hover:text-red-600 hoverEffect"
150 | />
151 | {product?.image && (
152 |
153 |
161 |
162 | )}
163 | {product?.name}
164 |
165 |
168 |
172 |
179 |
180 | );
181 | })}
182 |
187 | Reset Cart
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | Order Summary
196 |
197 |
198 |
199 |
SubTotal
200 |
201 |
202 |
203 |
Discount
204 |
207 |
208 |
209 |
210 |
211 |
Total
212 |
213 |
217 |
218 |
219 | Proceed to Checkout
220 |
221 |
225 | Continue Shopping
226 |
227 |
228 |
229 |
230 | {/* Product View end */}
231 |
232 | >
233 | ) : (
234 |
235 | )}
236 |
237 | ) : (
238 |
239 | )}
240 |
241 | );
242 | };
243 |
244 | export default CartPage;
245 |
--------------------------------------------------------------------------------
/sanity.types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * ---------------------------------------------------------------------------------
3 | * This file has been generated by Sanity TypeGen.
4 | * Command: `sanity typegen generate`
5 | *
6 | * Any modifications made directly to this file will be overwritten the next time
7 | * the TypeScript definitions are generated. Please make changes to the Sanity
8 | * schema definitions and/or GROQ queries if you need to update these types.
9 | *
10 | * For more information on how to use Sanity TypeGen, visit the official documentation:
11 | * https://www.sanity.io/docs/sanity-typegen
12 | * ---------------------------------------------------------------------------------
13 | */
14 |
15 | // Source: schema.json
16 | export type SanityImagePaletteSwatch = {
17 | _type: "sanity.imagePaletteSwatch";
18 | background?: string;
19 | foreground?: string;
20 | population?: number;
21 | title?: string;
22 | };
23 |
24 | export type SanityImagePalette = {
25 | _type: "sanity.imagePalette";
26 | darkMuted?: SanityImagePaletteSwatch;
27 | lightVibrant?: SanityImagePaletteSwatch;
28 | darkVibrant?: SanityImagePaletteSwatch;
29 | vibrant?: SanityImagePaletteSwatch;
30 | dominant?: SanityImagePaletteSwatch;
31 | lightMuted?: SanityImagePaletteSwatch;
32 | muted?: SanityImagePaletteSwatch;
33 | };
34 |
35 | export type SanityImageDimensions = {
36 | _type: "sanity.imageDimensions";
37 | height?: number;
38 | width?: number;
39 | aspectRatio?: number;
40 | };
41 |
42 | export type SanityFileAsset = {
43 | _id: string;
44 | _type: "sanity.fileAsset";
45 | _createdAt: string;
46 | _updatedAt: string;
47 | _rev: string;
48 | originalFilename?: string;
49 | label?: string;
50 | title?: string;
51 | description?: string;
52 | altText?: string;
53 | sha1hash?: string;
54 | extension?: string;
55 | mimeType?: string;
56 | size?: number;
57 | assetId?: string;
58 | uploadId?: string;
59 | path?: string;
60 | url?: string;
61 | source?: SanityAssetSourceData;
62 | };
63 |
64 | export type Geopoint = {
65 | _type: "geopoint";
66 | lat?: number;
67 | lng?: number;
68 | alt?: number;
69 | };
70 |
71 | export type Sale = {
72 | _id: string;
73 | _type: "sale";
74 | _createdAt: string;
75 | _updatedAt: string;
76 | _rev: string;
77 | title?: string;
78 | description?: string;
79 | badge?: string;
80 | discountAmount?: number;
81 | couponCode?: string;
82 | validFrom?: string;
83 | validUntil?: string;
84 | isActive?: boolean;
85 | image?: {
86 | asset?: {
87 | _ref: string;
88 | _type: "reference";
89 | _weak?: boolean;
90 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
91 | };
92 | hotspot?: SanityImageHotspot;
93 | crop?: SanityImageCrop;
94 | _type: "image";
95 | };
96 | };
97 |
98 | export type Order = {
99 | _id: string;
100 | _type: "order";
101 | _createdAt: string;
102 | _updatedAt: string;
103 | _rev: string;
104 | orderNumber?: string;
105 | stripeCheckoutSessionId?: string;
106 | stripeCustomerId?: string;
107 | clerkUserId?: string;
108 | customerName?: string;
109 | email?: string;
110 | stripePaymentIntentId?: string;
111 | products?: Array<{
112 | product?: {
113 | _ref: string;
114 | _type: "reference";
115 | _weak?: boolean;
116 | [internalGroqTypeReferenceTo]?: "product";
117 | };
118 | quantity?: number;
119 | _key: string;
120 | }>;
121 | totalPrice?: number;
122 | currency?: string;
123 | amountDiscount?: number;
124 | status?: "pending" | "paid" | "shipped" | "delivered" | "cancelled";
125 | orderDate?: string;
126 | };
127 |
128 | export type Product = {
129 | _id: string;
130 | _type: "product";
131 | _createdAt: string;
132 | _updatedAt: string;
133 | _rev: string;
134 | name?: string;
135 | slug?: Slug;
136 | image?: {
137 | asset?: {
138 | _ref: string;
139 | _type: "reference";
140 | _weak?: boolean;
141 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
142 | };
143 | hotspot?: SanityImageHotspot;
144 | crop?: SanityImageCrop;
145 | _type: "image";
146 | };
147 | description?: string;
148 | price?: number;
149 | discount?: number;
150 | categories?: Array<{
151 | _ref: string;
152 | _type: "reference";
153 | _weak?: boolean;
154 | _key: string;
155 | [internalGroqTypeReferenceTo]?: "category";
156 | }>;
157 | stock?: number;
158 | label?: string;
159 | status?: "new" | "hot" | "sale";
160 | };
161 |
162 | export type Category = {
163 | _id: string;
164 | _type: "category";
165 | _createdAt: string;
166 | _updatedAt: string;
167 | _rev: string;
168 | title?: string;
169 | slug?: Slug;
170 | description?: string;
171 | };
172 |
173 | export type Slug = {
174 | _type: "slug";
175 | current?: string;
176 | source?: string;
177 | };
178 |
179 | export type BlockContent = Array<{
180 | children?: Array<{
181 | marks?: Array;
182 | text?: string;
183 | _type: "span";
184 | _key: string;
185 | }>;
186 | style?: "normal" | "h1" | "h2" | "h3" | "h4" | "blockquote";
187 | listItem?: "bullet";
188 | markDefs?: Array<{
189 | href?: string;
190 | _type: "link";
191 | _key: string;
192 | }>;
193 | level?: number;
194 | _type: "block";
195 | _key: string;
196 | } | {
197 | asset?: {
198 | _ref: string;
199 | _type: "reference";
200 | _weak?: boolean;
201 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
202 | };
203 | hotspot?: SanityImageHotspot;
204 | crop?: SanityImageCrop;
205 | alt?: string;
206 | _type: "image";
207 | _key: string;
208 | }>;
209 |
210 | export type SanityImageCrop = {
211 | _type: "sanity.imageCrop";
212 | top?: number;
213 | bottom?: number;
214 | left?: number;
215 | right?: number;
216 | };
217 |
218 | export type SanityImageHotspot = {
219 | _type: "sanity.imageHotspot";
220 | x?: number;
221 | y?: number;
222 | height?: number;
223 | width?: number;
224 | };
225 |
226 | export type SanityImageAsset = {
227 | _id: string;
228 | _type: "sanity.imageAsset";
229 | _createdAt: string;
230 | _updatedAt: string;
231 | _rev: string;
232 | originalFilename?: string;
233 | label?: string;
234 | title?: string;
235 | description?: string;
236 | altText?: string;
237 | sha1hash?: string;
238 | extension?: string;
239 | mimeType?: string;
240 | size?: number;
241 | assetId?: string;
242 | uploadId?: string;
243 | path?: string;
244 | url?: string;
245 | metadata?: SanityImageMetadata;
246 | source?: SanityAssetSourceData;
247 | };
248 |
249 | export type SanityAssetSourceData = {
250 | _type: "sanity.assetSourceData";
251 | name?: string;
252 | id?: string;
253 | url?: string;
254 | };
255 |
256 | export type SanityImageMetadata = {
257 | _type: "sanity.imageMetadata";
258 | location?: Geopoint;
259 | dimensions?: SanityImageDimensions;
260 | palette?: SanityImagePalette;
261 | lqip?: string;
262 | blurHash?: string;
263 | hasAlpha?: boolean;
264 | isOpaque?: boolean;
265 | };
266 |
267 | export type AllSanitySchemaTypes = SanityImagePaletteSwatch | SanityImagePalette | SanityImageDimensions | SanityFileAsset | Geopoint | Sale | Order | Product | Category | Slug | BlockContent | SanityImageCrop | SanityImageHotspot | SanityImageAsset | SanityAssetSourceData | SanityImageMetadata;
268 | export declare const internalGroqTypeReferenceTo: unique symbol;
269 | // Source: ./sanity/helpers/index.ts
270 | // Variable: PRODUCTS_QUERY
271 | // Query: *[_type=="product"] | order(name asc)
272 | export type PRODUCTS_QUERYResult = Array<{
273 | _id: string;
274 | _type: "product";
275 | _createdAt: string;
276 | _updatedAt: string;
277 | _rev: string;
278 | name?: string;
279 | slug?: Slug;
280 | image?: {
281 | asset?: {
282 | _ref: string;
283 | _type: "reference";
284 | _weak?: boolean;
285 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
286 | };
287 | hotspot?: SanityImageHotspot;
288 | crop?: SanityImageCrop;
289 | _type: "image";
290 | };
291 | description?: string;
292 | price?: number;
293 | discount?: number;
294 | categories?: Array<{
295 | _ref: string;
296 | _type: "reference";
297 | _weak?: boolean;
298 | _key: string;
299 | [internalGroqTypeReferenceTo]?: "category";
300 | }>;
301 | stock?: number;
302 | label?: string;
303 | status?: "hot" | "new" | "sale";
304 | }>;
305 | // Variable: CATEGORIES_QUERY
306 | // Query: *[_type=="category"] | order(name asc)
307 | export type CATEGORIES_QUERYResult = Array<{
308 | _id: string;
309 | _type: "category";
310 | _createdAt: string;
311 | _updatedAt: string;
312 | _rev: string;
313 | title?: string;
314 | slug?: Slug;
315 | description?: string;
316 | }>;
317 | // Variable: ACTIVE_COUPON_CODE
318 | // Query: *[_type == 'sale' && isActive == true && couponCode == $couponCode] | order(validFrom desc)[0]
319 | export type ACTIVE_COUPON_CODEResult = {
320 | _id: string;
321 | _type: "sale";
322 | _createdAt: string;
323 | _updatedAt: string;
324 | _rev: string;
325 | title?: string;
326 | description?: string;
327 | badge?: string;
328 | discountAmount?: number;
329 | couponCode?: string;
330 | validFrom?: string;
331 | validUntil?: string;
332 | isActive?: boolean;
333 | image?: {
334 | asset?: {
335 | _ref: string;
336 | _type: "reference";
337 | _weak?: boolean;
338 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
339 | };
340 | hotspot?: SanityImageHotspot;
341 | crop?: SanityImageCrop;
342 | _type: "image";
343 | };
344 | } | null;
345 | // Variable: PRODUCT_SEARCH_QUERY
346 | // Query: *[_type == "product" && name match $searchParam] | order(name asc)
347 | export type PRODUCT_SEARCH_QUERYResult = Array<{
348 | _id: string;
349 | _type: "product";
350 | _createdAt: string;
351 | _updatedAt: string;
352 | _rev: string;
353 | name?: string;
354 | slug?: Slug;
355 | image?: {
356 | asset?: {
357 | _ref: string;
358 | _type: "reference";
359 | _weak?: boolean;
360 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
361 | };
362 | hotspot?: SanityImageHotspot;
363 | crop?: SanityImageCrop;
364 | _type: "image";
365 | };
366 | description?: string;
367 | price?: number;
368 | discount?: number;
369 | categories?: Array<{
370 | _ref: string;
371 | _type: "reference";
372 | _weak?: boolean;
373 | _key: string;
374 | [internalGroqTypeReferenceTo]?: "category";
375 | }>;
376 | stock?: number;
377 | label?: string;
378 | status?: "hot" | "new" | "sale";
379 | }>;
380 | // Variable: PRODUCT_BY_ID_QUERY
381 | // Query: *[_type == "product" && slug.current == $slug] | order(name asc) [0]
382 | export type PRODUCT_BY_ID_QUERYResult = {
383 | _id: string;
384 | _type: "product";
385 | _createdAt: string;
386 | _updatedAt: string;
387 | _rev: string;
388 | name?: string;
389 | slug?: Slug;
390 | image?: {
391 | asset?: {
392 | _ref: string;
393 | _type: "reference";
394 | _weak?: boolean;
395 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
396 | };
397 | hotspot?: SanityImageHotspot;
398 | crop?: SanityImageCrop;
399 | _type: "image";
400 | };
401 | description?: string;
402 | price?: number;
403 | discount?: number;
404 | categories?: Array<{
405 | _ref: string;
406 | _type: "reference";
407 | _weak?: boolean;
408 | _key: string;
409 | [internalGroqTypeReferenceTo]?: "category";
410 | }>;
411 | stock?: number;
412 | label?: string;
413 | status?: "hot" | "new" | "sale";
414 | } | null;
415 | // Variable: PRODUCT_BY_CATEGORY_QUERY
416 | // Query: *[_type == 'product' && references(*[_type == "category" && slug.current == $categorySlug]._id)] | order(name asc)
417 | export type PRODUCT_BY_CATEGORY_QUERYResult = Array<{
418 | _id: string;
419 | _type: "product";
420 | _createdAt: string;
421 | _updatedAt: string;
422 | _rev: string;
423 | name?: string;
424 | slug?: Slug;
425 | image?: {
426 | asset?: {
427 | _ref: string;
428 | _type: "reference";
429 | _weak?: boolean;
430 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
431 | };
432 | hotspot?: SanityImageHotspot;
433 | crop?: SanityImageCrop;
434 | _type: "image";
435 | };
436 | description?: string;
437 | price?: number;
438 | discount?: number;
439 | categories?: Array<{
440 | _ref: string;
441 | _type: "reference";
442 | _weak?: boolean;
443 | _key: string;
444 | [internalGroqTypeReferenceTo]?: "category";
445 | }>;
446 | stock?: number;
447 | label?: string;
448 | status?: "hot" | "new" | "sale";
449 | }>;
450 | // Variable: SALE_QUERY
451 | // Query: *[_type == 'sale'] | order(name asc)
452 | export type SALE_QUERYResult = Array<{
453 | _id: string;
454 | _type: "sale";
455 | _createdAt: string;
456 | _updatedAt: string;
457 | _rev: string;
458 | title?: string;
459 | description?: string;
460 | badge?: string;
461 | discountAmount?: number;
462 | couponCode?: string;
463 | validFrom?: string;
464 | validUntil?: string;
465 | isActive?: boolean;
466 | image?: {
467 | asset?: {
468 | _ref: string;
469 | _type: "reference";
470 | _weak?: boolean;
471 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
472 | };
473 | hotspot?: SanityImageHotspot;
474 | crop?: SanityImageCrop;
475 | _type: "image";
476 | };
477 | }>;
478 | // Variable: MY_ORDERS_QUERY
479 | // Query: *[_type == 'order' && clerkUserId == $userId] | order(orderData desc){ ...,products[]{ ...,product-> } }
480 | export type MY_ORDERS_QUERYResult = Array<{
481 | _id: string;
482 | _type: "order";
483 | _createdAt: string;
484 | _updatedAt: string;
485 | _rev: string;
486 | orderNumber?: string;
487 | stripeCheckoutSessionId?: string;
488 | stripeCustomerId?: string;
489 | clerkUserId?: string;
490 | customerName?: string;
491 | email?: string;
492 | stripePaymentIntentId?: string;
493 | products: Array<{
494 | product: {
495 | _id: string;
496 | _type: "product";
497 | _createdAt: string;
498 | _updatedAt: string;
499 | _rev: string;
500 | name?: string;
501 | slug?: Slug;
502 | image?: {
503 | asset?: {
504 | _ref: string;
505 | _type: "reference";
506 | _weak?: boolean;
507 | [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
508 | };
509 | hotspot?: SanityImageHotspot;
510 | crop?: SanityImageCrop;
511 | _type: "image";
512 | };
513 | description?: string;
514 | price?: number;
515 | discount?: number;
516 | categories?: Array<{
517 | _ref: string;
518 | _type: "reference";
519 | _weak?: boolean;
520 | _key: string;
521 | [internalGroqTypeReferenceTo]?: "category";
522 | }>;
523 | stock?: number;
524 | label?: string;
525 | status?: "hot" | "new" | "sale";
526 | } | null;
527 | quantity?: number;
528 | _key: string;
529 | }> | null;
530 | totalPrice?: number;
531 | currency?: string;
532 | amountDiscount?: number;
533 | status?: "cancelled" | "delivered" | "paid" | "pending" | "shipped";
534 | orderDate?: string;
535 | }>;
536 |
537 | // Query TypeMap
538 | import "@sanity/client";
539 | declare module "@sanity/client" {
540 | interface SanityQueries {
541 | "*[_type==\"product\"] | order(name asc)": PRODUCTS_QUERYResult;
542 | "*[_type==\"category\"] | order(name asc)": CATEGORIES_QUERYResult;
543 | "*[_type == 'sale' && isActive == true && couponCode == $couponCode] | order(validFrom desc)[0]": ACTIVE_COUPON_CODEResult;
544 | "*[_type == \"product\" && name match $searchParam] | order(name asc)": PRODUCT_SEARCH_QUERYResult;
545 | "*[_type == \"product\" && slug.current == $slug] | order(name asc) [0]": PRODUCT_BY_ID_QUERYResult;
546 | "*[_type == 'product' && references(*[_type == \"category\" && slug.current == $categorySlug]._id)] | order(name asc)": PRODUCT_BY_CATEGORY_QUERYResult;
547 | "*[_type == 'sale'] | order(name asc)": SALE_QUERYResult;
548 | "*[_type == 'order' && clerkUserId == $userId] | order(orderData desc){\n ...,products[]{\n ...,product->\n }\n }": MY_ORDERS_QUERYResult;
549 | }
550 | }
551 |
--------------------------------------------------------------------------------
/schema.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "sanity.imagePaletteSwatch",
4 | "type": "type",
5 | "value": {
6 | "type": "object",
7 | "attributes": {
8 | "_type": {
9 | "type": "objectAttribute",
10 | "value": {
11 | "type": "string",
12 | "value": "sanity.imagePaletteSwatch"
13 | }
14 | },
15 | "background": {
16 | "type": "objectAttribute",
17 | "value": {
18 | "type": "string"
19 | },
20 | "optional": true
21 | },
22 | "foreground": {
23 | "type": "objectAttribute",
24 | "value": {
25 | "type": "string"
26 | },
27 | "optional": true
28 | },
29 | "population": {
30 | "type": "objectAttribute",
31 | "value": {
32 | "type": "number"
33 | },
34 | "optional": true
35 | },
36 | "title": {
37 | "type": "objectAttribute",
38 | "value": {
39 | "type": "string"
40 | },
41 | "optional": true
42 | }
43 | }
44 | }
45 | },
46 | {
47 | "name": "sanity.imagePalette",
48 | "type": "type",
49 | "value": {
50 | "type": "object",
51 | "attributes": {
52 | "_type": {
53 | "type": "objectAttribute",
54 | "value": {
55 | "type": "string",
56 | "value": "sanity.imagePalette"
57 | }
58 | },
59 | "darkMuted": {
60 | "type": "objectAttribute",
61 | "value": {
62 | "type": "inline",
63 | "name": "sanity.imagePaletteSwatch"
64 | },
65 | "optional": true
66 | },
67 | "lightVibrant": {
68 | "type": "objectAttribute",
69 | "value": {
70 | "type": "inline",
71 | "name": "sanity.imagePaletteSwatch"
72 | },
73 | "optional": true
74 | },
75 | "darkVibrant": {
76 | "type": "objectAttribute",
77 | "value": {
78 | "type": "inline",
79 | "name": "sanity.imagePaletteSwatch"
80 | },
81 | "optional": true
82 | },
83 | "vibrant": {
84 | "type": "objectAttribute",
85 | "value": {
86 | "type": "inline",
87 | "name": "sanity.imagePaletteSwatch"
88 | },
89 | "optional": true
90 | },
91 | "dominant": {
92 | "type": "objectAttribute",
93 | "value": {
94 | "type": "inline",
95 | "name": "sanity.imagePaletteSwatch"
96 | },
97 | "optional": true
98 | },
99 | "lightMuted": {
100 | "type": "objectAttribute",
101 | "value": {
102 | "type": "inline",
103 | "name": "sanity.imagePaletteSwatch"
104 | },
105 | "optional": true
106 | },
107 | "muted": {
108 | "type": "objectAttribute",
109 | "value": {
110 | "type": "inline",
111 | "name": "sanity.imagePaletteSwatch"
112 | },
113 | "optional": true
114 | }
115 | }
116 | }
117 | },
118 | {
119 | "name": "sanity.imageDimensions",
120 | "type": "type",
121 | "value": {
122 | "type": "object",
123 | "attributes": {
124 | "_type": {
125 | "type": "objectAttribute",
126 | "value": {
127 | "type": "string",
128 | "value": "sanity.imageDimensions"
129 | }
130 | },
131 | "height": {
132 | "type": "objectAttribute",
133 | "value": {
134 | "type": "number"
135 | },
136 | "optional": true
137 | },
138 | "width": {
139 | "type": "objectAttribute",
140 | "value": {
141 | "type": "number"
142 | },
143 | "optional": true
144 | },
145 | "aspectRatio": {
146 | "type": "objectAttribute",
147 | "value": {
148 | "type": "number"
149 | },
150 | "optional": true
151 | }
152 | }
153 | }
154 | },
155 | {
156 | "name": "sanity.fileAsset",
157 | "type": "document",
158 | "attributes": {
159 | "_id": {
160 | "type": "objectAttribute",
161 | "value": {
162 | "type": "string"
163 | }
164 | },
165 | "_type": {
166 | "type": "objectAttribute",
167 | "value": {
168 | "type": "string",
169 | "value": "sanity.fileAsset"
170 | }
171 | },
172 | "_createdAt": {
173 | "type": "objectAttribute",
174 | "value": {
175 | "type": "string"
176 | }
177 | },
178 | "_updatedAt": {
179 | "type": "objectAttribute",
180 | "value": {
181 | "type": "string"
182 | }
183 | },
184 | "_rev": {
185 | "type": "objectAttribute",
186 | "value": {
187 | "type": "string"
188 | }
189 | },
190 | "originalFilename": {
191 | "type": "objectAttribute",
192 | "value": {
193 | "type": "string"
194 | },
195 | "optional": true
196 | },
197 | "label": {
198 | "type": "objectAttribute",
199 | "value": {
200 | "type": "string"
201 | },
202 | "optional": true
203 | },
204 | "title": {
205 | "type": "objectAttribute",
206 | "value": {
207 | "type": "string"
208 | },
209 | "optional": true
210 | },
211 | "description": {
212 | "type": "objectAttribute",
213 | "value": {
214 | "type": "string"
215 | },
216 | "optional": true
217 | },
218 | "altText": {
219 | "type": "objectAttribute",
220 | "value": {
221 | "type": "string"
222 | },
223 | "optional": true
224 | },
225 | "sha1hash": {
226 | "type": "objectAttribute",
227 | "value": {
228 | "type": "string"
229 | },
230 | "optional": true
231 | },
232 | "extension": {
233 | "type": "objectAttribute",
234 | "value": {
235 | "type": "string"
236 | },
237 | "optional": true
238 | },
239 | "mimeType": {
240 | "type": "objectAttribute",
241 | "value": {
242 | "type": "string"
243 | },
244 | "optional": true
245 | },
246 | "size": {
247 | "type": "objectAttribute",
248 | "value": {
249 | "type": "number"
250 | },
251 | "optional": true
252 | },
253 | "assetId": {
254 | "type": "objectAttribute",
255 | "value": {
256 | "type": "string"
257 | },
258 | "optional": true
259 | },
260 | "uploadId": {
261 | "type": "objectAttribute",
262 | "value": {
263 | "type": "string"
264 | },
265 | "optional": true
266 | },
267 | "path": {
268 | "type": "objectAttribute",
269 | "value": {
270 | "type": "string"
271 | },
272 | "optional": true
273 | },
274 | "url": {
275 | "type": "objectAttribute",
276 | "value": {
277 | "type": "string"
278 | },
279 | "optional": true
280 | },
281 | "source": {
282 | "type": "objectAttribute",
283 | "value": {
284 | "type": "inline",
285 | "name": "sanity.assetSourceData"
286 | },
287 | "optional": true
288 | }
289 | }
290 | },
291 | {
292 | "name": "geopoint",
293 | "type": "type",
294 | "value": {
295 | "type": "object",
296 | "attributes": {
297 | "_type": {
298 | "type": "objectAttribute",
299 | "value": {
300 | "type": "string",
301 | "value": "geopoint"
302 | }
303 | },
304 | "lat": {
305 | "type": "objectAttribute",
306 | "value": {
307 | "type": "number"
308 | },
309 | "optional": true
310 | },
311 | "lng": {
312 | "type": "objectAttribute",
313 | "value": {
314 | "type": "number"
315 | },
316 | "optional": true
317 | },
318 | "alt": {
319 | "type": "objectAttribute",
320 | "value": {
321 | "type": "number"
322 | },
323 | "optional": true
324 | }
325 | }
326 | }
327 | },
328 | {
329 | "name": "sale",
330 | "type": "document",
331 | "attributes": {
332 | "_id": {
333 | "type": "objectAttribute",
334 | "value": {
335 | "type": "string"
336 | }
337 | },
338 | "_type": {
339 | "type": "objectAttribute",
340 | "value": {
341 | "type": "string",
342 | "value": "sale"
343 | }
344 | },
345 | "_createdAt": {
346 | "type": "objectAttribute",
347 | "value": {
348 | "type": "string"
349 | }
350 | },
351 | "_updatedAt": {
352 | "type": "objectAttribute",
353 | "value": {
354 | "type": "string"
355 | }
356 | },
357 | "_rev": {
358 | "type": "objectAttribute",
359 | "value": {
360 | "type": "string"
361 | }
362 | },
363 | "title": {
364 | "type": "objectAttribute",
365 | "value": {
366 | "type": "string"
367 | },
368 | "optional": true
369 | },
370 | "description": {
371 | "type": "objectAttribute",
372 | "value": {
373 | "type": "string"
374 | },
375 | "optional": true
376 | },
377 | "badge": {
378 | "type": "objectAttribute",
379 | "value": {
380 | "type": "string"
381 | },
382 | "optional": true
383 | },
384 | "discountAmount": {
385 | "type": "objectAttribute",
386 | "value": {
387 | "type": "number"
388 | },
389 | "optional": true
390 | },
391 | "couponCode": {
392 | "type": "objectAttribute",
393 | "value": {
394 | "type": "string"
395 | },
396 | "optional": true
397 | },
398 | "validFrom": {
399 | "type": "objectAttribute",
400 | "value": {
401 | "type": "string"
402 | },
403 | "optional": true
404 | },
405 | "validUntil": {
406 | "type": "objectAttribute",
407 | "value": {
408 | "type": "string"
409 | },
410 | "optional": true
411 | },
412 | "isActive": {
413 | "type": "objectAttribute",
414 | "value": {
415 | "type": "boolean"
416 | },
417 | "optional": true
418 | },
419 | "image": {
420 | "type": "objectAttribute",
421 | "value": {
422 | "type": "object",
423 | "attributes": {
424 | "asset": {
425 | "type": "objectAttribute",
426 | "value": {
427 | "type": "object",
428 | "attributes": {
429 | "_ref": {
430 | "type": "objectAttribute",
431 | "value": {
432 | "type": "string"
433 | }
434 | },
435 | "_type": {
436 | "type": "objectAttribute",
437 | "value": {
438 | "type": "string",
439 | "value": "reference"
440 | }
441 | },
442 | "_weak": {
443 | "type": "objectAttribute",
444 | "value": {
445 | "type": "boolean"
446 | },
447 | "optional": true
448 | }
449 | },
450 | "dereferencesTo": "sanity.imageAsset"
451 | },
452 | "optional": true
453 | },
454 | "hotspot": {
455 | "type": "objectAttribute",
456 | "value": {
457 | "type": "inline",
458 | "name": "sanity.imageHotspot"
459 | },
460 | "optional": true
461 | },
462 | "crop": {
463 | "type": "objectAttribute",
464 | "value": {
465 | "type": "inline",
466 | "name": "sanity.imageCrop"
467 | },
468 | "optional": true
469 | },
470 | "_type": {
471 | "type": "objectAttribute",
472 | "value": {
473 | "type": "string",
474 | "value": "image"
475 | }
476 | }
477 | }
478 | },
479 | "optional": true
480 | }
481 | }
482 | },
483 | {
484 | "name": "order",
485 | "type": "document",
486 | "attributes": {
487 | "_id": {
488 | "type": "objectAttribute",
489 | "value": {
490 | "type": "string"
491 | }
492 | },
493 | "_type": {
494 | "type": "objectAttribute",
495 | "value": {
496 | "type": "string",
497 | "value": "order"
498 | }
499 | },
500 | "_createdAt": {
501 | "type": "objectAttribute",
502 | "value": {
503 | "type": "string"
504 | }
505 | },
506 | "_updatedAt": {
507 | "type": "objectAttribute",
508 | "value": {
509 | "type": "string"
510 | }
511 | },
512 | "_rev": {
513 | "type": "objectAttribute",
514 | "value": {
515 | "type": "string"
516 | }
517 | },
518 | "orderNumber": {
519 | "type": "objectAttribute",
520 | "value": {
521 | "type": "string"
522 | },
523 | "optional": true
524 | },
525 | "stripeCheckoutSessionId": {
526 | "type": "objectAttribute",
527 | "value": {
528 | "type": "string"
529 | },
530 | "optional": true
531 | },
532 | "stripeCustomerId": {
533 | "type": "objectAttribute",
534 | "value": {
535 | "type": "string"
536 | },
537 | "optional": true
538 | },
539 | "clerkUserId": {
540 | "type": "objectAttribute",
541 | "value": {
542 | "type": "string"
543 | },
544 | "optional": true
545 | },
546 | "customerName": {
547 | "type": "objectAttribute",
548 | "value": {
549 | "type": "string"
550 | },
551 | "optional": true
552 | },
553 | "email": {
554 | "type": "objectAttribute",
555 | "value": {
556 | "type": "string"
557 | },
558 | "optional": true
559 | },
560 | "stripePaymentIntentId": {
561 | "type": "objectAttribute",
562 | "value": {
563 | "type": "string"
564 | },
565 | "optional": true
566 | },
567 | "products": {
568 | "type": "objectAttribute",
569 | "value": {
570 | "type": "array",
571 | "of": {
572 | "type": "object",
573 | "attributes": {
574 | "product": {
575 | "type": "objectAttribute",
576 | "value": {
577 | "type": "object",
578 | "attributes": {
579 | "_ref": {
580 | "type": "objectAttribute",
581 | "value": {
582 | "type": "string"
583 | }
584 | },
585 | "_type": {
586 | "type": "objectAttribute",
587 | "value": {
588 | "type": "string",
589 | "value": "reference"
590 | }
591 | },
592 | "_weak": {
593 | "type": "objectAttribute",
594 | "value": {
595 | "type": "boolean"
596 | },
597 | "optional": true
598 | }
599 | },
600 | "dereferencesTo": "product"
601 | },
602 | "optional": true
603 | },
604 | "quantity": {
605 | "type": "objectAttribute",
606 | "value": {
607 | "type": "number"
608 | },
609 | "optional": true
610 | }
611 | },
612 | "rest": {
613 | "type": "object",
614 | "attributes": {
615 | "_key": {
616 | "type": "objectAttribute",
617 | "value": {
618 | "type": "string"
619 | }
620 | }
621 | }
622 | }
623 | }
624 | },
625 | "optional": true
626 | },
627 | "totalPrice": {
628 | "type": "objectAttribute",
629 | "value": {
630 | "type": "number"
631 | },
632 | "optional": true
633 | },
634 | "currency": {
635 | "type": "objectAttribute",
636 | "value": {
637 | "type": "string"
638 | },
639 | "optional": true
640 | },
641 | "amountDiscount": {
642 | "type": "objectAttribute",
643 | "value": {
644 | "type": "number"
645 | },
646 | "optional": true
647 | },
648 | "status": {
649 | "type": "objectAttribute",
650 | "value": {
651 | "type": "union",
652 | "of": [
653 | {
654 | "type": "string",
655 | "value": "pending"
656 | },
657 | {
658 | "type": "string",
659 | "value": "paid"
660 | },
661 | {
662 | "type": "string",
663 | "value": "shipped"
664 | },
665 | {
666 | "type": "string",
667 | "value": "delivered"
668 | },
669 | {
670 | "type": "string",
671 | "value": "cancelled"
672 | }
673 | ]
674 | },
675 | "optional": true
676 | },
677 | "orderDate": {
678 | "type": "objectAttribute",
679 | "value": {
680 | "type": "string"
681 | },
682 | "optional": true
683 | }
684 | }
685 | },
686 | {
687 | "name": "product",
688 | "type": "document",
689 | "attributes": {
690 | "_id": {
691 | "type": "objectAttribute",
692 | "value": {
693 | "type": "string"
694 | }
695 | },
696 | "_type": {
697 | "type": "objectAttribute",
698 | "value": {
699 | "type": "string",
700 | "value": "product"
701 | }
702 | },
703 | "_createdAt": {
704 | "type": "objectAttribute",
705 | "value": {
706 | "type": "string"
707 | }
708 | },
709 | "_updatedAt": {
710 | "type": "objectAttribute",
711 | "value": {
712 | "type": "string"
713 | }
714 | },
715 | "_rev": {
716 | "type": "objectAttribute",
717 | "value": {
718 | "type": "string"
719 | }
720 | },
721 | "name": {
722 | "type": "objectAttribute",
723 | "value": {
724 | "type": "string"
725 | },
726 | "optional": true
727 | },
728 | "slug": {
729 | "type": "objectAttribute",
730 | "value": {
731 | "type": "inline",
732 | "name": "slug"
733 | },
734 | "optional": true
735 | },
736 | "image": {
737 | "type": "objectAttribute",
738 | "value": {
739 | "type": "object",
740 | "attributes": {
741 | "asset": {
742 | "type": "objectAttribute",
743 | "value": {
744 | "type": "object",
745 | "attributes": {
746 | "_ref": {
747 | "type": "objectAttribute",
748 | "value": {
749 | "type": "string"
750 | }
751 | },
752 | "_type": {
753 | "type": "objectAttribute",
754 | "value": {
755 | "type": "string",
756 | "value": "reference"
757 | }
758 | },
759 | "_weak": {
760 | "type": "objectAttribute",
761 | "value": {
762 | "type": "boolean"
763 | },
764 | "optional": true
765 | }
766 | },
767 | "dereferencesTo": "sanity.imageAsset"
768 | },
769 | "optional": true
770 | },
771 | "hotspot": {
772 | "type": "objectAttribute",
773 | "value": {
774 | "type": "inline",
775 | "name": "sanity.imageHotspot"
776 | },
777 | "optional": true
778 | },
779 | "crop": {
780 | "type": "objectAttribute",
781 | "value": {
782 | "type": "inline",
783 | "name": "sanity.imageCrop"
784 | },
785 | "optional": true
786 | },
787 | "_type": {
788 | "type": "objectAttribute",
789 | "value": {
790 | "type": "string",
791 | "value": "image"
792 | }
793 | }
794 | }
795 | },
796 | "optional": true
797 | },
798 | "description": {
799 | "type": "objectAttribute",
800 | "value": {
801 | "type": "string"
802 | },
803 | "optional": true
804 | },
805 | "price": {
806 | "type": "objectAttribute",
807 | "value": {
808 | "type": "number"
809 | },
810 | "optional": true
811 | },
812 | "discount": {
813 | "type": "objectAttribute",
814 | "value": {
815 | "type": "number"
816 | },
817 | "optional": true
818 | },
819 | "categories": {
820 | "type": "objectAttribute",
821 | "value": {
822 | "type": "array",
823 | "of": {
824 | "type": "object",
825 | "attributes": {
826 | "_ref": {
827 | "type": "objectAttribute",
828 | "value": {
829 | "type": "string"
830 | }
831 | },
832 | "_type": {
833 | "type": "objectAttribute",
834 | "value": {
835 | "type": "string",
836 | "value": "reference"
837 | }
838 | },
839 | "_weak": {
840 | "type": "objectAttribute",
841 | "value": {
842 | "type": "boolean"
843 | },
844 | "optional": true
845 | }
846 | },
847 | "dereferencesTo": "category",
848 | "rest": {
849 | "type": "object",
850 | "attributes": {
851 | "_key": {
852 | "type": "objectAttribute",
853 | "value": {
854 | "type": "string"
855 | }
856 | }
857 | }
858 | }
859 | }
860 | },
861 | "optional": true
862 | },
863 | "stock": {
864 | "type": "objectAttribute",
865 | "value": {
866 | "type": "number"
867 | },
868 | "optional": true
869 | },
870 | "label": {
871 | "type": "objectAttribute",
872 | "value": {
873 | "type": "string"
874 | },
875 | "optional": true
876 | },
877 | "status": {
878 | "type": "objectAttribute",
879 | "value": {
880 | "type": "union",
881 | "of": [
882 | {
883 | "type": "string",
884 | "value": "new"
885 | },
886 | {
887 | "type": "string",
888 | "value": "hot"
889 | },
890 | {
891 | "type": "string",
892 | "value": "sale"
893 | }
894 | ]
895 | },
896 | "optional": true
897 | }
898 | }
899 | },
900 | {
901 | "name": "category",
902 | "type": "document",
903 | "attributes": {
904 | "_id": {
905 | "type": "objectAttribute",
906 | "value": {
907 | "type": "string"
908 | }
909 | },
910 | "_type": {
911 | "type": "objectAttribute",
912 | "value": {
913 | "type": "string",
914 | "value": "category"
915 | }
916 | },
917 | "_createdAt": {
918 | "type": "objectAttribute",
919 | "value": {
920 | "type": "string"
921 | }
922 | },
923 | "_updatedAt": {
924 | "type": "objectAttribute",
925 | "value": {
926 | "type": "string"
927 | }
928 | },
929 | "_rev": {
930 | "type": "objectAttribute",
931 | "value": {
932 | "type": "string"
933 | }
934 | },
935 | "title": {
936 | "type": "objectAttribute",
937 | "value": {
938 | "type": "string"
939 | },
940 | "optional": true
941 | },
942 | "slug": {
943 | "type": "objectAttribute",
944 | "value": {
945 | "type": "inline",
946 | "name": "slug"
947 | },
948 | "optional": true
949 | },
950 | "description": {
951 | "type": "objectAttribute",
952 | "value": {
953 | "type": "string"
954 | },
955 | "optional": true
956 | }
957 | }
958 | },
959 | {
960 | "name": "slug",
961 | "type": "type",
962 | "value": {
963 | "type": "object",
964 | "attributes": {
965 | "_type": {
966 | "type": "objectAttribute",
967 | "value": {
968 | "type": "string",
969 | "value": "slug"
970 | }
971 | },
972 | "current": {
973 | "type": "objectAttribute",
974 | "value": {
975 | "type": "string"
976 | },
977 | "optional": true
978 | },
979 | "source": {
980 | "type": "objectAttribute",
981 | "value": {
982 | "type": "string"
983 | },
984 | "optional": true
985 | }
986 | }
987 | }
988 | },
989 | {
990 | "name": "blockContent",
991 | "type": "type",
992 | "value": {
993 | "type": "array",
994 | "of": {
995 | "type": "union",
996 | "of": [
997 | {
998 | "type": "object",
999 | "attributes": {
1000 | "children": {
1001 | "type": "objectAttribute",
1002 | "value": {
1003 | "type": "array",
1004 | "of": {
1005 | "type": "object",
1006 | "attributes": {
1007 | "marks": {
1008 | "type": "objectAttribute",
1009 | "value": {
1010 | "type": "array",
1011 | "of": {
1012 | "type": "string"
1013 | }
1014 | },
1015 | "optional": true
1016 | },
1017 | "text": {
1018 | "type": "objectAttribute",
1019 | "value": {
1020 | "type": "string"
1021 | },
1022 | "optional": true
1023 | },
1024 | "_type": {
1025 | "type": "objectAttribute",
1026 | "value": {
1027 | "type": "string",
1028 | "value": "span"
1029 | }
1030 | }
1031 | },
1032 | "rest": {
1033 | "type": "object",
1034 | "attributes": {
1035 | "_key": {
1036 | "type": "objectAttribute",
1037 | "value": {
1038 | "type": "string"
1039 | }
1040 | }
1041 | }
1042 | }
1043 | }
1044 | },
1045 | "optional": true
1046 | },
1047 | "style": {
1048 | "type": "objectAttribute",
1049 | "value": {
1050 | "type": "union",
1051 | "of": [
1052 | {
1053 | "type": "string",
1054 | "value": "normal"
1055 | },
1056 | {
1057 | "type": "string",
1058 | "value": "h1"
1059 | },
1060 | {
1061 | "type": "string",
1062 | "value": "h2"
1063 | },
1064 | {
1065 | "type": "string",
1066 | "value": "h3"
1067 | },
1068 | {
1069 | "type": "string",
1070 | "value": "h4"
1071 | },
1072 | {
1073 | "type": "string",
1074 | "value": "blockquote"
1075 | }
1076 | ]
1077 | },
1078 | "optional": true
1079 | },
1080 | "listItem": {
1081 | "type": "objectAttribute",
1082 | "value": {
1083 | "type": "union",
1084 | "of": [
1085 | {
1086 | "type": "string",
1087 | "value": "bullet"
1088 | }
1089 | ]
1090 | },
1091 | "optional": true
1092 | },
1093 | "markDefs": {
1094 | "type": "objectAttribute",
1095 | "value": {
1096 | "type": "array",
1097 | "of": {
1098 | "type": "object",
1099 | "attributes": {
1100 | "href": {
1101 | "type": "objectAttribute",
1102 | "value": {
1103 | "type": "string"
1104 | },
1105 | "optional": true
1106 | },
1107 | "_type": {
1108 | "type": "objectAttribute",
1109 | "value": {
1110 | "type": "string",
1111 | "value": "link"
1112 | }
1113 | }
1114 | },
1115 | "rest": {
1116 | "type": "object",
1117 | "attributes": {
1118 | "_key": {
1119 | "type": "objectAttribute",
1120 | "value": {
1121 | "type": "string"
1122 | }
1123 | }
1124 | }
1125 | }
1126 | }
1127 | },
1128 | "optional": true
1129 | },
1130 | "level": {
1131 | "type": "objectAttribute",
1132 | "value": {
1133 | "type": "number"
1134 | },
1135 | "optional": true
1136 | },
1137 | "_type": {
1138 | "type": "objectAttribute",
1139 | "value": {
1140 | "type": "string",
1141 | "value": "block"
1142 | }
1143 | }
1144 | },
1145 | "rest": {
1146 | "type": "object",
1147 | "attributes": {
1148 | "_key": {
1149 | "type": "objectAttribute",
1150 | "value": {
1151 | "type": "string"
1152 | }
1153 | }
1154 | }
1155 | }
1156 | },
1157 | {
1158 | "type": "object",
1159 | "attributes": {
1160 | "asset": {
1161 | "type": "objectAttribute",
1162 | "value": {
1163 | "type": "object",
1164 | "attributes": {
1165 | "_ref": {
1166 | "type": "objectAttribute",
1167 | "value": {
1168 | "type": "string"
1169 | }
1170 | },
1171 | "_type": {
1172 | "type": "objectAttribute",
1173 | "value": {
1174 | "type": "string",
1175 | "value": "reference"
1176 | }
1177 | },
1178 | "_weak": {
1179 | "type": "objectAttribute",
1180 | "value": {
1181 | "type": "boolean"
1182 | },
1183 | "optional": true
1184 | }
1185 | },
1186 | "dereferencesTo": "sanity.imageAsset"
1187 | },
1188 | "optional": true
1189 | },
1190 | "hotspot": {
1191 | "type": "objectAttribute",
1192 | "value": {
1193 | "type": "inline",
1194 | "name": "sanity.imageHotspot"
1195 | },
1196 | "optional": true
1197 | },
1198 | "crop": {
1199 | "type": "objectAttribute",
1200 | "value": {
1201 | "type": "inline",
1202 | "name": "sanity.imageCrop"
1203 | },
1204 | "optional": true
1205 | },
1206 | "alt": {
1207 | "type": "objectAttribute",
1208 | "value": {
1209 | "type": "string"
1210 | },
1211 | "optional": true
1212 | },
1213 | "_type": {
1214 | "type": "objectAttribute",
1215 | "value": {
1216 | "type": "string",
1217 | "value": "image"
1218 | }
1219 | }
1220 | },
1221 | "rest": {
1222 | "type": "object",
1223 | "attributes": {
1224 | "_key": {
1225 | "type": "objectAttribute",
1226 | "value": {
1227 | "type": "string"
1228 | }
1229 | }
1230 | }
1231 | }
1232 | }
1233 | ]
1234 | }
1235 | }
1236 | },
1237 | {
1238 | "name": "sanity.imageCrop",
1239 | "type": "type",
1240 | "value": {
1241 | "type": "object",
1242 | "attributes": {
1243 | "_type": {
1244 | "type": "objectAttribute",
1245 | "value": {
1246 | "type": "string",
1247 | "value": "sanity.imageCrop"
1248 | }
1249 | },
1250 | "top": {
1251 | "type": "objectAttribute",
1252 | "value": {
1253 | "type": "number"
1254 | },
1255 | "optional": true
1256 | },
1257 | "bottom": {
1258 | "type": "objectAttribute",
1259 | "value": {
1260 | "type": "number"
1261 | },
1262 | "optional": true
1263 | },
1264 | "left": {
1265 | "type": "objectAttribute",
1266 | "value": {
1267 | "type": "number"
1268 | },
1269 | "optional": true
1270 | },
1271 | "right": {
1272 | "type": "objectAttribute",
1273 | "value": {
1274 | "type": "number"
1275 | },
1276 | "optional": true
1277 | }
1278 | }
1279 | }
1280 | },
1281 | {
1282 | "name": "sanity.imageHotspot",
1283 | "type": "type",
1284 | "value": {
1285 | "type": "object",
1286 | "attributes": {
1287 | "_type": {
1288 | "type": "objectAttribute",
1289 | "value": {
1290 | "type": "string",
1291 | "value": "sanity.imageHotspot"
1292 | }
1293 | },
1294 | "x": {
1295 | "type": "objectAttribute",
1296 | "value": {
1297 | "type": "number"
1298 | },
1299 | "optional": true
1300 | },
1301 | "y": {
1302 | "type": "objectAttribute",
1303 | "value": {
1304 | "type": "number"
1305 | },
1306 | "optional": true
1307 | },
1308 | "height": {
1309 | "type": "objectAttribute",
1310 | "value": {
1311 | "type": "number"
1312 | },
1313 | "optional": true
1314 | },
1315 | "width": {
1316 | "type": "objectAttribute",
1317 | "value": {
1318 | "type": "number"
1319 | },
1320 | "optional": true
1321 | }
1322 | }
1323 | }
1324 | },
1325 | {
1326 | "name": "sanity.imageAsset",
1327 | "type": "document",
1328 | "attributes": {
1329 | "_id": {
1330 | "type": "objectAttribute",
1331 | "value": {
1332 | "type": "string"
1333 | }
1334 | },
1335 | "_type": {
1336 | "type": "objectAttribute",
1337 | "value": {
1338 | "type": "string",
1339 | "value": "sanity.imageAsset"
1340 | }
1341 | },
1342 | "_createdAt": {
1343 | "type": "objectAttribute",
1344 | "value": {
1345 | "type": "string"
1346 | }
1347 | },
1348 | "_updatedAt": {
1349 | "type": "objectAttribute",
1350 | "value": {
1351 | "type": "string"
1352 | }
1353 | },
1354 | "_rev": {
1355 | "type": "objectAttribute",
1356 | "value": {
1357 | "type": "string"
1358 | }
1359 | },
1360 | "originalFilename": {
1361 | "type": "objectAttribute",
1362 | "value": {
1363 | "type": "string"
1364 | },
1365 | "optional": true
1366 | },
1367 | "label": {
1368 | "type": "objectAttribute",
1369 | "value": {
1370 | "type": "string"
1371 | },
1372 | "optional": true
1373 | },
1374 | "title": {
1375 | "type": "objectAttribute",
1376 | "value": {
1377 | "type": "string"
1378 | },
1379 | "optional": true
1380 | },
1381 | "description": {
1382 | "type": "objectAttribute",
1383 | "value": {
1384 | "type": "string"
1385 | },
1386 | "optional": true
1387 | },
1388 | "altText": {
1389 | "type": "objectAttribute",
1390 | "value": {
1391 | "type": "string"
1392 | },
1393 | "optional": true
1394 | },
1395 | "sha1hash": {
1396 | "type": "objectAttribute",
1397 | "value": {
1398 | "type": "string"
1399 | },
1400 | "optional": true
1401 | },
1402 | "extension": {
1403 | "type": "objectAttribute",
1404 | "value": {
1405 | "type": "string"
1406 | },
1407 | "optional": true
1408 | },
1409 | "mimeType": {
1410 | "type": "objectAttribute",
1411 | "value": {
1412 | "type": "string"
1413 | },
1414 | "optional": true
1415 | },
1416 | "size": {
1417 | "type": "objectAttribute",
1418 | "value": {
1419 | "type": "number"
1420 | },
1421 | "optional": true
1422 | },
1423 | "assetId": {
1424 | "type": "objectAttribute",
1425 | "value": {
1426 | "type": "string"
1427 | },
1428 | "optional": true
1429 | },
1430 | "uploadId": {
1431 | "type": "objectAttribute",
1432 | "value": {
1433 | "type": "string"
1434 | },
1435 | "optional": true
1436 | },
1437 | "path": {
1438 | "type": "objectAttribute",
1439 | "value": {
1440 | "type": "string"
1441 | },
1442 | "optional": true
1443 | },
1444 | "url": {
1445 | "type": "objectAttribute",
1446 | "value": {
1447 | "type": "string"
1448 | },
1449 | "optional": true
1450 | },
1451 | "metadata": {
1452 | "type": "objectAttribute",
1453 | "value": {
1454 | "type": "inline",
1455 | "name": "sanity.imageMetadata"
1456 | },
1457 | "optional": true
1458 | },
1459 | "source": {
1460 | "type": "objectAttribute",
1461 | "value": {
1462 | "type": "inline",
1463 | "name": "sanity.assetSourceData"
1464 | },
1465 | "optional": true
1466 | }
1467 | }
1468 | },
1469 | {
1470 | "name": "sanity.assetSourceData",
1471 | "type": "type",
1472 | "value": {
1473 | "type": "object",
1474 | "attributes": {
1475 | "_type": {
1476 | "type": "objectAttribute",
1477 | "value": {
1478 | "type": "string",
1479 | "value": "sanity.assetSourceData"
1480 | }
1481 | },
1482 | "name": {
1483 | "type": "objectAttribute",
1484 | "value": {
1485 | "type": "string"
1486 | },
1487 | "optional": true
1488 | },
1489 | "id": {
1490 | "type": "objectAttribute",
1491 | "value": {
1492 | "type": "string"
1493 | },
1494 | "optional": true
1495 | },
1496 | "url": {
1497 | "type": "objectAttribute",
1498 | "value": {
1499 | "type": "string"
1500 | },
1501 | "optional": true
1502 | }
1503 | }
1504 | }
1505 | },
1506 | {
1507 | "name": "sanity.imageMetadata",
1508 | "type": "type",
1509 | "value": {
1510 | "type": "object",
1511 | "attributes": {
1512 | "_type": {
1513 | "type": "objectAttribute",
1514 | "value": {
1515 | "type": "string",
1516 | "value": "sanity.imageMetadata"
1517 | }
1518 | },
1519 | "location": {
1520 | "type": "objectAttribute",
1521 | "value": {
1522 | "type": "inline",
1523 | "name": "geopoint"
1524 | },
1525 | "optional": true
1526 | },
1527 | "dimensions": {
1528 | "type": "objectAttribute",
1529 | "value": {
1530 | "type": "inline",
1531 | "name": "sanity.imageDimensions"
1532 | },
1533 | "optional": true
1534 | },
1535 | "palette": {
1536 | "type": "objectAttribute",
1537 | "value": {
1538 | "type": "inline",
1539 | "name": "sanity.imagePalette"
1540 | },
1541 | "optional": true
1542 | },
1543 | "lqip": {
1544 | "type": "objectAttribute",
1545 | "value": {
1546 | "type": "string"
1547 | },
1548 | "optional": true
1549 | },
1550 | "blurHash": {
1551 | "type": "objectAttribute",
1552 | "value": {
1553 | "type": "string"
1554 | },
1555 | "optional": true
1556 | },
1557 | "hasAlpha": {
1558 | "type": "objectAttribute",
1559 | "value": {
1560 | "type": "boolean"
1561 | },
1562 | "optional": true
1563 | },
1564 | "isOpaque": {
1565 | "type": "objectAttribute",
1566 | "value": {
1567 | "type": "boolean"
1568 | },
1569 | "optional": true
1570 | }
1571 | }
1572 | }
1573 | }
1574 | ]
1575 |
--------------------------------------------------------------------------------