@sayren/mcp 0.6.0 → 0.8.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/dist/index.mjs +6 -4
- package/package.json +3 -3
- package/template/.template-meta.json +2 -2
- package/template/README.md +32 -2
- package/template/package.json +6 -0
- package/template/src/components/fulfillment-codes.tsx +110 -0
- package/template/src/components/fulfillment-downloads.tsx +88 -0
- package/template/src/components/text-field.tsx +71 -0
- package/template/src/lib/catalog-queries.ts +47 -0
- package/template/src/lib/code-reveal.test.ts +30 -0
- package/template/src/lib/code-reveal.ts +32 -0
- package/template/src/lib/coupon-code.test.ts +19 -0
- package/template/src/lib/coupon-code.ts +60 -0
- package/template/src/lib/download.test.ts +41 -0
- package/template/src/lib/download.ts +41 -0
- package/template/src/lib/form-schemas.test.ts +144 -0
- package/template/src/lib/form-schemas.ts +131 -0
- package/template/src/lib/guest-order.test.ts +18 -0
- package/template/src/lib/guest-order.ts +13 -0
- package/template/src/lib/order-status.test.ts +29 -0
- package/template/src/lib/order-status.ts +28 -2
- package/template/src/lib/page-title.test.ts +19 -0
- package/template/src/lib/page-title.ts +23 -0
- package/template/src/lib/products-search.test.ts +25 -0
- package/template/src/lib/products-search.ts +49 -0
- package/template/src/router.tsx +26 -1
- package/template/src/routes/__root.tsx +13 -6
- package/template/src/routes/account.tsx +2 -1
- package/template/src/routes/auth.callback.ts +13 -9
- package/template/src/routes/cart.tsx +13 -6
- package/template/src/routes/checkout.complete.tsx +2 -1
- package/template/src/routes/checkout.index.tsx +441 -147
- package/template/src/routes/checkout.return.tsx +7 -1
- package/template/src/routes/guest-order.tsx +174 -72
- package/template/src/routes/login.tsx +2 -1
- package/template/src/routes/orders.$orderId.tsx +98 -3
- package/template/src/routes/orders.index.tsx +2 -1
- package/template/src/routes/products.$productId.tsx +4 -1
- package/template/src/routes/products.index.tsx +46 -47
- package/template/src/routes/signup.tsx +61 -90
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApiError } from "@sayren/storefront-sdk";
|
|
2
2
|
import { IdpCallbackError } from "@sayren/storefront-sdk/auth";
|
|
3
3
|
import { createFileRoute } from "@tanstack/react-router";
|
|
4
|
+
import queryString from "query-string";
|
|
4
5
|
import { authFor } from "../lib/api.server";
|
|
5
6
|
import { clearCartToken, readCartToken } from "../lib/cart-session.server";
|
|
6
7
|
import { readToken, signIn } from "../lib/session.server";
|
|
@@ -19,15 +20,20 @@ export const Route = createFileRoute("/auth/callback")({
|
|
|
19
20
|
const url = new URL(request.url);
|
|
20
21
|
const stored = readSocialLogin();
|
|
21
22
|
clearSocialLogin();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// 화면 밖(서버 라우트)의 주소는 query-string으로 만든다 — 값 인코딩을 손으로 하지 않는다
|
|
24
|
+
const go = (path: string, query?: Record<string, string>) =>
|
|
25
|
+
new Response(null, {
|
|
26
|
+
status: 302,
|
|
27
|
+
// 복귀 경로(`stored.redirectTo`)는 이미 완성된 주소라 그대로 쓴다
|
|
28
|
+
headers: { Location: query ? queryString.stringifyUrl({ url: path, query }) : path },
|
|
29
|
+
});
|
|
30
|
+
const fail = (reason: string) => go("/login", { error: reason });
|
|
25
31
|
if (!stored) return fail("state_mismatch");
|
|
26
32
|
|
|
27
33
|
// 계정 연결 — 로그인한 구매자에 공급자 계정을 붙이고 계정 화면으로 돌아간다
|
|
28
34
|
if (stored.linkProvider) {
|
|
29
35
|
const accessToken = readToken();
|
|
30
|
-
if (!accessToken) return go("/login
|
|
36
|
+
if (!accessToken) return go("/login", { redirectTo: "/account" });
|
|
31
37
|
try {
|
|
32
38
|
await authFor().linkIdpCallback(accessToken, stored.linkProvider, {
|
|
33
39
|
code: url.searchParams.get("code"),
|
|
@@ -36,12 +42,10 @@ export const Route = createFileRoute("/auth/callback")({
|
|
|
36
42
|
expectedState: stored.state,
|
|
37
43
|
codeVerifier: stored.codeVerifier,
|
|
38
44
|
});
|
|
39
|
-
return go(
|
|
45
|
+
return go("/account", { linked: stored.linkProvider });
|
|
40
46
|
} catch (error) {
|
|
41
|
-
if (error instanceof IdpCallbackError)
|
|
42
|
-
|
|
43
|
-
if (error instanceof ApiError)
|
|
44
|
-
return go(`/account?error=${encodeURIComponent(error.code)}`);
|
|
47
|
+
if (error instanceof IdpCallbackError) return go("/account", { error: error.reason });
|
|
48
|
+
if (error instanceof ApiError) return go("/account", { error: error.code });
|
|
45
49
|
throw error;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -7,6 +7,7 @@ import { SubmitButton } from "../components/submit-button";
|
|
|
7
7
|
import { apiFor } from "../lib/api.server";
|
|
8
8
|
import { readCartToken, writeCartToken } from "../lib/cart-session.server";
|
|
9
9
|
import { formatPrice } from "../lib/format";
|
|
10
|
+
import { pageTitle } from "../lib/page-title";
|
|
10
11
|
import { readToken } from "../lib/session.server";
|
|
11
12
|
|
|
12
13
|
const getCart = createServerFn({ method: "GET" }).handler(async () => {
|
|
@@ -15,7 +16,10 @@ const getCart = createServerFn({ method: "GET" }).handler(async () => {
|
|
|
15
16
|
// 비회원이 아직 장바구니를 만든 적이 없으면 서버에 물어볼 것이 없다
|
|
16
17
|
if (!accessToken && !cartToken) {
|
|
17
18
|
return {
|
|
18
|
-
cart: {
|
|
19
|
+
cart: {
|
|
20
|
+
items: [],
|
|
21
|
+
summary: { productAmount: 0, deliveryFee: 0, totalAmount: 0, requiresShipping: true },
|
|
22
|
+
},
|
|
19
23
|
loginRequired: false,
|
|
20
24
|
};
|
|
21
25
|
}
|
|
@@ -54,7 +58,7 @@ const changeCart = createServerFn({ method: "POST" })
|
|
|
54
58
|
|
|
55
59
|
export const Route = createFileRoute("/cart")({
|
|
56
60
|
loader: () => getCart(),
|
|
57
|
-
head: () => ({ meta: [{ title: "장바구니" }] }),
|
|
61
|
+
head: ({ matches }) => ({ meta: [{ title: pageTitle(matches, "장바구니") }] }),
|
|
58
62
|
component: CartPage,
|
|
59
63
|
});
|
|
60
64
|
|
|
@@ -164,10 +168,13 @@ function CartPage() {
|
|
|
164
168
|
<dt className="text-muted">상품 금액</dt>
|
|
165
169
|
<dd>{formatPrice(cart.summary.productAmount)}</dd>
|
|
166
170
|
</div>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
+
{/* 배송이 필요 없는 상품(이용권·방문 수령 등)만 담았으면 배송비가 없다 */}
|
|
172
|
+
{cart.summary.requiresShipping ? (
|
|
173
|
+
<div className="flex justify-between">
|
|
174
|
+
<dt className="text-muted">배송비</dt>
|
|
175
|
+
<dd>{formatPrice(cart.summary.deliveryFee)}</dd>
|
|
176
|
+
</div>
|
|
177
|
+
) : null}
|
|
171
178
|
<div className="flex justify-between border-line border-t pt-1 font-bold">
|
|
172
179
|
<dt>결제 금액</dt>
|
|
173
180
|
<dd>{formatPrice(cart.summary.totalAmount)}</dd>
|
|
@@ -3,6 +3,7 @@ import { createServerFn } from "@tanstack/react-start";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { apiFor } from "../lib/api.server";
|
|
5
5
|
import { formatPrice } from "../lib/format";
|
|
6
|
+
import { pageTitle } from "../lib/page-title";
|
|
6
7
|
import { readToken } from "../lib/session.server";
|
|
7
8
|
|
|
8
9
|
const completeSearch = z.object({ orderId: z.string().optional().catch(undefined) });
|
|
@@ -23,7 +24,7 @@ export const Route = createFileRoute("/checkout/complete")({
|
|
|
23
24
|
validateSearch: completeSearch,
|
|
24
25
|
loaderDeps: ({ search }) => search,
|
|
25
26
|
loader: ({ deps }) => getCompletedOrder({ data: deps }),
|
|
26
|
-
head: () => ({ meta: [{ title: "주문 완료" }] }),
|
|
27
|
+
head: ({ matches }) => ({ meta: [{ title: pageTitle(matches, "주문 완료") }] }),
|
|
27
28
|
component: CheckoutComplete,
|
|
28
29
|
});
|
|
29
30
|
|