@back23/promptly-sdk 1.3.0 → 1.3.1

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.
Files changed (2) hide show
  1. package/README.md +297 -179
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
- # @webbyon/promptly-sdk
1
+ # @back23/promptly-sdk
2
2
 
3
3
  Promptly AI CMS SDK for JavaScript/TypeScript
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @webbyon/promptly-sdk
8
+ npm install @back23/promptly-sdk
9
9
  ```
10
10
 
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { Promptly } from '@webbyon/promptly-sdk';
14
+ import { Promptly } from '@back23/promptly-sdk';
15
15
 
16
16
  const client = new Promptly({
17
17
  tenantId: 'demo',
@@ -19,6 +19,38 @@ const client = new Promptly({
19
19
  });
20
20
  ```
21
21
 
22
+ ## v1.3.0 Changes
23
+
24
+ ### Unified Response Structure
25
+
26
+ All list APIs now return a consistent `ListResponse<T>` format:
27
+
28
+ ```typescript
29
+ interface ListResponse<T> {
30
+ data: T[]; // Always an array (never null/undefined)
31
+ meta: {
32
+ current_page: number;
33
+ last_page: number;
34
+ per_page: number;
35
+ total: number;
36
+ from: number | null;
37
+ to: number | null;
38
+ };
39
+ }
40
+ ```
41
+
42
+ **No more defensive code needed:**
43
+
44
+ ```typescript
45
+ // Before (v1.1.0)
46
+ const posts = await client.blog.list();
47
+ const items = posts?.data ?? []; // Defensive check needed
48
+
49
+ // After (v1.3.0)
50
+ const { data, meta } = await client.blog.list();
51
+ data.map(post => ...); // data is always an array
52
+ ```
53
+
22
54
  ## API Overview
23
55
 
24
56
  | Resource | Public (No Auth) | Protected (Auth Required) |
