Refactor app architecture and clean local artifacts

This commit is contained in:
AzuTear
2026-06-24 23:43:14 +02:00
parent 17134b3b82
commit fef1d36fe8
274 changed files with 37724 additions and 6065 deletions
@@ -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'};`,
}
})
}