Files
RenewCMS/web/src/components/EndpointCard.vue
T

134 lines
4.5 KiB
Vue

<script setup lang="ts">
import { t, translate } from '@/i18n'
import { computed, ref } from 'vue'
import ApiFieldTable from '@/components/ApiFieldTable.vue'
import CodeSnippet from '@/components/CodeSnippet.vue'
import { RATE_LIMIT_SENTENCE, endpointExamples, type ApiEndpoint } from '@/data/apiCatalogue'
import { CHIP_CLASS, METHOD_CLASS, endpointAnchor } from '@/lib/apiReference'
const props = defineProps<{
endpoint: ApiEndpoint
origin: string
}>()
const open = ref(false)
const activeTab = ref('fetch')
const examples = computed(() => endpointExamples(props.endpoint, props.origin))
const rowLabel = computed(
() => `${props.endpoint.method} ${props.endpoint.path}. ${props.endpoint.description}`,
)
const bodyTitle = computed(() => {
if (props.endpoint.bodyKind === 'multipart') {
return t('Request body · multipart/form-data, 1 file field')
}
return props.endpoint.bodyKind === 'json' ? t('Request body · JSON') : t('Request body')
})
const snippetTabs = computed(() => [
{ k: 'fetch', label: 'fetch', code: examples.value.fetch },
{ k: 'curl', label: 'curl', code: examples.value.curl },
{ k: 'response', label: t('Response'), code: examples.value.response },
])
</script>
<template>
<UCollapsible
v-model:open="open"
:data-anchor="endpointAnchor(endpoint.id)"
class="scroll-mt-22 lg:scroll-mt-14 border-t border-default first:border-t-0"
:ui="{ content: 'border-t border-muted' }"
>
<button
type="button"
:aria-label="rowLabel"
class="flex min-h-11 w-full cursor-pointer flex-col items-stretch gap-1.5 px-3 py-3 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary lg:grid lg:grid-cols-[300px_minmax(0,1fr)_auto_16px] lg:items-center lg:gap-4 lg:px-4 lg:py-0"
:class="open ? 'bg-muted' : ''"
>
<span class="flex min-w-0 items-center gap-2.5">
<UBadge
:label="endpoint.method"
color="neutral"
variant="outline"
:class="METHOD_CLASS[endpoint.method]"
/>
<span class="min-w-0 truncate font-mono text-sm font-medium text-highlighted">{{
endpoint.path
}}</span>
<span class="flex-1 lg:hidden" />
<UIcon
:name="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
class="size-4 shrink-0 text-muted lg:hidden"
/>
</span>
<span class="min-w-0 text-toned lg:truncate">{{ endpoint.description }}</span>
<span class="flex flex-wrap gap-1.5">
<UBadge
:label="translate(endpoint.access)"
color="neutral"
variant="outline"
:class="
endpoint.access === 'Owner only' || endpoint.access === 'Owner or admin'
? CHIP_CLASS.strong
: CHIP_CLASS.neutral
"
/>
<UBadge
:label="t('API key OK')"
icon="i-lucide-check"
color="success"
variant="soft"
:class="CHIP_CLASS.success"
:ui="{ leadingIcon: 'size-3' }"
/>
</span>
<UIcon
:name="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
class="hidden size-4 text-muted lg:block"
/>
</button>
<template #content>
<div class="flex flex-col gap-3.5 px-3 pt-2.5 pb-4 lg:px-4">
<div class="flex flex-col gap-1.5">
<span class="font-semibold text-highlighted">{{ t('Query parameters') }}</span>
<ApiFieldTable v-if="endpoint.query.length > 0" :fields="endpoint.query" />
<span v-else class="text-muted">{{ t('No query parameters.') }}</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-semibold text-highlighted">{{ bodyTitle }}</span>
<ApiFieldTable v-if="endpoint.body.length > 0" :fields="endpoint.body" />
<span v-else class="text-muted">{{ t('No request body.') }}</span>
</div>
<CodeSnippet v-model:active="activeTab" :tabs="snippetTabs" />
<div v-if="endpoint.rateLimited" class="flex gap-2.5 text-toned">
<UIcon name="i-lucide-info" class="mt-0.5 size-4 shrink-0 text-info" />
<span class="text-pretty">{{ translate(RATE_LIMIT_SENTENCE) }}</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-semibold text-highlighted">{{ t('Errors') }}</span>
<div
v-for="error in endpoint.errors"
:key="`${error.status}-${error.caption}`"
:title="error.caption"
class="flex items-baseline gap-2.5"
>
<UBadge
:label="String(error.status)"
color="neutral"
variant="outline"
class="h-5 w-9 shrink-0 justify-center rounded-none border border-accented px-0 py-0 font-mono text-xs font-medium text-toned ring-0"
/>
<span class="min-w-0 text-pretty text-toned">{{ error.caption }}</span>
</div>
</div>
</div>
</template>
</UCollapsible>
</template>