webs-sdk 0.19.64 → 0.19.65
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/AGENTS.md +44 -0
- package/CLAUDE.md +80 -0
- package/README.md +513 -490
- package/dist/components/ARFilters/ARFilters.js +54 -54
- package/dist/components/ArtGenerator.js +141 -141
- package/dist/components/AvatarAI.js +14 -14
- package/dist/components/CookieConsent/constants.js +31 -31
- package/dist/components/CreativeFaceSwap.js +23 -23
- package/dist/components/GifsIa.js +76 -76
- package/dist/components/MemeGenerator.js +12 -12
- package/dist/components/PasswordReset/constants.js +33 -33
- package/dist/components/RecoveryPassword/constants.js +27 -27
- package/dist/components/SpinningWheel/SpinningWheel.js +7 -7
- package/dist/components/SpinningWheel/index.d.ts +1 -1
- package/dist/components/SpinningWheel/index.d.ts.map +1 -1
- package/dist/components/SpinningWheel/index.js +2 -1
- package/dist/components/SpinningWheel/index.js.map +1 -1
- package/dist/components/ui/ConfiguredMediaPlayer.d.ts +49 -0
- package/dist/components/ui/ConfiguredMediaPlayer.d.ts.map +1 -0
- package/dist/components/ui/ConfiguredMediaPlayer.js +111 -0
- package/dist/components/ui/ConfiguredMediaPlayer.js.map +1 -0
- package/dist/components/ui/badge.d.ts +1 -0
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/compose-refs.d.ts +6 -0
- package/dist/components/ui/compose-refs.d.ts.map +1 -0
- package/dist/components/ui/compose-refs.js +57 -0
- package/dist/components/ui/compose-refs.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +3 -1
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +2 -2
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +2 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/components/ui/media-player.d.ts +127 -0
- package/dist/components/ui/media-player.d.ts.map +1 -0
- package/dist/components/ui/media-player.js +1633 -0
- package/dist/components/ui/media-player.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +3 -1
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +2 -2
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components/ui/use-lazy-ref.d.ts +4 -0
- package/dist/components/ui/use-lazy-ref.d.ts.map +1 -0
- package/dist/components/ui/use-lazy-ref.js +45 -0
- package/dist/components/ui/use-lazy-ref.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/network.d.ts +12 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +15 -0
- package/dist/network.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +74 -0
- package/dist/server.js.map +1 -0
- package/dist/types/media-chrome-store-types.d.ts +107 -0
- package/dist/types/media-chrome-store-types.d.ts.map +1 -0
- package/dist/types/media-chrome-store-types.js +3 -0
- package/dist/types/media-chrome-store-types.js.map +1 -0
- package/dist/utils/QRCode.d.ts +1 -0
- package/dist/utils/QRCode.d.ts.map +1 -0
- package/dist/utils/QRCode.js +2 -0
- package/dist/utils/QRCode.js.map +1 -0
- package/dist/utils/imageValidation.d.ts +6 -0
- package/dist/utils/imageValidation.d.ts.map +1 -0
- package/dist/utils/imageValidation.js +64 -0
- package/dist/utils/imageValidation.js.map +1 -0
- package/docs/CONFIGURED_MEDIA_PLAYER.md +40 -0
- package/docs/CORP_AUTH_USAGE.md +687 -0
- package/docs/EXAMPLES_WALLPAPERSNAME_IMPLEMENTATION.md +1445 -0
- package/docs/EXAMPLE_ENDPOINTS_COMUNICATION.md +55 -0
- package/docs/EXTERNAL_SDK_GUIDE.md +1340 -0
- package/docs/FOR_YOU_360_FACESWAP_FIX.md +400 -0
- package/docs/FOR_YOU_IMPLEMENTATION_GUIDE.md +1413 -0
- package/docs/FOR_YOU_QUIZ_ARCHITECTURE.md +753 -0
- package/docs/FOR_YOU_SDK_WALLPAPERSNAME_BUG.md +106 -0
- package/docs/FOR_YOU_WALLPAPERSNAME_ENHANCEMENT_PLAN.md +210 -0
- package/docs/LEGAL_USAGE.md +190 -0
- package/docs/STREAK_IMPLEMENTATION_GUIDE.md +430 -0
- package/package.json +106 -84
- package/public/spin2win/assets/border-afristream.svg +26 -26
- package/public/spin2win/assets/border-efc.svg +23 -23
- package/public/spin2win/assets/border-streetreel.svg +23 -23
- package/public/spin2win/assets/border-totalgym.svg +27 -27
- package/public/spin2win/assets/border-womantoday.svg +26 -26
- package/public/spin2win/assets/indicator-afristream.svg +28 -28
- package/public/spin2win/assets/indicator-efc.svg +29 -29
- package/public/spin2win/assets/indicator-streetreel.svg +29 -29
- package/public/spin2win/assets/indicator-totalgym.svg +29 -29
- package/public/spin2win/fonts/switzer/README.md +22 -22
- package/scripts/install-agent-skill.cjs +34 -0
- package/skills/webs-sdk-package/SKILL.md +11 -0
- package/skills/webs-sdk-package/references/components.md +30 -0
- package/skills/webs-sdk-package/references/integration.md +32 -0
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
# ForYou — Guía de implementación del SDK
|
|
2
|
+
|
|
3
|
+
> **Versión SDK:** 0.18.83+
|
|
4
|
+
> **Componente principal:** `ForYouPage`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Índice
|
|
9
|
+
|
|
10
|
+
1. [Concepto](#concepto)
|
|
11
|
+
2. [Sistema anterior vs. sistema nuevo](#sistema-anterior-vs-sistema-nuevo)
|
|
12
|
+
3. [Instalación](#instalación)
|
|
13
|
+
4. [Uso básico — ForYouPage](#uso-básico--foryoupage)
|
|
14
|
+
5. [Internacionalización (locale)](#internacionalización-locale)
|
|
15
|
+
6. [Navegación](#navegación)
|
|
16
|
+
7. [Endpoints — modos de llamada a API](#endpoints--modos-de-llamada-a-api)
|
|
17
|
+
8. [Referencia de props: ForYouPage](#referencia-de-props-foryoupage)
|
|
18
|
+
9. [Referencia de props: ForYouFeatureConfig](#referencia-de-props-foryoufeatureconfig)
|
|
19
|
+
10. [Referencia de props: ForYouCardVisual](#referencia-de-props-foryoucardvisual)
|
|
20
|
+
11. [Referencia de props: ForYouPageTheme](#referencia-de-props-foryoupagetheme)
|
|
21
|
+
12. [forYouFetch — helper de API](#foryoufetch--helper-de-api)
|
|
22
|
+
13. [Features disponibles](#features-disponibles)
|
|
23
|
+
14. [Uso de hooks fuera de ForYouPage](#uso-de-hooks-fuera-de-foryoupage)
|
|
24
|
+
15. [Ejemplo completo Winsports](#ejemplo-completo-winsports)
|
|
25
|
+
16. [Descargas y CORS — Proxy de medios](#descargas-y-cors--proxy-de-medios)
|
|
26
|
+
17. [Feature: CreativeFaceSwap — ejemplos + endpoints](#feature-creativefaceswap--ejemplos--endpoints)
|
|
27
|
+
18. [Migración desde el sistema anterior](#migración-desde-el-sistema-anterior)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Concepto
|
|
32
|
+
|
|
33
|
+
`ForYouPage` es el único componente que necesitas importar para montar la sección completa de ForYou (Fanzone / Para Ti) en cualquier portal.
|
|
34
|
+
|
|
35
|
+
**Antes (sistema antiguo):** importabas componente a componente y construías la página en el proyecto.
|
|
36
|
+
**Ahora (sistema nuevo):** un único import, toda la lógica configurada vía props.
|
|
37
|
+
|
|
38
|
+
Ambos sistemas son **100% compatibles y coexistentes**. Puedes usar el sistema anterior en paralelo al nuevo sin conflicto.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Sistema anterior vs. sistema nuevo
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// ─── SISTEMA ANTERIOR ─────────────────────────────────────────────────────────
|
|
46
|
+
// Una feature = un import + su propia página con navegación manual
|
|
47
|
+
|
|
48
|
+
import { Wallpapers, Quiz, Ringtone } from 'webs-sdk'
|
|
49
|
+
|
|
50
|
+
function ForYouPage() {
|
|
51
|
+
const [active, setActive] = useState<string | null>(null)
|
|
52
|
+
// ...construir navegación, botones de volver, breadcrumbs, etc.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── SISTEMA NUEVO ────────────────────────────────────────────────────────────
|
|
56
|
+
// Un único import, toda la página configurada vía props
|
|
57
|
+
|
|
58
|
+
import { ForYouPage } from 'webs-sdk'
|
|
59
|
+
|
|
60
|
+
function MyForYouPage() {
|
|
61
|
+
return (
|
|
62
|
+
<ForYouPage
|
|
63
|
+
locale="es"
|
|
64
|
+
features={{
|
|
65
|
+
wallpapers: { card: { desktopImage: '/img/wallpapers.jpg' } },
|
|
66
|
+
quiz: { card: { desktopImage: '/img/quiz.jpg' } },
|
|
67
|
+
ringtone: { card: { desktopImage: '/img/ringtone.jpg' } },
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Instalación
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install webs-sdk
|
|
80
|
+
# o
|
|
81
|
+
yarn add webs-sdk
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Uso básico — ForYouPage
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { ForYouPage } from 'webs-sdk'
|
|
90
|
+
|
|
91
|
+
export default function ForYouSection() {
|
|
92
|
+
return (
|
|
93
|
+
<ForYouPage
|
|
94
|
+
locale="es"
|
|
95
|
+
features={{
|
|
96
|
+
ringtone: {
|
|
97
|
+
card: {
|
|
98
|
+
desktopImage: '/images/ringtone-desktop.jpg',
|
|
99
|
+
mobileImage: '/images/ringtone-mobile.jpg',
|
|
100
|
+
ctaColor: '#FF5500',
|
|
101
|
+
backgroundColor: '#F9F9F9',
|
|
102
|
+
},
|
|
103
|
+
config: { tags: 'ringtone' },
|
|
104
|
+
},
|
|
105
|
+
wallpapers: {
|
|
106
|
+
card: {
|
|
107
|
+
desktopImage: '/images/wallpapers-desktop.jpg',
|
|
108
|
+
imageOnRight: false,
|
|
109
|
+
ctaColor: '#E10600',
|
|
110
|
+
},
|
|
111
|
+
config: { tags: 'wallpaper' },
|
|
112
|
+
},
|
|
113
|
+
quiz: {
|
|
114
|
+
card: {
|
|
115
|
+
desktopImage: '/images/quiz-desktop.jpg',
|
|
116
|
+
ctaColor: '#0066CC',
|
|
117
|
+
},
|
|
118
|
+
config: { tags: 'quiz' },
|
|
119
|
+
},
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**¿Qué hace esto?**
|
|
127
|
+
- Muestra un **hub** con las cards de cada feature.
|
|
128
|
+
- Al pulsar una card, navega internamente a la vista del componente.
|
|
129
|
+
- Botón "Volver" con icono SVG para regresar al hub.
|
|
130
|
+
- Scroll automático al tope al entrar/salir.
|
|
131
|
+
- URL actualizada con `?feature=<key>` para deep-linking.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Internacionalización (locale)
|
|
136
|
+
|
|
137
|
+
El SDK incluye traducciones para **8 idiomas**:
|
|
138
|
+
|
|
139
|
+
| Código | Idioma | RTL |
|
|
140
|
+
|--------|--------|-----|
|
|
141
|
+
| `en` | English (default) | No |
|
|
142
|
+
| `es` | Español | No |
|
|
143
|
+
| `fr` | Français | No |
|
|
144
|
+
| `pl` | Polski | No |
|
|
145
|
+
| `nl` | Nederlands | No |
|
|
146
|
+
| `tr` | Türkçe | No |
|
|
147
|
+
| `ro` | Română | No |
|
|
148
|
+
| `ar` | العربية | **Sí** |
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
// Solo pasar el locale — el SDK usa sus propias traducciones
|
|
152
|
+
<ForYouPage locale="es" features={...} />
|
|
153
|
+
|
|
154
|
+
// Override puntual — solo los textos que necesites cambiar
|
|
155
|
+
<ForYouPage
|
|
156
|
+
locale="es"
|
|
157
|
+
translations={{
|
|
158
|
+
backButtonText: '← Atrás',
|
|
159
|
+
cards: {
|
|
160
|
+
ringtone: { title: 'Mis Tonos', ctaText: 'Ver más' },
|
|
161
|
+
},
|
|
162
|
+
}}
|
|
163
|
+
features={...}
|
|
164
|
+
/>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Árabe (RTL):** al pasar `locale="ar"`, el SDK añade automáticamente `dir="rtl"` al contenedor raíz.
|
|
168
|
+
|
|
169
|
+
### Lógica de merge
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
SDK defaults 'en' → SDK defaults [locale] → translations (proyecto)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Los textos del proyecto **siempre tienen prioridad** sobre los del SDK.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Navegación
|
|
180
|
+
|
|
181
|
+
`ForYouPage` gestiona toda la navegación internamente:
|
|
182
|
+
|
|
183
|
+
- **`?feature=<key>` en la URL** — se actualiza con `window.history.pushState` al entrar/salir. Permite deep-linking directo (p.ej.: `/fanzone?feature=quiz` abre el quiz directamente).
|
|
184
|
+
- **Botón "Atrás" del navegador** — listener `popstate` que vuelve al hub o a la feature anterior.
|
|
185
|
+
- **Scroll automático** — `containerRef.scrollIntoView()` al cambiar de vista.
|
|
186
|
+
- **`initialFeature` prop** — para arrancar directamente en una feature desde el código.
|
|
187
|
+
- **`hideBreadcrumb` en Wallpapers** — cuando Wallpapers se renderiza dentro de ForYouPage, su breadcrumb interno se oculta automáticamente (el hub ya proporciona navegación).
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// Abrir directamente en quiz (p.ej. desde un banner externo)
|
|
191
|
+
<ForYouPage initialFeature="quiz" features={...} />
|
|
192
|
+
|
|
193
|
+
// O leer el query param manualmente y pasarlo
|
|
194
|
+
const feature = new URLSearchParams(location.search).get('feature')
|
|
195
|
+
<ForYouPage initialFeature={feature as keyof ForYouFeatures} features={...} />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Endpoints — modos de llamada a API
|
|
201
|
+
|
|
202
|
+
### Modo 1: URL directa (sistema anterior — compatible)
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
config: {
|
|
206
|
+
apiUrl: '/api/get_contents_by_tags',
|
|
207
|
+
tags: 'quiz',
|
|
208
|
+
locale: 'es',
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Modo 2: apiBaseUrl (SDK construye la URL)
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
config: {
|
|
216
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
217
|
+
tags: ['quizws', 'quiz'],
|
|
218
|
+
locale: 'es',
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
El SDK mapea el preset `getGlobalContentsByTags` → `/api/get_contents_by_tags`.
|
|
223
|
+
|
|
224
|
+
### Modo 3: Multi-preset (múltiples llamadas en paralelo)
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
config: {
|
|
228
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
229
|
+
locale: 'es',
|
|
230
|
+
contents: [
|
|
231
|
+
{
|
|
232
|
+
preset: 'getGlobalContentsByIDs',
|
|
233
|
+
includeIds: [103433, 103420, 103396, 103371],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
preset: 'getGlobalContentsByTags',
|
|
237
|
+
tags: ['quizws'],
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Presets disponibles:**
|
|
244
|
+
|
|
245
|
+
| Preset | Endpoint |
|
|
246
|
+
|--------|----------|
|
|
247
|
+
| `getGlobalContentsByTags` | `/api/get_contents_by_tags` |
|
|
248
|
+
| `getGlobalContentsByIDs` | `/api/get_contents_by_tags` (con `include_ids`) |
|
|
249
|
+
| `getContentsByCategory` | `/api/get_global_api_contents_by_category` |
|
|
250
|
+
| `getCollectionByTags` | `/api/get_global_api_collections_by_tags` |
|
|
251
|
+
| `getContentPreset` | `/api/content-preset` |
|
|
252
|
+
|
|
253
|
+
Los resultados de múltiples llamadas se fusionan y **deduplicados por `id`**.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Referencia de props: ForYouPage
|
|
258
|
+
|
|
259
|
+
| Prop | Tipo | Default | Descripción |
|
|
260
|
+
|------|------|---------|-------------|
|
|
261
|
+
| `features` | `ForYouFeatures` | **requerido** | Features a mostrar. Cada key con `card` aparece en el hub. |
|
|
262
|
+
| `order` | `Array<keyof ForYouFeatures>` | Orden del objeto | Orden de renderizado de las cards en el hub. |
|
|
263
|
+
| `hubBanner` | `ForYouHubBanner` | - | Banner hero opcional encima de las cards. |
|
|
264
|
+
| `locale` | `ForYouLocale` | `'en'` | Idioma del SDK. |
|
|
265
|
+
| `translations` | `ForYouPageTranslations` | SDK defaults | Override de textos sobre el locale. |
|
|
266
|
+
| `pageTheme` | `ForYouPageTheme` | - | Tema visual de la página. |
|
|
267
|
+
| `initialFeature` | `keyof ForYouFeatures` | - | Feature que se abre al montar. |
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Referencia de props: WallpapersFeatureOverride — tabs
|
|
272
|
+
|
|
273
|
+
El feature `wallpapers` soporta un selector de pestañas opcionales para filtrar contenido por categoría (como en Winsports). Se activa añadiendo `tabs` a la configuración del feature.
|
|
274
|
+
|
|
275
|
+
| Prop | Tipo | Descripción |
|
|
276
|
+
|------|------|-------------|
|
|
277
|
+
| `tabs` | `WallpapersTab[]` | Array de pestañas. Si se define, aparece el selector encima del componente. |
|
|
278
|
+
| `tabsTheme` | `object` | Personalización visual del selector de tabs (ver tabla abajo). |
|
|
279
|
+
|
|
280
|
+
### WallpapersTab
|
|
281
|
+
|
|
282
|
+
| Prop | Tipo | Descripción |
|
|
283
|
+
|------|------|-------------|
|
|
284
|
+
| `label` | `string` | Texto visible en la pestaña. |
|
|
285
|
+
| `tag` | `string` | Tag de API para filtrar los wallpapers de esta categoría. |
|
|
286
|
+
|
|
287
|
+
### tabsTheme
|
|
288
|
+
|
|
289
|
+
| Prop | Tipo | Default | Descripción |
|
|
290
|
+
|------|------|---------|-------------|
|
|
291
|
+
| `activeColor` | `string` | `'#001B5E'` | Fondo de la tab activa. |
|
|
292
|
+
| `activeTextColor` | `string` | `'#FFFFFF'` | Texto de la tab activa. |
|
|
293
|
+
| `inactiveColor` | `string` | `'transparent'` | Fondo de la tab inactiva. |
|
|
294
|
+
| `inactiveTextColor` | `string` | `'#001B5E'` | Texto de la tab inactiva. |
|
|
295
|
+
| `borderColor` | `string` | `'#E5E7EB'` | Color del borde inferior del selector. |
|
|
296
|
+
|
|
297
|
+
### Ejemplo con tabs (estilo Winsports)
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
<ForYouPage
|
|
301
|
+
locale="es"
|
|
302
|
+
features={{
|
|
303
|
+
wallpapers: {
|
|
304
|
+
card: {
|
|
305
|
+
desktopImage: 'https://cdn.winsports.co/fanzone/wallpapers-card.jpg',
|
|
306
|
+
ctaColor: '#E6001E',
|
|
307
|
+
},
|
|
308
|
+
config: {
|
|
309
|
+
apiUrl: '/api/get_contents_by_tags',
|
|
310
|
+
locale: 'es',
|
|
311
|
+
website_id: '42',
|
|
312
|
+
heroBannerImage: 'https://cdn.winsports.co/fanzone/wallpapers-banner.jpg',
|
|
313
|
+
},
|
|
314
|
+
tabs: [
|
|
315
|
+
{ label: 'Fútbol', tag: 'wallpaper-futbol' },
|
|
316
|
+
{ label: 'Ciclismo', tag: 'wallpaper-ciclismo' },
|
|
317
|
+
{ label: 'Béisbol', tag: 'wallpaper-beisbol' },
|
|
318
|
+
{ label: 'Todos', tag: 'wallpaper-winsports' },
|
|
319
|
+
],
|
|
320
|
+
tabsTheme: {
|
|
321
|
+
activeColor: '#E6001E',
|
|
322
|
+
activeTextColor: '#FFFFFF',
|
|
323
|
+
inactiveTextColor: '#333333',
|
|
324
|
+
borderColor: '#E5E7EB',
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Sin tabs (por defecto)
|
|
332
|
+
|
|
333
|
+
Si no se define `tabs`, el componente muestra todos los wallpapers del tag configurado en `config.tags`:
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
wallpapers: {
|
|
337
|
+
card: { desktopImage: '/img/wallpapers.jpg' },
|
|
338
|
+
config: { apiUrl: '/api/get_contents_by_tags', tags: 'wallpaper' },
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Referencia de props: ForYouHubBanner
|
|
345
|
+
|
|
346
|
+
Banner hero configurable que aparece sobre las cards en la vista hub. Completamente opcional.
|
|
347
|
+
|
|
348
|
+
| Prop | Tipo | Default | Descripción |
|
|
349
|
+
|------|------|---------|-------------|
|
|
350
|
+
| `desktopImage` | `string` | **requerido** | Imagen de fondo desktop. |
|
|
351
|
+
| `mobileImage` | `string` | = desktopImage | Imagen de fondo mobile. |
|
|
352
|
+
| `imageAlt` | `string` | `''` | Alt text de la imagen. |
|
|
353
|
+
| `title` | `string` | - | Título principal sobre la imagen. |
|
|
354
|
+
| `subtitle` | `string` | - | Subtítulo / descripción. |
|
|
355
|
+
| `backgroundColor` | `string` | `'transparent'` | Color de fondo (visible si no hay imagen). |
|
|
356
|
+
| `titleColor` | `string` | `'#FFFFFF'` | Color del título. |
|
|
357
|
+
| `subtitleColor` | `string` | `'#FFFFFF'` | Color del subtítulo. |
|
|
358
|
+
| `minHeight` | `string` | `'500px'` | Altura mínima del banner. |
|
|
359
|
+
| `padding` | `string` | `'40px 24px'` | Padding interno del texto. |
|
|
360
|
+
| `highlights` | `ForYouHubBannerHighlight[]` | - | Barra de highlights (iconos + texto) debajo del banner. |
|
|
361
|
+
| `highlightsBackgroundColor` | `string` | `'#FFFFFF'` | Fondo de la barra de highlights. |
|
|
362
|
+
| `highlightsTextColor` | `string` | `'#111827'` | Color del texto de highlights. |
|
|
363
|
+
| `highlightsIconColor` | `string` | = highlightsTextColor | Color del icono SVG de highlights. |
|
|
364
|
+
|
|
365
|
+
### ForYouHubBannerHighlight
|
|
366
|
+
|
|
367
|
+
| Prop | Tipo | Descripción |
|
|
368
|
+
|------|------|-------------|
|
|
369
|
+
| `icon` | `string` | Raw SVG string (`<svg>...`) o URL de imagen. |
|
|
370
|
+
| `text` | `string` | Texto del highlight. |
|
|
371
|
+
|
|
372
|
+
### Ejemplo con banner completo (estilo Winsports)
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
<ForYouPage
|
|
376
|
+
locale="es"
|
|
377
|
+
hubBanner={{
|
|
378
|
+
desktopImage: 'https://cdn.winsports.co/fanzone/hub-banner-desktop.jpg',
|
|
379
|
+
mobileImage: 'https://cdn.winsports.co/fanzone/hub-banner-mobile.jpg',
|
|
380
|
+
title: '¡Bienvenido a Para Ti!',
|
|
381
|
+
subtitle: 'El espacio de WinSports para personalizar tu móvil con el contenido deportivo más original.',
|
|
382
|
+
backgroundColor: '#1A1063',
|
|
383
|
+
titleColor: '#FFFFFF',
|
|
384
|
+
subtitleColor: '#FFFFFF',
|
|
385
|
+
minHeight: '520px',
|
|
386
|
+
highlights: [
|
|
387
|
+
{
|
|
388
|
+
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
|
|
389
|
+
text: 'Elige el mejor contenido para cada momento',
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>',
|
|
393
|
+
text: 'Despierta la creatividad que tienes dentro',
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
icon: '/icons/share.svg',
|
|
397
|
+
text: 'Guarda y comparte tus creaciones',
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
highlightsBackgroundColor: '#FFFFFF',
|
|
401
|
+
highlightsTextColor: '#1A1063',
|
|
402
|
+
}}
|
|
403
|
+
features={WINSPORTS_FEATURES}
|
|
404
|
+
/>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Sin banner (por defecto)
|
|
408
|
+
|
|
409
|
+
Si no se pasa `hubBanner`, el hub muestra directamente las cards:
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
<ForYouPage locale="es" features={features} />
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Referencia de props: ForYouFeatureConfig
|
|
418
|
+
|
|
419
|
+
Compartido por todas las features que hacen llamada a API:
|
|
420
|
+
|
|
421
|
+
| Prop | Tipo | Descripción |
|
|
422
|
+
|------|------|-------------|
|
|
423
|
+
| `apiUrl` | `string` | URL directa del endpoint (sistema anterior). |
|
|
424
|
+
| `apiBaseUrl` | `string` | Base URL. El SDK construye la URL según el preset. |
|
|
425
|
+
| `tags` | `string \| string[]` | Tags para la petición. |
|
|
426
|
+
| `contents` | `ForYouContentPreset[]` | Multi-preset: varias llamadas en paralelo. |
|
|
427
|
+
| `locale` | `string` | Locale para esta feature (override del global). |
|
|
428
|
+
| `website_id` | `string` | Website ID para la petición. |
|
|
429
|
+
| `initialCount` | `number` | Items visibles al inicio. |
|
|
430
|
+
| `loadMoreCount` | `number` | Items adicionales por "load more". |
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Referencia de props: ForYouCardVisual
|
|
435
|
+
|
|
436
|
+
Configuración visual de la card del hub (textos en `translations.cards.*`):
|
|
437
|
+
|
|
438
|
+
| Prop | Tipo | Default | Descripción |
|
|
439
|
+
|------|------|---------|-------------|
|
|
440
|
+
| `desktopImage` | `string` | **requerido** | Imagen desktop. |
|
|
441
|
+
| `mobileImage` | `string` | = desktopImage | Imagen mobile. |
|
|
442
|
+
| `imageAlt` | `string` | `''` | Alt text. |
|
|
443
|
+
| `imageOnRight` | `boolean` | `true` | Imagen a la derecha o izquierda. |
|
|
444
|
+
| `titleColor` | `string` | `'#001B5E'` | Color del título. |
|
|
445
|
+
| `descriptionColor` | `string` | `'#001B5E'` | Color de la descripción. |
|
|
446
|
+
| `ctaColor` | `string` | `'#FF5500'` | Color del botón. |
|
|
447
|
+
| `ctaHoverColor` | `string` | `'#ff1600'` | Color hover del botón. |
|
|
448
|
+
| `backgroundColor` | `string` | `'#F9F9F9'` | Color de fondo. |
|
|
449
|
+
| `paddingClass` | `string` | `'py-12 md:py-16 lg:py-20'` | Padding vertical (Tailwind). |
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Referencia de props: ForYouPageTheme
|
|
454
|
+
|
|
455
|
+
| Prop | Tipo | Default | Descripción |
|
|
456
|
+
|------|------|---------|-------------|
|
|
457
|
+
| `backgroundColor` | `string` | `'transparent'` | Fondo de la página. |
|
|
458
|
+
| `sectionSpacing` | `string` | `'0'` | Margen entre secciones del hub. |
|
|
459
|
+
| `backButtonStyle` | `React.CSSProperties` | - | Estilos inline del botón volver. |
|
|
460
|
+
| `backButtonClassName` | `string` | - | Clase CSS del botón volver. |
|
|
461
|
+
| `backButtonIconColor` | `string` | `'currentColor'` | Color del icono SVG. |
|
|
462
|
+
| `showBackButtonIcon` | `boolean` | `true` | Mostrar/ocultar icono SVG. |
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## forYouFetch — helper de API
|
|
467
|
+
|
|
468
|
+
El helper `forYouFetch` se exporta independientemente para usarlo en proyectos sin `ForYouPage`:
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
import { forYouFetch } from 'webs-sdk'
|
|
472
|
+
|
|
473
|
+
// Modo simple
|
|
474
|
+
const items = await forYouFetch({
|
|
475
|
+
apiUrl: '/api/get_contents_by_tags',
|
|
476
|
+
tags: 'quiz',
|
|
477
|
+
locale: 'es',
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
// Modo multi-preset
|
|
481
|
+
const items = await forYouFetch({
|
|
482
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
483
|
+
locale: 'es',
|
|
484
|
+
website_id: '42',
|
|
485
|
+
contents: [
|
|
486
|
+
{ preset: 'getGlobalContentsByIDs', includeIds: [103433, 103420] },
|
|
487
|
+
{ preset: 'getGlobalContentsByTags', tags: ['quizws'] },
|
|
488
|
+
],
|
|
489
|
+
})
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## Features disponibles
|
|
495
|
+
|
|
496
|
+
| Key | Componente | Tags por defecto |
|
|
497
|
+
|-----|-----------|-----------------|
|
|
498
|
+
| `ringtone` | `Ringtone` | `ringtone` |
|
|
499
|
+
| `wallpapers` | `Wallpapers` | `wallpaper` |
|
|
500
|
+
| `wallpapersName` | `WallpapersName` | `wallpaper_name` |
|
|
501
|
+
| `memeGenerator` | `MemeGenerator` | `meme` |
|
|
502
|
+
| `quiz` | `Quiz` | `quiz` |
|
|
503
|
+
| `stickers` | `Stickers` | `stickers` |
|
|
504
|
+
| `avatarAI` | `AvatarAI` | (no aplica) |
|
|
505
|
+
| `creativeFaceSwap` | `CreativeFaceSwap` | (no aplica) |
|
|
506
|
+
| `arFilters` | `ARFilters` | (no aplica) |
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Uso de hooks fuera de ForYouPage
|
|
511
|
+
|
|
512
|
+
Los hooks también se exportan independientemente para casos donde se necesita lógica sin UI:
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
import { useQuiz, useWallpapers, useRingtone, useStickers } from 'webs-sdk'
|
|
516
|
+
|
|
517
|
+
// Con multi-preset
|
|
518
|
+
const { quizItems, handleOpenQuiz } = useQuiz({
|
|
519
|
+
config: {
|
|
520
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
521
|
+
locale: 'es',
|
|
522
|
+
contents: [
|
|
523
|
+
{ preset: 'getGlobalContentsByIDs', includeIds: [103433, 103420] },
|
|
524
|
+
{ preset: 'getGlobalContentsByTags', tags: ['quizws'] },
|
|
525
|
+
],
|
|
526
|
+
},
|
|
527
|
+
})
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Ejemplo completo Winsports
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
import { ForYouPage } from 'webs-sdk'
|
|
536
|
+
import type { ForYouFeatures, ForYouLocale } from 'webs-sdk'
|
|
537
|
+
|
|
538
|
+
const WINSPORTS_FEATURES: ForYouFeatures = {
|
|
539
|
+
quiz: {
|
|
540
|
+
card: {
|
|
541
|
+
desktopImage: 'https://cdn.winsports.co/fanzone/quiz-desktop.png',
|
|
542
|
+
mobileImage: 'https://cdn.winsports.co/fanzone/quiz-mobile.png',
|
|
543
|
+
imageOnRight: true,
|
|
544
|
+
ctaColor: '#E6001E',
|
|
545
|
+
backgroundColor: '#F5F5F5',
|
|
546
|
+
},
|
|
547
|
+
config: {
|
|
548
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
549
|
+
locale: 'es',
|
|
550
|
+
website_id: '42',
|
|
551
|
+
contents: [
|
|
552
|
+
{ preset: 'getGlobalContentsByIDs', includeIds: [103433, 103420, 103396, 103371] },
|
|
553
|
+
{ preset: 'getGlobalContentsByTags', tags: ['quizws'] },
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
onQuizStart: (quiz) => console.log('Quiz started:', quiz.title),
|
|
557
|
+
onQuizComplete: (score, total) => console.log(`Score: ${score}/${total}`),
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
ringtone: {
|
|
561
|
+
card: {
|
|
562
|
+
desktopImage: 'https://cdn.winsports.co/fanzone/ringtone-desktop.png',
|
|
563
|
+
imageOnRight: false,
|
|
564
|
+
ctaColor: '#E6001E',
|
|
565
|
+
backgroundColor: '#FFFFFF',
|
|
566
|
+
},
|
|
567
|
+
config: {
|
|
568
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
569
|
+
tags: ['ringtone', 'ringtone-winsports'],
|
|
570
|
+
locale: 'es',
|
|
571
|
+
website_id: '42',
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
wallpapers: {
|
|
576
|
+
card: {
|
|
577
|
+
desktopImage: 'https://cdn.winsports.co/fanzone/wallpapers-desktop.png',
|
|
578
|
+
ctaColor: '#E6001E',
|
|
579
|
+
backgroundColor: '#F5F5F5',
|
|
580
|
+
},
|
|
581
|
+
config: {
|
|
582
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
583
|
+
tags: 'wallpaper-winsports',
|
|
584
|
+
locale: 'es',
|
|
585
|
+
website_id: '42',
|
|
586
|
+
},
|
|
587
|
+
onDownloadSuccess: (w) => console.log('Downloaded:', w.title),
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
stickers: {
|
|
591
|
+
card: {
|
|
592
|
+
desktopImage: 'https://cdn.winsports.co/fanzone/stickers-desktop.png',
|
|
593
|
+
imageOnRight: false,
|
|
594
|
+
ctaColor: '#E6001E',
|
|
595
|
+
backgroundColor: '#FFFFFF',
|
|
596
|
+
},
|
|
597
|
+
config: {
|
|
598
|
+
apiBaseUrl: 'https://api.winsports.co',
|
|
599
|
+
tags: ['stickers', 'stickers-winsports'],
|
|
600
|
+
locale: 'es',
|
|
601
|
+
website_id: '42',
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export default function FanzoneForYou() {
|
|
607
|
+
return (
|
|
608
|
+
<ForYouPage
|
|
609
|
+
locale="es"
|
|
610
|
+
features={WINSPORTS_FEATURES}
|
|
611
|
+
order={['quiz', 'ringtone', 'wallpapers', 'stickers']}
|
|
612
|
+
pageTheme={{
|
|
613
|
+
backgroundColor: '#FFFFFF',
|
|
614
|
+
sectionSpacing: '0',
|
|
615
|
+
backButtonIconColor: '#E6001E',
|
|
616
|
+
}}
|
|
617
|
+
/>
|
|
618
|
+
)
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## Descargas y CORS — Proxy de medios
|
|
625
|
+
|
|
626
|
+
Algunos componentes del SDK (Stickers, MemeGenerator) descargan archivos desde CDNs de terceros.
|
|
627
|
+
Si el CDN no incluye el header `Access-Control-Allow-Origin`, el navegador bloquea la petición y
|
|
628
|
+
el SDK cae en el **fallback** abriendo la URL en una nueva pestaña en lugar de descargar el archivo.
|
|
629
|
+
|
|
630
|
+
> **Síntoma típico**: al pulsar "Download" en Stickers o "DOWNLOAD" en MemeGenerator, se abre el
|
|
631
|
+
> contenido en una nueva pestaña en lugar de descargarse directamente.
|
|
632
|
+
|
|
633
|
+
### Solución: proxy de medios en el proyecto
|
|
634
|
+
|
|
635
|
+
Hay que crear una ruta API en el propio proyecto que haga de intermediario. Como la petición sale
|
|
636
|
+
del servidor (no del navegador), no hay restricciones CORS.
|
|
637
|
+
|
|
638
|
+
#### Next.js — App Router (`app/api/media-proxy/route.ts`)
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
// app/api/media-proxy/route.ts
|
|
642
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
643
|
+
|
|
644
|
+
export async function GET(req: NextRequest) {
|
|
645
|
+
const url = req.nextUrl.searchParams.get('url')
|
|
646
|
+
if (!url) return new NextResponse('Missing url param', { status: 400 })
|
|
647
|
+
|
|
648
|
+
try {
|
|
649
|
+
const upstream = await fetch(url)
|
|
650
|
+
if (!upstream.ok) return new NextResponse('Upstream error', { status: upstream.status })
|
|
651
|
+
|
|
652
|
+
const blob = await upstream.blob()
|
|
653
|
+
|
|
654
|
+
return new NextResponse(blob, {
|
|
655
|
+
headers: {
|
|
656
|
+
'Content-Type': upstream.headers.get('content-type') || 'application/octet-stream',
|
|
657
|
+
'Content-Disposition': 'attachment',
|
|
658
|
+
'Cache-Control': 'public, max-age=86400',
|
|
659
|
+
},
|
|
660
|
+
})
|
|
661
|
+
} catch {
|
|
662
|
+
return new NextResponse('Proxy error', { status: 500 })
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
#### Next.js — Pages Router (`pages/api/media-proxy.ts`)
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
// pages/api/media-proxy.ts
|
|
671
|
+
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
672
|
+
|
|
673
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
674
|
+
const url = req.query.url as string
|
|
675
|
+
if (!url) return res.status(400).send('Missing url param')
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
const upstream = await fetch(url)
|
|
679
|
+
if (!upstream.ok) return res.status(upstream.status).send('Upstream error')
|
|
680
|
+
|
|
681
|
+
const buffer = Buffer.from(await upstream.arrayBuffer())
|
|
682
|
+
res.setHeader('Content-Type', upstream.headers.get('content-type') || 'application/octet-stream')
|
|
683
|
+
res.setHeader('Content-Disposition', 'attachment')
|
|
684
|
+
res.setHeader('Cache-Control', 'public, max-age=86400')
|
|
685
|
+
res.send(buffer)
|
|
686
|
+
} catch {
|
|
687
|
+
res.status(500).send('Proxy error')
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Configurar el proxy en ForYouPage
|
|
693
|
+
|
|
694
|
+
Una vez creada la ruta, pásala al `ForYouPage` mediante las props correspondientes:
|
|
695
|
+
|
|
696
|
+
```tsx
|
|
697
|
+
<ForYouPage
|
|
698
|
+
locale="es"
|
|
699
|
+
features={{
|
|
700
|
+
stickers: {
|
|
701
|
+
config: { ... },
|
|
702
|
+
// ← ruta relativa del proxy: sin CORS porque es el mismo origen
|
|
703
|
+
downloadProxyUrl: '/api/media-proxy?url=',
|
|
704
|
+
},
|
|
705
|
+
memeGenerator: {
|
|
706
|
+
config: { ... },
|
|
707
|
+
// ← mismo proxy, el SDK lo usa para cargar la imagen en el canvas
|
|
708
|
+
imageProxyUrl: '/api/media-proxy?url=',
|
|
709
|
+
},
|
|
710
|
+
}}
|
|
711
|
+
/>
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### Configurar el proxy en componentes standalone
|
|
715
|
+
|
|
716
|
+
```tsx
|
|
717
|
+
// Stickers standalone
|
|
718
|
+
<Stickers
|
|
719
|
+
config={stickersConfig}
|
|
720
|
+
downloadProxyUrl="/api/media-proxy?url="
|
|
721
|
+
/>
|
|
722
|
+
|
|
723
|
+
// MemeGenerator standalone
|
|
724
|
+
<MemeGenerator
|
|
725
|
+
config={memeConfig}
|
|
726
|
+
imageProxyUrl="/api/media-proxy?url="
|
|
727
|
+
/>
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Usar `downloadFile` del SDK directamente
|
|
731
|
+
|
|
732
|
+
Si necesitas descargar archivos en tu propio código (fuera de los componentes), el SDK exporta
|
|
733
|
+
la función `downloadFile`:
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
import { downloadFile } from 'webs-sdk'
|
|
737
|
+
|
|
738
|
+
// Con proxy (recomendado para CDNs de terceros):
|
|
739
|
+
await downloadFile(
|
|
740
|
+
'https://cdn.example.com/sticker.png',
|
|
741
|
+
'mi-sticker.png',
|
|
742
|
+
'/api/media-proxy?url='
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
// Sin proxy (solo si el CDN tiene CORS habilitado):
|
|
746
|
+
await downloadFile('https://cdn.example.com/sticker.png', 'mi-sticker.png')
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
> `downloadFile` intenta descargar via fetch+blob. Si falla (p.ej. CORS sin proxy), abre
|
|
750
|
+
> la URL en una nueva pestaña como fallback.
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
## Feature: Quiz — tipos de quiz y pantalla de resultado
|
|
755
|
+
|
|
756
|
+
El componente `Quiz` diferencia automáticamente entre **3 modos** según los datos del backend:
|
|
757
|
+
|
|
758
|
+
### Modo 1: Quiz por puntos (`personality = false`)
|
|
759
|
+
|
|
760
|
+
Cada respuesta tiene un valor numérico (`puntos`). Se muestra la puntuación obtenida vs el máximo posible, y el mensaje de resultado correspondiente al rango.
|
|
761
|
+
|
|
762
|
+
```
|
|
763
|
+
🏆
|
|
764
|
+
"You got 30/80 points" ← score / dynamicMax (suma de maxPoints por pregunta)
|
|
765
|
+
"Vous avez des notions..." ← resultMessage del rango matched
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Modo 2: Quiz de personalidad (`personality = true`)
|
|
769
|
+
|
|
770
|
+
El campo `personality: true` en el `metadata` del item indica que **no se debe mostrar la puntuación numérica**. Solo aparece el mensaje de resultado en grande.
|
|
771
|
+
|
|
772
|
+
```
|
|
773
|
+
🏆
|
|
774
|
+
"Eres un experto del fútbol" ← solo resultMessage, sin puntuación
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### Modo 3: Quiz legacy (correcto/incorrecto)
|
|
778
|
+
|
|
779
|
+
Items sin `quiz_configuration` (formato antiguo con `correct_option` / `option_a..d`). Se muestra el número de aciertos sobre el total de preguntas.
|
|
780
|
+
|
|
781
|
+
```
|
|
782
|
+
🏆
|
|
783
|
+
"3/5" ← score / totalPreguntas
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
### Cómo el SDK detecta el modo
|
|
787
|
+
|
|
788
|
+
El `personality` se extrae automáticamente del `metadata` del item de la lista:
|
|
789
|
+
|
|
790
|
+
```json
|
|
791
|
+
{
|
|
792
|
+
"id": 100,
|
|
793
|
+
"title": "¿Qué entrenador eres?",
|
|
794
|
+
"contents": "832",
|
|
795
|
+
"metadata": "{\"personality\": true}"
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
El SDK acepta tanto `boolean` como `string 'true'`. **No hay que configurar nada en el proyecto** — el SDK detecta el tipo por sí solo.
|
|
800
|
+
|
|
801
|
+
### Cálculo del total máximo (`dynamicMax`)
|
|
802
|
+
|
|
803
|
+
El SDK calcula el total máximo real sumando el `maxPoints` de cada pregunta (la opción con más puntos de cada pregunta):
|
|
804
|
+
|
|
805
|
+
```
|
|
806
|
+
dynamicMax = Σ Math.max(...options[i].points)
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Fallbacks si alguna pregunta no tiene `maxPoints`:
|
|
810
|
+
1. `results[last].maxPoints` (tope del último resultado definido en el backend)
|
|
811
|
+
2. `score` del usuario (fallback extremo)
|
|
812
|
+
|
|
813
|
+
### `scoreLabel` — personalización
|
|
814
|
+
|
|
815
|
+
El texto del score es configurable vía `texts.scoreLabel`. Usa `{score}` y `{total}` como variables:
|
|
816
|
+
|
|
817
|
+
```tsx
|
|
818
|
+
quiz: {
|
|
819
|
+
texts: {
|
|
820
|
+
scoreLabel: 'Obtuviste {score} de {total} puntos', // es
|
|
821
|
+
// o
|
|
822
|
+
scoreLabel: 'Tu as obtenu {score}/{total} points', // fr
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Default del SDK: `'You got {score}/{total} points'`
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
831
|
+
## Feature: WallpapersName — modo dual (clásico vs AETemplates)
|
|
832
|
+
|
|
833
|
+
La feature `wallpapersName` soporta **dos modos** seleccionados automáticamente según las props:
|
|
834
|
+
|
|
835
|
+
### Modo clásico (sistema anterior)
|
|
836
|
+
|
|
837
|
+
Usa `WallpapersName` con contenido cargado vía `get_contents_by_tags`. Compatible con el sistema antiguo.
|
|
838
|
+
|
|
839
|
+
```tsx
|
|
840
|
+
wallpapersName: {
|
|
841
|
+
card: { desktopImage: '/img/wallpapers-name.jpg' },
|
|
842
|
+
config: {
|
|
843
|
+
apiUrl: '/api/get_contents_by_tags',
|
|
844
|
+
tags: 'wallpaper_name',
|
|
845
|
+
website_id: '42',
|
|
846
|
+
locale: 'es',
|
|
847
|
+
},
|
|
848
|
+
howItWorksSteps: [
|
|
849
|
+
{ title: 'Elige una plantilla', description: 'Busca la que más te guste' },
|
|
850
|
+
{ title: 'Escribe tu nombre', description: 'Personaliza con tu nombre' },
|
|
851
|
+
{ title: 'Descarga', description: 'Guárdalo en tu móvil' },
|
|
852
|
+
],
|
|
853
|
+
}
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### Modo nuevo — AETemplates (sistema unificado)
|
|
857
|
+
|
|
858
|
+
Cuando se proporciona `aeTemplatesConfig`, `ForYouPage` renderiza `WallpapersNamePage`, que integra en una sola pieza:
|
|
859
|
+
1. **Header** con título y descripción
|
|
860
|
+
2. **"How does it work?"** — carousel de pasos con dots de navegación
|
|
861
|
+
3. **AETemplatesSection** — motor de templates de After Effects con tabs, modal de personalización y botón CUSTOMISE
|
|
862
|
+
|
|
863
|
+
```tsx
|
|
864
|
+
import type { WallpapersNameFeatureOverride } from 'webs-sdk'
|
|
865
|
+
|
|
866
|
+
const wallpapersNameFeature: WallpapersNameFeatureOverride = {
|
|
867
|
+
card: {
|
|
868
|
+
desktopImage: '/img/wallpapers-name-desktop.jpg',
|
|
869
|
+
ctaColor: '#E6001E',
|
|
870
|
+
},
|
|
871
|
+
// Textos del header
|
|
872
|
+
pageTexts: {
|
|
873
|
+
title: 'Wallpapers con tu nombre',
|
|
874
|
+
description: 'Personaliza wallpapers deportivos con tu nombre o el de tu equipo',
|
|
875
|
+
howItWorksTitle: '¿Cómo funciona?',
|
|
876
|
+
},
|
|
877
|
+
// Pasos del carousel "Cómo funciona"
|
|
878
|
+
howItWorksSteps: [
|
|
879
|
+
{ title: 'Elige una plantilla', image: '/img/step1.jpg' },
|
|
880
|
+
{ title: 'Escribe tu nombre', image: '/img/step2.jpg' },
|
|
881
|
+
{ title: 'Descarga', image: '/img/step3.jpg' },
|
|
882
|
+
],
|
|
883
|
+
// ← La presencia de aeTemplatesConfig activa el modo nuevo
|
|
884
|
+
aeTemplatesConfig: {
|
|
885
|
+
apiUrl: '/api/get_ae_templates',
|
|
886
|
+
langId: 27,
|
|
887
|
+
clientId: 50,
|
|
888
|
+
tabs: [
|
|
889
|
+
{ id: 'entertainment', label: 'Entertainment', type: 9 },
|
|
890
|
+
{ id: 'sports', label: 'Sports', type: 10 },
|
|
891
|
+
],
|
|
892
|
+
customiseApiUrl: '/api/create-custom-content',
|
|
893
|
+
userId: user.id,
|
|
894
|
+
wid: website_id,
|
|
895
|
+
webLang: locale,
|
|
896
|
+
},
|
|
897
|
+
aeTemplatesTexts: {
|
|
898
|
+
customiseButton: 'PERSONALIZAR',
|
|
899
|
+
downloadButton: 'Descargar',
|
|
900
|
+
loadingText: 'Cargando plantillas...',
|
|
901
|
+
noTemplatesText: 'No hay plantillas disponibles',
|
|
902
|
+
},
|
|
903
|
+
aeTemplatesTheme: {
|
|
904
|
+
primaryColor: '#E6001E',
|
|
905
|
+
buttonTextColor: '#FFFFFF',
|
|
906
|
+
},
|
|
907
|
+
}
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### Props de `WallpapersNameFeatureOverride`
|
|
911
|
+
|
|
912
|
+
| Prop | Tipo | Descripción |
|
|
913
|
+
|------|------|-------------|
|
|
914
|
+
| `card` | `ForYouCardVisual` | Visual de la card en el hub. |
|
|
915
|
+
| `config` | `ForYouFeatureConfig & { wid? }` | Config para el modo clásico. |
|
|
916
|
+
| `howItWorksSteps` | `WallpapersNameHowItWorksStep[]` | Pasos del carousel "¿Cómo funciona?". |
|
|
917
|
+
| `aeTemplatesConfig` | `AETemplatesConfig` | **Activa el modo nuevo.** Config de AETemplates. |
|
|
918
|
+
| `aeTemplatesTexts` | `AETemplatesTexts` | Textos de la sección AETemplates. |
|
|
919
|
+
| `aeTemplatesTheme` | `AETemplatesTheme` | Tema de colores de AETemplates. |
|
|
920
|
+
| `pageTexts` | `WallpapersNamePageTexts` | Textos del header (título, descripción, howItWorksTitle). Solo modo nuevo. |
|
|
921
|
+
| `pageTheme` | `WallpapersNamePageTheme` | Tema del header. Solo modo nuevo. |
|
|
922
|
+
|
|
923
|
+
> **Regla de selección:** si `aeTemplatesConfig` está definido → `WallpapersNamePage` (modo nuevo); si no → `WallpapersName` clásico.
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## Feature: AETemplatesSection — fuente dual (get_ae_templates + get_contents_by_tags)
|
|
928
|
+
|
|
929
|
+
A partir de `v0.18.82`, `AETemplatesSection` puede combinar **dos fuentes de datos** en el mismo selector de tabs.
|
|
930
|
+
|
|
931
|
+
### Cuándo usarlo
|
|
932
|
+
|
|
933
|
+
Cuando tienes templates de After Effects en `get_ae_templates` **y** además contenidos clásicos en `get_contents_by_tags` que quieres mostrar como templates personalizables en el mismo interfaz.
|
|
934
|
+
|
|
935
|
+
### Props nuevas en `AETemplatesConfig`
|
|
936
|
+
|
|
937
|
+
| Prop | Tipo | Default | Descripción |
|
|
938
|
+
|------|------|---------|-------------|
|
|
939
|
+
| `enableDefaultTemplates` | `boolean` | `true` | Activa los tabs de `get_ae_templates` (`apiUrl`). |
|
|
940
|
+
| `enableCustomTemplates` | `boolean` | `false` | Activa los tabs de `get_contents_by_tags` (`customApiUrl`). |
|
|
941
|
+
| `customApiUrl` | `string` | - | URL del endpoint `get_contents_by_tags`. |
|
|
942
|
+
| `customApiTags` | `string[]` | - | Tags para la llamada a `customApiUrl`. |
|
|
943
|
+
| `customTabs` | `AETab[]` | - | Tabs adicionales que llaman a `customApiUrl`. Se añaden después de los tabs default en el selector. |
|
|
944
|
+
|
|
945
|
+
### Campo `custom_content` en cada item
|
|
946
|
+
|
|
947
|
+
El botón **Customise** de cada card se habilita o deshabilita según el campo `custom_content` del item:
|
|
948
|
+
|
|
949
|
+
| Valor del campo | Estado del botón |
|
|
950
|
+
|-----------------|-----------------|
|
|
951
|
+
| `"1"` o `1` | ✅ Activo — abre el modal de personalización |
|
|
952
|
+
| Ausente, `null`, otro valor | 🚫 Deshabilitado (gris, `cursor: not-allowed`) |
|
|
953
|
+
|
|
954
|
+
Para los items de `get_ae_templates`, el campo viene directamente del endpoint.
|
|
955
|
+
Para los items de `get_contents_by_tags`, el SDK lo lee del `metadata` del item:
|
|
956
|
+
|
|
957
|
+
```json
|
|
958
|
+
{
|
|
959
|
+
"id": 123,
|
|
960
|
+
"title": "Mi plantilla",
|
|
961
|
+
"thumbnail": "https://cdn.example.com/preview.jpg",
|
|
962
|
+
"metadata": "{\"custom_content\": \"1\"}"
|
|
963
|
+
}
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
### Ejemplo: solo fuente AETemplates (comportamiento anterior)
|
|
967
|
+
|
|
968
|
+
```tsx
|
|
969
|
+
aeTemplatesConfig: {
|
|
970
|
+
apiUrl: '/api/get_ae_templates',
|
|
971
|
+
langId: 27,
|
|
972
|
+
clientId: 50,
|
|
973
|
+
tabs: [
|
|
974
|
+
{ id: 'sports', label: 'Sports', type: 10 },
|
|
975
|
+
{ id: 'entertainment', label: 'Entertainment', type: 9 },
|
|
976
|
+
],
|
|
977
|
+
// enableDefaultTemplates: true ← por defecto, no hace falta declararlo
|
|
978
|
+
customiseApiUrl: '/api/create-custom-content',
|
|
979
|
+
userId: user.id,
|
|
980
|
+
wid: '42',
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### Ejemplo: solo fuente custom (get_contents_by_tags)
|
|
985
|
+
|
|
986
|
+
```tsx
|
|
987
|
+
aeTemplatesConfig: {
|
|
988
|
+
apiUrl: '/api/get_ae_templates', // requerido aunque no se use
|
|
989
|
+
langId: 27,
|
|
990
|
+
clientId: 50,
|
|
991
|
+
tabs: [], // sin tabs default
|
|
992
|
+
enableDefaultTemplates: false, // ← desactiva get_ae_templates
|
|
993
|
+
enableCustomTemplates: true, // ← activa get_contents_by_tags
|
|
994
|
+
customApiUrl: '/api/get_contents_by_tags',
|
|
995
|
+
customApiTags: ['wallpaper_name'],
|
|
996
|
+
customTabs: [
|
|
997
|
+
{ id: 'mis-plantillas', label: 'Mis Plantillas', type: 0 },
|
|
998
|
+
],
|
|
999
|
+
customiseApiUrl: '/api/create-custom-content',
|
|
1000
|
+
userId: user.id,
|
|
1001
|
+
wid: '42',
|
|
1002
|
+
}
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### Ejemplo: ambas fuentes activas a la vez
|
|
1006
|
+
|
|
1007
|
+
Los tabs de ambas fuentes aparecen en el mismo selector. Al cambiar de tab, el SDK detecta automáticamente cuál endpoint llamar.
|
|
1008
|
+
|
|
1009
|
+
```tsx
|
|
1010
|
+
aeTemplatesConfig: {
|
|
1011
|
+
// ── Fuente 1: get_ae_templates ────────────────────────────────────────
|
|
1012
|
+
apiUrl: '/api/get_ae_templates',
|
|
1013
|
+
langId: 27,
|
|
1014
|
+
clientId: 50,
|
|
1015
|
+
enableDefaultTemplates: true,
|
|
1016
|
+
tabs: [
|
|
1017
|
+
{ id: 'sports', label: 'Sports', type: 10 },
|
|
1018
|
+
{ id: 'entertainment', label: 'Entertainment', type: 9 },
|
|
1019
|
+
{ id: 'abstract', label: 'Abstract', type: 11 },
|
|
1020
|
+
],
|
|
1021
|
+
|
|
1022
|
+
// ── Fuente 2: get_contents_by_tags ───────────────────────────────────
|
|
1023
|
+
enableCustomTemplates: true,
|
|
1024
|
+
customApiUrl: '/api/get_contents_by_tags',
|
|
1025
|
+
customApiTags: ['wallpaper_name'],
|
|
1026
|
+
customTabs: [
|
|
1027
|
+
{ id: 'mis-plantillas', label: 'Mis Plantillas', type: 0 },
|
|
1028
|
+
],
|
|
1029
|
+
|
|
1030
|
+
// ── Config compartida ─────────────────────────────────────────────────
|
|
1031
|
+
customiseApiUrl: '/api/create-custom-content',
|
|
1032
|
+
userId: user.id,
|
|
1033
|
+
wid: '42',
|
|
1034
|
+
webLang: 'es',
|
|
1035
|
+
}
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
El selector de tabs resultante sería:
|
|
1039
|
+
|
|
1040
|
+
```
|
|
1041
|
+
[Sports] [Entertainment] [Abstract] [Mis Plantillas]
|
|
1042
|
+
↑ get_ae_templates ↑ get_contents_by_tags
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
### Deduplicación automática
|
|
1046
|
+
|
|
1047
|
+
Si un mismo `id` aparece en ambas fuentes (caso raro), el SDK **descarta el duplicado** manteniendo el primero que llegó.
|
|
1048
|
+
|
|
1049
|
+
### Comportamiento del scrollbar en el selector de tabs
|
|
1050
|
+
|
|
1051
|
+
El selector de tabs tiene scroll horizontal en caso de overflow (muchas tabs en móvil), pero el scrollbar nativo está **oculto visualmente** para todos los navegadores (`scrollbar-width: none` + `::-webkit-scrollbar { display: none }`). El scroll sigue siendo funcional.
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## Feature: CreativeFaceSwap — ejemplos + endpoints
|
|
1056
|
+
|
|
1057
|
+
El componente `CreativeFaceSwap` permite a los usuarios subir una foto, elegir un ejemplo de carrusel y generar una imagen nueva con IA (face swap / creative generation).
|
|
1058
|
+
|
|
1059
|
+
### Partes del componente
|
|
1060
|
+
|
|
1061
|
+
```
|
|
1062
|
+
┌─────────────────────────┬──────────────────────────────────┐
|
|
1063
|
+
│ Columna izquierda │ Columna derecha │
|
|
1064
|
+
│ ───────────────── │ ────────────── │
|
|
1065
|
+
│ · Upload foto │ · Textarea descripción │
|
|
1066
|
+
│ · Carrusel ejemplos │ · Botón CREATE │
|
|
1067
|
+
│ │ · Área resultado (progreso/img) │
|
|
1068
|
+
└─────────────────────────┴──────────────────────────────────┘
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
### Props de `CreativeFaceSwapFeatureOverride`
|
|
1072
|
+
|
|
1073
|
+
| Prop | Tipo | Descripción |
|
|
1074
|
+
|------|------|-------------|
|
|
1075
|
+
| `card` | `ForYouCardVisual` | Visual de la card en el hub. |
|
|
1076
|
+
| `config` | `FaceSwapConfig` | Endpoints de generación + credenciales. **Requerido.** |
|
|
1077
|
+
| `examples` | `FaceSwapExample[]` | Ejemplos estáticos para el carrusel (prioridad máxima). |
|
|
1078
|
+
| `examplesTag` | `string` | Tag para cargar ejemplos vía `get_contents_by_tags` (carga interna del componente). |
|
|
1079
|
+
| `contentsConfig` | `ForYouFeatureConfig` | **Nuevo en v0.18.83.** Config de fetch de ejemplos con presets (multi-source). Prioridad sobre `examplesTag`. |
|
|
1080
|
+
| `theme` | `Partial<FaceSwapTheme>` | Override del tema de colores. |
|
|
1081
|
+
| `texts` | `Partial<FaceSwapTexts>` | Override de textos. |
|
|
1082
|
+
| `apiBaseUrl` | `string` | Base URL para el fetch de `examplesTag` (si se usa). |
|
|
1083
|
+
| `locale` | `string` | Locale override para esta feature. |
|
|
1084
|
+
| `onGenerationComplete` | `(imageUrl: string) => void` | Callback cuando la imagen está lista. |
|
|
1085
|
+
| `onError` | `(error: string) => void` | Callback de error. |
|
|
1086
|
+
|
|
1087
|
+
### `config` — endpoints de generación (FaceSwapConfig)
|
|
1088
|
+
|
|
1089
|
+
Este objeto **no** carga los ejemplos del carrusel. Contiene los endpoints del servicio de generación de imágenes y las credenciales del usuario:
|
|
1090
|
+
|
|
1091
|
+
```ts
|
|
1092
|
+
config: {
|
|
1093
|
+
userId: USER_ID, // number — ID del usuario
|
|
1094
|
+
userLogin: USER_LOGIN, // number — login del usuario
|
|
1095
|
+
wid: WEBSITE_ID, // string — website ID (opcional)
|
|
1096
|
+
apiEndpoints: {
|
|
1097
|
+
generate: '/api/face-swap/generate', // POST — inicia el job de generación
|
|
1098
|
+
progress: '/api/face-swap/progress', // POST — polling del % de progreso
|
|
1099
|
+
getImage: '/api/face-swap/get-image', // POST — obtiene la imagen final
|
|
1100
|
+
},
|
|
1101
|
+
}
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
> ⚠️ Estas rutas deben existir como **rutas API en el proyecto** (no son endpoints del CMS).
|
|
1105
|
+
> El SDK llama a estas rutas directamente desde el navegador del usuario.
|
|
1106
|
+
|
|
1107
|
+
### `contentsConfig` — carga de ejemplos del carrusel (v0.18.83+)
|
|
1108
|
+
|
|
1109
|
+
Permite cargar los ejemplos del carrusel desde la API del CMS usando el sistema de presets:
|
|
1110
|
+
|
|
1111
|
+
```tsx
|
|
1112
|
+
creativeFaceSwap: {
|
|
1113
|
+
config: {
|
|
1114
|
+
userId: USER_ID,
|
|
1115
|
+
userLogin: USER_LOGIN,
|
|
1116
|
+
wid: WEBSITE_ID,
|
|
1117
|
+
apiEndpoints: {
|
|
1118
|
+
generate: '/api/face-swap/generate',
|
|
1119
|
+
progress: '/api/face-swap/progress',
|
|
1120
|
+
getImage: '/api/face-swap/get-image',
|
|
1121
|
+
},
|
|
1122
|
+
},
|
|
1123
|
+
// ← carga los ejemplos del carrusel desde el CMS
|
|
1124
|
+
contentsConfig: {
|
|
1125
|
+
apiBaseUrl: 'https://api.example.com',
|
|
1126
|
+
locale: 'en',
|
|
1127
|
+
website_id: WEBSITE_ID,
|
|
1128
|
+
contents: [
|
|
1129
|
+
{
|
|
1130
|
+
preset: 'getGlobalContentsByIDs',
|
|
1131
|
+
includeIds: [104557, 100180, 98423, 97811],
|
|
1132
|
+
},
|
|
1133
|
+
],
|
|
1134
|
+
},
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
También puedes combinar con tags:
|
|
1139
|
+
|
|
1140
|
+
```ts
|
|
1141
|
+
contentsConfig: {
|
|
1142
|
+
apiBaseUrl: 'https://api.example.com',
|
|
1143
|
+
locale: 'en',
|
|
1144
|
+
contents: [
|
|
1145
|
+
{ preset: 'getGlobalContentsByIDs', includeIds: [104557, 100180] },
|
|
1146
|
+
{ preset: 'getGlobalContentsByTags', tags: ['faceswap-examples'] },
|
|
1147
|
+
],
|
|
1148
|
+
}
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
O solo con un tag simple:
|
|
1152
|
+
|
|
1153
|
+
```ts
|
|
1154
|
+
contentsConfig: {
|
|
1155
|
+
apiBaseUrl: 'https://api.example.com',
|
|
1156
|
+
locale: 'en',
|
|
1157
|
+
tags: 'faceswap-examples',
|
|
1158
|
+
}
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
### Prioridad de ejemplos
|
|
1162
|
+
|
|
1163
|
+
El SDK aplica este orden (el primero que tenga datos gana):
|
|
1164
|
+
|
|
1165
|
+
```
|
|
1166
|
+
1. examples[] → array estático pasado en la prop
|
|
1167
|
+
2. contentsConfig → fetch interno con forYouFetch (v0.18.83+)
|
|
1168
|
+
3. examplesTag → fetch simple interno del componente
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
### Qué campos necesita cada item del CMS
|
|
1172
|
+
|
|
1173
|
+
| Campo del item | Se usa como |
|
|
1174
|
+
|----------------|-------------|
|
|
1175
|
+
| `thumbnail` | Imagen del ejemplo en el carrusel |
|
|
1176
|
+
| `images.img1` | Imagen (fallback si no hay thumbnail) |
|
|
1177
|
+
| `imageUrl` | Imagen (segundo fallback) |
|
|
1178
|
+
| `title` | Descripción debajo de la imagen |
|
|
1179
|
+
| `description` | Descripción (fallback si no hay title) |
|
|
1180
|
+
|
|
1181
|
+
> Si el item no tiene ningún campo de imagen, **se descarta** y no aparece en el carrusel.
|
|
1182
|
+
|
|
1183
|
+
### Personalización: `FaceSwapTheme`
|
|
1184
|
+
|
|
1185
|
+
| Prop | Default | Descripción |
|
|
1186
|
+
|------|---------|-------------|
|
|
1187
|
+
| `primaryColor` | `'#FF5500'` | Color principal (botones, spinner). |
|
|
1188
|
+
| `primaryHoverColor` | `'#ff1600'` | Color hover de botones. |
|
|
1189
|
+
| `secondaryColor` | `'#001B5E'` | Color secundario. |
|
|
1190
|
+
| `textColor` | `'#001B5E'` | Color del texto. |
|
|
1191
|
+
| `backgroundColor` | `'#F9F9F9'` | Fondo del componente. |
|
|
1192
|
+
|
|
1193
|
+
### Personalización: `FaceSwapTexts`
|
|
1194
|
+
|
|
1195
|
+
| Prop | Default |
|
|
1196
|
+
|------|---------|
|
|
1197
|
+
| `uploadTitle` | `'Your original photo'` |
|
|
1198
|
+
| `uploadButton` | `'UPLOAD IMAGE'` |
|
|
1199
|
+
| `changeImageButton` | `'CHANGE IMAGE'` |
|
|
1200
|
+
| `examplesTitle` | `'Get inspired by these examples'` |
|
|
1201
|
+
| `tryButton` | `'TRY'` |
|
|
1202
|
+
| `describeTitle` | `'Describe your desired image'` |
|
|
1203
|
+
| `descriptionPlaceholder` | `'Young curly-haired girl...'` |
|
|
1204
|
+
| `createButton` | `'CREATE'` |
|
|
1205
|
+
| `generatingButton` | `'GENERATING...'` |
|
|
1206
|
+
| `resultTitle` | `'Your result'` |
|
|
1207
|
+
| `emptyStateText` | `'Your generated image will appear here'` |
|
|
1208
|
+
| `startAgainButton` | `'START AGAIN'` |
|
|
1209
|
+
| `downloadButton` | `'DOWNLOAD'` |
|
|
1210
|
+
| `progressText` | `'Progress'` |
|
|
1211
|
+
| `successMessage` | `'Image generated successfully!'` |
|
|
1212
|
+
|
|
1213
|
+
### Ejemplo completo
|
|
1214
|
+
|
|
1215
|
+
```tsx
|
|
1216
|
+
import { ForYouPage } from 'webs-sdk'
|
|
1217
|
+
import type { ForYouFeatures } from 'webs-sdk'
|
|
1218
|
+
|
|
1219
|
+
const features: ForYouFeatures = {
|
|
1220
|
+
creativeFaceSwap: {
|
|
1221
|
+
card: {
|
|
1222
|
+
desktopImage: '/images/faceswap-desktop.jpg',
|
|
1223
|
+
mobileImage: '/images/faceswap-mobile.jpg',
|
|
1224
|
+
imageOnRight: true,
|
|
1225
|
+
ctaColor: '#FF5500',
|
|
1226
|
+
backgroundColor: '#F9F9F9',
|
|
1227
|
+
},
|
|
1228
|
+
config: {
|
|
1229
|
+
userId: USER_ID,
|
|
1230
|
+
userLogin: USER_LOGIN,
|
|
1231
|
+
wid: WEBSITE_ID,
|
|
1232
|
+
apiEndpoints: {
|
|
1233
|
+
generate: '/api/face-swap/generate',
|
|
1234
|
+
progress: '/api/face-swap/progress',
|
|
1235
|
+
getImage: '/api/face-swap/get-image',
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
contentsConfig: {
|
|
1239
|
+
apiBaseUrl: 'https://api.example.com',
|
|
1240
|
+
locale: 'en',
|
|
1241
|
+
website_id: WEBSITE_ID,
|
|
1242
|
+
contents: [
|
|
1243
|
+
{ preset: 'getGlobalContentsByIDs', includeIds: [104557, 100180, 98423] },
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
theme: {
|
|
1247
|
+
primaryColor: '#FF5500',
|
|
1248
|
+
textColor: '#001B5E',
|
|
1249
|
+
},
|
|
1250
|
+
texts: {
|
|
1251
|
+
uploadTitle: 'Tu foto',
|
|
1252
|
+
createButton: 'GENERAR',
|
|
1253
|
+
examplesTitle: 'Inspírate en estos ejemplos',
|
|
1254
|
+
},
|
|
1255
|
+
onGenerationComplete: (imageUrl) => console.log('Listo:', imageUrl),
|
|
1256
|
+
onError: (error) => console.error('Error:', error),
|
|
1257
|
+
},
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
export default function ForYouSection() {
|
|
1261
|
+
return <ForYouPage locale="en" features={features} />
|
|
1262
|
+
}
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
### Respuesta esperada de los endpoints
|
|
1266
|
+
|
|
1267
|
+
El SDK espera las siguientes estructuras JSON. Si el servicio tiene un formato diferente, hay que crear una ruta proxy en el proyecto que adapte la respuesta.
|
|
1268
|
+
|
|
1269
|
+
**`/generate` → POST**
|
|
1270
|
+
```json
|
|
1271
|
+
{ "success": true, "data": { "md5": "abc123", "i_jobs": 1 } }
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
**`/progress` → POST**
|
|
1275
|
+
```json
|
|
1276
|
+
{ "success": true, "data": { "pct": 45 } }
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
**`/get-image` → POST**
|
|
1280
|
+
```json
|
|
1281
|
+
{ "success": true, "data": { "s3_image": "https%3A%2F%2Fcdn.example.com%2Fresult.jpg" } }
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
> `s3_image` debe estar URL-encoded. El SDK aplica `decodeURIComponent()` internamente.
|
|
1285
|
+
|
|
1286
|
+
### Fix en v0.18.83 — error HTML en el endpoint generate
|
|
1287
|
+
|
|
1288
|
+
Si el endpoint `/generate` no existe o falla con un 404/500, Next.js devuelve una página HTML en lugar de JSON. Antes de v0.18.83, esto causaba un crash poco descriptivo:
|
|
1289
|
+
|
|
1290
|
+
```
|
|
1291
|
+
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
A partir de v0.18.83, el SDK detecta el `Content-Type` de la respuesta antes de parsear. Si no es JSON, muestra un error legible en la UI:
|
|
1295
|
+
|
|
1296
|
+
```
|
|
1297
|
+
HTTP 404: The generate endpoint returned an unexpected response.
|
|
1298
|
+
Check that the API route exists and is correctly configured in this project.
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
---
|
|
1302
|
+
|
|
1303
|
+
## Migración desde el sistema anterior
|
|
1304
|
+
|
|
1305
|
+
Si tienes el sistema antiguo funcionando, **no necesitas migrar todavía**. Ambos sistemas coexisten.
|
|
1306
|
+
|
|
1307
|
+
Cuando quieras migrar, el patrón es:
|
|
1308
|
+
|
|
1309
|
+
```tsx
|
|
1310
|
+
// ANTES
|
|
1311
|
+
import { Quiz } from 'webs-sdk'
|
|
1312
|
+
<Quiz config={{ apiUrl: '/api/get_contents_by_tags', tags: ['quiz'], locale: 'es' }} />
|
|
1313
|
+
|
|
1314
|
+
// DESPUÉS (dentro de ForYouPage)
|
|
1315
|
+
<ForYouPage
|
|
1316
|
+
locale="es"
|
|
1317
|
+
features={{
|
|
1318
|
+
quiz: {
|
|
1319
|
+
card: { desktopImage: '/img/quiz.jpg' }, // ← para mostrar la card en el hub
|
|
1320
|
+
config: { tags: 'quiz' }, // ← misma config de antes
|
|
1321
|
+
}
|
|
1322
|
+
}}
|
|
1323
|
+
/>
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
Los componentes individuales (`Quiz`, `Wallpapers`, `Ringtone`, etc.) **siguen funcionando exactamente igual** que antes.
|
|
1327
|
+
|
|
1328
|
+
---
|
|
1329
|
+
|
|
1330
|
+
## Changelog
|
|
1331
|
+
|
|
1332
|
+
### 0.18.83
|
|
1333
|
+
- `CreativeFaceSwapFeatureOverride`: nueva prop `contentsConfig?: ForYouFeatureConfig` — permite cargar los ejemplos del carrusel desde el CMS usando el sistema de presets (`getGlobalContentsByIDs`, `getGlobalContentsByTags`, etc.), igual que el resto de features.
|
|
1334
|
+
- `ForYouPage` case `creativeFaceSwap`: nuevo sub-componente interno `CreativeFaceSwapWithContents` que ejecuta `forYouFetch(contentsConfig)` al montar, mapea los items a `FaceSwapExample[]` y los pasa como `examples` al componente.
|
|
1335
|
+
- Prioridad de ejemplos: `examples[]` estáticos → `contentsConfig` (fetch interno) → `examplesTag` (fetch del componente).
|
|
1336
|
+
- `CreativeFaceSwap.tsx` — `handleCreate`: detección de respuesta HTML antes de `response.json()`. Si el endpoint devuelve un 404/500 de Next.js (HTML en lugar de JSON), el SDK lanza un error legible (`"HTTP 404: The generate endpoint returned an unexpected response..."`) en lugar de crashear con `SyntaxError`.
|
|
1337
|
+
|
|
1338
|
+
### 0.18.82
|
|
1339
|
+
- `AETemplatesConfig`: nuevas props `enableDefaultTemplates`, `enableCustomTemplates`, `customApiUrl`, `customApiTags`, `customTabs` — soporte para fuente dual (`get_ae_templates` + `get_contents_by_tags`) en el mismo selector de tabs.
|
|
1340
|
+
- `AETemplateItem`: nuevo campo `custom_content?: string | number` — el botón Customise se activa solo si el item devuelve `"1"` o `1`. En caso contrario aparece deshabilitado.
|
|
1341
|
+
- `AETemplatesSection`: selector de tabs dinámico (fusiona tabs default + customTabs), fetch por fuente, `Promise.all` + deduplicación por `id`, fallback automático al primer tab si el activo desaparece.
|
|
1342
|
+
- `mapContentToAETemplateItem`: helper interno que normaliza items de `get_contents_by_tags` → `AETemplateItem`, leyendo `custom_content` del `metadata` o del campo raíz.
|
|
1343
|
+
- Scrollbar del selector de tabs oculto visualmente (`scrollbar-width: none` + `::-webkit-scrollbar { display: none }`).
|
|
1344
|
+
|
|
1345
|
+
### 0.18.80
|
|
1346
|
+
- `WallpapersNamePage` (carousel "How does it work?"): eliminados `backgroundColor` (#EAECF0) y `padding` del card de paso — sin fondo, sin contenedor visible.
|
|
1347
|
+
- `WallpapersNamePage`: `maxWidth` de la imagen del paso `260px` → `600px` + `width: 100%` — imagen más grande y responsiva.
|
|
1348
|
+
|
|
1349
|
+
### 0.18.79
|
|
1350
|
+
- `WallpapersNamePage` (carousel "How does it work?"): fondo del card de paso `#FFFDE7` (amarillo) → `#EAECF0` (gris neutro).
|
|
1351
|
+
- `WallpapersNamePage`: `maxWidth` de imagen `160px` → `260px`.
|
|
1352
|
+
- `WallpapersNamePage`: padding del card `40px 24px` → `48px 32px 36px`.
|
|
1353
|
+
- `WallpapersNamePage`: tipografía del título del paso `1.1rem` → `1.25rem`.
|
|
1354
|
+
- `WallpapersNamePage`: tipografía de descripción `0.9rem` → `0.95rem` + `maxWidth: 320px`.
|
|
1355
|
+
|
|
1356
|
+
### 0.18.78
|
|
1357
|
+
- `Quiz`: fix `scoreLabel` — el default `'You got {score}/{total} {type}'` tenía `{type}` que nunca se reemplazaba, apareciendo literalmente en pantalla. Corregido a `'You got {score}/{total} points'`.
|
|
1358
|
+
- `Quiz`: eliminado carácter `←` hardcodeado en el botón "Previous". El texto es ahora controlado exclusivamente por `texts.previousButton`.
|
|
1359
|
+
|
|
1360
|
+
### 0.18.77
|
|
1361
|
+
- `Quiz`: nuevo campo `QuizItem.personality?: boolean` — extraído del `metadata` del item. Acepta `boolean` o `string 'true'`. Identifica quizzes de personalidad.
|
|
1362
|
+
- `Quiz`: nuevo campo `QuizQuestion.maxPoints?: number` — máximo de puntos alcanzable por pregunta (`Math.max(...options.map(o => o.points))`).
|
|
1363
|
+
- `Quiz`: cálculo de `totalMax` (dynamicMax) corregido — usa `Σ q.maxPoints` para el total real en lugar de un valor fijo del backend.
|
|
1364
|
+
- `Quiz`: pantalla de resultado diferenciada en 3 modos: personality (solo mensaje), puntos (score/max + mensaje) y legacy (aciertos/total).
|
|
1365
|
+
- `Quiz`: `personality` propagado desde `activeQuiz` al `<QuizModal>`.
|
|
1366
|
+
- `Quiz`: optional chaining en `options[optionIdx]?.points ?? 0` para evitar crashes con índices inválidos.
|
|
1367
|
+
|
|
1368
|
+
### 0.18.76
|
|
1369
|
+
- `WallpapersName`: nuevo componente `WallpapersNamePage` — integra header, carousel "¿Cómo funciona?" y `AETemplatesSection` en una sola pieza.
|
|
1370
|
+
- `ForYouPage` case `wallpapersName`: modo dual — si `aeTemplatesConfig` está presente renderiza `WallpapersNamePage` (modo nuevo); si no, usa `WallpapersName` clásico.
|
|
1371
|
+
- Nuevas props en `WallpapersNameFeatureOverride`: `aeTemplatesConfig`, `aeTemplatesTexts`, `aeTemplatesTheme`, `pageTexts`, `pageTheme`.
|
|
1372
|
+
- Nuevos tipos exportados: `WallpapersNamePageTexts`, `WallpapersNamePageTheme`, `WallpapersNamePageProps`.
|
|
1373
|
+
|
|
1374
|
+
### 0.18.75
|
|
1375
|
+
- `ForYouPage` case `wallpapersName`: normalización `website_id` → `wid` (campo específico de `WallpapersNameConfig`). Fix para "No wallpapers available" al usar `ForYouPage` con la feature `wallpapersName`.
|
|
1376
|
+
- Auditoría completa de normalizaciones: el resto de cases (`ringtone`, `wallpapers`, `memeGenerator`, `quiz`, `stickers`, `arFilters`) ya normalizaban correctamente.
|
|
1377
|
+
|
|
1378
|
+
### 0.18.74
|
|
1379
|
+
- Nuevo util exportado: `downloadFile` — función de descarga con soporte de proxy CORS. Extrae la lógica inline de `Stickers.tsx` a `src/utils/downloadFile.ts` y la exporta desde `webs-sdk`.
|
|
1380
|
+
- `Stickers.tsx`: usa `downloadFile` del util en lugar de definición inline.
|
|
1381
|
+
- Documentación: nueva sección **"Descargas y CORS — Proxy de medios"** en la guía de implementación con código del endpoint proxy (Next.js App Router y Pages Router) y configuración de `downloadProxyUrl` / `imageProxyUrl`.
|
|
1382
|
+
|
|
1383
|
+
### 0.18.72
|
|
1384
|
+
- `ForYouPage`: scroll al top usa `window.scrollTo({ top: 0 })` en lugar de `scrollIntoView` (scroll ahora va al top de la ventana, no solo al componente)
|
|
1385
|
+
- `ForYouPage` case `ringtone`: normaliza correctamente `tags` → `ringtoneTag` (string) que espera `Ringtone.tsx`
|
|
1386
|
+
- `ForYouPage` case `quiz`: `apiUrl` siempre tiene valor (construido con `buildPresetUrl` si se usa `apiBaseUrl`); se pasa `questionsApiUrl` correctamente a `Quiz.tsx`
|
|
1387
|
+
- `ForYouPage` case `memeGenerator`: `apiUrl` siempre tiene valor; normaliza `tags` → `memeTag` (string) que espera `MemeGenerator.tsx`
|
|
1388
|
+
- `ForYouPage` case `wallpapers`: nuevo prop `tabs?: WallpapersTab[]` — selector de pestañas por categoría encima del componente Wallpapers
|
|
1389
|
+
- `ForYouPage` case `wallpapers`: nuevo prop `tabsTheme` — personalización visual del selector de tabs
|
|
1390
|
+
- `ForYouPage` case `wallpapers`: `heroBannerImage` y `fallbackImage` se pasan correctamente al config de `Wallpapers.tsx`
|
|
1391
|
+
- Nuevo tipo exportado: `WallpapersTab`
|
|
1392
|
+
|
|
1393
|
+
### 0.18.70
|
|
1394
|
+
- `ForYouPage`: nuevo prop `hubBanner` (`ForYouHubBanner`) — banner hero opcional encima de las cards en el hub
|
|
1395
|
+
- `ForYouHubBanner`: soporta imagen desktop + mobile, título, subtítulo, color de fondo, altura y padding configurables
|
|
1396
|
+
- `ForYouHubBannerHighlight`: barra de highlights con icono (SVG raw o URL imagen) + texto debajo del banner
|
|
1397
|
+
- Nuevos tipos exportados: `ForYouHubBanner`, `ForYouHubBannerHighlight`
|
|
1398
|
+
|
|
1399
|
+
### 0.18.69
|
|
1400
|
+
- `ForYouPage`: nuevo prop `locale` con 8 idiomas (en, es, fr, pl, nl, tr, ro, ar)
|
|
1401
|
+
- `ForYouPage`: traducciones integradas en el SDK (`getForYouTranslations`)
|
|
1402
|
+
- `ForYouPage`: RTL automático para árabe (`dir="rtl"`)
|
|
1403
|
+
- `ForYouPage`: navegación con `window.history.pushState` + `?feature=<key>` en URL
|
|
1404
|
+
- `ForYouPage`: listener `popstate` para botón "Atrás" del navegador
|
|
1405
|
+
- `ForYouPage`: scroll automático al tope al entrar/salir de features
|
|
1406
|
+
- `ForYouPage`: botón volver con icono SVG configurable
|
|
1407
|
+
- `ForYouPage`: nuevo `ForYouFeatureConfig.apiBaseUrl` — el SDK construye las URLs
|
|
1408
|
+
- `ForYouPage`: nuevo `ForYouFeatureConfig.contents[]` — multi-preset en paralelo
|
|
1409
|
+
- `Wallpapers`: nuevo prop `hideBreadcrumb` — oculta el breadcrumb interno
|
|
1410
|
+
- Todos los hooks: migrados a `forYouFetch` con soporte `contents[]` y `apiBaseUrl`
|
|
1411
|
+
- Nuevo: `forYouFetch` helper exportado independientemente
|
|
1412
|
+
- Nuevo: `getForYouTranslations`, `isRTL`, `FOR_YOU_I18N`, `FOR_YOU_LOCALES`
|
|
1413
|
+
- Nuevo: tipos `ForYouLocale`, `ForYouContentPreset`, `ForYouFetchOptions` y variantes
|