Refactor app architecture and clean local artifacts
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
import { computed, type ComputedRef, type Ref } from 'vue'
|
||||
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import { useAwardsStore } from '../../stores/awards'
|
||||
import type { HomeDisplayCategory, HomeInteractionModalKind } from './homeLandingTypes'
|
||||
|
||||
type AwardsStore = ReturnType<typeof useAwardsStore>
|
||||
type AuthStore = ReturnType<typeof useAuthStore>
|
||||
type HomeTimelineKey = 'nomination' | 'voting' | 'review' | 'show'
|
||||
|
||||
const CATEGORY_ICONS = ['✦', '★', '✧', '♬', '⚔', '☻', '♡', '✶'] as const
|
||||
|
||||
export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore: AuthStore) {
|
||||
const role = computed<'guest' | 'user' | 'admin'>(() => {
|
||||
if (!authStore.session) return 'guest'
|
||||
return authStore.isAdmin ? 'admin' : 'user'
|
||||
})
|
||||
const isGuest = computed(() => role.value === 'guest')
|
||||
const isUser = computed(() => role.value === 'user')
|
||||
const isAdmin = computed(() => role.value === 'admin')
|
||||
const twitchUser = computed(() => authStore.session?.twitchUserId ?? 'local_user')
|
||||
const siteContent = computed(() => store.overview.siteContent)
|
||||
const faqItems = computed(() =>
|
||||
(store.overview.faq ?? []).filter((item) => item?.question && item.answer),
|
||||
)
|
||||
const publicStreamUrl = computed(() => store.overview.showStreamUrl || 'https://twitch.tv/jayuhime')
|
||||
const showDate = computed(() => store.overview.showDate)
|
||||
const showStartsAt = computed(() => store.overview.showStartsAt || '20:00:00')
|
||||
const currentYear = computed(() => store.overview.year ? String(store.overview.year) : '')
|
||||
const displayCategories = computed<HomeDisplayCategory[]>(() =>
|
||||
store.categories.categories.map((category, index) => ({
|
||||
id: String(category.id),
|
||||
name: category.name,
|
||||
icon: CATEGORY_ICONS[index % CATEGORY_ICONS.length] ?? '✦',
|
||||
candidates: category.candidates,
|
||||
})),
|
||||
)
|
||||
const candidateCount = computed(() =>
|
||||
displayCategories.value.reduce((sum, category) => sum + category.candidates.length, 0),
|
||||
)
|
||||
const bootstrapArchiveYears = computed<Array<{ year: number }>>(() =>
|
||||
store.overview.winnersPreview.length > 0
|
||||
? [...new Set(store.overview.winnersPreview.map((winner) => winner.year))].map((year) => ({ year }))
|
||||
: [{ year: store.overview.year - 1 }],
|
||||
)
|
||||
|
||||
function timelineItem(key: HomeTimelineKey) {
|
||||
return store.overview.timeline.find((entry) => entry.key === key)
|
||||
}
|
||||
|
||||
function formatRange(key: Exclude<HomeTimelineKey, 'show'>) {
|
||||
const item = timelineItem(key)
|
||||
if (!item) return ''
|
||||
return `${formatDateLabel(item.startsAt)} – ${formatDateLabel(item.endsAt)}`
|
||||
}
|
||||
|
||||
function formatTimelineRange(key: HomeTimelineKey) {
|
||||
const item = timelineItem(key)
|
||||
if (!item) return 'Noch offen'
|
||||
return item.startsAt === item.endsAt
|
||||
? formatDateLabel(item.startsAt)
|
||||
: `${formatDateLabel(item.startsAt)} – ${formatDateLabel(item.endsAt)}`
|
||||
}
|
||||
|
||||
function formatShowDate() {
|
||||
return formatDateLabel(store.overview.showDate)
|
||||
}
|
||||
|
||||
return {
|
||||
role,
|
||||
isGuest,
|
||||
isUser,
|
||||
isAdmin,
|
||||
twitchUser,
|
||||
siteContent,
|
||||
faqItems,
|
||||
publicStreamUrl,
|
||||
showDate,
|
||||
showStartsAt,
|
||||
currentYear,
|
||||
displayCategories,
|
||||
candidateCount,
|
||||
bootstrapArchiveYears,
|
||||
formatRange,
|
||||
formatTimelineRange,
|
||||
formatShowDate,
|
||||
initialsFor,
|
||||
}
|
||||
}
|
||||
|
||||
export function useHomeModalCandidatePresentation(params: {
|
||||
displayCategories: ComputedRef<HomeDisplayCategory[]>
|
||||
activeCat: Ref<number>
|
||||
modal: Ref<null | HomeInteractionModalKind>
|
||||
votes: Ref<Record<string, number>>
|
||||
activeCategory: ComputedRef<HomeDisplayCategory | null>
|
||||
setCat: (index: number) => void
|
||||
pickNominee: (categoryId: string, index: number) => void
|
||||
}) {
|
||||
const {
|
||||
displayCategories,
|
||||
activeCat,
|
||||
modal,
|
||||
votes,
|
||||
activeCategory,
|
||||
setCat,
|
||||
pickNominee,
|
||||
} = params
|
||||
|
||||
const catList = computed(() => buildCatList(displayCategories, activeCat, votes, setCat))
|
||||
const noms = computed(() => {
|
||||
const cat = activeCategory.value
|
||||
const list = cat?.candidates ?? []
|
||||
const voteMode = modal.value === 'vote'
|
||||
return list.map((candidate, idx) => {
|
||||
const selected = cat ? votes.value[cat.id] === idx : false
|
||||
const clipUrl = candidate.clipUrl?.trim() || null
|
||||
const clipTitle = candidate.clipTitle?.trim() || 'Highlight-Clip ansehen'
|
||||
const clipPlatform = clipUrl ? candidate.clipPlatform?.trim() || candidate.platform : 'Clip fehlt'
|
||||
return {
|
||||
name: candidate.displayName,
|
||||
handle: candidate.channelSlug,
|
||||
platform: candidate.platform,
|
||||
initials: initialsFor(candidate.displayName),
|
||||
clipUrl,
|
||||
clipTitle,
|
||||
clipPlatform,
|
||||
idx,
|
||||
selected,
|
||||
hasClip: Boolean(clipUrl),
|
||||
showPick: voteMode,
|
||||
onPick: () => cat && pickNominee(cat.id, idx),
|
||||
cardStyle: `display:flex;align-items:center;justify-content:space-between;gap:12px;padding:13px 15px;border-radius:13px;transition:all .15s;border:1.5px solid ${selected ? '#8b6cdb;background:#f6f1fd;' : '#ece4f6;background:#fff;'}`,
|
||||
btnStyle: "flex:none;white-space:nowrap;padding:8px 16px;border-radius:9px;border:none;cursor:pointer;font-family:'Outfit',sans-serif;font-weight:600;font-size:13px;transition:all .15s;" + (selected ? 'background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;' : 'background:#f1ecfb;color:#8b6cdb;'),
|
||||
btnLabel: selected ? '✓ Gewählt' : 'Auswählen',
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
catList,
|
||||
noms,
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateLabel(value: string) {
|
||||
if (!value) return 'Noch nicht terminiert'
|
||||
const date = new Date(`${value}T00:00:00`)
|
||||
return Number.isNaN(date.getTime())
|
||||
? value
|
||||
: date.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' })
|
||||
}
|
||||
|
||||
function initialsFor(value: string) {
|
||||
return value
|
||||
.split(/[\s&.-]+/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((part) => part[0]?.toUpperCase() ?? '')
|
||||
.join('')
|
||||
}
|
||||
|
||||
function buildCatList(
|
||||
displayCategories: ComputedRef<HomeDisplayCategory[]>,
|
||||
activeCat: Ref<number>,
|
||||
votes: Ref<Record<string, number>>,
|
||||
setCat: (index: number) => void,
|
||||
) {
|
||||
const baseRow = "display:flex;align-items:center;gap:10px;padding:11px 13px;border-radius:11px;cursor:pointer;font-size:14px;font-weight:600;transition:all .15s;border:1px solid transparent;outline:none;-webkit-tap-highlight-color:transparent;text-align:left;width:100%;background:transparent;font-family:'Outfit',sans-serif;"
|
||||
return displayCategories.value.map((category, index) => {
|
||||
const active = index === activeCat.value
|
||||
const done = votes.value[category.id] != null
|
||||
return {
|
||||
name: category.name,
|
||||
icon: category.icon,
|
||||
idx: index,
|
||||
done,
|
||||
onClick: () => setCat(index),
|
||||
rowStyle: baseRow + (active ? 'background:#f1ecfb;border-color:#d8c9f2;color:#5f44ad;' : 'border-color:transparent;color:#6f6685;'),
|
||||
iconStyle: "flex:none;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;font-size:14px;" + (active ? 'background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;' : 'background:#efe7fb;color:#9a7fce;'),
|
||||
checkStyle: `flex:none;margin-left:auto;color:#1f9d5a;font-size:14px;font-weight:700;display:${done ? 'inline' : 'none'};`,
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user