Skip to content

Commit 6dc26a2

Browse files
committed
feat: digital products + trieve error handling
1 parent bf6165f commit 6dc26a2

34 files changed

+973
-1167
lines changed

biome.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"indentStyle": "tab",
1717
"indentWidth": 2,
1818
"lineEnding": "lf",
19-
"lineWidth": 100,
19+
"lineWidth": 110,
2020
"attributePosition": "auto",
2121
"bracketSpacing": true,
2222
"ignore": ["**/.next", "**/node_modules", "**/pnpm-lock.yaml"]

package.json

+15-15
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"docker:run": "docker run -d -p 3000:3000 yournextstore"
1717
},
1818
"dependencies": {
19-
"@next/mdx": "15.0.0-canary.177",
19+
"@next/mdx": "15.0.0-canary.182",
2020
"@radix-ui/react-checkbox": "1.1.2",
2121
"@radix-ui/react-collapsible": "1.1.1",
2222
"@radix-ui/react-dialog": "1.1.2",
@@ -33,28 +33,28 @@
3333
"@radix-ui/react-toggle": "1.1.0",
3434
"@radix-ui/react-toggle-group": "1.1.0",
3535
"@radix-ui/react-tooltip": "1.1.3",
36-
"@stripe/react-stripe-js": "2.8.0",
37-
"@stripe/stripe-js": "4.6.0",
36+
"@stripe/react-stripe-js": "2.8.1",
37+
"@stripe/stripe-js": "4.8.0",
3838
"@t3-oss/env-nextjs": "0.11.1",
3939
"@vercel/analytics": "1.3.1",
40-
"@vercel/blob": "0.24.1",
40+
"@vercel/blob": "0.25.0",
4141
"@vercel/speed-insights": "1.0.12",
4242
"class-variance-authority": "0.7.0",
4343
"clsx": "2.1.1",
4444
"cmdk": "0.2.1",
45-
"commerce-kit": "0.0.28",
46-
"lucide-react": "0.447.0",
45+
"commerce-kit": "0.0.34",
46+
"lucide-react": "0.451.0",
4747
"nanoid": "5.0.7",
48-
"next": "15.0.0-canary.177",
49-
"next-intl": "3.20.0",
48+
"next": "15.0.0-canary.182",
49+
"next-intl": "3.21.1",
5050
"next-mdx-remote": "5.0.0",
5151
"next-themes": "0.3.0",
52-
"react": "19.0.0-rc-0751fac7-20241002",
53-
"react-dom": "19.0.0-rc-0751fac7-20241002",
52+
"react": "19.0.0-rc-38af456a-20241010",
53+
"react-dom": "19.0.0-rc-38af456a-20241010",
5454
"schema-dts": "1.1.2",
5555
"server-only": "0.0.1",
5656
"sonner": "1.5.0",
57-
"stripe": "17.1.0",
57+
"stripe": "17.2.0",
5858
"tailwind-merge": "2.5.3",
5959
"tailwindcss-animate": "1.0.7",
6060
"trieve-ts-sdk": "0.0.12",
@@ -66,7 +66,7 @@
6666
"@commitlint/cli": "19.5.0",
6767
"@commitlint/config-conventional": "19.5.0",
6868
"@commitlint/types": "19.5.0",
69-
"@next/env": "15.0.0-canary.177",
69+
"@next/env": "15.0.0-canary.182",
7070
"@semantic-release/changelog": "6.0.3",
7171
"@semantic-release/commit-analyzer": "13.0.0",
7272
"@semantic-release/git": "10.0.1",
@@ -78,12 +78,12 @@
7878
"@testing-library/jest-dom": "6.5.0",
7979
"@testing-library/react": "16.0.1",
8080
"@types/mdx": "2.0.13",
81-
"@types/node": "^20.16.10",
81+
"@types/node": "^20.16.11",
8282
"@types/react": "npm:[email protected]",
8383
"@types/react-dom": "npm:[email protected]",
8484
"@vitejs/plugin-react": "4.3.2",
8585
"autoprefixer": "10.4.20",
86-
"babel-plugin-react-compiler": "0.0.0-experimental-27e0f40-20241002",
86+
"babel-plugin-react-compiler": "0.0.0-experimental-58c2b1c-20241009",
8787
"husky": "9.1.6",
8888
"lint-staged": "15.2.10",
8989
"mdx": "0.3.1",
@@ -92,7 +92,7 @@
9292
"sharp": "0.33.5",
9393
"tailwindcss": "3.4.13",
9494
"tsx": "4.19.1",
95-
"typescript": "5.6.2",
95+
"typescript": "5.6.3",
9696
"vite-tsconfig-paths": "5.0.1",
9797
"vitest": "2.1.2"
9898
},

