@ph-cms/client-sdk 0.1.9-beta.1 → 0.1.9

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
@@ -516,6 +516,32 @@ function MyComponent() {
516
516
  }
517
517
  ```
518
518
 
519
+ ### Profile Update (`useUpdateProfile`)
520
+
521
+ 사용자가 자신의 프로필을 수정할 수 있는 훅입니다. 업데이트가 성공하면 내부적으로 `refreshUser()`가 호출되어 컨텍스트와 UI가 즉각적으로 갱신됩니다.
522
+
523
+ ```tsx
524
+ import { useUpdateProfile } from '@ph-cms/client-sdk';
525
+
526
+ function ProfileEditor({ userUid }) {
527
+ const { mutateAsync: updateProfile, isPending } = useUpdateProfile();
528
+
529
+ const handleSave = async () => {
530
+ await updateProfile({
531
+ uid: userUid,
532
+ data: {
533
+ display_name: 'New Name',
534
+ avatar_url: 'https://example.com/new-avatar.png',
535
+ profile_data: { bio: 'Hello World' },
536
+ }
537
+ });
538
+ alert('프로필이 업데이트되었습니다.');
539
+ };
540
+
541
+ return <button onClick={handleSave} disabled={isPending}>저장</button>;
542
+ }
543
+ ```
544
+
519
545
  ### Standalone (Non-React) Usage
520
546
 
521
547
  React 없이 `PHCMSClient`를 직접 사용할 수 있습니다.
@@ -746,54 +772,69 @@ console.log(status?.liked); // true or false
746
772
 
747
773
  스탬프 투어 기능을 사용하여 특정 지점(Marker) 방문을 인증하고 진행 현황을 조회할 수 있습니다.
748
774
 
749
- ### 스탬프 획득 (인증)
775
+ #### 스탬프 획득 (인증)
750
776
 
751
777
  사용자의 현재 GPS 좌표를 전송하여 특정 투어 내의 마커 방문을 인증합니다.
752
778
 
753
- ```ts
754
- // Standalone Usage
755
- const result = await client.content.stamp(tourUid, markerUid, {
756
- lat: 37.5511,
757
- lng: 126.9882
758
- });
779
+ ```tsx
780
+ import { useStamp } from '@ph-cms/client-sdk';
781
+
782
+ function StampButton({ tourUid, markerUid }) {
783
+ const { mutateAsync: collectStamp, isPending } = useStamp();
784
+
785
+ const handleStamp = async () => {
786
+ try {
787
+ const result = await collectStamp({
788
+ tourUid,
789
+ markerUid,
790
+ data: { lat: 37.5511, lng: 126.9882 }
791
+ });
792
+
793
+ console.log(`거리: ${result.distance}m`);
794
+ if (result.isCompletion) {
795
+ alert('축하합니다! 투어를 완주하셨습니다.');
796
+ }
797
+ } catch (error) {
798
+ alert('스탬프를 획득할 수 없습니다. (너무 멀거나 이미 획득함)');
799
+ }
800
+ };
759
801
 
760
- console.log(`거리: ${result.distance}m`);
761
- if (result.isCompletion) {
762
- alert('축하합니다! 투어를 완주하셨습니다.');
802
+ return <button onClick={handleStamp} disabled={isPending}>스탬프 찍기</button>;
763
803
  }
764
804
  ```
765
805
 
766
- ### 투어 진행 현황 조회
806
+ #### 투어 진행 현황 조회
767
807
 
768
808
  특정 투어에 대해 내가 획득한 스탬프 목록과 완주 여부를 확인합니다.
769
809
 
770
- ```ts
771
- const status = await client.content.getStampStatus(tourUid);
810
+ ```tsx
811
+ import { useStampStatus } from '@ph-cms/client-sdk';
772
812
 
