@mundogamernetwork/shared-ui 1.2.7 → 1.2.8

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.
@@ -0,0 +1,234 @@
1
+ <template>
2
+ <div class="mk-images">
3
+ <!-- Avatar -->
4
+ <div class="mk-field">
5
+ <label class="mk-label">{{ t("media_kit.images.avatar") }}</label>
6
+ <div class="mk-single">
7
+ <img v-if="avatar" :src="avatar" class="mk-thumb" alt="avatar" />
8
+ <label class="mk-upload" :class="{ 'is-busy': busy === 'avatar' }">
9
+ <input type="file" accept="image/*" class="mk-file" :disabled="!!busy" @change="onSingle($event, 'avatar')" />
10
+ {{ busy === 'avatar' ? t("media_kit.images.uploading") : t("media_kit.images.upload") }}
11
+ </label>
12
+ <button v-if="avatar" type="button" class="mk-remove" @click="clearSingle('avatar')">×</button>
13
+ </div>
14
+ </div>
15
+
16
+ <!-- Cover -->
17
+ <div class="mk-field">
18
+ <label class="mk-label">{{ t("media_kit.images.cover") }}</label>
19
+ <div class="mk-single">
20
+ <img v-if="cover" :src="cover" class="mk-thumb mk-thumb--wide" alt="cover" />
21
+ <label class="mk-upload" :class="{ 'is-busy': busy === 'cover' }">
22
+ <input type="file" accept="image/*" class="mk-file" :disabled="!!busy" @change="onSingle($event, 'cover')" />
23
+ {{ busy === 'cover' ? t("media_kit.images.uploading") : t("media_kit.images.upload") }}
24
+ </label>
25
+ <button v-if="cover" type="button" class="mk-remove" @click="clearSingle('cover')">×</button>
26
+ </div>
27
+ </div>
28
+
29
+ <!-- Portfolio gallery -->
30
+ <div class="mk-field">
31
+ <label class="mk-label">{{ t("media_kit.images.portfolio") }}</label>
32
+ <div class="mk-gallery">
33
+ <div v-for="(url, i) in (portfolio || [])" :key="'p' + i" class="mk-gitem">
34
+ <img :src="url" class="mk-thumb" alt="portfolio" />
35
+ <button type="button" class="mk-remove" @click="removeFromList('portfolio', i)">×</button>
36
+ </div>
37
+ <label class="mk-add" :class="{ 'is-busy': busy === 'portfolio' }">
38
+ <input type="file" accept="image/*" class="mk-file" :disabled="!!busy" @change="onMulti($event, 'portfolio')" />
39
+ {{ busy === 'portfolio' ? '…' : '+' }}
40
+ </label>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- Brand logos gallery -->
45
+ <div class="mk-field">
46
+ <label class="mk-label">{{ t("media_kit.images.brands") }}</label>
47
+ <div class="mk-gallery">
48
+ <div v-for="(url, i) in (brands || [])" :key="'b' + i" class="mk-gitem">
49
+ <img :src="url" class="mk-thumb" alt="brand" />
50
+ <button type="button" class="mk-remove" @click="removeFromList('brands', i)">×</button>
51
+ </div>
52
+ <label class="mk-add" :class="{ 'is-busy': busy === 'brands' }">
53
+ <input type="file" accept="image/*" class="mk-file" :disabled="!!busy" @change="onMulti($event, 'brands')" />
54
+ {{ busy === 'brands' ? '…' : '+' }}
55
+ </label>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import { uploadMediaKitImage } from "../../services/mediaKitService";
63
+
64
+ const props = defineProps<{
65
+ avatar?: string | null;
66
+ cover?: string | null;
67
+ portfolio?: string[];
68
+ brands?: string[];
69
+ }>();
70
+
71
+ const emit = defineEmits<{
72
+ (e: "update", key: string, value: any): void;
73
+ (e: "error", message: string): void;
74
+ }>();
75
+
76
+ const { t } = useI18n();
77
+
78
+ const busy = ref<string | null>(null);
79
+
80
+ async function upload(file: File): Promise<string | null> {
81
+ if (file.size > 10 * 1024 * 1024) {
82
+ emit("error", t("media_kit.images.too_large"));
83
+ return null;
84
+ }
85
+ try {
86
+ return await uploadMediaKitImage(file);
87
+ } catch (e) {
88
+ emit("error", t("media_kit.images.upload_error"));
89
+ console.error("Media kit image upload failed:", e);
90
+ return null;
91
+ }
92
+ }
93
+
94
+ async function onSingle(e: Event, key: "avatar" | "cover") {
95
+ const input = e.target as HTMLInputElement;
96
+ const file = input.files?.[0];
97
+ if (!file) return;
98
+ busy.value = key;
99
+ const url = await upload(file);
100
+ busy.value = null;
101
+ input.value = "";
102
+ if (url) emit("update", key === "avatar" ? "avatar_url" : "cover_url", url);
103
+ }
104
+
105
+ function clearSingle(key: "avatar" | "cover") {
106
+ emit("update", key === "avatar" ? "avatar_url" : "cover_url", null);
107
+ }
108
+
109
+ async function onMulti(e: Event, key: "portfolio" | "brands") {
110
+ const input = e.target as HTMLInputElement;
111
+ const file = input.files?.[0];
112
+ if (!file) return;
113
+ busy.value = key;
114
+ const url = await upload(file);
115
+ busy.value = null;
116
+ input.value = "";
117
+ if (!url) return;
118
+ const field = key === "portfolio" ? "portfolio_images" : "brand_logos";
119
+ const current = (key === "portfolio" ? props.portfolio : props.brands) || [];
120
+ emit("update", field, [...current, url]);
121
+ }
122
+
123
+ function removeFromList(key: "portfolio" | "brands", idx: number) {
124
+ const field = key === "portfolio" ? "portfolio_images" : "brand_logos";
125
+ const current = (key === "portfolio" ? props.portfolio : props.brands) || [];
126
+ emit("update", field, current.filter((_, i) => i !== idx));
127
+ }
128
+ </script>
129
+
130
+ <style scoped lang="scss">
131
+ .mk-images {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 1.25rem;
135
+ }
136
+
137
+ .mk-field {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 0.5rem;
141
+ }
142
+
143
+ .mk-label {
144
+ color: #c0c4d0;
145
+ font-size: 0.8rem;
146
+ font-weight: 600;
147
+ text-transform: uppercase;
148
+ letter-spacing: 0.4px;
149
+ }
150
+
151
+ .mk-single,
152
+ .mk-gallery {
153
+ display: flex;
154
+ align-items: center;
155
+ flex-wrap: wrap;
156
+ gap: 0.6rem;
157
+ }
158
+
159
+ .mk-thumb {
160
+ width: 64px;
161
+ height: 64px;
162
+ object-fit: cover;
163
+ border: 1px solid #272930;
164
+
165
+ &--wide {
166
+ width: 128px;
167
+ }
168
+ }
169
+
170
+ .mk-gitem {
171
+ position: relative;
172
+ }
173
+
174
+ .mk-upload,
175
+ .mk-add {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ cursor: pointer;
180
+ border: 1px solid #fdb215;
181
+ color: #fdb215;
182
+ font-size: 0.8rem;
183
+ font-weight: 600;
184
+ background: transparent;
185
+ transition: background 0.15s, color 0.15s;
186
+
187
+ &:hover {
188
+ background: #fdb215;
189
+ color: #13161c;
190
+ }
191
+
192
+ &.is-busy {
193
+ opacity: 0.6;
194
+ pointer-events: none;
195
+ }
196
+ }
197
+
198
+ .mk-upload {
199
+ padding: 0.45rem 0.85rem;
200
+ }
201
+
202
+ .mk-add {
203
+ width: 64px;
204
+ height: 64px;
205
+ font-size: 1.4rem;
206
+ }
207
+
208
+ .mk-file {
209
+ position: absolute;
210
+ width: 1px;
211
+ height: 1px;
212
+ opacity: 0;
213
+ pointer-events: none;
214
+ }
215
+
216
+ .mk-remove {
217
+ position: absolute;
218
+ top: -8px;
219
+ right: -8px;
220
+ width: 20px;
221
+ height: 20px;
222
+ line-height: 1;
223
+ background: #13161c;
224
+ border: 1px solid #e05555;
225
+ color: #e05555;
226
+ cursor: pointer;
227
+
228
+ .mk-single & {
229
+ position: static;
230
+ width: 24px;
231
+ height: 24px;
232
+ }
233
+ }
234
+ </style>