@tanstack/create 0.64.0 → 0.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/dist/create-app.js +72 -16
- package/dist/file-helpers.js +14 -0
- package/dist/frameworks/react/add-ons/ai/info.json +1 -1
- package/dist/frameworks/react/add-ons/better-auth/info.json +1 -1
- package/dist/frameworks/react/add-ons/clerk/README.md +42 -1
- package/dist/frameworks/react/add-ons/clerk/assets/src/routes/demo/clerk.tsx +94 -11
- package/dist/frameworks/react/add-ons/clerk/info.json +1 -1
- package/dist/frameworks/react/add-ons/compiler/info.json +1 -1
- package/dist/frameworks/react/add-ons/convex/info.json +1 -1
- package/dist/frameworks/react/add-ons/drizzle/info.json +1 -1
- package/dist/frameworks/react/add-ons/mcp/info.json +1 -1
- package/dist/frameworks/react/add-ons/neon/info.json +1 -1
- package/dist/frameworks/react/add-ons/paraglide/info.json +1 -1
- package/dist/frameworks/react/add-ons/prisma/info.json +1 -1
- package/dist/frameworks/react/add-ons/shadcn/info.json +1 -1
- package/dist/frameworks/react/add-ons/shopify/README.md +86 -0
- package/dist/frameworks/react/add-ons/shopify/assets/_dot_env.local.append +19 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/account-nav.tsx.ejs +41 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx +48 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-line-item.tsx +94 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-summary.tsx +111 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/empty-state.tsx +29 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/money.tsx +11 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/product-card.tsx +74 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/product-grid.tsx +24 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/shop-image.tsx +57 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/shop.css +58 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/variant-selector.tsx +79 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/hooks/use-cart.ts +276 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/hooks/use-customer.ts.ejs +22 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/integrations/shopify/header-cart.tsx +37 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/customer-queries.ts.ejs +228 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/format.ts +33 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/queries.ts +684 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.addresses.tsx.ejs +67 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.callback.tsx.ejs +45 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.index.tsx.ejs +70 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.login.tsx.ejs +59 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.logout.tsx.ejs +16 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.$id.tsx.ejs +126 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.tsx.ejs +50 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.tsx.ejs +34 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.cart.tsx +45 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.collections.$handle.tsx +66 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.index.tsx +36 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.pages.$handle.tsx +39 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.policies.$handle.tsx +30 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.products.$handle.tsx +106 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.search.tsx +75 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.tsx +78 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/cart.functions.ts +207 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/catalog.functions.ts +244 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/cookies.ts +29 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-client.ts.ejs +99 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-cookies.ts.ejs +49 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer.functions.ts.ejs +168 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/env.ts +89 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/oauth.ts.ejs +301 -0
- package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/storefront-client.ts +101 -0
- package/dist/frameworks/react/add-ons/shopify/info.json +104 -0
- package/dist/frameworks/react/add-ons/shopify/package.json +6 -0
- package/dist/frameworks/react/add-ons/shopify/small-logo.svg +1 -0
- package/dist/frameworks/react/add-ons/strapi/info.json +1 -1
- package/dist/frameworks/react/add-ons/t3env/info.json +1 -1
- package/dist/frameworks/react/add-ons/workos/info.json +1 -1
- package/dist/frameworks/react/examples/shopify-storefront/README.md +39 -0
- package/dist/frameworks/react/examples/shopify-storefront/assets/src/components/FeaturedCollections.tsx +43 -0
- package/dist/frameworks/react/examples/shopify-storefront/assets/src/components/ShopHero.tsx +39 -0
- package/dist/frameworks/react/examples/shopify-storefront/assets/src/routes/index.tsx +65 -0
- package/dist/frameworks/react/examples/shopify-storefront/info.json +18 -0
- package/dist/frameworks/react/examples/shopify-storefront/package.json +3 -0
- package/dist/frameworks/react/hosts/cloudflare/README.md +11 -0
- package/dist/frameworks/react/hosts/cloudflare/info.json +1 -1
- package/dist/frameworks/react/hosts/netlify/README.md +11 -0
- package/dist/frameworks/react/hosts/netlify/info.json +1 -1
- package/dist/frameworks/react/hosts/nitro/README.md +12 -0
- package/dist/frameworks/react/hosts/nitro/info.json +1 -1
- package/dist/frameworks/react/hosts/railway/README.md +10 -0
- package/dist/frameworks/react/hosts/railway/info.json +1 -1
- package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +34 -34
- package/dist/frameworks/solid/add-ons/better-auth/info.json +1 -1
- package/dist/frameworks/solid/add-ons/convex/info.json +1 -1
- package/dist/frameworks/solid/add-ons/solid-ui/info.json +1 -1
- package/dist/frameworks/solid/add-ons/strapi/info.json +1 -1
- package/dist/frameworks/solid/add-ons/t3env/info.json +1 -1
- package/dist/frameworks/solid/hosts/cloudflare/README.md +11 -0
- package/dist/frameworks/solid/hosts/cloudflare/info.json +1 -1
- package/dist/frameworks/solid/hosts/netlify/README.md +11 -0
- package/dist/frameworks/solid/hosts/netlify/info.json +1 -1
- package/dist/frameworks/solid/hosts/nitro/README.md +12 -0
- package/dist/frameworks/solid/hosts/nitro/info.json +1 -1
- package/dist/frameworks/solid/hosts/railway/README.md +10 -0
- package/dist/frameworks/solid/hosts/railway/info.json +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/intent.js +1 -1
- package/dist/types/file-helpers.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/create-app.ts +86 -22
- package/src/file-helpers.ts +20 -0
- package/src/frameworks/react/add-ons/ai/info.json +1 -1
- package/src/frameworks/react/add-ons/better-auth/info.json +1 -1
- package/src/frameworks/react/add-ons/clerk/README.md +42 -1
- package/src/frameworks/react/add-ons/clerk/assets/src/routes/demo/clerk.tsx +94 -11
- package/src/frameworks/react/add-ons/clerk/info.json +1 -1
- package/src/frameworks/react/add-ons/compiler/info.json +1 -1
- package/src/frameworks/react/add-ons/convex/info.json +1 -1
- package/src/frameworks/react/add-ons/drizzle/info.json +1 -1
- package/src/frameworks/react/add-ons/mcp/info.json +1 -1
- package/src/frameworks/react/add-ons/neon/info.json +1 -1
- package/src/frameworks/react/add-ons/paraglide/info.json +1 -1
- package/src/frameworks/react/add-ons/prisma/info.json +1 -1
- package/src/frameworks/react/add-ons/shadcn/info.json +1 -1
- package/src/frameworks/react/add-ons/shopify/README.md +86 -0
- package/src/frameworks/react/add-ons/shopify/assets/_dot_env.local.append +19 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/account-nav.tsx.ejs +41 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx +48 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-line-item.tsx +94 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-summary.tsx +111 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/empty-state.tsx +29 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/money.tsx +11 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/product-card.tsx +74 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/product-grid.tsx +24 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/shop-image.tsx +57 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/shop.css +58 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/variant-selector.tsx +79 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/hooks/use-cart.ts +276 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/hooks/use-customer.ts.ejs +22 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/integrations/shopify/header-cart.tsx +37 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/customer-queries.ts.ejs +228 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/format.ts +33 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/queries.ts +684 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.addresses.tsx.ejs +67 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.callback.tsx.ejs +45 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.index.tsx.ejs +70 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.login.tsx.ejs +59 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.logout.tsx.ejs +16 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.$id.tsx.ejs +126 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.tsx.ejs +50 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.tsx.ejs +34 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.cart.tsx +45 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.collections.$handle.tsx +66 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.index.tsx +36 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.pages.$handle.tsx +39 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.policies.$handle.tsx +30 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.products.$handle.tsx +106 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.search.tsx +75 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.tsx +78 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/cart.functions.ts +207 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/catalog.functions.ts +244 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/cookies.ts +29 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-client.ts.ejs +99 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-cookies.ts.ejs +49 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer.functions.ts.ejs +168 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/env.ts +89 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/oauth.ts.ejs +301 -0
- package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/storefront-client.ts +101 -0
- package/src/frameworks/react/add-ons/shopify/info.json +104 -0
- package/src/frameworks/react/add-ons/shopify/package.json +6 -0
- package/src/frameworks/react/add-ons/shopify/small-logo.svg +1 -0
- package/src/frameworks/react/add-ons/strapi/info.json +1 -1
- package/src/frameworks/react/add-ons/t3env/info.json +1 -1
- package/src/frameworks/react/add-ons/workos/info.json +1 -1
- package/src/frameworks/react/examples/shopify-storefront/README.md +39 -0
- package/src/frameworks/react/examples/shopify-storefront/assets/src/components/FeaturedCollections.tsx +43 -0
- package/src/frameworks/react/examples/shopify-storefront/assets/src/components/ShopHero.tsx +39 -0
- package/src/frameworks/react/examples/shopify-storefront/assets/src/routes/index.tsx +65 -0
- package/src/frameworks/react/examples/shopify-storefront/info.json +18 -0
- package/src/frameworks/react/examples/shopify-storefront/package.json +3 -0
- package/src/frameworks/react/hosts/cloudflare/README.md +11 -0
- package/src/frameworks/react/hosts/cloudflare/info.json +1 -1
- package/src/frameworks/react/hosts/netlify/README.md +11 -0
- package/src/frameworks/react/hosts/netlify/info.json +1 -1
- package/src/frameworks/react/hosts/nitro/README.md +12 -0
- package/src/frameworks/react/hosts/nitro/info.json +1 -1
- package/src/frameworks/react/hosts/railway/README.md +10 -0
- package/src/frameworks/react/hosts/railway/info.json +1 -1
- package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +34 -34
- package/src/frameworks/solid/add-ons/better-auth/info.json +1 -1
- package/src/frameworks/solid/add-ons/convex/info.json +1 -1
- package/src/frameworks/solid/add-ons/solid-ui/info.json +1 -1
- package/src/frameworks/solid/add-ons/strapi/info.json +1 -1
- package/src/frameworks/solid/add-ons/t3env/info.json +1 -1
- package/src/frameworks/solid/hosts/cloudflare/README.md +11 -0
- package/src/frameworks/solid/hosts/cloudflare/info.json +1 -1
- package/src/frameworks/solid/hosts/netlify/README.md +11 -0
- package/src/frameworks/solid/hosts/netlify/info.json +1 -1
- package/src/frameworks/solid/hosts/nitro/README.md +12 -0
- package/src/frameworks/solid/hosts/nitro/info.json +1 -1
- package/src/frameworks/solid/hosts/railway/README.md +10 -0
- package/src/frameworks/solid/hosts/railway/info.json +1 -1
- package/src/index.ts +1 -0
- package/src/integrations/intent.ts +1 -1
- package/tests/create-app.test.ts +95 -0
|
@@ -1,3 +1,44 @@
|
|
|
1
1
|
## Setting up Clerk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
1. Sign up at [clerk.com](https://clerk.com) and create an application
|
|
4
|
+
2. Copy the **Publishable Key** from the Clerk dashboard
|
|
5
|
+
3. Set it in your `.env.local`:
|
|
6
|
+
```bash
|
|
7
|
+
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
|
|
8
|
+
```
|
|
9
|
+
4. Visit the demo route at `/demo/clerk` once `npm run dev` is running
|
|
10
|
+
|
|
11
|
+
### What's wired up
|
|
12
|
+
|
|
13
|
+
- **`<ClerkProvider>`** at the app root (`src/integrations/clerk/provider.tsx`) handles auth context for the whole tree
|
|
14
|
+
- **`<SignInButton>` / `<UserButton>`** in the header swap based on auth state
|
|
15
|
+
- **`/demo/clerk`** shows Clerk's prebuilt sign-in UI and a signed-in greeting
|
|
16
|
+
|
|
17
|
+
### Protecting a route
|
|
18
|
+
|
|
19
|
+
Wrap any component in `<SignedIn>` / `<SignedOut>`:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { SignedIn, SignedOut, RedirectToSignIn } from '@clerk/clerk-react'
|
|
23
|
+
|
|
24
|
+
function ProtectedPage() {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<SignedIn>
|
|
28
|
+
<YourPageContent />
|
|
29
|
+
</SignedIn>
|
|
30
|
+
<SignedOut>
|
|
31
|
+
<RedirectToSignIn />
|
|
32
|
+
</SignedOut>
|
|
33
|
+
</>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For server-side checks (route loaders, server functions), see the Clerk docs on [`auth()`](https://clerk.com/docs/references/backend/auth).
|
|
39
|
+
|
|
40
|
+
### Production checklist
|
|
41
|
+
|
|
42
|
+
- Replace the test keys with **production keys** from a dedicated production Clerk instance
|
|
43
|
+
- Configure your production domain under **Domains** in the Clerk dashboard
|
|
44
|
+
- Set up social providers (Google, GitHub, etc.) under **User & Authentication → Social Connections**
|
|
@@ -1,20 +1,103 @@
|
|
|
1
1
|
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
SignIn,
|
|
4
|
+
SignedIn,
|
|
5
|
+
SignedOut,
|
|
6
|
+
useUser,
|
|
7
|
+
} from '@clerk/clerk-react'
|
|
3
8
|
|
|
4
9
|
export const Route = createFileRoute('/demo/clerk')({
|
|
5
|
-
component:
|
|
10
|
+
component: ClerkDemo,
|
|
6
11
|
})
|
|
7
12
|
|
|
8
|
-
function
|
|
9
|
-
|
|
13
|
+
function ClerkDemo() {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex justify-center py-10 px-4">
|
|
16
|
+
<div className="w-full max-w-md p-6 space-y-6">
|
|
17
|
+
<SignedOut>
|
|
18
|
+
<div className="space-y-1.5">
|
|
19
|
+
<h1 className="text-lg font-semibold leading-none tracking-tight">
|
|
20
|
+
Sign in to continue
|
|
21
|
+
</h1>
|
|
22
|
+
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
|
23
|
+
Clerk renders the sign-in UI, manages sessions, and handles social providers for you.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
<div className="flex justify-center pt-2">
|
|
27
|
+
<SignIn routing="hash" />
|
|
28
|
+
</div>
|
|
29
|
+
<p className="text-xs text-center text-neutral-400 dark:text-neutral-500">
|
|
30
|
+
Built with{' '}
|
|
31
|
+
<a
|
|
32
|
+
href="https://clerk.com"
|
|
33
|
+
target="_blank"
|
|
34
|
+
rel="noopener noreferrer"
|
|
35
|
+
className="font-medium hover:text-neutral-600 dark:hover:text-neutral-300"
|
|
36
|
+
>
|
|
37
|
+
CLERK
|
|
38
|
+
</a>
|
|
39
|
+
.
|
|
40
|
+
</p>
|
|
41
|
+
</SignedOut>
|
|
10
42
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
43
|
+
<SignedIn>
|
|
44
|
+
<SignedInGreeting />
|
|
45
|
+
</SignedIn>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function SignedInGreeting() {
|
|
52
|
+
const { user } = useUser()
|
|
53
|
+
if (!user) return null
|
|
54
|
+
|
|
55
|
+
const email = user.primaryEmailAddress?.emailAddress
|
|
56
|
+
const initial = (user.firstName || email || 'U').charAt(0).toUpperCase()
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-6">
|
|
60
|
+
<div className="space-y-1.5">
|
|
61
|
+
<h1 className="text-lg font-semibold leading-none tracking-tight">
|
|
62
|
+
Welcome back
|
|
63
|
+
</h1>
|
|
64
|
+
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
|
65
|
+
You're signed in as {email}
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
14
68
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
69
|
+
<div className="flex items-center gap-3">
|
|
70
|
+
{user.imageUrl ? (
|
|
71
|
+
<img src={user.imageUrl} alt="" className="h-10 w-10 rounded-full" />
|
|
72
|
+
) : (
|
|
73
|
+
<div className="h-10 w-10 bg-neutral-200 dark:bg-neutral-800 flex items-center justify-center rounded-full">
|
|
74
|
+
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
|
75
|
+
{initial}
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
<div className="flex-1 min-w-0">
|
|
80
|
+
<p className="text-sm font-medium truncate">
|
|
81
|
+
{user.firstName} {user.lastName}
|
|
82
|
+
</p>
|
|
83
|
+
<p className="text-xs text-neutral-500 dark:text-neutral-400 truncate">
|
|
84
|
+
{email}
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
18
88
|
|
|
19
|
-
|
|
89
|
+
<p className="text-xs text-center text-neutral-400 dark:text-neutral-500">
|
|
90
|
+
Manage your account from the avatar in the header. Built with{' '}
|
|
91
|
+
<a
|
|
92
|
+
href="https://clerk.com"
|
|
93
|
+
target="_blank"
|
|
94
|
+
rel="noopener noreferrer"
|
|
95
|
+
className="font-medium hover:text-neutral-600 dark:hover:text-neutral-300"
|
|
96
|
+
>
|
|
97
|
+
CLERK
|
|
98
|
+
</a>
|
|
99
|
+
.
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
20
103
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Compiler",
|
|
3
3
|
"phase": "setup",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Auto-memoize components and hooks (fewer re-renders, no manual useMemo/useCallback).",
|
|
5
5
|
"link": "https://react.dev/learn/react-compiler",
|
|
6
6
|
"modes": ["code-router", "file-router"],
|
|
7
7
|
"type": "add-on",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "MCP",
|
|
3
3
|
"phase": "setup",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Expose your app as an MCP server so AI clients (Claude, Cursor) can call into it.",
|
|
5
5
|
"link": "https://mcp.dev",
|
|
6
6
|
"modes": ["file-router"],
|
|
7
7
|
"type": "add-on",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Prisma",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "Type-safe database client with schema migrations (Postgres, MySQL, SQLite, MongoDB).",
|
|
4
4
|
"phase": "add-on",
|
|
5
5
|
"type": "add-on",
|
|
6
6
|
"category": "orm",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Shopify
|
|
2
|
+
|
|
3
|
+
Headless Shopify storefront for TanStack Start. Mounts `/shop/*` routes
|
|
4
|
+
alongside your existing app — your home page stays untouched.
|
|
5
|
+
|
|
6
|
+
The default `.env.local` points at Shopify's public Hydrogen demo store, so the
|
|
7
|
+
storefront renders real products on first run with zero setup.
|
|
8
|
+
|
|
9
|
+
## Routes
|
|
10
|
+
|
|
11
|
+
| Route | What it does |
|
|
12
|
+
|--------------------------------|---------------------------------------------|
|
|
13
|
+
| `/shop` | Shop landing — featured products + collections |
|
|
14
|
+
| `/shop/products/$handle` | Product detail (variants, images, options) |
|
|
15
|
+
| `/shop/collections/$handle` | Collection grid with sort + pagination |
|
|
16
|
+
| `/shop/cart` | Cart line items, discount codes, checkout |
|
|
17
|
+
| `/shop/search` | Product search |
|
|
18
|
+
| `/shop/pages/$handle` | Shopify CMS pages (about, etc.) |
|
|
19
|
+
| `/shop/policies/$handle` | Privacy, refund, terms, shipping |
|
|
20
|
+
|
|
21
|
+
If you opted into customer accounts during scaffold:
|
|
22
|
+
|
|
23
|
+
| Route | What it does |
|
|
24
|
+
|--------------------------------------|------------------------------------|
|
|
25
|
+
| `/shop/account/login` | Kick off Shopify OAuth |
|
|
26
|
+
| `/shop/account/callback` | OAuth callback handler |
|
|
27
|
+
| `/shop/account/logout` | End the customer session |
|
|
28
|
+
| `/shop/account` | Dashboard |
|
|
29
|
+
| `/shop/account/orders` | Order history |
|
|
30
|
+
| `/shop/account/orders/$id` | Order detail |
|
|
31
|
+
| `/shop/account/addresses` | Manage saved addresses |
|
|
32
|
+
|
|
33
|
+
## Connect your store
|
|
34
|
+
|
|
35
|
+
1. In Shopify admin, go to **Settings > Apps and sales channels > Develop apps**.
|
|
36
|
+
2. Create a new app, enable the **Storefront API**, and copy the public access token.
|
|
37
|
+
3. Set in `.env.local`:
|
|
38
|
+
```
|
|
39
|
+
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
|
|
40
|
+
SHOPIFY_PUBLIC_STOREFRONT_TOKEN=...
|
|
41
|
+
```
|
|
42
|
+
4. (Optional) For higher rate limits + buyer-IP forwarding, also create a private
|
|
43
|
+
token and set `SHOPIFY_PRIVATE_STOREFRONT_TOKEN`.
|
|
44
|
+
|
|
45
|
+
## Enable customer accounts
|
|
46
|
+
|
|
47
|
+
If `customerAccount=enabled` was selected during scaffold:
|
|
48
|
+
|
|
49
|
+
1. In Shopify admin, go to **Settings > Customer accounts > Headless**.
|
|
50
|
+
2. Register a public client. Add `http://localhost:3000/shop/account/callback`
|
|
51
|
+
*and* your production callback URL to the redirect URIs.
|
|
52
|
+
3. Copy the Client ID and Shop ID into `.env.local`:
|
|
53
|
+
```
|
|
54
|
+
SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_ID=...
|
|
55
|
+
SHOPIFY_CUSTOMER_ACCOUNT_SHOP_ID=...
|
|
56
|
+
SHOPIFY_SESSION_SECRET=$(openssl rand -hex 32)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The Hydrogen demo store doesn't have customer accounts configured, so the
|
|
60
|
+
default demo creds won't work for `/shop/account/*` — you'll need a real store.
|
|
61
|
+
|
|
62
|
+
## Architecture
|
|
63
|
+
|
|
64
|
+
- **Storefront API client** — server-only fetch in `src/server/shopify/storefront-client.ts`.
|
|
65
|
+
All product/cart reads go through the server (private token never reaches the browser).
|
|
66
|
+
- **Cart state** — Cart ID stored in an httpOnly cookie (`tanstack_cart_id`). React
|
|
67
|
+
Query owns the cache (single key `['shopify', 'cart']`); optimistic updates with
|
|
68
|
+
a module-level mutation counter to batch invalidations.
|
|
69
|
+
- **GraphQL queries** — hand-written strings in `src/lib/shopify/queries.ts`, types
|
|
70
|
+
sliced from `@shopify/hydrogen-react/storefront-api-types` (type-only import; zero runtime).
|
|
71
|
+
- **Customer accounts** — hand-rolled OAuth 2.1 PKCE with `.well-known` discovery
|
|
72
|
+
(no usable npm client exists yet). Tokens in a signed httpOnly cookie.
|
|
73
|
+
- **Checkout** — redirects to `cart.checkoutUrl` (Shopify-hosted).
|
|
74
|
+
|
|
75
|
+
## Deployment
|
|
76
|
+
|
|
77
|
+
Works anywhere TanStack Start runs:
|
|
78
|
+
|
|
79
|
+
- **Node** — `npm run build && npm start`
|
|
80
|
+
- **Cloudflare Workers / Shopify Oxygen** — Oxygen is just Workers under the hood;
|
|
81
|
+
build with the Workers preset and deploy to either platform.
|
|
82
|
+
- **Vercel / Netlify** — set the env vars in the dashboard.
|
|
83
|
+
- **Bun, Deno** — supported via Start's adapters.
|
|
84
|
+
|
|
85
|
+
For the customer-account flow, register both your local *and* production
|
|
86
|
+
callback URLs in the Shopify admin's headless app config.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ─── Shopify ─────────────────────────────────────────────────────────────────
|
|
2
|
+
# These defaults point to Shopify's public Hydrogen demo store so the storefront
|
|
3
|
+
# works on first run with zero config. Replace with your store's values to go live.
|
|
4
|
+
# Get tokens at: Shopify admin > Settings > Apps and sales channels > Develop apps.
|
|
5
|
+
SHOPIFY_STORE_DOMAIN=hydrogen-preview.myshopify.com
|
|
6
|
+
SHOPIFY_STOREFRONT_API_VERSION=2026-01
|
|
7
|
+
SHOPIFY_PUBLIC_STOREFRONT_TOKEN=3b580e70970c4528da70c98e097c2fa0
|
|
8
|
+
# Optional — set to a private storefront token for higher rate limits + buyer-IP forwarding.
|
|
9
|
+
SHOPIFY_PRIVATE_STOREFRONT_TOKEN=
|
|
10
|
+
|
|
11
|
+
# ─── Shopify Customer Account API ────────────────────────────────────────────
|
|
12
|
+
# Required only if you opted into customer accounts during scaffold.
|
|
13
|
+
# Register a public client in Shopify admin > Settings > Customer accounts > Headless,
|
|
14
|
+
# then paste the client ID and your numeric shop ID below.
|
|
15
|
+
SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_ID=
|
|
16
|
+
SHOPIFY_CUSTOMER_ACCOUNT_SHOP_ID=
|
|
17
|
+
SHOPIFY_CUSTOMER_ACCOUNT_REDIRECT_URI=http://localhost:3000/shop/account/callback
|
|
18
|
+
# 32+ random chars, e.g. `openssl rand -hex 32`. Used to sign customer session cookies.
|
|
19
|
+
SHOPIFY_SESSION_SECRET=
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<% if (addOnOption.shopify.customerAccount !== 'enabled') { ignoreFile(); return; } %>
|
|
2
|
+
import { Link } from '@tanstack/react-router'
|
|
3
|
+
|
|
4
|
+
const ITEMS = [
|
|
5
|
+
{ to: '/shop/account', label: 'Overview', exact: true },
|
|
6
|
+
{ to: '/shop/account/orders', label: 'Orders', exact: false },
|
|
7
|
+
{ to: '/shop/account/addresses', label: 'Addresses', exact: false },
|
|
8
|
+
] as const
|
|
9
|
+
|
|
10
|
+
export function AccountNav() {
|
|
11
|
+
return (
|
|
12
|
+
<nav className="flex flex-col gap-1 text-sm">
|
|
13
|
+
{ITEMS.map((item) => (
|
|
14
|
+
<Link
|
|
15
|
+
key={item.to}
|
|
16
|
+
to={item.to}
|
|
17
|
+
activeOptions={{ exact: item.exact }}
|
|
18
|
+
className="rounded-md px-2 py-1.5 text-[var(--storefront-fg-muted)] hover:bg-[var(--storefront-line)]/40 hover:text-[var(--storefront-fg)]"
|
|
19
|
+
activeProps={{
|
|
20
|
+
className:
|
|
21
|
+
'rounded-md px-2 py-1.5 bg-[var(--storefront-line)]/60 text-[var(--storefront-fg)] font-medium',
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
{item.label}
|
|
25
|
+
</Link>
|
|
26
|
+
))}
|
|
27
|
+
<form
|
|
28
|
+
action="/shop/account/logout"
|
|
29
|
+
method="post"
|
|
30
|
+
className="mt-4 border-t border-[var(--storefront-line)] pt-4"
|
|
31
|
+
>
|
|
32
|
+
<button
|
|
33
|
+
type="submit"
|
|
34
|
+
className="rounded-md px-2 py-1.5 text-left text-sm text-[var(--storefront-fg-muted)] hover:text-[var(--storefront-fg)]"
|
|
35
|
+
>
|
|
36
|
+
Sign out
|
|
37
|
+
</button>
|
|
38
|
+
</form>
|
|
39
|
+
</nav>
|
|
40
|
+
)
|
|
41
|
+
}
|
package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useAddToCart } from '#/hooks/use-cart'
|
|
2
|
+
import type { ProductDetail, ProductDetailVariant } from '#/lib/shopify/queries'
|
|
3
|
+
|
|
4
|
+
type AddToCartButtonProps = {
|
|
5
|
+
product: ProductDetail
|
|
6
|
+
variant: ProductDetailVariant | undefined
|
|
7
|
+
quantity?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function AddToCartButton({
|
|
11
|
+
product,
|
|
12
|
+
variant,
|
|
13
|
+
quantity = 1,
|
|
14
|
+
}: AddToCartButtonProps) {
|
|
15
|
+
const { mutate, isPending } = useAddToCart()
|
|
16
|
+
const disabled = !variant || !variant.availableForSale || isPending
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
disabled={disabled}
|
|
22
|
+
onClick={() => {
|
|
23
|
+
if (!variant) return
|
|
24
|
+
mutate({
|
|
25
|
+
variantId: variant.id,
|
|
26
|
+
quantity,
|
|
27
|
+
line: {
|
|
28
|
+
productTitle: product.title,
|
|
29
|
+
productHandle: product.handle,
|
|
30
|
+
variantTitle: variant.title,
|
|
31
|
+
price: variant.price,
|
|
32
|
+
image: variant.image ?? product.images.nodes[0] ?? null,
|
|
33
|
+
selectedOptions: variant.selectedOptions,
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
}}
|
|
37
|
+
className="w-full rounded-full bg-[var(--storefront-accent)] px-6 py-3.5 text-sm font-medium text-[var(--storefront-accent-fg)] transition disabled:cursor-not-allowed disabled:opacity-50 hover:opacity-90"
|
|
38
|
+
>
|
|
39
|
+
{!variant
|
|
40
|
+
? 'Select options'
|
|
41
|
+
: !variant.availableForSale
|
|
42
|
+
? 'Sold out'
|
|
43
|
+
: isPending
|
|
44
|
+
? 'Adding…'
|
|
45
|
+
: 'Add to cart'}
|
|
46
|
+
</button>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { Money } from '#/components/shop/money'
|
|
4
|
+
import { ShopImage } from '#/components/shop/shop-image'
|
|
5
|
+
import { useRemoveCartLine, useUpdateCartLine } from '#/hooks/use-cart'
|
|
6
|
+
import type { CartLineDetail } from '#/lib/shopify/queries'
|
|
7
|
+
|
|
8
|
+
type CartLineItemProps = {
|
|
9
|
+
line: CartLineDetail
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CartLineItem({ line }: CartLineItemProps) {
|
|
13
|
+
const update = useUpdateCartLine()
|
|
14
|
+
const remove = useRemoveCartLine()
|
|
15
|
+
const merch = line.merchandise
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<li className="flex gap-4 border-b border-[var(--storefront-line)] py-6">
|
|
19
|
+
<Link
|
|
20
|
+
to="/shop/products/$handle"
|
|
21
|
+
params={{ handle: merch.product.handle }}
|
|
22
|
+
className="flex-shrink-0"
|
|
23
|
+
>
|
|
24
|
+
<ShopImage
|
|
25
|
+
src={merch.image?.url}
|
|
26
|
+
alt={merch.image?.altText ?? merch.product.title}
|
|
27
|
+
width={120}
|
|
28
|
+
height={150}
|
|
29
|
+
className="h-[150px] w-[120px] rounded-md object-cover"
|
|
30
|
+
/>
|
|
31
|
+
</Link>
|
|
32
|
+
<div className="flex flex-1 flex-col gap-2">
|
|
33
|
+
<div className="flex items-start justify-between gap-4">
|
|
34
|
+
<div>
|
|
35
|
+
<Link
|
|
36
|
+
to="/shop/products/$handle"
|
|
37
|
+
params={{ handle: merch.product.handle }}
|
|
38
|
+
className="text-base font-medium no-underline text-[var(--storefront-fg)]"
|
|
39
|
+
>
|
|
40
|
+
{merch.product.title}
|
|
41
|
+
</Link>
|
|
42
|
+
{merch.title && merch.title !== 'Default Title' && (
|
|
43
|
+
<p className="text-sm text-[var(--storefront-fg-muted)]">
|
|
44
|
+
{merch.selectedOptions.map((o) => o.value).join(' · ')}
|
|
45
|
+
</p>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
<Money
|
|
49
|
+
amount={line.cost.totalAmount.amount}
|
|
50
|
+
currencyCode={line.cost.totalAmount.currencyCode}
|
|
51
|
+
className="text-base font-medium"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="mt-auto flex items-center justify-between gap-4">
|
|
55
|
+
<div className="inline-flex items-center rounded-full border border-[var(--storefront-line)]">
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
aria-label="Decrease quantity"
|
|
59
|
+
disabled={update.isPending || line.quantity <= 1}
|
|
60
|
+
onClick={() =>
|
|
61
|
+
update.mutate({ lineId: line.id, quantity: line.quantity - 1 })
|
|
62
|
+
}
|
|
63
|
+
className="px-3 py-1.5 text-base disabled:opacity-40"
|
|
64
|
+
>
|
|
65
|
+
−
|
|
66
|
+
</button>
|
|
67
|
+
<span className="min-w-[2rem] text-center text-sm">
|
|
68
|
+
{line.quantity}
|
|
69
|
+
</span>
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
aria-label="Increase quantity"
|
|
73
|
+
disabled={update.isPending}
|
|
74
|
+
onClick={() =>
|
|
75
|
+
update.mutate({ lineId: line.id, quantity: line.quantity + 1 })
|
|
76
|
+
}
|
|
77
|
+
className="px-3 py-1.5 text-base disabled:opacity-40"
|
|
78
|
+
>
|
|
79
|
+
+
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
onClick={() => remove.mutate({ lineId: line.id })}
|
|
85
|
+
disabled={remove.isPending}
|
|
86
|
+
className="text-sm text-[var(--storefront-fg-muted)] underline underline-offset-4 hover:text-[var(--storefront-fg)] disabled:opacity-40"
|
|
87
|
+
>
|
|
88
|
+
Remove
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</li>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Money } from '#/components/shop/money'
|
|
4
|
+
import {
|
|
5
|
+
useApplyDiscountCode,
|
|
6
|
+
useRemoveDiscountCode,
|
|
7
|
+
} from '#/hooks/use-cart'
|
|
8
|
+
import type { CartDetail } from '#/lib/shopify/queries'
|
|
9
|
+
|
|
10
|
+
type CartSummaryProps = {
|
|
11
|
+
cart: CartDetail
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function CartSummary({ cart }: CartSummaryProps) {
|
|
15
|
+
const [code, setCode] = useState('')
|
|
16
|
+
const apply = useApplyDiscountCode()
|
|
17
|
+
const remove = useRemoveDiscountCode()
|
|
18
|
+
const appliedCode = cart.discountCodes.find((c) => c.applicable)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<aside className="flex flex-col gap-4 rounded-2xl border border-[var(--storefront-line)] p-6">
|
|
22
|
+
<h2 className="text-lg font-medium">Order summary</h2>
|
|
23
|
+
|
|
24
|
+
<div className="flex justify-between text-sm">
|
|
25
|
+
<span>Subtotal</span>
|
|
26
|
+
<Money
|
|
27
|
+
amount={cart.cost.subtotalAmount.amount}
|
|
28
|
+
currencyCode={cart.cost.subtotalAmount.currencyCode}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
{cart.cost.totalTaxAmount && Number(cart.cost.totalTaxAmount.amount) > 0 && (
|
|
32
|
+
<div className="flex justify-between text-sm text-[var(--storefront-fg-muted)]">
|
|
33
|
+
<span>Estimated tax</span>
|
|
34
|
+
<Money
|
|
35
|
+
amount={cart.cost.totalTaxAmount.amount}
|
|
36
|
+
currencyCode={cart.cost.totalTaxAmount.currencyCode}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
<p className="text-sm text-[var(--storefront-fg-muted)]">
|
|
41
|
+
Shipping calculated at checkout.
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<form
|
|
45
|
+
onSubmit={(e) => {
|
|
46
|
+
e.preventDefault()
|
|
47
|
+
if (code.trim()) {
|
|
48
|
+
apply.mutate(
|
|
49
|
+
{ code: code.trim() },
|
|
50
|
+
{ onSuccess: () => setCode('') },
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
className="flex flex-col gap-2"
|
|
55
|
+
>
|
|
56
|
+
<div className="flex gap-2">
|
|
57
|
+
<input
|
|
58
|
+
type="text"
|
|
59
|
+
value={code}
|
|
60
|
+
onChange={(e) => setCode(e.target.value)}
|
|
61
|
+
placeholder="Discount code"
|
|
62
|
+
className="min-w-0 flex-1 rounded-full border border-[var(--storefront-line)] bg-transparent px-4 py-2 text-sm focus:border-[var(--storefront-accent)] focus:outline-none"
|
|
63
|
+
/>
|
|
64
|
+
<button
|
|
65
|
+
type="submit"
|
|
66
|
+
disabled={apply.isPending || !code.trim()}
|
|
67
|
+
className="rounded-full border border-[var(--storefront-line)] px-4 py-2 text-sm font-medium hover:border-[var(--storefront-accent)] disabled:opacity-40"
|
|
68
|
+
>
|
|
69
|
+
Apply
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
{apply.error && (
|
|
73
|
+
<p className="text-xs text-red-600">{(apply.error as Error).message}</p>
|
|
74
|
+
)}
|
|
75
|
+
{appliedCode && (
|
|
76
|
+
<div className="flex items-center justify-between rounded-full bg-[var(--storefront-line)]/40 px-4 py-2 text-xs">
|
|
77
|
+
<span>
|
|
78
|
+
Applied: <strong>{appliedCode.code}</strong>
|
|
79
|
+
</span>
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onClick={() => remove.mutate()}
|
|
83
|
+
disabled={remove.isPending}
|
|
84
|
+
className="underline underline-offset-2 disabled:opacity-40"
|
|
85
|
+
>
|
|
86
|
+
Remove
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</form>
|
|
91
|
+
|
|
92
|
+
<div className="flex justify-between border-t border-[var(--storefront-line)] pt-4 text-base font-medium">
|
|
93
|
+
<span>Total</span>
|
|
94
|
+
<Money
|
|
95
|
+
amount={cart.cost.totalAmount.amount}
|
|
96
|
+
currencyCode={cart.cost.totalAmount.currencyCode}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<a
|
|
101
|
+
href={cart.checkoutUrl}
|
|
102
|
+
className="block w-full rounded-full bg-[var(--storefront-accent)] px-6 py-3.5 text-center text-sm font-medium text-[var(--storefront-accent-fg)] no-underline transition hover:opacity-90"
|
|
103
|
+
>
|
|
104
|
+
Checkout
|
|
105
|
+
</a>
|
|
106
|
+
<p className="text-center text-xs text-[var(--storefront-fg-muted)]">
|
|
107
|
+
Secure checkout powered by Shopify
|
|
108
|
+
</p>
|
|
109
|
+
</aside>
|
|
110
|
+
)
|
|
111
|
+
}
|