pukaad-ui-lib 1.234.0 → 1.235.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/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.234.0",
4
+ "version": "1.235.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -24,9 +24,9 @@ type __VLS_Props = {
24
24
  isMyProfile?: boolean;
25
25
  };
26
26
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
27
- "blog-edit": (item: CardUserBlogProps) => any;
27
+ "blog-updated": () => any;
28
28
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
29
- "onBlog-edit"?: ((item: CardUserBlogProps) => any) | undefined;
29
+ "onBlog-updated"?: (() => any) | undefined;
30
30
  }>, {
31
31
  isMyProfile: boolean;
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -74,20 +74,34 @@
74
74
  state="blog"
75
75
  />
76
76
  </div>
77
+
78
+ <!-- Edit Blog Drawer -->
79
+ <DrawerPostBlog
80
+ v-if="editBlogItem"
81
+ v-model="isEditDrawerOpen"
82
+ :item="editBlogItem"
83
+ @success="onEditSuccess"
84
+ />
77
85
  </template>
78
86
 
79
87
  <script setup>
80
88
  import { useRouter } from "vue-router";
81
89
  import { ref, watch } from "vue";
90
+ import { useNuxtApp } from "nuxt/app";
82
91
  import { useConvert } from "@/runtime/composables/useConvert";
92
+ import { useApi } from "../../composables/useApi";
83
93
  const { convertNumber, convertDateTorelativeText } = useConvert();
94
+ const { $toast } = useNuxtApp();
95
+ const api = useApi();
84
96
  const props = defineProps({
85
97
  item: { type: Object, required: true },
86
98
  isMyProfile: { type: Boolean, required: false, default: false }
87
99
  });
88
100
  const router = useRouter();
89
- const emit = defineEmits(["blog-edit"]);
101
+ const emit = defineEmits(["blog-updated"]);
90
102
  const isPinned = ref(props.item.ispinned);
103
+ const isEditDrawerOpen = ref(false);
104
+ const editBlogItem = ref(null);
91
105
  watch(
92
106
  () => props.item.ispinned,
93
107
  (val) => {
@@ -97,16 +111,46 @@ watch(
97
111
  const onProfileClick = () => {
98
112
  router.push(`/@${props.item.user.path_name}`);
99
113
  };
100
- const onBlogPin = () => {
101
- isPinned.value = true;
114
+ const onBlogPin = async () => {
115
+ try {
116
+ await api(`/blogs/${props.item.id}/pin`, { method: "POST" });
117
+ isPinned.value = true;
118
+ $toast.success("\u0E1B\u0E31\u0E01\u0E2B\u0E21\u0E38\u0E14\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08");
119
+ } catch (e) {
120
+ $toast.error(e?.data?.message?.description || "\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E1B\u0E31\u0E01\u0E2B\u0E21\u0E38\u0E14\u0E44\u0E14\u0E49");
121
+ }
102
122
  };
103
- const onBlogUnpin = () => {
104
- isPinned.value = false;
123
+ const onBlogUnpin = async () => {
124
+ try {
125
+ await api(`/blogs/${props.item.id}/pin`, { method: "DELETE" });
126
+ isPinned.value = false;
127
+ $toast.success("\u0E40\u0E2D\u0E32\u0E2B\u0E21\u0E38\u0E14\u0E2D\u0E2D\u0E01\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08");
128
+ } catch (e) {
129
+ $toast.error(e?.data?.message?.description || "\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E40\u0E2D\u0E32\u0E2B\u0E21\u0E38\u0E14\u0E2D\u0E2D\u0E01\u0E44\u0E14\u0E49");
130
+ }
105
131
  };
106
132
  const onBlogClick = () => {
107
133
  router.push(`/blog/${props.item.id}`);
108
134
  };
109
- const onBlogEdit = () => {
110
- emit("blog-edit", props.item);
135
+ const onBlogEdit = async () => {
136
+ try {
137
+ const res = await api(`/blogs/${props.item.id}`, { method: "GET" });
138
+ const blog = res.data;
139
+ const content = blog.content?.ops ?? (Array.isArray(blog.content) ? blog.content : []);
140
+ editBlogItem.value = {
141
+ id: blog.id,
142
+ title: blog.title,
143
+ content,
144
+ tags: blog.tags ?? [],
145
+ disableComment: false,
146
+ coverImage: blog.cover_image_url ? [{ url: blog.cover_image_url }] : []
147
+ };
148
+ isEditDrawerOpen.value = true;
149
+ } catch (e) {
150
+ $toast.error(e?.data?.message?.description || "\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E42\u0E2B\u0E25\u0E14\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E44\u0E14\u0E49");
151
+ }
152
+ };
153
+ const onEditSuccess = () => {
154
+ emit("blog-updated");
111
155
  };
112
156
  </script>
@@ -24,9 +24,9 @@ type __VLS_Props = {
24
24
  isMyProfile?: boolean;
25
25
  };
26
26
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
27
- "blog-edit": (item: CardUserBlogProps) => any;
27
+ "blog-updated": () => any;
28
28
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
29
- "onBlog-edit"?: ((item: CardUserBlogProps) => any) | undefined;
29
+ "onBlog-updated"?: (() => any) | undefined;
30
30
  }>, {
31
31
  isMyProfile: boolean;
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,13 +1,37 @@
1
1
  <template>
2
- <Drawer class="w-[748px]" :title="drawerTitle" @close="onClose" disabled-auto-close @submit="onSubmit"
3
- v-model="isOpen">
2
+ <Drawer
3
+ class="w-[748px]"
4
+ :title="drawerTitle"
5
+ @close="onClose"
6
+ disabled-auto-close
7
+ @submit="onSubmit"
8
+ v-model="isOpen"
9
+ >
4
10
  <div class="flex flex-col gap-[16px]">
5
- <InputTextField name="title" label="หัวข้อ" placeholder="ตั้งชื่อบทความให้น่าสนใจ" showCounter :limit="180"
6
- v-model="form.title" disabled-border required />
11
+ <InputTextField
12
+ name="title"
13
+ label="หัวข้อ"
14
+ placeholder="ตั้งชื่อบทความให้น่าสนใจ"
15
+ showCounter
16
+ :limit="180"
17
+ v-model="form.title"
18
+ disabled-border
19
+ required
20
+ />
7
21
 
8
- <InputContent placeholder="อยากเล่าอะไร" :height="288" v-model="form.content" />
22
+ <InputContent
23
+ placeholder="อยากเล่าอะไร"
24
+ :height="288"
25
+ v-model="form.content"
26
+ />
9
27
 
10
- <InputTag v-model="form.tags" name="tags" label="แท็ก" placeholder="เพิ่มแท็ก" :limit="5" />
28
+ <InputTag
29
+ v-model="form.tags"
30
+ name="tags"
31
+ label="แท็ก"
32
+ placeholder="เพิ่มแท็ก"
33
+ :limit="5"
34
+ />
11
35
 
12
36
  <!-- <InputCheckbox
13
37
  name="disableComment"
@@ -17,8 +41,12 @@
17
41
 
18
42
  <div class="flex flex-col gap-[8px]">
19
43
  <div class="text-gray font-body-large">ภาพหน้าปก</div>
20
- <InputFile :limit="1" name="coverImage" accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
21
- v-model="form.coverImage" />
44
+ <InputFile
45
+ :limit="1"
46
+ name="coverImage"
47
+ accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
48
+ v-model="form.coverImage"
49
+ />
22
50
  <div class="flex flex-col text-gray font-body-small">
23
51
  <div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
24
52
  <div>ขนาดไม่เกิน 30 mb</div>
@@ -30,7 +58,11 @@
30
58
  <div class="flex justify-end gap-[16px] items-center">
31
59
  <Button variant="outline" @click="onClose">ยกเลิก</Button>
32
60
  <Button @click="onSaveDraft"> บันทึกแบบร่าง </Button>
33
- <Button type="submit" color="primary" :disabled="!meta.valid || isLoading">
61
+ <Button
62
+ type="submit"
63
+ color="primary"
64
+ :disabled="!meta.valid || isLoading"
65
+ >
34
66
  <span v-if="isLoading">กำลังบันทึก...</span>
35
67
  <span v-else>เผยแพร่</span>
36
68
  </Button>
@@ -99,10 +131,6 @@ const uploadImage = async (file) => {
99
131
  method: "POST",
100
132
  headers: {
101
133
  "Content-Type": "application/json"
102
- // Note: We might need Auth token here if the endpoint requires it,
103
- // but user instruction said "ห้าม ส่ง Authorization Header ไปยัง S3"
104
- // It didn't say forbid Auth to /storage/presigned-urls.
105
- // Let's try adding token manually if useApi was doing it.
106
134
  },
107
135
  body: JSON.stringify({
108
136
  file_name: [file.name],
@@ -119,8 +147,8 @@ const uploadImage = async (file) => {
119
147
  }
120
148
  }
121
149
  });
122
- if (res.code !== 200 || !res.data?.items?.[0]) {
123
- throw new Error(res.message || "Failed to generate upload URL");
150
+ if (!res.data?.items?.[0]) {
151
+ throw new Error("\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E2A\u0E23\u0E49\u0E32\u0E07 upload URL \u0E44\u0E14\u0E49");
124
152
  }
125
153
  const item = res.data.items[0];
126
154
  console.log("Uploading to S3:", item.upload_url);
@@ -155,24 +183,19 @@ const onSubmit = async () => {
155
183
  tags: form.value.tags.map((t) => t.name || t),
156
184
  cover_image_url: coverImageUrl
157
185
  };
158
- const res = await api(
159
- isEditMode.value ? `/blogs/${form.value.id}` : "/profiles/me/blog",
160
- {
161
- method: isEditMode.value ? "PUT" : "POST",
162
- body: payload
163
- }
186
+ const res = await api(isEditMode.value ? `/blogs/${form.value.id}` : "/profiles/me/blog", {
187
+ method: isEditMode.value ? "PUT" : "POST",
188
+ body: payload
189
+ });
190
+ $toast.success(
191
+ isEditMode.value ? "\u0E2D\u0E31\u0E1B\u0E40\u0E14\u0E15\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08" : "\u0E2A\u0E23\u0E49\u0E32\u0E07\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08"
164
192
  );
165
- const isSuccess = isEditMode.value ? res.code === "SUCCESS_OK" : res.code === "SUCCESS_CREATED";
166
- if (isSuccess) {
167
- $toast.success(isEditMode.value ? "\u0E2D\u0E31\u0E1B\u0E40\u0E14\u0E15\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08" : "\u0E2A\u0E23\u0E49\u0E32\u0E07\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08");
168
- emit("submit", form.value);
169
- emit("success", res.data);
170
- isOpen.value = false;
171
- } else {
172
- throw new Error(res.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14");
173
- }
193
+ emit("submit", form.value);
194
+ emit("success", res.data);
195
+ isOpen.value = false;
174
196
  } catch (error) {
175
- $toast.error(error.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E43\u0E19\u0E01\u0E32\u0E23\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21");
197
+ const msg = error?.data?.message?.description || error?.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E43\u0E19\u0E01\u0E32\u0E23\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21";
198
+ $toast.error(msg);
176
199
  console.error("Submit error:", error);
177
200
  } finally {
178
201
  isLoading.value = false;
@@ -207,11 +207,11 @@ const fetchTags = async (type) => {
207
207
  const res = await api(
208
208
  `/tags/${type}?page=${state.page}&page_size=${PAGE_SIZE}`
209
209
  );
210
- if ((res.code === 200 || res.code === "SUCCESS_OK") && res.data) {
210
+ if (res.data && Array.isArray(res.data)) {
211
211
  const newTags = res.data.map(mapTag);
212
212
  state.data = [...state.data, ...newTags];
213
213
  state.page++;
214
- state.hasMore = state.page <= res.meta.total_page;
214
+ state.hasMore = state.page <= (res.meta?.total_page ?? 1);
215
215
  } else {
216
216
  state.hasMore = false;
217
217
  }
@@ -1,9 +1,9 @@
1
1
  import type { PickerOptionMenuUserProps } from "@/types/components/picker/picker-option-menu/picker-option-menu-user";
2
2
  declare const __VLS_export: import("vue").DefineComponent<PickerOptionMenuUserProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
3
- "blog-edit": () => any;
4
3
  "report-announce": () => any;
5
4
  "blog-unpin": () => any;
6
5
  "blog-pin": () => any;
6
+ "blog-edit": () => any;
7
7
  "profile-name-updated": (name: string) => any;
8
8
  "profile-edit": () => any;
9
9
  "profile-share": () => any;
@@ -19,10 +19,10 @@ declare const __VLS_export: import("vue").DefineComponent<PickerOptionMenuUserPr
19
19
  "review-delete": () => any;
20
20
  archive: () => any;
21
21
  }, string, import("vue").PublicProps, Readonly<PickerOptionMenuUserProps> & Readonly<{
22
- "onBlog-edit"?: (() => any) | undefined;
23
22
  "onReport-announce"?: (() => any) | undefined;
24
23
  "onBlog-unpin"?: (() => any) | undefined;
25
24
  "onBlog-pin"?: (() => any) | undefined;
25
+ "onBlog-edit"?: (() => any) | undefined;
26
26
  "onProfile-name-updated"?: ((name: string) => any) | undefined;
27
27
  "onProfile-edit"?: (() => any) | undefined;
28
28
  "onProfile-share"?: (() => any) | undefined;
@@ -1,9 +1,9 @@
1
1
  import type { PickerOptionMenuUserProps } from "@/types/components/picker/picker-option-menu/picker-option-menu-user";
2
2
  declare const __VLS_export: import("vue").DefineComponent<PickerOptionMenuUserProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
3
- "blog-edit": () => any;
4
3
  "report-announce": () => any;
5
4
  "blog-unpin": () => any;
6
5
  "blog-pin": () => any;
6
+ "blog-edit": () => any;
7
7
  "profile-name-updated": (name: string) => any;
8
8
  "profile-edit": () => any;
9
9
  "profile-share": () => any;
@@ -19,10 +19,10 @@ declare const __VLS_export: import("vue").DefineComponent<PickerOptionMenuUserPr
19
19
  "review-delete": () => any;
20
20
  archive: () => any;
21
21
  }, string, import("vue").PublicProps, Readonly<PickerOptionMenuUserProps> & Readonly<{
22
- "onBlog-edit"?: (() => any) | undefined;
23
22
  "onReport-announce"?: (() => any) | undefined;
24
23
  "onBlog-unpin"?: (() => any) | undefined;
25
24
  "onBlog-pin"?: (() => any) | undefined;
25
+ "onBlog-edit"?: (() => any) | undefined;
26
26
  "onProfile-name-updated"?: ((name: string) => any) | undefined;
27
27
  "onProfile-edit"?: (() => any) | undefined;
28
28
  "onProfile-share"?: (() => any) | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.234.0",
3
+ "version": "1.235.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",