@@ -26,12 +58,13 @@ const client = new Promptly({
26
58
  | **Boards** | list, get | - |
27
59
  | **Posts** | listPosts, getPost | createPost, updatePost, deletePost |
28
60
  | **Comments** | listComments | createComment, updateComment, deleteComment |
29
- | **Blog** | list, get | - |
61
+ | **Blog** | list, get, featured, byCategory, byTag | - |
30
62
  | **Shop** | listProducts, getProduct, listCategories | getCart, addToCart, listOrders, createOrder |
31
63
  | **Forms** | list, get, submit | mySubmissions |
32
64
  | **Auth** | login, register | logout, me, updateProfile |
33
65
  | **Media** | - | upload, list, delete |
34
66
  | **Entities** | list, getSchema, listRecords, getRecord | createRecord, updateRecord, deleteRecord |
67
+ | **Reservation** | getSettings, listServices, listStaff, getAvailableDates, getAvailableSlots | create, list, get, cancel |
35
68
 
36
69
  ## API Reference
37
70
 
@@ -39,20 +72,20 @@ const client = new Promptly({
39
72
 
40
73
  ```typescript
41
74
  // 게시판 목록
42
- const boards = await client.boards.list();
43
- // Returns: Board[]
75
+ const { data: boards, meta } = await client.boards.list();
76
+ // Returns: ListResponse<Board>
44
77
 
45
78
  // 게시판 상세
46
79
  const board = await client.boards.get('first'); // slug or id
47
80
  // Returns: Board
48
81
 
49
82
  // 게시판 글 목록
50
- const posts = await client.boards.listPosts('first', {
83
+ const { data: posts, meta } = await client.boards.listPosts('first', {
51
84
  page: 1,
52
85
  per_page: 10,
53
86
  search: '검색어', // optional
54
87
  });
55
- // Returns: { data: BoardPost[], meta: { current_page, last_page, per_page, total } }
88
+ // Returns: ListResponse<BoardPost>
56
89
 
57
90
  // 글 상세
58
91
  const post = await client.boards.getPost(1);
@@ -60,7 +93,7 @@ const post = await client.boards.getPost(1);
60
93
 
61
94
  // 댓글 목록
62
95
  const comments = await client.boards.listComments(1);
63
- // Returns: BoardComment[]
96
+ // Returns: BoardComment[] (always an array)
64
97
  ```
65
98
 
66
99
  ### Posts & Comments - Protected (로그인 필요)
@@ -105,14 +138,14 @@ await client.boards.deleteComment(commentId);
105
138
 
106
139
  ```typescript
107
140
  // 블로그 글 목록
108
- const posts = await client.blog.list({
141
+ const { data: posts, meta } = await client.blog.list({
109
142
  page: 1,
110
143
  per_page: 10,
111
144
  category: 'news', // optional
112
145
  tag: 'featured', // optional
113
146
  search: '검색어', // optional
114
147
  });
115
- // Returns: { data: BlogPost[], meta: {...} }
148
+ // Returns: ListResponse<BlogPost>
116
149
 
117
150
  // 블로그 글 상세
118
151
  const post = await client.blog.get('post-slug');
@@ -120,13 +153,21 @@ const post = await client.blog.get('post-slug');
120
153
 
121
154
  // 추천 글
122
155
  const featured = await client.blog.featured(5);
123
- // Returns: BlogPost[]
156
+ // Returns: BlogPost[] (always an array)
124
157
 
125
158
  // 카테고리별 조회
126
- const newsPosts = await client.blog.byCategory('news');
159
+ const { data: newsPosts } = await client.blog.byCategory('news');
127
160
 
128
161
  // 태그별 조회
129
- const taggedPosts = await client.blog.byTag('featured');
162
+ const { data: taggedPosts } = await client.blog.byTag('featured');
163
+
164
+ // 카테고리 목록
165
+ const categories = await client.blog.categories();
166
+ // Returns: string[] (always an array)
167
+
168
+ // 태그 목록
169
+ const tags = await client.blog.tags();
170
+ // Returns: string[] (always an array)
130
171
  ```
131
172
 
132
173
  ### Shop (쇼핑)
@@ -135,14 +176,14 @@ const taggedPosts = await client.blog.byTag('featured');
135
176
 
136
177
  ```typescript
137
178
  // 상품 목록
138
- const products = await client.shop.listProducts({
179
+ const { data: products, meta } = await client.shop.listProducts({
139
180
  page: 1,
140
181
  per_page: 10,
141
182
  category: 'electronics', // optional
142
183
  is_featured: true, // optional
143
184
  search: '검색어', // optional
144
185
  });
145
- // Returns: { data: Product[], meta: {...} }
186
+ // Returns: ListResponse<Product>
146
187
 
147
188
  // 상품 상세
148
189
  const product = await client.shop.getProduct('product-slug');
@@ -150,11 +191,11 @@ const product = await client.shop.getProduct('product-slug');
150
191
 
151
192
  // 추천 상품
152
193
  const featured = await client.shop.featuredProducts(8);
153
- // Returns: Product[]
194
+ // Returns: Product[] (always an array)
154
195
 
155
196
  // 카테고리 목록
156
197
  const categories = await client.shop.listCategories();
157
- // Returns: ProductCategory[]
198
+ // Returns: ProductCategory[] (always an array)
158
199
  ```
159
200
 
160
201
  #### Protected (로그인 필요)
@@ -195,8 +236,8 @@ const order = await client.shop.createOrder({
195
236
  });
196
237
 
197
238
  // 주문 목록
198
- const orders = await client.shop.listOrders();
199
- // Returns: { data: Order[], meta: {...} }
239
+ const { data: orders, meta } = await client.shop.listOrders();
240
+ // Returns: ListResponse<Order>
200
241
 
201
242
  // 주문 상세
202
243
  const order = await client.shop.getOrder(orderId);
@@ -208,6 +249,89 @@ await client.shop.cancelOrder(orderId);
208
249
  // 쿠폰 검증
209
250
  const validation = await client.shop.validateCoupon('SAVE10', 50000);
210
251
  // Returns: { valid: boolean, discount_amount: number, coupon: Coupon }
252
+
253
+ // 내 쿠폰 목록
254
+ const coupons = await client.shop.myCoupons();
255
+ // Returns: Coupon[] (always an array)
256
+ ```
257
+
258
+ ### Reservation (예약) - NEW in v1.3.0
259
+
260
+ #### Public (로그인 불필요)
261
+
262
+ ```typescript
263
+ // 예약 설정 조회
264
+ const settings = await client.reservation.getSettings();
265
+ // Returns: ReservationSettings
266
+
267
+ // 서비스 목록
268
+ const services = await client.reservation.listServices();
269
+ // Returns: ReservationService[] (always an array)
270
+
271
+ // 담당자 목록
272
+ const staffs = await client.reservation.listStaff();
273
+ // Returns: ReservationStaff[] (always an array)
274
+
275
+ // 특정 서비스의 담당자만 조회
276
+ const serviceStaffs = await client.reservation.listStaff(serviceId);
277
+
278
+ // 예약 가능 날짜 조회
279
+ const dates = await client.reservation.getAvailableDates({
280
+ service_id: 1,
281
+ staff_id: 2, // optional
282
+ start_date: '2026-01-01', // optional
283
+ end_date: '2026-01-31', // optional
284
+ });
285
+ // Returns: string[] (YYYY-MM-DD format)
286
+
287
+ // 예약 가능 시간 슬롯 조회
288
+ const slots = await client.reservation.getAvailableSlots({
289
+ service_id: 1,
290
+ date: '2026-01-15',
291
+ staff_id: 2, // optional
292
+ });
293
+ // Returns: ReservationSlot[]
294
+ ```
295
+
296
+ #### Protected (로그인 필요)
297
+
298
+ ```typescript
299
+ // 예약 생성
300
+ const result = await client.reservation.create({
301
+ service_id: 1,
302
+ staff_id: 2, // optional
303
+ reservation_date: '2026-01-15',
304
+ start_time: '14:00',
305
+ customer_name: '홍길동',
306
+ customer_phone: '010-1234-5678', // optional
307
+ customer_email: 'hong@example.com', // optional
308
+ customer_memo: '요청사항', // optional
309
+ });
310
+ // Returns: { reservation: Reservation, requires_payment: boolean, deposit: number }
311
+
312
+ // 내 예약 목록
313
+ const { data: reservations, meta } = await client.reservation.list({
314
+ status: 'confirmed', // optional
315
+ upcoming: true, // optional
316
+ past: false, // optional
317
+ });
318
+ // Returns: ListResponse<Reservation>
319
+
320
+ // 다가오는 예약
321
+ const upcoming = await client.reservation.upcoming(5);
322
+ // Returns: Reservation[] (always an array)
323
+
324
+ // 지난 예약
325
+ const past = await client.reservation.past(10);
326
+ // Returns: Reservation[] (always an array)
327
+
328
+ // 예약 상세
329
+ const reservation = await client.reservation.get('RES-20260115-001');
330
+ // Returns: Reservation
331
+
332
+ // 예약 취소
333
+ const cancelled = await client.reservation.cancel('RES-20260115-001', '일정 변경');
334
+ // Returns: Reservation
211
335
  ```
212
336
 
213
337
  ### Auth (인증)
@@ -280,8 +404,8 @@ const response = await client.auth.socialCallback('google', code);
280
404
 
281
405
  ```typescript
282
406
  // 폼 목록
283
- const forms = await client.forms.list();
284
- // Returns: Form[]
407
+ const { data: forms } = await client.forms.list();
408
+ // Returns: ListResponse<Form>
285
409
 
286
410
  // 폼 상세
287
411
  const form = await client.forms.get('contact');
@@ -293,6 +417,10 @@ await client.forms.submit('contact', {
293
417
  email: 'user@example.com',
294
418
  message: '문의 내용',
295
419
  });
420
+
421
+ // 내 제출 목록 (로그인 필요)
422
+ const { data: submissions } = await client.forms.mySubmissions();
423
+ // Returns: ListResponse<FormSubmission>
296
424
  ```
297
425
 
298
426
  ### Media (미디어) - Protected
@@ -307,12 +435,12 @@ const mediaList = await client.media.uploadMultiple([file1, file2]);
307
435
  // Returns: Media[]
308
436
 
309
437
  // 내 미디어 목록
310
- const mediaList = await client.media.list({
438
+ const { data: mediaList, meta } = await client.media.list({
311
439
  page: 1,
312
440
  per_page: 20,
313
441
  type: 'image/jpeg', // optional
314
442
  });
315
- // Returns: { data: Media[], meta: {...} }
443
+ // Returns: ListResponse<Media>
316
444
 
317
445
  // 미디어 삭제
318
446
  await client.media.delete(mediaId);
@@ -327,22 +455,22 @@ AI가 MCP를 통해 생성한 커스텀 데이터 구조에 접근합니다.
327
455
  ```typescript
328
456
  // 엔티티 목록 조회
329
457
  const entities = await client.entities.list();
330
- // Returns: CustomEntity[]
458
+ // Returns: CustomEntity[] (always an array)
331
459
 
332
460
  // 엔티티 스키마 조회
333
461
  const schema = await client.entities.getSchema('customer');
334
462
  // Returns: EntitySchema
335
463
 
336
464
  // 레코드 목록 조회
337
- const customers = await client.entities.listRecords('customer', {
465
+ const { data: customers, meta } = await client.entities.listRecords('customer', {
338
466
  page: 1,
339
467
  per_page: 20,
340
468
  status: 'active',
341
469
  });
342
- // Returns: { data: EntityRecord[], meta: {...} }
470
+ // Returns: ListResponse<EntityRecord>
343
471
 
344
472
  // 데이터 필드로 필터링
345
- const vipCustomers = await client.entities.listRecords('customer', {
473
+ const { data: vipCustomers } = await client.entities.listRecords('customer', {
346
474
  'data.tier': 'vip',
347
475
  });
348
476
 
@@ -415,6 +543,27 @@ const settings = await client.getSettings();
415
543
 
416
544
  ## Types
417
545
 
546
+ ### Common Types
547
+
548
+ ```typescript
549
+ // Unified list response type
550
+ interface ListResponse<T> {
551
+ data: T[];
552
+ meta: PaginationMeta;
553
+ }
554
+
555
+ interface PaginationMeta {
556
+ current_page: number;
557
+ last_page: number;
558
+ per_page: number;
559
+ total: number;
560
+ from: number | null;
561
+ to: number | null;
562
+ }
563
+ ```
564
+
565
+ ### Resource Types
566
+
418
567
  ```typescript
419
568
  interface Board {
420
569
  id: number;
@@ -440,16 +589,6 @@ interface BoardPost {
440
589
  created_at: string;
441
590
  }
442
591
 
443
- interface BoardComment {
444
- id: number;
445
- post_id: number;
446
- member?: Member;
447
- parent_id?: number;
448
- content: string;
449
- replies?: BoardComment[];
450
- created_at: string;
451
- }
452
-
453
592
  interface BlogPost {
454
593
  id: number;
455
594
  slug: string;
@@ -463,8 +602,6 @@ interface BlogPost {
463
602
  is_published: boolean;
464
603
  published_at?: string;
465
604
  view_count: number;
466
- seo_title?: string;
467
- seo_description?: string;
468
605
  created_at: string;
469
606
  }
470
607
 
@@ -473,38 +610,60 @@ interface Product {
473
610
  slug: string;
474
611
  name: string;
475
612
  description?: string;
476
- content?: string;
477
613
  price: number;
478
614
  compare_price?: number;
479
615
  thumbnail?: string;
480
616
  images?: string[];
481
617
  status: 'draft' | 'active' | 'inactive';
482
618
  is_featured: boolean;
483
- has_options: boolean;
484
- variants?: ProductVariant[];
485
619
  in_stock?: boolean;
486
620
  discount_percent?: number;
487
621
  created_at: string;
488
622
  }
489
623
 
490
- interface Cart {
624
+ interface Reservation {
491
625
  id: number;
492
- items: CartItem[];
493
- total: number;
494
- total_quantity: number;
495
- item_count: number;
626
+ reservation_number: string;
627
+ status: 'pending' | 'confirmed' | 'completed' | 'cancelled' | 'no_show';
628
+ status_label: string;
629
+ reservation_date: string;
630
+ start_time: string;
631
+ end_time: string;
632
+ time_range: string;
633
+ customer_name: string;
634
+ customer_phone: string | null;
635
+ customer_email: string | null;
636
+ price: number;
637
+ deposit: number;
638
+ payment_status: 'pending' | 'paid' | 'refunded' | 'partial';
639
+ can_cancel: boolean;
640
+ service: { id: number; name: string; duration: number; } | null;
641
+ staff: { id: number; name: string; avatar: string | null; } | null;
642
+ created_at: string;
496
643
  }
497
644
 
498
- interface Order {
645
+ interface ReservationService {
499
646
  id: number;
500
- order_number: string;
501
- status: 'pending' | 'paid' | 'preparing' | 'shipping' | 'delivered' | 'cancelled';
502
- subtotal: number;
503
- discount_amount: number;
504
- shipping_fee: number;
505
- total: number;
506
- items?: OrderItem[];
507
- created_at: string;
647
+ name: string;
648
+ slug: string;
649
+ description: string | null;
650
+ thumbnail: string | null;
651
+ duration: number;
652
+ price: number;
653
+ requires_staff: boolean;
654
+ requires_payment: boolean;
655
+ deposit: number;
656
+ staffs: Array<{ id: number; name: string; avatar: string | null; }>;
657
+ }
658
+
659
+ interface ReservationSettings {
660
+ timezone: string;
661
+ slot_interval: number;
662
+ min_notice_hours: number;
663
+ max_advance_days: number;
664
+ cancellation_hours: number;
665
+ allow_online_payment: boolean;
666
+ bookable_date_range: { start: string; end: string; };
508
667
  }
509
668
 
510
669
  interface Member {
@@ -517,56 +676,16 @@ interface Member {
517
676
  created_at: string;
518
677
  }
519
678
 
520
- interface Form {
521
- id: number;
522
- slug: string;
523
- name: string;
524
- description?: string;
525
- fields: FormField[];
526
- settings: FormSettings;
527
- is_active: boolean;
528
- }
529
-
530
- interface Media {
531
- id: number;
532
- url: string;
533
- thumbnail_url?: string;
534
- filename: string;
535
- mime_type: string;
536
- size: number;
537
- created_at: string;
538
- }
539
-
540
679
  interface CustomEntity {
541
680
  id: number;
542
681
  name: string;
543
682
  slug: string;
544
683
  description?: string;
545
684
  schema: EntitySchema;
546
- icon?: string;
547
685
  is_active: boolean;
548
- records_count?: number;
549
686
  created_at: string;
550
687
  }
551
688
 
552
- interface EntitySchema {
553
- fields: EntityField[];
554
- display?: {
555
- title_field?: string;
556
- list_fields?: string;
557
- };
558
- }
559
-
560
- interface EntityField {
561
- name: string;
562
- label: string;
563
- type: 'text' | 'textarea' | 'number' | 'email' | 'url' | 'date' | 'datetime' | 'boolean' | 'select' | 'multiselect';
564
- required?: boolean;
565
- searchable?: boolean;
566
- default?: any;
567
- options?: Array<{ value: string; label: string }>;
568
- }
569
-
570
689
  interface EntityRecord {
571
690
  id: number;
572
691
  entity_id: number;
@@ -580,7 +699,7 @@ interface EntityRecord {
580
699
  ## Error Handling
581
700
 
582
701
  ```typescript
583
- import { Promptly, PromptlyError } from '@webbyon/promptly-sdk';
702
+ import { Promptly, PromptlyError } from '@back23/promptly-sdk';
584
703
 
585
704
  try {
586
705
  await client.auth.login({ email: 'wrong@email.com', password: 'wrong' });
@@ -597,107 +716,106 @@ try {
597
716
 
598
717
  ```tsx
599
718
  import { useState, useEffect } from 'react';
600
- import { Promptly } from '@webbyon/promptly-sdk';
719
+ import { Promptly } from '@back23/promptly-sdk';
601
720
 
602
721
  const client = new Promptly({
603
722
  tenantId: 'demo',
604
723
  baseUrl: 'https://promptly.webbyon.com',
605
724
  });
606
725
 
607
- // 게시판 글 목록
608
- function BoardPosts({ boardSlug }) {
726
+ // 블로그 글 목록 (with pagination)
727
+ function BlogList() {
609
728
  const [posts, setPosts] = useState([]);
610
- const [loading, setLoading] = useState(true);
729
+ const [meta, setMeta] = useState(null);
730
+ const [page, setPage] = useState(1);
611
731
 
612
732
  useEffect(() => {
613
- client.boards.listPosts(boardSlug)
614
- .then(res => setPosts(res.data))
615
- .finally(() => setLoading(false));
616
- }, [boardSlug]);
617
-
618
- if (loading) return <div>Loading...</div>;
733
+ client.blog.list({ page, per_page: 10 })
734
+ .then(({ data, meta }) => {
735
+ setPosts(data); // Always an array
736
+ setMeta(meta);
737
+ });
738
+ }, [page]);
619
739
 
620
740
  return (
621
- <table>
622
- <thead>
623
- <tr>
624
- <th>제목</th>
625
- <th>작성자</th>
626
- <th>조회수</th>
627
- <th>작성일</th>
628
- </tr>
629
- </thead>
630
- <tbody>
631
- {posts.map(post => (
632
- <tr key={post.id}>
633
- <td>{post.title}</td>
634
- <td>{post.author}</td>
635
- <td>{post.views}</td>
636
- <td>{new Date(post.created_at).toLocaleDateString()}</td>
637
- </tr>
638
- ))}
639
- </tbody>
640
- </table>
641
- );
642
- }
643
-
644
- // 로그인 폼
645
- function LoginForm() {
646
- const [email, setEmail] = useState('');
647
- const [password, setPassword] = useState('');
648
- const [error, setError] = useState('');
649
-
650
- const handleSubmit = async (e) => {
651
- e.preventDefault();
652
- try {
653
- await client.auth.login({ email, password });
654
- // 로그인 성공 - 리다이렉트 등
655
- } catch (err) {
656
- setError(err.message);
657
- }
658
- };
741
+ <div>
742
+ {posts.map(post => (
743
+ <article key={post.id}>
744
+ <h2>{post.title}</h2>
745
+ <p>{post.excerpt}</p>
746
+ </article>
747
+ ))}
659
748
 
660
- return (
661
- <form onSubmit={handleSubmit}>
662
- <input
663
- type="email"
664
- value={email}
665
- onChange={e => setEmail(e.target.value)}
666
- placeholder="이메일"
667
- />
668
- <input
669
- type="password"
670
- value={password}
671
- onChange={e => setPassword(e.target.value)}
672
- placeholder="비밀번호"
673
- />
674
- {error && <p style={{color: 'red'}}>{error}</p>}
675
- <button type="submit">로그인</button>
676
- </form>
749
+ {meta && (
750
+ <div>
751
+ Page {meta.current_page} of {meta.last_page}
752
+ <button
753
+ onClick={() => setPage(p => p - 1)}
754
+ disabled={page <= 1}
755
+ >
756
+ Previous
757
+ </button>
758
+ <button
759
+ onClick={() => setPage(p => p + 1)}
760
+ disabled={page >= meta.last_page}
761
+ >
762
+ Next
763
+ </button>
764
+ </div>
765
+ )}
766
+ </div>
677
767
  );
678
768
  }
679
769
 
680
- // 상품 목록
681
- function ProductList() {
682
- const [products, setProducts] = useState([]);
770
+ // 예약
771
+ function ReservationForm() {
772
+ const [services, setServices] = useState([]);
773
+ const [selectedService, setSelectedService] = useState(null);
774
+ const [dates, setDates] = useState([]);
775
+ const [slots, setSlots] = useState([]);
683
776
 
684
777
  useEffect(() => {
685
- client.shop.listProducts({ is_featured: true })
686
- .then(res => setProducts(res.data));
778
+ client.reservation.listServices().then(setServices);
687
779
  }, []);
688
780
 
781
+ useEffect(() => {
782
+ if (selectedService) {
783
+ client.reservation.getAvailableDates({
784
+ service_id: selectedService,
785
+ }).then(setDates);
786
+ }
787
+ }, [selectedService]);
788
+
789
+ const handleDateSelect = async (date) => {
790
+ const availableSlots = await client.reservation.getAvailableSlots({
791
+ service_id: selectedService,
792
+ date,
793
+ });
794
+ setSlots(availableSlots);
795
+ };
796
+
689
797
  return (
690
- <div className="grid grid-cols-4 gap-4">
691
- {products.map(product => (
692
- <div key={product.id} className="border p-4">
693
- <img src={product.thumbnail} alt={product.name} />
694
- <h3>{product.name}</h3>
695
- <p>{product.price.toLocaleString()}원</p>
696
- {product.compare_price && (
697
- <p className="line-through">{product.compare_price.toLocaleString()}원</p>
698
- )}
699
- </div>
700
- ))}
798
+ <div>
799
+ <select onChange={e => setSelectedService(Number(e.target.value))}>
800
+ <option>서비스 선택</option>
801
+ {services.map(s => (
802
+ <option key={s.id} value={s.id}>{s.name}</option>
803
+ ))}
804
+ </select>
805
+
806
+ <div>
807
+ {dates.map(date => (
808
+ <button key={date} onClick={() => handleDateSelect(date)}>
809
+ {date}
810
+ </button>
811
+ ))}
812
+ </div>
813
+
814
+ <div>
815
+ {slots.filter(s => s.available).map(slot => (
816
+ <button key={slot.time}>{slot.time}</button>
817
+ ))}
818
+ </div>
701
819
  </div>
702
820
  );
703
821
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@back23/promptly-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Promptly AI CMS SDK for JavaScript/TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",