medusa-gift-wrap-logic 1.0.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/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Medusa Gift Wrap Logic
2
+
3
+ A reusable gift-wrap hook and service for Medusa storefronts. This package provides the logic necessary to manage per-line-item gift metadata without any UI dependencies.
4
+
5
+ ## Features
6
+
7
+ - **Per-Line-Item Gift Metadata**: Easily apply gift metadata (`is_gift`, `gift_from`, `gift_to`, `gift_message`) directly to line items in the cart.
8
+ - **Headless & UI Agnostic**: Contains only the business logic and hooks. You can build your own UI components using these hooks.
9
+ - **Medusa Compatible**: Works seamlessly with `@medusajs/js-sdk` and `@medusajs/types`.
10
+ - **TypeScript Ready**: Written in TypeScript and provides complete type definitions.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install medusa-gift-wrap-logic
16
+ ```
17
+
18
+ ## Setup
19
+
20
+ First, make sure you have the required peer dependencies installed in your project:
21
+
22
+ ```bash
23
+ npm install react react-dom
24
+ ```
25
+
26
+ ## Basic Usage
27
+
28
+ The package exports a custom React hook that you can use in your React storefront or Next.js app to handle gift-wrapping logic for any product inside the cart.
29
+
30
+ ```tsx
31
+ import { useGiftWrap } from 'medusa-gift-wrap-logic';
32
+
33
+ export default function GiftWrapComponent({ lineItemId }) {
34
+ const { isGift, setGiftInfo, updateLineItem, isUpdating } = useGiftWrap(lineItemId);
35
+
36
+ // Implementation of your UI based on the state returned from the hook
37
+ }
38
+ ```
39
+
40
+ ## Development
41
+
42
+ - `npm run dev` - Starts Vite development server
43
+ - `npm run build` - Builds the package using TypeScript and Vite
44
+ - `npm run preview` - Previews the build
45
+
46
+ ## Requirements
47
+ - `react` 18 or 19
48
+ - `@medusajs/js-sdk` v2+
@@ -0,0 +1,55 @@
1
+ import { HttpTypes } from '@medusajs/types';
2
+ import { default as Medusa } from '@medusajs/js-sdk';
3
+ import { GiftMetadata } from '../../../medusa-services/gift-wrap';
4
+
5
+ export interface GiftFormState {
6
+ giftFrom: string;
7
+ giftTo: string;
8
+ giftMessage: string;
9
+ }
10
+ export interface UseGiftWrapOptions {
11
+ /** The cart line item to manage gift state for */
12
+ item: HttpTypes.StoreCartLineItem;
13
+ /** Initialised Medusa JS SDK instance */
14
+ sdk: Medusa;
15
+ /** Cart ID that owns the line item */
16
+ cartId: string;
17
+ /** Called after a successful save or remove (e.g. to trigger a page refresh) */
18
+ onSuccess?: () => void;
19
+ /** Called when an API error occurs */
20
+ onError?: (error: Error) => void;
21
+ }
22
+ export interface UseGiftWrapReturn {
23
+ /** Whether the item is currently marked as a gift */
24
+ isGift: boolean;
25
+ /** Whether any personalisation details (from/to/message) have been set */
26
+ hasDetails: boolean;
27
+ /** The raw gift metadata from the line item */
28
+ giftMetadata: GiftMetadata;
29
+ /** Whether the gift details modal should be open */
30
+ showModal: boolean;
31
+ /** Current values of the gift form fields */
32
+ form: GiftFormState;
33
+ /** True while the save mutation is in flight */
34
+ loading: boolean;
35
+ /** True while the remove mutation is in flight */
36
+ removing: boolean;
37
+ /** Last API error message, or null */
38
+ error: string | null;
39
+ /** Open the gift details modal (pre-populates form from existing metadata) */
40
+ openModal: () => void;
41
+ /** Close the gift details modal without saving */
42
+ closeModal: () => void;
43
+ /**
44
+ * Handler for the gift checkbox — opens the modal when checking,
45
+ * triggers removal when unchecking (delegates to handleRemove).
46
+ */
47
+ handleCheck: () => void;
48
+ /** Save the current form values as gift metadata */
49
+ handleSubmit: () => Promise<void>;
50
+ /** Remove the gift flag and all personalisation fields */
51
+ handleRemove: () => Promise<void>;
52
+ /** Update a single form field by key */
53
+ setFormField: (field: keyof GiftFormState, value: string) => void;
54
+ }
55
+ export declare function useGiftWrap({ item, sdk, cartId, onSuccess, onError, }: UseGiftWrapOptions): UseGiftWrapReturn;
@@ -0,0 +1,2 @@
1
+ export * from './hooks/useGiftWrap';
2
+ export * from '../../medusa-services/gift-wrap';
@@ -0,0 +1,29 @@
1
+ export interface GiftMetadata {
2
+ is_gift?: boolean;
3
+ gift_from?: string;
4
+ gift_to?: string;
5
+ gift_message?: string;
6
+ [key: string]: unknown;
7
+ }
8
+ export interface UpdateGiftMetadataOptions {
9
+ sdk: any;
10
+ cartId: string;
11
+ lineId: string;
12
+ quantity: number;
13
+ metadata: GiftMetadata;
14
+ }
15
+ export interface RemoveGiftMetadataOptions {
16
+ sdk: any;
17
+ cartId: string;
18
+ lineId: string;
19
+ quantity: number;
20
+ existingMeta: GiftMetadata;
21
+ }
22
+ /**
23
+ * Updates the metadata for a specific line item in a cart to include gift information.
24
+ */
25
+ export declare function updateGiftMetadata({ sdk, cartId, lineId, quantity, metadata, }: UpdateGiftMetadataOptions): Promise<any>;
26
+ /**
27
+ * Removes gift-related metadata from a specific line item in a cart.
28
+ */
29
+ export declare function removeGiftMetadata({ sdk, cartId, lineId, quantity, existingMeta, }: RemoveGiftMetadataOptions): Promise<any>;
@@ -0,0 +1,126 @@
1
+ import { useState as d, useCallback as n } from "react";
2
+ async function C({
3
+ sdk: a,
4
+ cartId: g,
5
+ lineId: o,
6
+ quantity: e,
7
+ metadata: i
8
+ }) {
9
+ return await a.store.cart.updateLineItem(g, o, {
10
+ quantity: e,
11
+ metadata: i
12
+ });
13
+ }
14
+ async function R({
15
+ sdk: a,
16
+ cartId: g,
17
+ lineId: o,
18
+ quantity: e,
19
+ existingMeta: i
20
+ }) {
21
+ const {
22
+ is_gift: t,
23
+ gift_from: _,
24
+ gift_to: y,
25
+ gift_message: M,
26
+ ...l
27
+ } = i;
28
+ return await a.store.cart.updateLineItem(g, o, {
29
+ quantity: e,
30
+ metadata: {
31
+ ...l,
32
+ is_gift: !1,
33
+ gift_from: "",
34
+ gift_to: "",
35
+ gift_message: ""
36
+ }
37
+ });
38
+ }
39
+ function I({
40
+ item: a,
41
+ sdk: g,
42
+ cartId: o,
43
+ onSuccess: e,
44
+ onError: i
45
+ }) {
46
+ const t = a.metadata ?? {}, _ = t.is_gift === !0, y = !!t.gift_from || !!t.gift_to || !!t.gift_message, [M, l] = d(!1), [F, p] = d(!1), [q, w] = d(!1), [v, m] = d(null), [r, h] = d({
47
+ giftFrom: "",
48
+ giftTo: "",
49
+ giftMessage: ""
50
+ }), c = n(() => {
51
+ h({
52
+ giftFrom: t.gift_from ?? "",
53
+ giftTo: t.gift_to ?? "",
54
+ giftMessage: t.gift_message ?? ""
55
+ }), m(null), l(!0);
56
+ }, [t.gift_from, t.gift_to, t.gift_message]), G = n(() => {
57
+ l(!1);
58
+ }, []), T = n(() => {
59
+ _ || c();
60
+ }, [_, c]), B = n(
61
+ (f, s) => {
62
+ h((u) => ({ ...u, [f]: s }));
63
+ },
64
+ []
65
+ ), L = n(async () => {
66
+ m(null), p(!0);
67
+ try {
68
+ const f = r.giftFrom.trim(), s = r.giftTo.trim(), u = r.giftMessage.trim(), x = {
69
+ ...t,
70
+ is_gift: !0,
71
+ ...f && { gift_from: f },
72
+ ...s && { gift_to: s },
73
+ ...u && { gift_message: u }
74
+ };
75
+ await C({
76
+ sdk: g,
77
+ cartId: o,
78
+ lineId: a.id,
79
+ quantity: a.quantity,
80
+ metadata: x
81
+ }), l(!1), e == null || e();
82
+ } catch (f) {
83
+ const s = f instanceof Error ? f : new Error("Failed to save gift");
84
+ m(s.message), i == null || i(s);
85
+ } finally {
86
+ p(!1);
87
+ }
88
+ }, [r, t, g, o, a.id, a.quantity, e, i]), b = n(async () => {
89
+ w(!0), m(null);
90
+ try {
91
+ await R({
92
+ sdk: g,
93
+ cartId: o,
94
+ lineId: a.id,
95
+ quantity: a.quantity,
96
+ existingMeta: t
97
+ }), e == null || e();
98
+ } catch (f) {
99
+ const s = f instanceof Error ? f : new Error("Failed to remove gift");
100
+ m(s.message), i == null || i(s);
101
+ } finally {
102
+ w(!1);
103
+ }
104
+ }, [t, g, o, a.id, a.quantity, e, i]);
105
+ return {
106
+ isGift: _,
107
+ hasDetails: y,
108
+ giftMetadata: t,
109
+ showModal: M,
110
+ form: r,
111
+ loading: F,
112
+ removing: q,
113
+ error: v,
114
+ openModal: c,
115
+ closeModal: G,
116
+ handleCheck: T,
117
+ handleSubmit: L,
118
+ handleRemove: b,
119
+ setFormField: B
120
+ };
121
+ }
122
+ export {
123
+ R as removeGiftMetadata,
124
+ C as updateGiftMetadata,
125
+ I as useGiftWrap
126
+ };
@@ -0,0 +1 @@
1
+ (function(o,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("react")):typeof define=="function"&&define.amd?define(["exports","react"],e):(o=typeof globalThis<"u"?globalThis:o||self,e(o.MedusaGiftWrapLogic={},o.React))})(this,function(o,e){"use strict";async function c({sdk:a,cartId:g,lineId:l,quantity:i,metadata:f}){return await a.store.cart.updateLineItem(g,l,{quantity:i,metadata:f})}async function p({sdk:a,cartId:g,lineId:l,quantity:i,existingMeta:f}){const{is_gift:t,gift_from:r,gift_to:M,gift_message:h,...u}=f;return await a.store.cart.updateLineItem(g,l,{quantity:i,metadata:{...u,is_gift:!1,gift_from:"",gift_to:"",gift_message:""}})}function v({item:a,sdk:g,cartId:l,onSuccess:i,onError:f}){const t=a.metadata??{},r=t.is_gift===!0,M=!!t.gift_from||!!t.gift_to||!!t.gift_message,[h,u]=e.useState(!1),[G,w]=e.useState(!1),[q,b]=e.useState(!1),[C,d]=e.useState(null),[m,F]=e.useState({giftFrom:"",giftTo:"",giftMessage:""}),y=e.useCallback(()=>{F({giftFrom:t.gift_from??"",giftTo:t.gift_to??"",giftMessage:t.gift_message??""}),d(null),u(!0)},[t.gift_from,t.gift_to,t.gift_message]),T=e.useCallback(()=>{u(!1)},[]),k=e.useCallback(()=>{r||y()},[r,y]),L=e.useCallback((s,n)=>{F(_=>({..._,[s]:n}))},[]),B=e.useCallback(async()=>{d(null),w(!0);try{const s=m.giftFrom.trim(),n=m.giftTo.trim(),_=m.giftMessage.trim(),W={...t,is_gift:!0,...s&&{gift_from:s},...n&&{gift_to:n},..._&&{gift_message:_}};await c({sdk:g,cartId:l,lineId:a.id,quantity:a.quantity,metadata:W}),u(!1),i==null||i()}catch(s){const n=s instanceof Error?s:new Error("Failed to save gift");d(n.message),f==null||f(n)}finally{w(!1)}},[m,t,g,l,a.id,a.quantity,i,f]),R=e.useCallback(async()=>{b(!0),d(null);try{await p({sdk:g,cartId:l,lineId:a.id,quantity:a.quantity,existingMeta:t}),i==null||i()}catch(s){const n=s instanceof Error?s:new Error("Failed to remove gift");d(n.message),f==null||f(n)}finally{b(!1)}},[t,g,l,a.id,a.quantity,i,f]);return{isGift:r,hasDetails:M,giftMetadata:t,showModal:h,form:m,loading:G,removing:q,error:C,openModal:y,closeModal:T,handleCheck:k,handleSubmit:B,handleRemove:R,setFormField:L}}o.removeGiftMetadata=p,o.updateGiftMetadata=c,o.useGiftWrap=v,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "medusa-gift-wrap-logic",
3
+ "version": "1.0.0",
4
+ "description": "Reusable gift-wrap hook and service for Medusa storefronts. Manages per-line-item gift metadata (is_gift, gift_from, gift_to, gift_message) without any UI dependencies.",
5
+ "type": "module",
6
+ "main": "./dist/ui-library.umd.cjs",
7
+ "module": "./dist/ui-library.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/ui-library.js",
12
+ "require": "./dist/ui-library.umd.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "tsc && vite build",
22
+ "preview": "vite preview"
23
+ },
24
+ "keywords": [
25
+ "medusa",
26
+ "gift-wrap",
27
+ "cart",
28
+ "ecommerce",
29
+ "react",
30
+ "hook"
31
+ ],
32
+ "peerDependencies": {
33
+ "react": "^18.0.0 || ^19.0.0",
34
+ "react-dom": "^18.0.0 || ^19.0.0"
35
+ },
36
+ "dependencies": {
37
+ "@medusajs/js-sdk": "^2.11.2",
38
+ "@medusajs/types": "^2.11.2"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.0.0",
42
+ "@types/react": "^18.0.0 || ^19.0.0",
43
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
44
+ "@vitejs/plugin-react": "^4.0.0",
45
+ "react": "^19.0.0",
46
+ "react-dom": "^19.0.0",
47
+ "typescript": "^5.0.0",
48
+ "vite": "^5.0.0",
49
+ "vite-plugin-dts": "^3.0.0"
50
+ }
51
+ }