773
- console.log(`전체 마커: ${status.total_markers}`);
774
- console.log(`획득 마커: ${status.collected_markers}`);
775
- console.log(`완주 여부: ${status.is_completed}`);
813
+ function TourProgress({ tourUid }) {
814
+ const { data: status, isLoading } = useStampStatus(tourUid);
776
815
 
777
- status.stamps.forEach(s => {
778
- console.log(`마커 ${s.marker_uid} 획득 일시: ${s.collected_at}`);
779
- });
816
+ if (isLoading) return <div>Loading...</div>;
817
+
818
+ return (
819
+ <div>
820
+ <p>진행도: {status?.collected_markers} / {status?.total_markers}</p>
821
+ {status?.is_completed && <p>🏆 완주 완료!</p>}
822
+ </div>
823
+ );
824
+ }
780
825
  ```
781
826
 
782
- ### 투어 전체 통계 (관리자용)
827
+ #### 투어 전체 통계 (관리자용)
783
828
 
784
829
  투어의 총 참여자 수, 완주율 등 관리용 데이터를 조회합니다. (적절한 권한 필요)
785
830
 
786
- ```ts
787
- const stats = await client.content.getTourStats(tourUid);
788
-
789
- console.log(`총 참여자: ${stats.total_participants}`);
790
- console.log(`총 완주자: ${stats.total_completions}`);
791
- console.log(`완주율: ${stats.completion_rate * 100}%`);
792
- console.log(`평균 오차 거리: ${stats.avg_distance_meter}m`);
831
+ ```tsx
832
+ import { useTourStats } from '@ph-cms/client-sdk';
793
833
 
794
- stats.stamps_by_marker.forEach(m => {
795
- console.log(`${m.marker_title}: ${m.stamp_count} 방문`);
796
- });
834
+ function AdminStats({ tourUid }) {
835
+ const { data: stats, isLoading } = useTourStats(tourUid);
836
+ // ...
837
+ }
797
838
  ```
798
839
 
799
840
  ---
