@ph-cms/client-sdk 0.1.15 → 0.1.17

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 CHANGED
@@ -382,12 +382,16 @@ function AuthComponent() {
382
382
  login, // (data: LoginRequest) => Promise<AuthResponse>
383
383
  loginWithFirebase, // (data: FirebaseExchangeRequest) => Promise<AuthResponse>
384
384
  register, // (data: RegisterRequest) => Promise<AuthResponse>
385
+ loginAnonymous, // (data?: AnonymousLoginRequest) => Promise<AuthResponse>
386
+ upgradeAnonymous, // (data: { email, password, display_name?, username? }) => Promise<UserDto>
385
387
  logout, // () => Promise<void>
386
388
 
387
389
  // 뮤테이션 상태 (isPending, error 등)
388
390
  loginStatus,
389
391
  loginWithFirebaseStatus,
390
392
  registerStatus,
393
+ loginAnonymousStatus,
394
+ upgradeAnonymousStatus,
391
395
  logoutStatus,
392
396
  } = useAuth();
393
397
  }
@@ -457,6 +461,114 @@ function FirebaseLoginButton() {
457
461
  }
458
462
  ```
459
463
 
464
+ #### 익명 회원가입
465
+
466
+ 이메일·비밀번호 없이 임시 계정을 생성합니다. 생성된 계정은 나중에 이메일 계정으로 전환할 수 있으며, 그 동안 쌓인 활동 히스토리(좋아요, 스탬프 등)는 그대로 유지됩니다.
467
+
468
+ 두 가지 방식이 있으며 **결과는 동일**합니다.
469
+
470
+ ---
471
+
472
+ **방식 1 — 순수 익명 (Firebase 없이)**
473
+
474
+ 서버가 임시 계정(`provider: anonymous`)을 직접 생성합니다.
475
+
476
+ ```tsx
477
+ function GuestButton() {
478
+ const { loginAnonymous, loginAnonymousStatus } = useAuth();
479
+
480
+ return (
481
+ <button
482
+ onClick={() => loginAnonymous()}
483
+ disabled={loginAnonymousStatus.isPending}
484
+ >
485
+ {loginAnonymousStatus.isPending ? '입장 중...' : '게스트로 시작하기'}
486
+ </button>
487
+ );
488
+ }
489
+ ```
490
+
491
+ `channelUid`는 `PHCMSProvider`에 설정된 값이 자동으로 사용됩니다. 직접 지정하려면 인자로 전달합니다:
492
+
493
+ ```ts
494
+ await loginAnonymous({ channelUid: 'channel-uid', displayName: '손님1' });
495
+ ```
496
+
497
+ ---
498
+
499
+ **방식 2 — Firebase 익명 (`signInAnonymously` 연동)**
500
+
501
+ Firebase의 `signInAnonymously()`로 익명 인증을 먼저 수행한 뒤, 발급된 ID 토큰을 `loginAnonymous()`에 전달합니다. 서버는 이를 검증하여 `provider: firebase:anonymous` 계정을 생성합니다.
502
+
503
+ 이 방식은 Firebase 익명 계정을 나중에 `linkWithCredential()` 등으로 실계정에 연결하는 Firebase 네이티브 플로우와 함께 사용할 때 적합합니다.
504
+
505
+ ```tsx
506
+ import { getAuth, signInAnonymously } from 'firebase/auth';
507
+
508
+ function FirebaseGuestButton() {
509
+ const { loginAnonymous, loginAnonymousStatus } = useAuth();
510
+
511
+ const handleAnonymous = async () => {
512
+ const firebaseAuth = getAuth();
513
+ const { user } = await signInAnonymously(firebaseAuth);
514
+ const firebaseIdToken = await user.getIdToken();
515
+
516
+ await loginAnonymous({ firebaseIdToken });
517
+ // 성공 → 자동으로 me() 호출 → 인증 상태 갱신
518
+ };
519
+
520
+ return (
521
+ <button onClick={handleAnonymous} disabled={loginAnonymousStatus.isPending}>
522
+ {loginAnonymousStatus.isPending ? '입장 중...' : '게스트로 시작하기'}
523
+ </button>
524
+ );
525
+ }
526
+ ```
527
+
528
+ > **주의:** Firebase 익명 계정은 `POST /auth/firebase/exchange`(토큰 교환)로는 등록되지 않습니다.
529
+ > 신규 익명 유저는 반드시 `loginAnonymous({ firebaseIdToken })`을 통해 등록해야 합니다.
530
+ > `FirebaseAuthSync`를 사용하는 경우 이 분기가 자동으로 처리됩니다 ([Firebase Auth Sync](#firebase-auth-sync) 참고).
531
+
532
+ ---
533
+
534
+ **익명 계정 → 이메일 계정 전환**
535
+
536
+ `upgradeAnonymous()`를 호출하면 user ID를 유지한 채로 정식 이메일 계정으로 전환됩니다.
537
+
538
+ ```tsx
539
+ function UpgradeForm() {
540
+ const { upgradeAnonymous, upgradeAnonymousStatus, user } = useAuth();
541
+
542
+ const handleUpgrade = async (email: string, password: string, displayName: string) => {
543
+ await upgradeAnonymous({ email, password, display_name: displayName });
544
+ // 성공 → role에서 'anonymous' 제거됨 → 기존 히스토리 유지
545
+ };
546
+
547
+ if (!user?.role.includes('anonymous')) return null;
548
+
549
+ return (
550
+ <form onSubmit={e => {
551
+ e.preventDefault();
552
+ const fd = new FormData(e.currentTarget);
553
+ handleUpgrade(
554
+ fd.get('email') as string,
555
+ fd.get('password') as string,
556
+ fd.get('display_name') as string,
557
+ );
558
+ }}>
559
+ <input name="email" type="email" placeholder="이메일" />
560
+ <input name="password" type="password" placeholder="비밀번호" />
561
+ <input name="display_name" placeholder="이름" />
562
+ <button type="submit" disabled={upgradeAnonymousStatus.isPending}>
563
+ {upgradeAnonymousStatus.isPending ? '전환 중...' : '계정 만들기'}
564
+ </button>
565
+ </form>
566
+ );
567
+ }
568
+ ```
569
+
570
+ ---
571
+
460
572
  #### 회원가입
461
573
 
462
574
  ```tsx