pnpm-lock.yaml

+807-871
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/(store)/order/success/page.tsx

+3-8
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,7 @@ export default async function OrderDetailsPage(props: {
140140
<div className="grid gap-8 sm:grid-cols-2">
141141
{order.order.shipping?.address && (
142142
<div>
143-
<h3 className="font-semibold leading-none text-neutral-700">
144-
{t("shippingAddress")}
145-
</h3>
143+
<h3 className="font-semibold leading-none text-neutral-700">{t("shippingAddress")}</h3>
146144
<p className="mt-3 text-sm">
147145
{[
148146
order.order.shipping.name,
@@ -178,8 +176,7 @@ export default async function OrderDetailsPage(props: {
178176
order.order.payment_method.billing_details.address.postal_code,
179177
order.order.payment_method.billing_details.address.city,
180178
order.order.payment_method.billing_details.address.state,
181-
findMatchingCountry(order.order.payment_method?.billing_details?.address?.country)
182-
?.label,
179+
findMatchingCountry(order.order.payment_method?.billing_details?.address?.country)?.label,
183180
"\n",
184181
order.order.payment_method.billing_details.phone,
185182
order.order.receipt_email,
@@ -204,9 +201,7 @@ export default async function OrderDetailsPage(props: {
204201
order.order.payment_method.card.brand in paymentMethods && (
205202
<Image
206203
src={
207-
paymentMethods[
208-
order.order.payment_method.card.brand as keyof typeof paymentMethods
209-
]
204+
paymentMethods[order.order.payment_method.card.brand as keyof typeof paymentMethods]
210205
}
211206
className="mr-1 inline-block w-6 align-text-bottom"
212207
alt=""

src/app/(store)/page.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ export default async function Home() {
2222
<section className="rounded bg-neutral-100 py-8 sm:py-12">
2323
<div className="mx-auto grid grid-cols-1 items-center justify-items-center gap-8 px-8 sm:px-16 md:grid-cols-2">
2424
<div className="max-w-md space-y-4">
25-
<h2 className="text-balance text-3xl font-bold tracking-tight md:text-4xl">
26-
{t("hero.title")}
27-
</h2>
25+
<h2 className="text-balance text-3xl font-bold tracking-tight md:text-4xl">{t("hero.title")}</h2>
2826
<p className="text-pretty text-neutral-600">{t("hero.description")}</p>
2927
<YnsLink
3028
className="inline-flex h-10 items-center justify-center rounded-full bg-neutral-900 px-6 font-medium text-neutral-50 transition-colors hover:bg-neutral-900/90 focus:outline-none focus:ring-1 focus:ring-neutral-950"

src/app/(store)/product/[slug]/opengraph-image.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ export default async function Image(props: { params: Promise<{ slug: string }> }
2222
// const geistBold = fetch(new URL("./Geist-Bold.ttf", import.meta.url)).then((res) =>
2323
// res.arrayBuffer(),
2424
// );
25-
const [accountResult, [product]] = await Promise.all([
26-
accountGet(),
27-
productGet({ slug: params.slug }),
28-
]);
25+
const [accountResult, [product]] = await Promise.all([accountGet(), productGet({ slug: params.slug })]);
2926

3027
if (!product) {
3128
return null;

src/app/(store)/product/[slug]/page.tsx

+5-16
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,15 @@ export default async function SingleProductPage(props: {
7575
<Breadcrumb>
7676
<BreadcrumbList>
7777
<BreadcrumbItem>
78-
<BreadcrumbLink
79-
asChild
80-
className="inline-flex min-h-12 min-w-12 items-center justify-center"
81-
>
78+
<BreadcrumbLink asChild className="inline-flex min-h-12 min-w-12 items-center justify-center">
8279
<YnsLink href="/">{t("allProducts")}</YnsLink>
8380
</BreadcrumbLink>
8481
</BreadcrumbItem>
8582
{category && (
8683
<>
8784
<BreadcrumbSeparator />
8885
<BreadcrumbItem>
89-
<BreadcrumbLink
90-
className="inline-flex min-h-12 min-w-12 items-center justify-center"
91-
asChild
92-
>
86+
<BreadcrumbLink className="inline-flex min-h-12 min-w-12 items-center justify-center" asChild>
9387
<YnsLink href={`/category/${category}`}>{deslugify(category)}</YnsLink>
9488
</BreadcrumbLink>
9589
</BreadcrumbItem>
@@ -112,9 +106,7 @@ export default async function SingleProductPage(props: {
112106

113107
<div className="mt-4 grid gap-4 lg:grid-cols-12">
114108
<div className="lg:col-span-5 lg:col-start-8">
115-
<h1 className="text-3xl font-bold leading-none tracking-tight text-foreground">
116-
{product.name}
117-
</h1>
109+
<h1 className="text-3xl font-bold leading-none tracking-tight text-foreground">{product.name}</h1>
118110
{product.default_price.unit_amount && (
119111
<p className="mt-2 text-2xl font-medium leading-none tracking-tight text-foreground/70">
120112
{formatMoney({
@@ -202,6 +194,7 @@ export default async function SingleProductPage(props: {
202194

203195
async function SimilarProducts({ id }: { id: string }) {
204196
const products = await getRecommendedProducts({ productId: id, limit: 4 });
197+
205198
if (!products) {
206199
return null;
207200
}
@@ -232,11 +225,7 @@ async function SimilarProducts({ id }: { id: string }) {
232225
)}
233226
<div className="p-4">
234227
<h3 className="text-lg font-semibold mb-2">
235-
<YnsLink
236-
href={product.link || "#"}
237-
className="hover:text-primary"
238-
prefetch={false}
239-
>
228+
<YnsLink href={product.link || "#"} className="hover:text-primary" prefetch={false}>
240229
{trieveMetadata.name}
241230
</YnsLink>
242231
</h3>

src/app/(store)/products/page.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ export default async function AllProductsPage() {
1818

1919
return (
2020
<main className="pb-8">
21-
<h1 className="text-3xl font-bold leading-none tracking-tight text-foreground">
22-
{t("title")}
23-
</h1>
21+
<h1 className="text-3xl font-bold leading-none tracking-tight text-foreground">{t("title")}</h1>
2422
<ProductList products={products} />
2523
</main>
2624
);

src/app/api/stripe-webhook/route.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ export async function POST(request: Request) {
2121
});
2222

2323
const [error, event] = await unpackPromise(
24-
stripe.webhooks.constructEventAsync(
25-
await (await request.text)(),
26-
signature,
27-
env.STRIPE_WEBHOOK_SECRET,
28-
),
24+
stripe.webhooks.constructEventAsync(await (await request.text)(), signature, env.STRIPE_WEBHOOK_SECRET),
2925
);
3026

3127
if (error) {

src/lib/cart.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ export type CartCookieJson = { id: string; linesCount: number };
77

88
export function setCartCookieJson(cartCookieJson: CartCookieJson): void {
99
try {
10-
(cookies() as unknown as UnsafeUnwrappedCookies).set(
11-
CART_COOKIE,
12-
JSON.stringify(cartCookieJson),
13-
);
10+
(cookies() as unknown as UnsafeUnwrappedCookies).set(CART_COOKIE, JSON.stringify(cartCookieJson));
1411
} catch (error) {
1512
console.error("Failed to set cart cookie", error);
1613
}

src/lib/countries.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
export const findMatchingCountry = (
2-
value: null | undefined | string | number | readonly string[],
3-
) => {
1+
export const findMatchingCountry = (value: null | undefined | string | number | readonly string[]) => {
42
if (typeof value !== "string") {
53
return null;
64
}

src/lib/search/trieve.ts

+17-11
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ const datasetId = process.env.TRIEVE_DATASET_ID;
77

88
export const trieve = apiKey && datasetId ? new TrieveSDK({ apiKey, datasetId }) : null;
99

10-
export const getRecommendedProducts = cache(
11-
({ productId, limit }: { productId: string; limit: number }) =>
12-
unstable_cache(
13-
async () => {
14-
if (!trieve) {
15-
return null;
16-
}
10+
export const getRecommendedProducts = cache(({ productId, limit }: { productId: string; limit: number }) =>
11+
unstable_cache(
12+
async () => {
13+
if (!trieve) {
14+
return null;
15+
}
1716

17+
try {
1818
const response = await trieve.getRecommendedChunks({
1919
positive_tracking_ids: [productId],
2020
strategy: "best_score",
@@ -34,8 +34,14 @@ export const getRecommendedProducts = cache(
3434
return [];
3535
});
3636
return products;
37-
},
38-
[`getRecommendedProducts-${productId}-${limit}`],
39-
{ tags: ["getRecommendedProducts", `getRecommendedProducts-${productId}`] },
40-
)(),
37+
} catch (error) {
38+
console.error(error);
39+
return null;
40+
}
41+
},
42+
[`getRecommendedProducts-${productId}-${limit}`],
43+
{
44+
tags: ["getRecommendedProducts", `getRecommendedProducts-${productId}`],
45+
},
46+
)(),
4147
);

src/lib/types.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ export type NestedPick<
99
? { [NewKey in TObjectKey]: TObj[TObjectKey] }
1010
: never;
1111

12-
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (
13-
k: infer I,
14-
) => void
12+
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void
1513
? I
1614
: never;
1715

src/lib/utils.ts

+5-19
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,7 @@ export const pluralize = (count: number, words: CardinalWords) => {
5050
return words[rule] ?? words.other;
5151
};
5252

53-
export const getFieldsByPrefix = <Prefix extends string, Obj extends object>(
54-
obj: Obj,
55-
prefix: Prefix,
56-
) => {
53+
export const getFieldsByPrefix = <Prefix extends string, Obj extends object>(obj: Obj, prefix: Prefix) => {
5754
const prefixWithDot = prefix + ".";
5855
return Object.fromEntries(
5956
Object.entries(obj)
@@ -64,14 +61,9 @@ export const getFieldsByPrefix = <Prefix extends string, Obj extends object>(
6461
};
6562
};
6663

67-
export const addPrefixToFields = <Prefix extends string, Obj extends object>(
68-
obj: Obj,
69-
prefix: Prefix,
70-
) => {
64+
export const addPrefixToFields = <Prefix extends string, Obj extends object>(obj: Obj, prefix: Prefix) => {
7165
const prefixWithDot = prefix + ".";
72-
return Object.fromEntries(
73-
Object.entries(obj).map(([key, value]) => [prefixWithDot + key, value]),
74-
) as {
66+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [prefixWithDot + key, value])) as {
7567
[K in keyof Obj as `${Prefix}.${K & string}`]: Obj[K];
7668
};
7769
};
@@ -120,9 +112,7 @@ export const calculateCartTotalPossiblyWithTax = (cart: {
120112
return cart.cart.amount;
121113
}
122114

123-
return (
124-
(cart.shippingRate?.fixed_amount?.amount ?? 0) + calculateCartTotalNetWithoutShipping(cart)
125-
);
115+
return (cart.shippingRate?.fixed_amount?.amount ?? 0) + calculateCartTotalNetWithoutShipping(cart);
126116
};
127117

128118
export const calculateCartTotalNetWithoutShipping = (cart: {
@@ -176,11 +166,7 @@ export const getDecimalFromStripeAmount = ({ amount: minor, currency }: Money) =
176166
return Number.parseFloat((minor / multiplier).toFixed(decimals));
177167
};
178168

179-
export const formatMoney = ({
180-
amount: minor,
181-
currency,
182-
locale = "en-US",
183-
}: Money & { locale?: string }) => {
169+
export const formatMoney = ({ amount: minor, currency, locale = "en-US" }: Money & { locale?: string }) => {
184170
const amount = getDecimalFromStripeAmount({ amount: minor, currency });
185171
return new Intl.NumberFormat(locale, {
186172
style: "currency",

src/ui/checkout/cart-summary-table.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
"use client";
22

33
import { calculateCartTotalPossiblyWithTax, formatMoney, formatProductName } from "@/lib/utils";
4-
import {
5-
CartAmountWithSpinner,
6-
CartItemLineTotal,
7-
CartItemQuantity,
8-
} from "@/ui/checkout/cart-items.client";
4+
import { CartAmountWithSpinner, CartItemLineTotal, CartItemQuantity } from "@/ui/checkout/cart-items.client";
95
import { FormatDeliveryEstimate } from "@/ui/checkout/shipping-rates-section";
106
import {
117
Table,

src/ui/checkout/shipping-rates-section.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ export const FormatDeliveryEstimate = ({
9595
type i18n = ReturnType<typeof useTranslations<"Global.deliveryEstimates">>;
9696
const deliveryUnitToText = (
9797
value: number,
98-
unit:
99-
| Stripe.ShippingRate.DeliveryEstimate.Maximum.Unit
100-
| Stripe.ShippingRate.DeliveryEstimate.Minimum.Unit,
98+
unit: Stripe.ShippingRate.DeliveryEstimate.Maximum.Unit | Stripe.ShippingRate.DeliveryEstimate.Minimum.Unit,
10199
t: i18n,
102100
) => {
103101
switch (unit) {

src/ui/checkout/stripe-elements-container.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
import { env } from "@/env.mjs";
44
import { invariant } from "@/lib/invariant";
55
import { Elements } from "@stripe/react-stripe-js";
6-
import {
7-
type StripeElementLocale,
8-
type StripeElementsOptions,
9-
loadStripe,
10-
} from "@stripe/stripe-js";
6+
import { type StripeElementLocale, type StripeElementsOptions, loadStripe } from "@stripe/stripe-js";
117
import { useLocale } from "next-intl";
128
import { type ReactNode, useMemo } from "react";
139

0 commit comments

Comments
 (0)