From 3d79fe1abca5ed6de00d4cbd6bda62042de38966 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Fri, 10 Apr 2026 19:00:00 +0200 Subject: [PATCH] feat(modules): redesign admin marketplace cards and detail view ModuleCard moves badges to the top-right, shows a cover placeholder when art is missing, and drops the rating/pricing chrome that was never populated by the marketplace. ModuleDetailView splits into a hero row (cover + module info, two-thirds width) plus a sticky action card on the right (one-third) so install/update/purchase buttons stay visible when scrolling long descriptions. ModuleIndexView promotes the marketplace API token form to a persistent card at the top of the page and adds an authenticated/premium status pill so super-admins can see whether the current token unlocks premium listings. The tabs and empty state were reorganized so 'installed' and 'marketplace' feel like peers. The admin modules store tracks marketplace auth status, adds checkApiToken() and setApiToken() methods, and unifies the install-request shape into ModuleInstallPayload so both the free and paid install buttons route through the same code path. --- .../admin/modules/components/ModuleCard.vue | 95 ++-- .../scripts/features/admin/modules/store.ts | 88 +-- .../admin/modules/views/ModuleDetailView.vue | 516 +++++++++--------- .../admin/modules/views/ModuleIndexView.vue | 236 +++++--- 4 files changed, 525 insertions(+), 410 deletions(-) diff --git a/resources/scripts/features/admin/modules/components/ModuleCard.vue b/resources/scripts/features/admin/modules/components/ModuleCard.vue index bd1e5445..44028f10 100644 --- a/resources/scripts/features/admin/modules/components/ModuleCard.vue +++ b/resources/scripts/features/admin/modules/components/ModuleCard.vue @@ -1,83 +1,72 @@ diff --git a/resources/scripts/features/admin/modules/store.ts b/resources/scripts/features/admin/modules/store.ts index 1bdd7dcc..f8fc27c2 100644 --- a/resources/scripts/features/admin/modules/store.ts +++ b/resources/scripts/features/admin/modules/store.ts @@ -2,25 +2,17 @@ import { defineStore } from 'pinia' import { moduleService } from '../../../api/services/module.service' import type { Module, - ModuleReview, - ModuleFaq, - ModuleLink, - ModuleScreenshot, } from '../../../types/domain/module' +import type { + ModuleCheckResponse, + ModuleDetailResponse, + ModuleInstallPayload, +} from '../../../api/services/module.service' // ---------------------------------------------------------------- // Types // ---------------------------------------------------------------- -export interface ModuleDetailMeta { - modules: Module[] -} - -export interface ModuleDetailResponse { - data: Module - meta: ModuleDetailMeta -} - export interface InstallationStep { translationKey: string stepUrl: string @@ -40,6 +32,11 @@ export interface ModuleState { currentUser: { api_token: string | null } + marketplaceStatus: { + authenticated: boolean + premium: boolean + invalidToken: boolean + } enableModules: string[] } @@ -51,6 +48,11 @@ export const useModuleStore = defineStore('modules', { currentUser: { api_token: null, }, + marketplaceStatus: { + authenticated: false, + premium: false, + invalidToken: false, + }, enableModules: [], }), @@ -70,25 +72,30 @@ export const useModuleStore = defineStore('modules', { async fetchModule(slug: string): Promise { const response = await moduleService.get(slug) - const data = response as unknown as ModuleDetailResponse - - if ((data as Record).error === 'invalid_token') { - this.currentModule = null - this.modules = [] - this.apiToken = null - this.currentUser.api_token = null - return data - } - - this.currentModule = data - return data + this.currentModule = response + return response }, - async checkApiToken(token: string): Promise<{ success: boolean; error?: string }> { + async checkApiToken(token: string): Promise { const response = await moduleService.checkToken(token) - return { - success: response.success ?? false, - error: response.error, + this.marketplaceStatus = { + authenticated: response.authenticated ?? false, + premium: response.premium ?? false, + invalidToken: response.error === 'invalid_token', + } + return response + }, + + setApiToken(token: string | null): void { + this.apiToken = token + this.currentUser.api_token = token + }, + + clearMarketplaceStatus(): void { + this.marketplaceStatus = { + authenticated: false, + premium: false, + invalidToken: false, } }, @@ -101,8 +108,7 @@ export const useModuleStore = defineStore('modules', { }, async installModule( - moduleName: string, - version: string, + payload: ModuleInstallPayload, onStepUpdate?: (step: InstallationStep) => void, ): Promise { const steps: InstallationStep[] = [ @@ -145,13 +151,25 @@ export const useModuleStore = defineStore('modules', { try { const stepFns: Record Promise>> = { '/api/v1/modules/download': () => - moduleService.download({ module: moduleName, version, path: path ?? undefined } as never) as Promise>, + moduleService.download({ + ...payload, + path: path ?? undefined, + }) as Promise>, '/api/v1/modules/unzip': () => - moduleService.unzip({ module: moduleName, version, path: path ?? undefined } as never) as Promise>, + moduleService.unzip({ + ...payload, + path: path ?? undefined, + }) as Promise>, '/api/v1/modules/copy': () => - moduleService.copy({ module: moduleName, version, path: path ?? undefined } as never) as Promise>, + moduleService.copy({ + ...payload, + path: path ?? undefined, + }) as Promise>, '/api/v1/modules/complete': () => - moduleService.complete({ module: moduleName, version, path: path ?? undefined } as never) as Promise>, + moduleService.complete({ + ...payload, + path: path ?? undefined, + }) as Promise>, } const result = await stepFns[step.stepUrl]() diff --git a/resources/scripts/features/admin/modules/views/ModuleDetailView.vue b/resources/scripts/features/admin/modules/views/ModuleDetailView.vue index 206d800c..d3ea2912 100644 --- a/resources/scripts/features/admin/modules/views/ModuleDetailView.vue +++ b/resources/scripts/features/admin/modules/views/ModuleDetailView.vue @@ -15,280 +15,300 @@ -
- -
-
- -