@@ -645,7 +757,15 @@ Firebase onAuthStateChanged
645
757
 
646
758
  ├─ fbUser 존재 + PH-CMS 비인증 상태
647
759
  │ → fbUser.getIdToken()
648
- → client.auth.loginWithFirebase({ idToken })
760
+
761
+ │ ├─ fbUser.isAnonymous === true
762
+ │ │ → client.auth.loginAnonymous({ channelUid, firebaseIdToken })
763
+ │ │ ※ POST /auth/anonymous (등록 또는 기존 계정 토큰 재발급)
764
+ │ │
765
+ │ └─ fbUser.isAnonymous === false (Google, 이메일 등)
766
+ │ → client.auth.loginWithFirebase({ idToken })
767
+ │ ※ POST /auth/firebase/exchange (기존 유저 로그인 전용)
768
+
649
769
  │ → provider.setTokens(...)
650
770
  │ → refreshUser() ← me() 호출하여 프로필 로드
651
771
 
@@ -654,6 +774,10 @@ Firebase onAuthStateChanged
654
774
  → refreshUser() ← 상태 초기화
655
775
  ```
656
776
 
777
+ > `POST /auth/firebase/exchange`는 **기존 유저 로그인 전용**입니다.
778
+ > 신규 익명 유저를 이 엔드포인트로 생성하려 하면 서버가 에러를 반환합니다.
779
+ > `FirebaseAuthSync`를 사용하면 익명/비익명 분기가 자동으로 처리됩니다.
780
+
657
781
  #### `<FirebaseAuthSync>` 컴포넌트
658
782
 
659
783
  `<PHCMSProvider>` 안에서 사용합니다:
package/bin/cli.js CHANGED
@@ -59,6 +59,12 @@ const { data: terms } = useChannelTerms();
59
59
  const { mutate: agree } = useAgreeTerms();
60
60
  \`\`\`
61
61
 
62
+ ## 📚 Detailed Documentation
63
+ For the complete API reference, advanced configuration, and comprehensive examples, always refer to the full documentation:
64
+ - **Location**: \`node_modules/@ph-cms/client-sdk/README.md\`
65
+
66
+ If you need specific details about a module (e.g., \`ContentModule\`, \`AuthModule\`), read that file to ensure correct parameter usage.
67
+
62
68
  ## ⚠️ Common Pitfalls
63
69
  - **Manual channelUid passing**: Avoid \`useContentList({ channelUid: '...', ... })\` unless necessary. Rely on the provider.
64
70
  - **Hook-less API calls**: When using \`client.content.list()\` directly, you MUST provide \`channelUid\` in the params as there is no context available.
@@ -8,8 +8,8 @@ export declare const contentKeys: {
8
8
  status?: string | undefined;
9
9
  type?: string | undefined;
10
10
  channelUid?: string | undefined;
11
- tags?: string[] | undefined;
12
11
  channelSlug?: string | undefined;
12
+ tags?: string[] | undefined;
13
13
  parentUid?: string | undefined;
14
14
  authorUid?: string | undefined;
15
15
  uids?: string[] | undefined;
@@ -45,6 +45,7 @@ export declare const useCreateContent: () => import("@tanstack/react-query").Use
45
45
  title: string;
46
46
  status?: string | undefined;
47
47
  channelUid?: string | undefined;
48
+ channelSlug?: string | undefined;
48
49
  geometry?: {
49
50
  type: "Point" | "LineString" | "Polygon" | "MultiPoint" | "MultiLineString" | "MultiPolygon" | "GeometryCollection" | "Feature" | "FeatureCollection";
50
51
  coordinates?: any[] | undefined;
@@ -59,7 +60,6 @@ export declare const useCreateContent: () => import("@tanstack/react-query").Use
59
60
  summary?: string | null | undefined;
60
61
  slug?: string | null | undefined;
61
62
  tags?: string[] | undefined;
62
- channelSlug?: string | undefined;
63
63
  parentUid?: string | undefined;
64
64
  sortOrder?: number | undefined;
65
65
  mediaAttachments?: string[] | undefined;
@@ -106,9 +106,15 @@ const useFirebaseAuthSync = (options) => {
106
106
  setIsSyncing(true);
107
107
  try {
108
108
  const idToken = await fbUser.getIdToken();
109
- await client.auth.loginWithFirebase({ idToken, channelUid });
110
- // loginWithFirebase stores the tokens in the provider.
111
- // Now refresh the context so `isAuthenticated` and `user` update.
109
+ if (fbUser.isAnonymous) {
110
+ // 익명 유저는 POST /auth/anonymous 등록/로그인
111
+ await client.auth.loginAnonymous({ channelUid, firebaseIdToken: idToken });
112
+ }
113
+ else {
114
+ // 일반 Firebase 유저는 토큰 교환 (기존 유저 로그인)
115
+ await client.auth.loginWithFirebase({ idToken, channelUid });
116
+ }
117
+ // Refresh the context so `isAuthenticated` and `user` update.
112
118
  await refreshUser();
113
119
  onSyncSuccessRef.current?.();
114
120
  }
@@ -11,6 +11,7 @@ export declare const useCreateStampTour: () => import("@tanstack/react-query").U
11
11
  markerUids: string[];
12
12
  status?: string | undefined;
13
13
  channelUid?: string | undefined;
14
+ channelSlug?: string | undefined;
14
15
  geometry?: {
15
16
  type: "Point" | "LineString" | "Polygon" | "MultiPoint" | "MultiLineString" | "MultiPolygon" | "GeometryCollection" | "Feature" | "FeatureCollection";
16
17
  coordinates?: any[] | undefined;
@@ -22,7 +23,6 @@ export declare const useCreateStampTour: () => import("@tanstack/react-query").U
22
23
  image?: string | null | undefined;
23
24
  summary?: string | null | undefined;
24
25
  tags?: string[] | undefined;
25
- channelSlug?: string | undefined;
26
26
  startsAt?: string | null | undefined;
27
27
  endsAt?: string | null | undefined;
28
28
  }, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ph-cms/client-sdk",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Unified PH-CMS Client SDK (React + Core)",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  "LICENSE"
26
26
  ],
27
27
  "dependencies": {
28
- "@ph-cms/api-contract": "^0.1.6",
28
+ "@ph-cms/api-contract": "^0.1.7",
29
29
  "@tanstack/react-query": "^5.0.0",
30
30
  "axios": "^1.6.0",
31
31
  "zod": "^3.22.4"