@mundogamernetwork/shared-ui 1.1.66 → 1.1.68

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,311 @@
1
+ <script lang="ts" setup>
2
+ import { ref, inject, onMounted } from "vue"
3
+ import { useRoute, navigateTo } from "#app"
4
+ import { useNuxtApp } from "#app"
5
+ import { revealKey, fetchKeyRequestById } from "../../services/campaignService"
6
+
7
+ const route = useRoute()
8
+ const { $i18n } = useNuxtApp()
9
+ const locale = $i18n.locale
10
+
11
+ const currentUser = inject<any>("currentUser", null)
12
+ const loginUrl = inject<string>("loginUrl", "/")
13
+
14
+ const requestId = Number(route.query.requestId)
15
+ const keyItemsParam = route.query.keyItems as string | undefined
16
+
17
+ const loading = ref(true)
18
+ const revealing = ref(false)
19
+ const keyCode = ref<string | null>(null)
20
+ const error = ref("")
21
+ const requestData = ref<any>(null)
22
+ const copied = ref(false)
23
+
24
+ onMounted(async () => {
25
+ if (!currentUser) {
26
+ const returnTo = typeof window !== "undefined" ? window.location.href : ""
27
+ navigateTo(`${loginUrl}?redirect_to=${encodeURIComponent(returnTo)}`, { external: true })
28
+ return
29
+ }
30
+ if (!requestId) {
31
+ navigateTo(`/${locale.value}/key-campaigns`)
32
+ return
33
+ }
34
+ try {
35
+ const res = await fetchKeyRequestById(requestId)
36
+ requestData.value = res?.data?.data || res?.data || null
37
+ } catch {
38
+ error.value = $i18n.t("keys.campaigns.error_loading")
39
+ } finally {
40
+ loading.value = false
41
+ }
42
+ })
43
+
44
+ async function reveal() {
45
+ revealing.value = true
46
+ error.value = ""
47
+ try {
48
+ const res = await revealKey(requestId)
49
+ const data = res?.data?.data || res?.data || null
50
+ keyCode.value = data?.key || data?.code || data?.value || null
51
+ if (!keyCode.value) {
52
+ error.value = $i18n.t("keys.campaigns.error_reveal")
53
+ }
54
+ } catch (e: any) {
55
+ error.value = e?.response?.data?.message || $i18n.t("keys.campaigns.error_reveal")
56
+ } finally {
57
+ revealing.value = false
58
+ }
59
+ }
60
+
61
+ async function copyCode() {
62
+ if (!keyCode.value) return
63
+ try {
64
+ await navigator.clipboard.writeText(keyCode.value)
65
+ copied.value = true
66
+ setTimeout(() => { copied.value = false }, 2000)
67
+ } catch {
68
+ // fallback: select text
69
+ }
70
+ }
71
+
72
+ function goBack() {
73
+ if (typeof window !== "undefined" && window.history.length > 1) {
74
+ window.history.back()
75
+ } else {
76
+ navigateTo(`/${locale.value}/key-campaigns`)
77
+ }
78
+ }
79
+ </script>
80
+
81
+ <template>
82
+ <div id="shared-redeem-key-approved">
83
+ <div class="container">
84
+ <div class="header">
85
+ <NuxtLink @click.prevent="goBack" class="back">
86
+ <MGIcon size="2x" icon="chevron-left" />
87
+ <span>{{ $t("keys.campaigns.back") }}</span>
88
+ </NuxtLink>
89
+ </div>
90
+
91
+ <div v-if="loading" class="state">
92
+ <div class="skeleton" v-for="i in 2" :key="i" />
93
+ </div>
94
+
95
+ <div v-else-if="error && !keyCode" class="state error">{{ error }}</div>
96
+
97
+ <div v-else class="content">
98
+ <div class="title-bar">
99
+ <MGIcon icon="version" size="2x" class="title-icon" />
100
+ <div>
101
+ <h3>{{ $t("keys.campaigns.status_key.reveal_key") }}</h3>
102
+ <p v-if="requestData?.key?.name" class="campaign-name">{{ requestData.key.name }}</p>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- Key not yet revealed -->
107
+ <div v-if="!keyCode" class="reveal-block">
108
+ <p class="hint">{{ $t("keys.campaigns.reveal_hint") }}</p>
109
+ <button
110
+ class="btn"
111
+ :class="{ disabled: revealing }"
112
+ :disabled="revealing"
113
+ @click="reveal"
114
+ >
115
+ {{ revealing ? $t("keys.campaigns.revealing") : $t("keys.campaigns.status_key.reveal_key") }}
116
+ </button>
117
+ <div v-if="error" class="req-error">{{ error }}</div>
118
+ </div>
119
+
120
+ <!-- Key revealed -->
121
+ <div v-else class="key-block">
122
+ <div class="key-label">{{ $t("keys.campaigns.your_key") }}</div>
123
+ <div class="key-code-row">
124
+ <span class="key-code">{{ keyCode }}</span>
125
+ <button class="copy-btn" @click="copyCode">
126
+ <MGIcon :icon="copied ? 'check' : 'copy'" size="1x" />
127
+ {{ copied ? $t("keys.campaigns.copied") : $t("keys.campaigns.copy") }}
128
+ </button>
129
+ </div>
130
+ <p class="redeem-info">{{ $t("keys.campaigns.redeem_on_platform") }}</p>
131
+ </div>
132
+
133
+ <!-- Request info -->
134
+ <div v-if="requestData" class="info-grid">
135
+ <div class="info-row" v-if="requestData.status_request?.name">
136
+ <span class="info-label">{{ $t("keys.campaigns.status") }}</span>
137
+ <span class="info-val">{{ requestData.status_request.name }}</span>
138
+ </div>
139
+ <div class="info-row" v-if="requestData.key?.name">
140
+ <span class="info-label">{{ $t("keys.campaigns.campaign.title") }}</span>
141
+ <span class="info-val">{{ requestData.key.name }}</span>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </template>
148
+
149
+ <style lang="scss" scoped>
150
+ #shared-redeem-key-approved {
151
+ align-items: center;
152
+ width: 100%;
153
+ justify-content: center;
154
+ overflow: auto;
155
+ height: 100%;
156
+
157
+ .container { max-width: 680px !important; padding: 32px 16px 64px; }
158
+
159
+ .header {
160
+ margin-bottom: 24px;
161
+ .back {
162
+ display: inline-flex;
163
+ align-items: center;
164
+ gap: 8px;
165
+ font-size: 14px;
166
+ color: var(--secondary-info-fg);
167
+ cursor: pointer;
168
+ text-decoration: none;
169
+ &:hover { color: var(--card-article-title); }
170
+ }
171
+ }
172
+
173
+ .state {
174
+ display: flex;
175
+ flex-direction: column;
176
+ align-items: center;
177
+ gap: 12px;
178
+ padding: 64px 0;
179
+ text-align: center;
180
+ color: var(--secondary-info-fg);
181
+ &.error { color: #ff4800; }
182
+ }
183
+
184
+ .skeleton {
185
+ width: 100%;
186
+ height: 48px;
187
+ background: var(--bg-app-badge);
188
+ animation: pulse 1.4s ease-in-out infinite;
189
+ & + & { margin-top: 8px; }
190
+ }
191
+
192
+ .content { display: flex; flex-direction: column; gap: 28px; }
193
+
194
+ .title-bar {
195
+ display: flex;
196
+ align-items: flex-start;
197
+ gap: 14px;
198
+ .title-icon { color: var(--key-accent, var(--primary, #D297FF)); margin-top: 2px; }
199
+ h3 { font-size: 20px; font-weight: 700; color: var(--card-article-title); margin: 0 0 4px; }
200
+ .campaign-name { font-size: 13px; color: var(--secondary-info-fg); margin: 0; }
201
+ }
202
+
203
+ .reveal-block {
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 16px;
207
+ padding: 24px;
208
+ background: var(--bg-app-badge);
209
+ border: 1px solid var(--button-secondary-default-bg);
210
+
211
+ .hint { font-size: 14px; color: var(--secondary-info-fg); margin: 0; }
212
+ }
213
+
214
+ .key-block {
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: 12px;
218
+ padding: 24px;
219
+ background: var(--bg-app-badge);
220
+ border: 1px solid var(--key-accent, var(--primary, #D297FF));
221
+ }
222
+
223
+ .key-label {
224
+ font-size: 12px;
225
+ font-weight: 600;
226
+ text-transform: uppercase;
227
+ letter-spacing: 0.5px;
228
+ color: var(--secondary-info-fg);
229
+ }
230
+
231
+ .key-code-row {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: 12px;
235
+ flex-wrap: wrap;
236
+ }
237
+
238
+ .key-code {
239
+ font-size: 20px;
240
+ font-weight: 700;
241
+ font-family: monospace;
242
+ letter-spacing: 2px;
243
+ color: var(--card-article-title);
244
+ word-break: break-all;
245
+ }
246
+
247
+ .copy-btn {
248
+ display: inline-flex;
249
+ align-items: center;
250
+ gap: 6px;
251
+ padding: 6px 14px;
252
+ font-size: 13px;
253
+ font-weight: 600;
254
+ background: transparent;
255
+ border: 1px solid var(--key-accent, var(--primary, #D297FF));
256
+ color: var(--key-accent, var(--primary, #D297FF));
257
+ cursor: pointer;
258
+ transition: background 0.15s;
259
+ &:hover { background: rgba(210, 151, 255, 0.1); }
260
+ }
261
+
262
+ .redeem-info {
263
+ font-size: 12px;
264
+ color: var(--secondary-info-fg);
265
+ margin: 0;
266
+ }
267
+
268
+ .req-error { font-size: 13px; color: #ff4800; }
269
+
270
+ .info-grid {
271
+ display: flex;
272
+ flex-direction: column;
273
+ gap: 8px;
274
+ padding-top: 8px;
275
+ border-top: 1px solid var(--button-secondary-default-bg);
276
+ }
277
+
278
+ .info-row {
279
+ display: flex;
280
+ gap: 12px;
281
+ font-size: 13px;
282
+ }
283
+
284
+ .info-label {
285
+ color: var(--secondary-info-fg);
286
+ min-width: 100px;
287
+ }
288
+
289
+ .info-val { color: var(--card-article-title); }
290
+
291
+ .btn {
292
+ align-self: flex-start;
293
+ padding: 0 28px;
294
+ height: 44px;
295
+ font-size: 14px;
296
+ font-weight: 700;
297
+ background: var(--key-accent, var(--primary, #D297FF));
298
+ color: #fff;
299
+ border: none;
300
+ cursor: pointer;
301
+ transition: opacity 0.15s;
302
+ &.disabled, &:disabled { opacity: 0.45; cursor: not-allowed; }
303
+ &:not(.disabled):hover { opacity: 0.85; }
304
+ }
305
+ }
306
+
307
+ @keyframes pulse {
308
+ 0%, 100% { opacity: 1; }
309
+ 50% { opacity: 0.4; }
310
+ }
311
+ </style>
@@ -0,0 +1,320 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, inject, onMounted } from "vue"
3
+ import { useRoute, navigateTo } from "#app"
4
+ import { useNuxtApp } from "#app"
5
+ import {
6
+ fetchCampaignBySlug,
7
+ fetchKeyPlatforms,
8
+ fetchKeyRegions,
9
+ requestKey,
10
+ } from "../../services/campaignService"
11
+
12
+ const route = useRoute()
13
+ const { $i18n } = useNuxtApp()
14
+ const locale = $i18n.locale
15
+
16
+ const currentUser = inject<any>("currentUser", null)
17
+ const loginUrl = inject<string>("loginUrl", "/")
18
+
19
+ const keyId = Number(route.query.keyId)
20
+
21
+ // Redirect away if no keyId
22
+ if (!keyId) {
23
+ navigateTo(`/${locale.value}/key-campaigns`)
24
+ }
25
+
26
+ const campaign = ref<any>(null)
27
+ const platforms = ref<any[]>([])
28
+ const regions = ref<any[]>([])
29
+ const loading = ref(true)
30
+ const submitting = ref(false)
31
+ const error = ref("")
32
+ const success = ref(false)
33
+ const requestError = ref("")
34
+
35
+ const selectedPlatformId = ref<number | null>(null)
36
+ const selectedRegionId = ref<number | null>(null)
37
+ const description = ref("")
38
+
39
+ onMounted(async () => {
40
+ if (!currentUser) {
41
+ const returnTo = typeof window !== "undefined" ? window.location.href : ""
42
+ navigateTo(`${loginUrl}?redirect_to=${encodeURIComponent(returnTo)}`, { external: true })
43
+ return
44
+ }
45
+ try {
46
+ const [campaignRes, platformsRes, regionsRes] = await Promise.all([
47
+ fetchCampaignBySlug(String(keyId)),
48
+ fetchKeyPlatforms(keyId),
49
+ fetchKeyRegions(keyId),
50
+ ])
51
+ campaign.value = campaignRes?.data?.data || campaignRes?.data || null
52
+ platforms.value = platformsRes?.data?.data || platformsRes?.data || []
53
+ regions.value = regionsRes?.data?.data || regionsRes?.data || []
54
+
55
+ // Pre-select if only one option
56
+ if (platforms.value.length === 1) selectedPlatformId.value = platforms.value[0].id
57
+ if (regions.value.length === 1) selectedRegionId.value = regions.value[0].id
58
+ } catch {
59
+ error.value = $i18n.t("keys.campaigns.error_loading")
60
+ } finally {
61
+ loading.value = false
62
+ }
63
+ })
64
+
65
+ const canSubmit = computed(() =>
66
+ !!selectedPlatformId.value && !!selectedRegionId.value && !submitting.value,
67
+ )
68
+
69
+ async function submit() {
70
+ if (!canSubmit.value) return
71
+ submitting.value = true
72
+ requestError.value = ""
73
+ try {
74
+ await requestKey({
75
+ key_id: keyId,
76
+ platform_ids: [selectedPlatformId.value],
77
+ region_ids: [selectedRegionId.value],
78
+ description: description.value || "-",
79
+ quantity_keys: 1,
80
+ })
81
+ success.value = true
82
+ setTimeout(() => {
83
+ navigateTo(`/${locale.value}/key-campaigns`)
84
+ }, 2000)
85
+ } catch (e: any) {
86
+ const msg = e?.response?.data?.message || e?.message || ""
87
+ requestError.value = msg || $i18n.t("keys.campaigns.error_request")
88
+ } finally {
89
+ submitting.value = false
90
+ }
91
+ }
92
+
93
+ function goBack() {
94
+ if (typeof window !== "undefined" && window.history.length > 1) {
95
+ window.history.back()
96
+ } else {
97
+ navigateTo(`/${locale.value}/key-campaigns`)
98
+ }
99
+ }
100
+ </script>
101
+
102
+ <template>
103
+ <div id="shared-redeem-key">
104
+ <div class="container">
105
+ <div class="header">
106
+ <NuxtLink @click.prevent="goBack" class="back">
107
+ <MGIcon size="2x" icon="chevron-left" />
108
+ <span>{{ $t("keys.campaigns.back") }}</span>
109
+ </NuxtLink>
110
+ </div>
111
+
112
+ <div v-if="loading" class="state">
113
+ <div class="skeleton" v-for="i in 3" :key="i" />
114
+ </div>
115
+
116
+ <div v-else-if="error" class="state error">{{ error }}</div>
117
+
118
+ <div v-else-if="success" class="state success">
119
+ <MGIcon icon="check-circle" size="3x" />
120
+ <p>{{ $t("keys.campaigns.request_sent") }}</p>
121
+ </div>
122
+
123
+ <div v-else class="form-content">
124
+ <div class="title-bar">
125
+ <h3>{{ $t("keys.campaigns.redeem_title") }}</h3>
126
+ <p v-if="campaign?.name" class="campaign-name">{{ campaign.name }}</p>
127
+ </div>
128
+
129
+ <form class="form" @submit.prevent="submit">
130
+ <!-- Platform -->
131
+ <div class="field">
132
+ <label class="label">{{ $t("keys.campaigns.platform") }}</label>
133
+ <div class="options">
134
+ <button
135
+ v-for="p in platforms"
136
+ :key="p.id"
137
+ type="button"
138
+ class="option"
139
+ :class="{ selected: selectedPlatformId === p.id }"
140
+ @click="selectedPlatformId = p.id"
141
+ >
142
+ {{ p.name }}
143
+ </button>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Region -->
148
+ <div class="field">
149
+ <label class="label">{{ $t("keys.campaigns.region") }}</label>
150
+ <div class="options">
151
+ <button
152
+ v-for="r in regions"
153
+ :key="r.id"
154
+ type="button"
155
+ class="option"
156
+ :class="{ selected: selectedRegionId === r.id }"
157
+ @click="selectedRegionId = r.id"
158
+ >
159
+ {{ r.name }}
160
+ </button>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Description (optional) -->
165
+ <div class="field">
166
+ <label class="label">{{ $t("keys.campaigns.description_optional") }}</label>
167
+ <textarea
168
+ v-model="description"
169
+ class="textarea"
170
+ :placeholder="$t('keys.campaigns.description_placeholder')"
171
+ rows="3"
172
+ />
173
+ </div>
174
+
175
+ <div v-if="requestError" class="req-error">{{ requestError }}</div>
176
+
177
+ <button
178
+ type="submit"
179
+ class="btn"
180
+ :class="{ disabled: !canSubmit }"
181
+ :disabled="!canSubmit"
182
+ >
183
+ <span v-if="submitting">{{ $t("keys.campaigns.sending") }}</span>
184
+ <span v-else>{{ $t("keys.campaigns.campaign.redeem") }}</span>
185
+ </button>
186
+ </form>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </template>
191
+
192
+ <style lang="scss" scoped>
193
+ #shared-redeem-key {
194
+ align-items: center;
195
+ width: 100%;
196
+ justify-content: center;
197
+ overflow: auto;
198
+ height: 100%;
199
+
200
+ .container { max-width: 680px !important; padding: 32px 16px 64px; }
201
+
202
+ .header {
203
+ margin-bottom: 24px;
204
+ .back {
205
+ display: inline-flex;
206
+ align-items: center;
207
+ gap: 8px;
208
+ font-size: 14px;
209
+ color: var(--secondary-info-fg);
210
+ cursor: pointer;
211
+ text-decoration: none;
212
+ &:hover { color: var(--card-article-title); }
213
+ }
214
+ }
215
+
216
+ .state {
217
+ display: flex;
218
+ flex-direction: column;
219
+ align-items: center;
220
+ gap: 12px;
221
+ padding: 64px 0;
222
+ text-align: center;
223
+ color: var(--secondary-info-fg);
224
+
225
+ &.error { color: #ff4800; }
226
+ &.success { color: #4caf50; font-size: 16px; }
227
+ }
228
+
229
+ .skeleton {
230
+ width: 100%;
231
+ height: 48px;
232
+ background: var(--bg-app-badge);
233
+ animation: pulse 1.4s ease-in-out infinite;
234
+ & + & { margin-top: 8px; }
235
+ }
236
+
237
+ .title-bar {
238
+ margin-bottom: 28px;
239
+ h3 { font-size: 20px; font-weight: 700; color: var(--card-article-title); margin: 0 0 6px; }
240
+ .campaign-name { font-size: 13px; color: var(--secondary-info-fg); margin: 0; }
241
+ }
242
+
243
+ .form { display: flex; flex-direction: column; gap: 24px; }
244
+
245
+ .field {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 10px;
249
+ }
250
+
251
+ .label {
252
+ font-size: 13px;
253
+ font-weight: 600;
254
+ color: var(--card-article-title);
255
+ }
256
+
257
+ .options {
258
+ display: flex;
259
+ flex-wrap: wrap;
260
+ gap: 8px;
261
+ }
262
+
263
+ .option {
264
+ padding: 8px 16px;
265
+ font-size: 13px;
266
+ font-weight: 500;
267
+ border: 1px solid var(--button-secondary-default-bg);
268
+ background: transparent;
269
+ color: var(--secondary-info-fg);
270
+ cursor: pointer;
271
+ transition: border-color 0.15s, color 0.15s;
272
+
273
+ &:hover { border-color: var(--key-accent, var(--primary, #D297FF)); color: var(--card-article-title); }
274
+ &.selected {
275
+ border-color: var(--key-accent, var(--primary, #D297FF));
276
+ color: var(--key-accent, var(--primary, #D297FF));
277
+ background: rgba(210, 151, 255, 0.08);
278
+ }
279
+ }
280
+
281
+ .textarea {
282
+ width: 100%;
283
+ background: var(--bg-app-badge);
284
+ border: 1px solid var(--button-secondary-default-bg);
285
+ color: var(--card-article-title);
286
+ font-size: 13px;
287
+ padding: 10px 12px;
288
+ resize: vertical;
289
+ outline: none;
290
+ font-family: inherit;
291
+ &:focus { border-color: var(--key-accent, var(--primary, #D297FF)); }
292
+ }
293
+
294
+ .req-error {
295
+ font-size: 13px;
296
+ color: #ff4800;
297
+ }
298
+
299
+ .btn {
300
+ align-self: flex-start;
301
+ padding: 0 28px;
302
+ height: 44px;
303
+ font-size: 14px;
304
+ font-weight: 700;
305
+ background: var(--key-accent, var(--primary, #D297FF));
306
+ color: #fff;
307
+ border: none;
308
+ cursor: pointer;
309
+ transition: opacity 0.15s;
310
+
311
+ &.disabled, &:disabled { opacity: 0.45; cursor: not-allowed; }
312
+ &:not(.disabled):hover { opacity: 0.85; }
313
+ }
314
+ }
315
+
316
+ @keyframes pulse {
317
+ 0%, 100% { opacity: 1; }
318
+ 50% { opacity: 0.4; }
319
+ }
320
+ </style>
@@ -181,7 +181,6 @@ useSeoMeta({
181
181
  <img v-if="kit.cover_image_url" class="kit-cover-poster" :src="kit.cover_image_url" :alt="kit.name" />
182
182
  <div class="kit-cover-text">
183
183
  <span v-if="kit.release_status" class="kit-eyebrow">{{ String(kit.release_status).replace(/_/g, ' ') }}</span>
184
- <img v-if="kit.logo_url" class="kit-cover-logo-img" :src="kit.logo_url" :alt="kit.name" />
185
184
  <h1 class="kit-cover-title">{{ kit.name }}</h1>
186
185
  <p v-if="kit.tagline" class="kit-tagline">{{ kit.tagline }}</p>
187
186
  <p v-if="kit.developer" class="kit-kicker">{{ kit.developer }}</p>
@@ -1,9 +1,15 @@
1
1
  export default defineNuxtPlugin({
2
2
  name: "shared-ui-auth",
3
- enforce: "pre",
4
- async setup() {
3
+ async setup(nuxtApp) {
5
4
  try {
6
- const authStore = useAuthStore();
5
+ // Pinia must be installed first. Resolve it from nuxtApp and bail out
6
+ // if it isn't ready yet (mirrors middleware/auth.global.ts). Running
7
+ // with enforce:"pre" used to execute this before @pinia/nuxt, so
8
+ // useAuthStore() hit an undefined pinia (`reading '_s'`).
9
+ const pinia = (nuxtApp as any).$pinia;
10
+ if (!pinia) return;
11
+
12
+ const authStore = useAuthStore(pinia);
7
13
 
8
14
  if (!authStore.signedIn) {
9
15
  await authStore.getUser();
@@ -8,14 +8,21 @@ import axios, { type AxiosInstance } from "axios"
8
8
  let _mainClient: AxiosInstance | null = null
9
9
  let _authClient: AxiosInstance | null = null
10
10
 
11
+ // Strip /api/v1 suffix so service paths (/api/v1/...) don't double up when the
12
+ // env var already contains it (e.g. VITE_BASE_URL_NETWORK=https://host/api/v1).
13
+ function normalizeBaseUrl(url: string): string {
14
+ return url.replace(/\/api\/v1\/?$/, "")
15
+ }
16
+
11
17
  function getMainClient(): AxiosInstance {
12
18
  if (_mainClient) return _mainClient
13
19
 
14
- const baseURL =
20
+ const baseURL = normalizeBaseUrl(
15
21
  import.meta.env.VITE_CAMPAIGN_API_URL ||
16
22
  import.meta.env.VITE_BASE_URL_NETWORK ||
17
23
  import.meta.env.VITE_API_BASE_URL ||
18
- ""
24
+ "",
25
+ )
19
26
 
20
27
  _mainClient = axios.create({ baseURL })
21
28
 
@@ -41,11 +48,12 @@ function getMainClient(): AxiosInstance {
41
48
  function getAuthClient(): AxiosInstance {
42
49
  if (_authClient) return _authClient
43
50
 
44
- const baseURL =
51
+ const baseURL = normalizeBaseUrl(
45
52
  import.meta.env.VITE_CAMPAIGN_API_URL ||
46
53
  import.meta.env.VITE_BASE_URL_NETWORK ||
47
54
  import.meta.env.VITE_API_BASE_URL ||
48
- ""
55
+ "",
56
+ )
49
57
 
50
58
  _authClient = axios.create({ baseURL, withCredentials: true })
51
59