@@ -881,12 +922,19 @@ const client = new PHCMSClient({
881
922
  client.authProvider // AuthProvider | undefined
882
923
  client.axiosInstance // AxiosInstance — 내부 axios 인스턴스 직접 접근
883
924
  client.auth // AuthModule
925
+ client.user // UserModule
884
926
  client.content // ContentModule
885
927
  client.channel // ChannelModule
886
928
  client.terms // TermsModule
887
929
  client.media // MediaModule
888
930
  ```
889
931
 
932
+ ### `UserModule` (`client.user`)
933
+
934
+ | 메서드 | 설명 |
935
+ |---|---|
936
+ | `updateProfile(uid: string, data: UpdateUserProfileRequest)` | 유저의 프로필 정보 업데이트 (일반 유저는 `UpdateUserProfileRequest` 필드만 허용) → `UserDto` |
937
+
890
938
  ### `AuthModule` (`client.auth`)
891
939
 
892
940
  | 메서드 | 설명 |
package/dist/client.d.ts CHANGED
@@ -5,6 +5,7 @@ import { ChannelModule } from './modules/channel';
5
5
  import { ContentModule } from './modules/content';
6
6
  import { MediaModule } from './modules/media';
7
7
  import { TermsModule } from './modules/terms';
8
+ import { UserModule } from './modules/user';
8
9
  export interface PHCMSClientConfig {
9
10
  baseURL: string;
10
11
  apiPrefix?: string;
@@ -19,6 +20,7 @@ export declare class PHCMSClient {
19
20
  readonly channel: ChannelModule;
20
21
  readonly terms: TermsModule;
21
22
  readonly media: MediaModule;
23
+ readonly user: UserModule;
22
24
  /**
23
25
  * Whether a token refresh is currently in flight (used by the 401
24
26
  * interceptor to de-duplicate concurrent refresh attempts).
package/dist/client.js CHANGED
@@ -11,6 +11,7 @@ const channel_1 = require("./modules/channel");
11
11
  const content_1 = require("./modules/content");
12
12
  const media_1 = require("./modules/media");
13
13
  const terms_1 = require("./modules/terms");
14
+ const user_1 = require("./modules/user");
14
15
  class PHCMSClient {
15
16
  /** Exposes the auth provider so UI layers can check token state synchronously. */
16
17
  get authProvider() {
@@ -44,6 +45,7 @@ class PHCMSClient {
44
45
  this.channel = new channel_1.ChannelModule(this.axiosInstance);
45
46
  this.terms = new terms_1.TermsModule(this.axiosInstance);
46
47
  this.media = new media_1.MediaModule(this.axiosInstance);
48
+ this.user = new user_1.UserModule(this.axiosInstance);
47
49
  // Wire the refresh function into the auth provider so it can
48
50
  // proactively refresh tokens inside `getToken()` without needing
49
51
  // a direct reference to the AuthModule / axios instance.
@@ -0,0 +1,37 @@
1
+ import { CollectStampRequest } from '@ph-cms/api-contract';
2
+ export declare const stampTourKeys: {
3
+ all: readonly ["stamp-tours"];
4
+ status: (tourUid: string) => readonly ["stamp-tours", "status", string];
5
+ stats: (tourUid: string) => readonly ["stamp-tours", "stats", string];
6
+ };
7
+ export declare const useStamp: () => import("@tanstack/react-query").UseMutationResult<{
8
+ isCompletion: boolean;
9
+ distance: number;
10
+ }, Error, {
11
+ tourUid: string;
12
+ markerUid: string;
13
+ data: CollectStampRequest;
14
+ }, unknown>;
15
+ export declare const useStampStatus: (tourUid: string, enabled?: boolean) => import("@tanstack/react-query").UseQueryResult<{
16
+ total_markers: number;
17
+ collected_markers: number;
18
+ is_completed: boolean;
19
+ stamps: {
20
+ marker_uid: string;
21
+ collected_at: string;
22
+ }[];
23
+ }, Error>;
24
+ export declare const useTourStats: (tourUid: string, enabled?: boolean) => import("@tanstack/react-query").UseQueryResult<{
25
+ total_markers: number;
26
+ tour_uid: string;
27
+ tour_title: string;
28
+ total_participants: number;
29
+ total_completions: number;
30
+ completion_rate: number;
31
+ avg_distance_meter: number;
32
+ stamps_by_marker: {
33
+ marker_uid: string;
34
+ marker_title: string;
35
+ stamp_count: number;
36
+ }[];
37
+ }, Error>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTourStats = exports.useStampStatus = exports.useStamp = exports.stampTourKeys = void 0;
4
+ const react_query_1 = require("@tanstack/react-query");
5
+ const context_1 = require("../context");
6
+ exports.stampTourKeys = {
7
+ all: ['stamp-tours'],
8
+ status: (tourUid) => [...exports.stampTourKeys.all, 'status', tourUid],
9
+ stats: (tourUid) => [...exports.stampTourKeys.all, 'stats', tourUid],
10
+ };
11
+ const useStamp = () => {
12
+ const client = (0, context_1.usePHCMS)();
13
+ const queryClient = (0, react_query_1.useQueryClient)();
14
+ return (0, react_query_1.useMutation)({
15
+ mutationFn: ({ tourUid, markerUid, data }) => client.content.stamp(tourUid, markerUid, data),
16
+ onSuccess: (data, variables) => {
17
+ queryClient.invalidateQueries({ queryKey: exports.stampTourKeys.status(variables.tourUid) });
18
+ },
19
+ });
20
+ };
21
+ exports.useStamp = useStamp;
22
+ const useStampStatus = (tourUid, enabled = true) => {
23
+ const client = (0, context_1.usePHCMS)();
24
+ return (0, react_query_1.useQuery)({
25
+ queryKey: exports.stampTourKeys.status(tourUid),
26
+ queryFn: () => client.content.getStampStatus(tourUid),
27
+ enabled: !!tourUid && enabled,
28
+ });
29
+ };
30
+ exports.useStampStatus = useStampStatus;
31
+ const useTourStats = (tourUid, enabled = true) => {
32
+ const client = (0, context_1.usePHCMS)();
33
+ return (0, react_query_1.useQuery)({
34
+ queryKey: exports.stampTourKeys.stats(tourUid),
35
+ queryFn: () => client.content.getTourStats(tourUid),
36
+ enabled: !!tourUid && enabled,
37
+ });
38
+ };
39
+ exports.useTourStats = useTourStats;
@@ -0,0 +1,26 @@
1
+ import { UpdateUserProfileRequest } from '@ph-cms/api-contract';
2
+ /**
3
+ * Hook to update a user's profile.
4
+ * Typically used by a user to update their own profile.
5
+ */
6
+ export declare const useUpdateProfile: () => import("@tanstack/react-query").UseMutationResult<{
7
+ uid: string;
8
+ email: string;
9
+ username: string | null;
10
+ display_name: string;
11
+ avatar_url: string | null;
12
+ phone_number: string | null;
13
+ email_verified_at: string | null;
14
+ phone_verified_at: string | null;
15
+ locale: string;
16
+ timezone: string;
17
+ status: string;
18
+ role: string[];
19
+ profile_data: Record<string, any>;
20
+ last_login_at: string | null;
21
+ created_at: string;
22
+ updated_at: string;
23
+ }, Error, {
24
+ uid: string;
25
+ data: UpdateUserProfileRequest | Record<string, any>;
26
+ }, unknown>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useUpdateProfile = void 0;
4
+ const react_query_1 = require("@tanstack/react-query");
5
+ const context_1 = require("../context");
6
+ /**
7
+ * Hook to update a user's profile.
8
+ * Typically used by a user to update their own profile.
9
+ */
10
+ const useUpdateProfile = () => {
11
+ const client = (0, context_1.usePHCMS)();
12
+ const { refreshUser } = (0, context_1.usePHCMSContext)();
13
+ return (0, react_query_1.useMutation)({
14
+ mutationFn: (params) => client.user.updateProfile(params.uid, params.data),
15
+ onSuccess: async () => {
16
+ // Refresh user in context after a successful update so the UI gets the new data
17
+ await refreshUser();
18
+ },
19
+ });
20
+ };
21
+ exports.useUpdateProfile = useUpdateProfile;
package/dist/index.d.ts CHANGED
@@ -10,11 +10,14 @@ export * from './modules/channel';
10
10
  export * from './modules/content';
11
11
  export * from './modules/media';
12
12
  export * from './modules/terms';
13
+ export * from './modules/user';
13
14
  export * from './context';
14
15
  export * from './hooks/useAuth';
15
16
  export * from './hooks/useChannel';
16
17
  export * from './hooks/useContent';
18
+ export * from './hooks/useStampTour';
17
19
  export * from './hooks/useFirebaseAuthSync';
18
20
  export * from './hooks/useMedia';
19
21
  export * from './hooks/useTerms';
22
+ export * from './hooks/useUser';
20
23
  export * from './types';
package/dist/index.js CHANGED
@@ -26,11 +26,14 @@ __exportStar(require("./modules/channel"), exports);
26
26
  __exportStar(require("./modules/content"), exports);
27
27
  __exportStar(require("./modules/media"), exports);
28
28
  __exportStar(require("./modules/terms"), exports);
29
+ __exportStar(require("./modules/user"), exports);
29
30
  __exportStar(require("./context"), exports);
30
31
  __exportStar(require("./hooks/useAuth"), exports);
31
32
  __exportStar(require("./hooks/useChannel"), exports);
32
33
  __exportStar(require("./hooks/useContent"), exports);
34
+ __exportStar(require("./hooks/useStampTour"), exports);
33
35
  __exportStar(require("./hooks/useFirebaseAuthSync"), exports);
34
36
  __exportStar(require("./hooks/useMedia"), exports);
35
37
  __exportStar(require("./hooks/useTerms"), exports);
38
+ __exportStar(require("./hooks/useUser"), exports);
36
39
  __exportStar(require("./types"), exports);
@@ -0,0 +1,17 @@
1
+ import { UpdateUserProfileRequest, UserDto } from "@ph-cms/api-contract";
2
+ import { AxiosInstance } from "axios";
3
+ export declare class UserModule {
4
+ private client;
5
+ constructor(client: AxiosInstance);
6
+ /**
7
+ * Updates a user's profile.
8
+ *
9
+ * Normal users can only update specific fields defined in `UpdateUserProfileRequest`
10
+ * (e.g., `display_name`, `avatar_url`, `profile_data`).
11
+ * Admin users can pass any updatable fields.
12
+ *
13
+ * @param uid - The UID of the user to update.
14
+ * @param data - The update payload.
15
+ */
16
+ updateProfile(uid: string, data: UpdateUserProfileRequest | Record<string, any>): Promise<UserDto>;
17
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserModule = void 0;
4
+ class UserModule {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /**
9
+ * Updates a user's profile.
10
+ *
11
+ * Normal users can only update specific fields defined in `UpdateUserProfileRequest`
12
+ * (e.g., `display_name`, `avatar_url`, `profile_data`).
13
+ * Admin users can pass any updatable fields.
14
+ *
15
+ * @param uid - The UID of the user to update.
16
+ * @param data - The update payload.
17
+ */
18
+ async updateProfile(uid, data) {
19
+ return this.client.patch(`/api/users/${uid}`, data);
20
+ }
21
+ }
22
+ exports.UserModule = UserModule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ph-cms/client-sdk",
3
- "version": "0.1.9-beta.1",
3
+ "version": "0.1.9",
4
4
  "description": "Unified PH-CMS Client SDK (React + Core)",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  "LICENSE"
22
22
  ],
23
23
  "dependencies": {
24
- "@ph-cms/api-contract": "0.1.2-beta.1",
24
+ "@ph-cms/api-contract": "0.1.2",
25
25
  "@tanstack/react-query": "^5.0.0",
26
26
  "axios": "^1.6.0",
27
27
  "zod": "^3.22.4"