You've already forked AstralRinth
forked from didirus/AstralRinth
* refactor: migrate to common eslint+prettier configs * fix: prettier frontend * feat: config changes * fix: lint issues * fix: lint * fix: type imports * fix: cyclical import issue * fix: lockfile * fix: missing dep * fix: switch to tabs * fix: continue switch to tabs * fix: rustfmt parity * fix: moderation lint issue * fix: lint issues * fix: ui intl * fix: lint issues * Revert "fix: rustfmt parity" This reverts commit cb99d2376c321d813d4b7fc7e2a213bb30a54711. * feat: revert last rs
122 lines
3.5 KiB
Vue
122 lines
3.5 KiB
Vue
<template>
|
|
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-8 shadow-xl">
|
|
<div class="flex flex-col items-center text-center">
|
|
<div class="flex flex-col items-center gap-4">
|
|
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
|
<component :is="icon" class="size-12 text-orange" />
|
|
</div>
|
|
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">{{ title }}</h1>
|
|
</div>
|
|
<div v-if="!description">
|
|
<slot name="description" />
|
|
</div>
|
|
<p v-else class="text-lg text-secondary">{{ description }}</p>
|
|
</div>
|
|
|
|
<div v-if="errorDetails" class="my-4 w-full rounded-lg border border-divider bg-bg-raised">
|
|
<div class="divide-y divide-divider">
|
|
<div
|
|
v-for="detail in errorDetails.filter((detail) => detail.type !== 'hidden')"
|
|
:key="detail.label"
|
|
class="px-4 py-3"
|
|
>
|
|
<div v-if="detail.type === 'inline'" class="flex items-center justify-between">
|
|
<span class="font-medium text-secondary">{{ detail.label }}</span>
|
|
<div class="flex items-center gap-2">
|
|
<code class="rounded-lg bg-code-bg px-2 py-1 text-sm text-code-text">
|
|
{{ detail.value }}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else-if="detail.type === 'block'" class="flex flex-col gap-2">
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-medium text-secondary">{{ detail.label }}</span>
|
|
</div>
|
|
<div class="w-full overflow-hidden rounded-lg bg-code-bg p-3">
|
|
<code
|
|
class="block w-full overflow-x-auto break-words text-sm text-code-text whitespace-pre-wrap"
|
|
>
|
|
{{ detail.value }}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 flex !w-full flex-row gap-4">
|
|
<ButtonStyled
|
|
v-if="action"
|
|
size="large"
|
|
:color="action.color || 'brand'"
|
|
:disabled="action.disabled"
|
|
@click="action.onClick"
|
|
>
|
|
<button class="!w-full">
|
|
<component :is="action.icon" v-if="action.icon && !action.showAltIcon" class="size-4" />
|
|
<component
|
|
:is="action.altIcon"
|
|
v-else-if="action.icon && action.showAltIcon"
|
|
class="size-4"
|
|
/>
|
|
{{ action.label }}
|
|
</button>
|
|
</ButtonStyled>
|
|
|
|
<ButtonStyled v-if="errorDetails" size="large" color="standard" @click="copyErrorInformation">
|
|
<button class="!w-full">
|
|
<CopyIcon v-if="!infoCopied" class="size-4" />
|
|
<CheckIcon v-else class="size-4" />
|
|
Copy Information
|
|
</button>
|
|
</ButtonStyled>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { CheckIcon, CopyIcon } from '@modrinth/assets'
|
|
import type { Component } from 'vue'
|
|
import { ref } from 'vue'
|
|
|
|
import ButtonStyled from './ButtonStyled.vue'
|
|
|
|
const infoCopied = ref(false)
|
|
|
|
const props = defineProps<{
|
|
title: string
|
|
description?: string
|
|
icon: Component
|
|
errorDetails?: {
|
|
label?: string
|
|
value?: string
|
|
type?: 'inline' | 'block' | 'hidden'
|
|
}[]
|
|
action?: {
|
|
label: string
|
|
onClick: () => void
|
|
color?: 'brand' | 'standard' | 'red' | 'orange' | 'blue'
|
|
disabled?: boolean
|
|
icon?: Component
|
|
altIcon?: Component
|
|
showAltIcon?: boolean
|
|
}
|
|
}>()
|
|
|
|
const copyErrorInformation = async () => {
|
|
if (!props.errorDetails || props.errorDetails.length === 0) return
|
|
|
|
const formattedErrorInfo = props.errorDetails
|
|
.filter((detail) => detail.label && detail.value)
|
|
.map((detail) => `${detail.label}: ${detail.value}`)
|
|
.join('\n\n')
|
|
|
|
await navigator.clipboard.writeText(formattedErrorInfo)
|
|
infoCopied.value = true
|
|
setTimeout(() => {
|
|
infoCopied.value = false
|
|
}, 2000)
|
|
}
|
|
</script>
|