@mundogamernetwork/shared-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +283 -0
- package/components/PressKit/AssetGallery.vue +349 -0
- package/components/PressKit/Awards.vue +100 -0
- package/components/PressKit/Credits.vue +78 -0
- package/components/PressKit/FactSheet.vue +204 -0
- package/components/PressKit/Hero.vue +143 -0
- package/components/PressKit/Quotes.vue +80 -0
- package/components/PressKit/VideoPlayer.vue +134 -0
- package/components/checkout/MgCartItemList.vue +214 -0
- package/components/checkout/MgCartSummary.vue +204 -0
- package/components/checkout/MgCheckoutSidebar.vue +230 -0
- package/components/checkout/MgGuestEmailForm.vue +97 -0
- package/components/checkout/MgPaymentMethodSelector.vue +162 -0
- package/components/checkout/MgPixQRCode.vue +222 -0
- package/components/indie-wall/IndieWallLeaderboard.vue +208 -0
- package/components/indie-wall/MuralCanvas.vue +481 -0
- package/components/indie-wall/StepBlock.vue +314 -0
- package/components/indie-wall/StepCustomize.vue +530 -0
- package/components/indie-wall/StepGoal.vue +169 -0
- package/components/indie-wall/StepPackage.vue +145 -0
- package/components/indie-wall/StepPay.vue +209 -0
- package/components/indie-wall/SupportStepper.vue +372 -0
- package/components/invoices/MgInvoiceDownload.vue +50 -0
- package/components/pricing/MgBillingToggle.vue +74 -0
- package/components/pricing/MgPricingCard.vue +245 -0
- package/components/ui/Header/MgMessageCard.vue +147 -0
- package/components/ui/Header/MgMessageModal.vue +414 -0
- package/components/ui/Header/MgNotificationCard.vue +200 -0
- package/components/ui/Header/MgNotificationsModal.vue +125 -0
- package/components/ui/MgAnnouncementBanner.vue +147 -0
- package/components/ui/MgBanners.vue +23 -0
- package/components/ui/MgHeaderComponent.vue +283 -0
- package/components/ui/MgHeaderUIConfig.vue +225 -0
- package/components/ui/MgHeaderUIUser.vue +301 -0
- package/components/ui/MgLoginModal.vue +156 -0
- package/components/ui/MgPromotionBanner.vue +185 -0
- package/composables/useLogout.ts +42 -0
- package/composables/useMgCheckout.ts +287 -0
- package/composables/useMgUserNotifications.ts +122 -0
- package/composables/usePaymentMethods.ts +75 -0
- package/composables/useSubscription.ts +163 -0
- package/middleware/auth.global.ts +40 -0
- package/nuxt.config.ts +31 -0
- package/package.json +40 -0
- package/pages/[slug]/index.vue +112 -0
- package/pages/about.vue +133 -0
- package/pages/blog.vue +430 -0
- package/pages/careers.vue +329 -0
- package/pages/contact.vue +339 -0
- package/pages/faq.vue +317 -0
- package/pages/health-check.vue +20 -0
- package/pages/icons.vue +58 -0
- package/pages/magazine/[slug].vue +209 -0
- package/pages/magazine/index.vue +267 -0
- package/pages/media-kit/[slug].vue +625 -0
- package/pages/mural/[slug].vue +1058 -0
- package/pages/partners.vue +290 -0
- package/pages/press.vue +237 -0
- package/pages/presskit/[slug].vue +191 -0
- package/pages/roadmap.vue +355 -0
- package/pages/status.vue +199 -0
- package/pages/team.vue +266 -0
- package/pages/wall/[slug].vue +11 -0
- package/plugins/auth.client.ts +17 -0
- package/plugins/echo.client.ts +132 -0
- package/services/authService.ts +95 -0
- package/services/chatService.ts +53 -0
- package/services/contactService.ts +35 -0
- package/services/documentService.ts +16 -0
- package/services/httpService.ts +95 -0
- package/services/indieWallService.ts +174 -0
- package/services/institutionalService.ts +248 -0
- package/services/mediaKitService.ts +51 -0
- package/services/notificationsService.ts +20 -0
- package/services/pressKitService.ts +55 -0
- package/stores/announcement.ts +129 -0
- package/stores/auth.ts +86 -0
- package/stores/chat.ts +150 -0
- package/stores/contact.ts +28 -0
- package/stores/document.ts +27 -0
- package/stores/index.ts +34 -0
- package/stores/institutional.ts +231 -0
- package/stores/login.ts +27 -0
- package/stores/notifications.ts +133 -0
- package/stores/promotion.ts +154 -0
- package/types/index.ts +135 -0
- package/utils/serialize.ts +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# @mundogamernetwork/shared-ui
|
|
2
|
+
|
|
3
|
+
Nuxt 3 Layer compartilhado entre todos os frontends da Mundo Gamer Network.
|
|
4
|
+
|
|
5
|
+
## O que inclui
|
|
6
|
+
|
|
7
|
+
### Componentes
|
|
8
|
+
- **MgHeaderComponent** - Header unificado com notificações, chat, user menu, settings
|
|
9
|
+
- **MgHeaderUIUser** - Dropdown do usuário (login/register, avatar, logout)
|
|
10
|
+
- **MgHeaderUIConfig** - Dropdown de configurações (dark mode, idioma)
|
|
11
|
+
- **MgLoginModal** - Modal de login/registro
|
|
12
|
+
- **MgNotificationsModal** - Lista de notificações com infinite scroll
|
|
13
|
+
- **MgNotificationCard** - Card individual de notificação
|
|
14
|
+
- **MgMessageModal** - Interface completa de chat
|
|
15
|
+
- **MgMessageCard** - Card de conversa na lista de chats
|
|
16
|
+
|
|
17
|
+
### Páginas institucionais (More)
|
|
18
|
+
`about`, `faq`, `careers`, `team`, `press`, `partners`, `roadmap`, `status`, `blog`, `contact`, `magazine`, `health-check`, `icons`
|
|
19
|
+
|
|
20
|
+
Todas filtram por `systemId` (platform_id) e compartilham a mesma estrutura visual.
|
|
21
|
+
|
|
22
|
+
### Páginas de erro
|
|
23
|
+
- **error.vue** - Página de erro global (404, 500, etc.)
|
|
24
|
+
|
|
25
|
+
### Páginas dinâmicas
|
|
26
|
+
- **[slug]/index.vue** - Páginas de documentos dinâmicos (termos, políticas, etc.)
|
|
27
|
+
|
|
28
|
+
### Services
|
|
29
|
+
- **httpService** - Axios com interceptors (error handling, lang, timezone, bearer token)
|
|
30
|
+
- **authService** - Autenticação (authenticate, getUser, logout)
|
|
31
|
+
- **notificationsService** - CRUD de notificações
|
|
32
|
+
- **chatService** - CRUD de chat (endpoints configuráveis)
|
|
33
|
+
- **institutionalService** - APIs institucionais (FAQ, careers, team, press, etc.)
|
|
34
|
+
- **contactService** - Formulário de contato
|
|
35
|
+
- **documentService** - Documentos dinâmicos (termos, políticas)
|
|
36
|
+
|
|
37
|
+
### Stores (Pinia)
|
|
38
|
+
- **useAuthStore** - Estado do usuário com persistência em localStorage
|
|
39
|
+
- **useLoginStore** - Estado do modal de login
|
|
40
|
+
- **useIndexStore** - Estado de sidebar e modais
|
|
41
|
+
- **useNotificationsStore** - Notificações com filtro por systemId
|
|
42
|
+
- **useChatStore** - Chat com WebSocket e polling fallback
|
|
43
|
+
- **useInstitutionalStore** - Cache de dados institucionais
|
|
44
|
+
- **useContactStore** - Estado do formulário de contato
|
|
45
|
+
- **useDocumentStore** - Documentos dinâmicos
|
|
46
|
+
|
|
47
|
+
### Composables
|
|
48
|
+
- **useLogout()** - Logout unificado (API + cookies + localStorage + store reset + redirect)
|
|
49
|
+
- **useUserNotifications()** - Notificações reativas com filtro por systemId
|
|
50
|
+
|
|
51
|
+
### Plugins
|
|
52
|
+
- **echo.client** - Laravel Echo / Pusher com auto-reconnect
|
|
53
|
+
- **auth.client** - Auto-fetch do usuário ao montar o app
|
|
54
|
+
|
|
55
|
+
### Middleware
|
|
56
|
+
- **auth.global** - Proteção de rotas configurável via `protectedRoutes`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Instalação
|
|
61
|
+
|
|
62
|
+
### 1. Adicionar dependência
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install git+ssh://git@github.com/Mundo-Gamer-Network/shared-ui.git#v1.0.0
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Ou no `package.json`:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@mundogamernetwork/shared-ui": "git+ssh://git@github.com/Mundo-Gamer-Network/shared-ui.git#v1.0.0"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Configurar como Nuxt Layer
|
|
79
|
+
|
|
80
|
+
No `nuxt.config.ts` do app:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
export default defineNuxtConfig({
|
|
84
|
+
extends: ['@mundogamernetwork/shared-ui'],
|
|
85
|
+
|
|
86
|
+
runtimeConfig: {
|
|
87
|
+
public: {
|
|
88
|
+
mgSharedUi: {
|
|
89
|
+
platform: 'MGC', // Identificador do app (MGC, MGTV, MGN, etc.)
|
|
90
|
+
systemId: '1', // VITE_SYSTEM_ID - filtra notificações, FAQ, careers, etc.
|
|
91
|
+
apiBaseURL: 'https://api.mundogamer.network',
|
|
92
|
+
accountsBaseUrl: 'https://accounts.mundogamer.network',
|
|
93
|
+
networkBaseUrl: 'https://network.mundogamer.network',
|
|
94
|
+
features: {
|
|
95
|
+
chat: true, // Habilita chat no header
|
|
96
|
+
notifications: true, // Habilita notificações no header
|
|
97
|
+
wallet: false, // Habilita sistema de moedas (futuro)
|
|
98
|
+
darkMode: true, // Habilita toggle de dark mode
|
|
99
|
+
search: false, // Habilita barra de busca no header
|
|
100
|
+
statusOnline: true // Habilita toggle de status online
|
|
101
|
+
},
|
|
102
|
+
languages: ['pt-BR', 'en', 'es', 'de', 'ro'],
|
|
103
|
+
protectedRoutes: [ // Rotas que exigem autenticação
|
|
104
|
+
'/profile',
|
|
105
|
+
'/settings',
|
|
106
|
+
'/dashboard'
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Peer dependencies
|
|
115
|
+
|
|
116
|
+
O app precisa ter instalado:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"@pinia/nuxt": ">=0.5.0",
|
|
121
|
+
"@pinia-plugin-persistedstate/nuxt": ">=1.0.0",
|
|
122
|
+
"axios": ">=1.0.0",
|
|
123
|
+
"laravel-echo": ">=1.15.0",
|
|
124
|
+
"pinia": ">=2.1.0",
|
|
125
|
+
"pusher-js": ">=8.0.0"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Configuração por App
|
|
132
|
+
|
|
133
|
+
### Feature flags
|
|
134
|
+
|
|
135
|
+
Cada app habilita apenas o que usa:
|
|
136
|
+
|
|
137
|
+
| App | chat | notifications | wallet | darkMode | search | statusOnline |
|
|
138
|
+
|-----|------|--------------|--------|----------|--------|-------------|
|
|
139
|
+
| community-frontend | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
|
140
|
+
| tv-frontend | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
|
141
|
+
| jobs-frontend | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
142
|
+
| academy-frontend | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
|
|
143
|
+
| token-frontend | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
144
|
+
| club-frontend | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
|
|
145
|
+
| agency-frontend | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
146
|
+
| network-site | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
147
|
+
| network-accounts | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
148
|
+
|
|
149
|
+
### Chat endpoints customizados (jobs-frontend)
|
|
150
|
+
|
|
151
|
+
O jobs-frontend usa endpoints diferentes para o chat. Configure assim:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
mgSharedUi: {
|
|
155
|
+
chatEndpoints: {
|
|
156
|
+
list: '/public/vacancy-application/chats',
|
|
157
|
+
send: '/public/vacancy-application/chats/messages/',
|
|
158
|
+
show: '/public/vacancy-application/chats/{chatId}',
|
|
159
|
+
unread: '/public/vacancy-application/chats/messages/unread/count',
|
|
160
|
+
delete: '/public/vacancy-application/chats/{chatId}',
|
|
161
|
+
deleteMessage: '/public/vacancy-application/chats/messages/{messageId}'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Rotas protegidas
|
|
167
|
+
|
|
168
|
+
Defina as rotas que exigem login. O middleware redireciona para accounts com `redirect_to`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
mgSharedUi: {
|
|
172
|
+
protectedRoutes: ['/profile', '/settings', '/dashboard']
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Sobrescrita (Override)
|
|
179
|
+
|
|
180
|
+
Por ser um Nuxt Layer, qualquer arquivo pode ser sobrescrito no app consumidor. Basta criar o arquivo com o mesmo caminho:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
# Sobrescrever a página about
|
|
184
|
+
app/pages/about.vue → substitui shared-ui/pages/about.vue
|
|
185
|
+
|
|
186
|
+
# Sobrescrever um componente
|
|
187
|
+
app/components/ui/MgHeaderComponent.vue → substitui o header padrão
|
|
188
|
+
|
|
189
|
+
# Adicionar um store extra
|
|
190
|
+
app/stores/custom.ts → adiciona sem conflito
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Atualização
|
|
196
|
+
|
|
197
|
+
Para atualizar para uma nova versão:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Se usa tag específica, mude a tag no package.json e reinstale:
|
|
201
|
+
npm install git+ssh://git@github.com/Mundo-Gamer-Network/shared-ui.git#v1.1.0
|
|
202
|
+
|
|
203
|
+
# Se aponta para main (sem tag):
|
|
204
|
+
npm update @mundogamernetwork/shared-ui
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Variáveis de ambiente necessárias no app
|
|
210
|
+
|
|
211
|
+
```env
|
|
212
|
+
VITE_API_BASE_URL=https://api.mundogamer.network
|
|
213
|
+
VITE_BASE_ACCOUNTS_URL=https://accounts.mundogamer.network
|
|
214
|
+
VITE_BASE_URL_NETWORK=https://network.mundogamer.network
|
|
215
|
+
VITE_SYSTEM_ID=1
|
|
216
|
+
VITE_PUSHER_APP_KEY=your_pusher_key
|
|
217
|
+
VITE_PUSHER_APP_CLUSTER=mt1
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Estrutura
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
shared-ui/
|
|
226
|
+
├── components/ui/
|
|
227
|
+
│ ├── Header/
|
|
228
|
+
│ │ ├── MgMessageCard.vue
|
|
229
|
+
│ │ ├── MgMessageModal.vue
|
|
230
|
+
│ │ ├── MgNotificationCard.vue
|
|
231
|
+
│ │ └── MgNotificationsModal.vue
|
|
232
|
+
│ ├── MgHeaderComponent.vue
|
|
233
|
+
│ ├── MgHeaderUIConfig.vue
|
|
234
|
+
│ ├── MgHeaderUIUser.vue
|
|
235
|
+
│ └── MgLoginModal.vue
|
|
236
|
+
├── composables/
|
|
237
|
+
│ ├── useLogout.ts
|
|
238
|
+
│ └── useUserNotifications.ts
|
|
239
|
+
├── middleware/
|
|
240
|
+
│ └── auth.global.ts
|
|
241
|
+
├── pages/
|
|
242
|
+
│ ├── [slug]/index.vue
|
|
243
|
+
│ ├── magazine/
|
|
244
|
+
│ │ ├── index.vue
|
|
245
|
+
│ │ └── [slug].vue
|
|
246
|
+
│ ├── about.vue
|
|
247
|
+
│ ├── blog.vue
|
|
248
|
+
│ ├── careers.vue
|
|
249
|
+
│ ├── contact.vue
|
|
250
|
+
│ ├── faq.vue
|
|
251
|
+
│ ├── health-check.vue
|
|
252
|
+
│ ├── icons.vue
|
|
253
|
+
│ ├── partners.vue
|
|
254
|
+
│ ├── press.vue
|
|
255
|
+
│ ├── roadmap.vue
|
|
256
|
+
│ ├── status.vue
|
|
257
|
+
│ └── team.vue
|
|
258
|
+
├── plugins/
|
|
259
|
+
│ ├── auth.client.ts
|
|
260
|
+
│ └── echo.client.ts
|
|
261
|
+
├── services/
|
|
262
|
+
│ ├── authService.ts
|
|
263
|
+
│ ├── chatService.ts
|
|
264
|
+
│ ├── contactService.ts
|
|
265
|
+
│ ├── documentService.ts
|
|
266
|
+
│ ├── httpService.ts
|
|
267
|
+
│ ├── institutionalService.ts
|
|
268
|
+
│ └── notificationsService.ts
|
|
269
|
+
├── stores/
|
|
270
|
+
│ ├── auth.ts
|
|
271
|
+
│ ├── chat.ts
|
|
272
|
+
│ ├── contact.ts
|
|
273
|
+
│ ├── document.ts
|
|
274
|
+
│ ├── index.ts
|
|
275
|
+
│ ├── institutional.ts
|
|
276
|
+
│ ├── login.ts
|
|
277
|
+
│ └── notifications.ts
|
|
278
|
+
├── types/index.ts
|
|
279
|
+
├── utils/serialize.ts
|
|
280
|
+
├── error.vue
|
|
281
|
+
├── nuxt.config.ts
|
|
282
|
+
└── package.json
|
|
283
|
+
```
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Asset {
|
|
3
|
+
id: number;
|
|
4
|
+
type: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
url: string;
|
|
7
|
+
thumbnail_url?: string;
|
|
8
|
+
file_size?: number;
|
|
9
|
+
mime_type?: string;
|
|
10
|
+
is_featured?: boolean;
|
|
11
|
+
sort_order?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
assets: Asset[];
|
|
16
|
+
allowDownload?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<Props>(), { allowDownload: true });
|
|
20
|
+
|
|
21
|
+
const { t } = useI18n();
|
|
22
|
+
|
|
23
|
+
const selectedType = ref('all');
|
|
24
|
+
const lightboxIndex = ref<number | null>(null);
|
|
25
|
+
|
|
26
|
+
const assetTypes = computed(() => {
|
|
27
|
+
const types = new Set(props.assets.map(a => a.type));
|
|
28
|
+
return ['all', ...Array.from(types)];
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const filteredAssets = computed(() => {
|
|
32
|
+
if (selectedType.value === 'all') return props.assets;
|
|
33
|
+
return props.assets.filter(a => a.type === selectedType.value);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const imageAssets = computed(() => filteredAssets.value.filter(a =>
|
|
37
|
+
['screenshot', 'artwork', 'logo', 'gif'].includes(a.type)
|
|
38
|
+
));
|
|
39
|
+
|
|
40
|
+
function openLightbox(idx: number) {
|
|
41
|
+
lightboxIndex.value = idx;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function closeLightbox() {
|
|
45
|
+
lightboxIndex.value = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function prevImage() {
|
|
49
|
+
if (lightboxIndex.value !== null && lightboxIndex.value > 0) {
|
|
50
|
+
lightboxIndex.value--;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function nextImage() {
|
|
55
|
+
if (lightboxIndex.value !== null && lightboxIndex.value < imageAssets.value.length - 1) {
|
|
56
|
+
lightboxIndex.value++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formatFileSize(bytes?: number) {
|
|
61
|
+
if (!bytes) return '';
|
|
62
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
63
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
64
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function downloadAsset(asset: Asset) {
|
|
68
|
+
window.open(asset.url, '_blank');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function downloadAll() {
|
|
72
|
+
props.assets.forEach(asset => {
|
|
73
|
+
window.open(asset.url, '_blank');
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
79
|
+
<div class="asset-gallery">
|
|
80
|
+
<div class="gallery-header">
|
|
81
|
+
<h3 class="section-title">{{ $t('press_kit.assets') }}</h3>
|
|
82
|
+
<button v-if="allowDownload && assets.length" class="btn-download-all" @click="downloadAll">
|
|
83
|
+
<MGIcon icon="download" /> {{ $t('press_kit.download_all') }}
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div v-if="assetTypes.length > 2" class="type-filters">
|
|
88
|
+
<button
|
|
89
|
+
v-for="type in assetTypes"
|
|
90
|
+
:key="type"
|
|
91
|
+
:class="['filter-btn', { active: selectedType === type }]"
|
|
92
|
+
@click="selectedType = type"
|
|
93
|
+
>
|
|
94
|
+
{{ type === 'all' ? $t('press_kit.all') : type }}
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="gallery-grid">
|
|
99
|
+
<div
|
|
100
|
+
v-for="(asset, idx) in filteredAssets"
|
|
101
|
+
:key="asset.id"
|
|
102
|
+
class="asset-card"
|
|
103
|
+
@click="['screenshot', 'artwork', 'logo', 'gif'].includes(asset.type) ? openLightbox(imageAssets.indexOf(asset)) : downloadAsset(asset)"
|
|
104
|
+
>
|
|
105
|
+
<div class="asset-thumb">
|
|
106
|
+
<img
|
|
107
|
+
v-if="['screenshot', 'artwork', 'logo', 'gif'].includes(asset.type)"
|
|
108
|
+
:src="asset.thumbnail_url || asset.url"
|
|
109
|
+
:alt="asset.title || asset.type"
|
|
110
|
+
loading="lazy"
|
|
111
|
+
/>
|
|
112
|
+
<div v-else class="asset-icon">
|
|
113
|
+
<MGIcon :icon="asset.type === 'document' ? 'file' : 'film'" />
|
|
114
|
+
</div>
|
|
115
|
+
<span class="asset-type-badge">{{ asset.type }}</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="asset-info">
|
|
118
|
+
<span class="asset-title">{{ asset.title || asset.type }}</span>
|
|
119
|
+
<span v-if="asset.file_size" class="asset-size">{{ formatFileSize(asset.file_size) }}</span>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div v-if="!assets.length" class="empty-state">
|
|
125
|
+
{{ $t('press_kit.no_assets') }}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Lightbox -->
|
|
129
|
+
<div v-if="lightboxIndex !== null" class="lightbox" @click.self="closeLightbox">
|
|
130
|
+
<button class="lb-close" @click="closeLightbox">×</button>
|
|
131
|
+
<button v-if="lightboxIndex > 0" class="lb-nav lb-prev" @click="prevImage">‹</button>
|
|
132
|
+
<img :src="imageAssets[lightboxIndex]?.url" :alt="imageAssets[lightboxIndex]?.title" />
|
|
133
|
+
<button v-if="lightboxIndex < imageAssets.length - 1" class="lb-nav lb-next" @click="nextImage">›</button>
|
|
134
|
+
<div class="lb-info">
|
|
135
|
+
<span>{{ imageAssets[lightboxIndex]?.title || imageAssets[lightboxIndex]?.type }}</span>
|
|
136
|
+
<button v-if="allowDownload" class="lb-download" @click="downloadAsset(imageAssets[lightboxIndex])">
|
|
137
|
+
<MGIcon icon="download" /> {{ $t('press_kit.download') }}
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</template>
|
|
143
|
+
|
|
144
|
+
<style lang="scss" scoped>
|
|
145
|
+
.asset-gallery {
|
|
146
|
+
.gallery-header {
|
|
147
|
+
display: flex;
|
|
148
|
+
justify-content: space-between;
|
|
149
|
+
align-items: center;
|
|
150
|
+
margin-bottom: 16px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.section-title {
|
|
154
|
+
font-size: 1.2rem;
|
|
155
|
+
font-weight: 700;
|
|
156
|
+
color: #fff;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.btn-download-all {
|
|
160
|
+
background: var(--color-primary, #FDB215);
|
|
161
|
+
color: #13161C;
|
|
162
|
+
border: none;
|
|
163
|
+
padding: 8px 16px;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
font-size: 0.85rem;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 6px;
|
|
170
|
+
transition: opacity 0.2s;
|
|
171
|
+
|
|
172
|
+
&:hover { opacity: 0.85; }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.type-filters {
|
|
176
|
+
display: flex;
|
|
177
|
+
gap: 8px;
|
|
178
|
+
margin-bottom: 16px;
|
|
179
|
+
flex-wrap: wrap;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.filter-btn {
|
|
183
|
+
background: #2a2d35;
|
|
184
|
+
color: #aaa;
|
|
185
|
+
border: 1px solid #3a3d45;
|
|
186
|
+
padding: 6px 14px;
|
|
187
|
+
font-size: 0.85rem;
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
text-transform: capitalize;
|
|
190
|
+
transition: all 0.2s;
|
|
191
|
+
|
|
192
|
+
&.active {
|
|
193
|
+
background: var(--color-primary, #FDB215);
|
|
194
|
+
color: #13161C;
|
|
195
|
+
border-color: var(--color-primary, #FDB215);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
&:hover:not(.active) {
|
|
199
|
+
background: #3a3d45;
|
|
200
|
+
color: #fff;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.gallery-grid {
|
|
205
|
+
display: grid;
|
|
206
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
207
|
+
gap: 16px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.asset-card {
|
|
211
|
+
background: #191B20;
|
|
212
|
+
border: 1px solid #1e2028;
|
|
213
|
+
overflow: hidden;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
transition: border-color 0.2s;
|
|
216
|
+
|
|
217
|
+
&:hover {
|
|
218
|
+
border-color: #3a3d45;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.asset-thumb {
|
|
223
|
+
position: relative;
|
|
224
|
+
aspect-ratio: 16 / 10;
|
|
225
|
+
background: #13161C;
|
|
226
|
+
|
|
227
|
+
img {
|
|
228
|
+
width: 100%;
|
|
229
|
+
height: 100%;
|
|
230
|
+
object-fit: cover;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.asset-icon {
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
height: 100%;
|
|
239
|
+
font-size: 2rem;
|
|
240
|
+
color: #555;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.asset-type-badge {
|
|
244
|
+
position: absolute;
|
|
245
|
+
top: 8px;
|
|
246
|
+
left: 8px;
|
|
247
|
+
background: rgba(0, 0, 0, 0.7);
|
|
248
|
+
color: #fff;
|
|
249
|
+
padding: 2px 8px;
|
|
250
|
+
font-size: 0.7rem;
|
|
251
|
+
text-transform: uppercase;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.asset-info {
|
|
255
|
+
padding: 10px 12px;
|
|
256
|
+
display: flex;
|
|
257
|
+
justify-content: space-between;
|
|
258
|
+
align-items: center;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.asset-title {
|
|
262
|
+
font-size: 0.85rem;
|
|
263
|
+
color: #fff;
|
|
264
|
+
text-transform: capitalize;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.asset-size {
|
|
268
|
+
font-size: 0.75rem;
|
|
269
|
+
color: #888;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.empty-state {
|
|
273
|
+
text-align: center;
|
|
274
|
+
padding: 40px;
|
|
275
|
+
color: #666;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Lightbox
|
|
279
|
+
.lightbox {
|
|
280
|
+
position: fixed;
|
|
281
|
+
inset: 0;
|
|
282
|
+
background: rgba(0, 0, 0, 0.95);
|
|
283
|
+
z-index: 9999;
|
|
284
|
+
display: flex;
|
|
285
|
+
align-items: center;
|
|
286
|
+
justify-content: center;
|
|
287
|
+
|
|
288
|
+
img {
|
|
289
|
+
max-width: 90vw;
|
|
290
|
+
max-height: 80vh;
|
|
291
|
+
object-fit: contain;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.lb-close {
|
|
296
|
+
position: absolute;
|
|
297
|
+
top: 20px;
|
|
298
|
+
right: 20px;
|
|
299
|
+
background: none;
|
|
300
|
+
border: none;
|
|
301
|
+
color: #fff;
|
|
302
|
+
font-size: 2.5rem;
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
z-index: 1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.lb-nav {
|
|
308
|
+
position: absolute;
|
|
309
|
+
top: 50%;
|
|
310
|
+
transform: translateY(-50%);
|
|
311
|
+
background: rgba(255, 255, 255, 0.1);
|
|
312
|
+
border: none;
|
|
313
|
+
color: #fff;
|
|
314
|
+
font-size: 3rem;
|
|
315
|
+
padding: 20px 12px;
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
transition: background 0.2s;
|
|
318
|
+
|
|
319
|
+
&:hover { background: rgba(255, 255, 255, 0.2); }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.lb-prev { left: 20px; }
|
|
323
|
+
.lb-next { right: 20px; }
|
|
324
|
+
|
|
325
|
+
.lb-info {
|
|
326
|
+
position: absolute;
|
|
327
|
+
bottom: 20px;
|
|
328
|
+
left: 50%;
|
|
329
|
+
transform: translateX(-50%);
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 16px;
|
|
333
|
+
color: #fff;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.lb-download {
|
|
337
|
+
background: var(--color-primary, #FDB215);
|
|
338
|
+
color: #13161C;
|
|
339
|
+
border: none;
|
|
340
|
+
padding: 6px 14px;
|
|
341
|
+
font-weight: 600;
|
|
342
|
+
font-size: 0.85rem;
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
gap: 6px;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
</style>
|