140 lines
6.0 KiB
Vue
140 lines
6.0 KiB
Vue
<script setup lang="ts">
|
|
const { rates } = useVatRates()
|
|
|
|
const selectedCode = ref('DE')
|
|
const response = ref('')
|
|
const statusCode = ref<number | null>(null)
|
|
const isLoading = ref(false)
|
|
const showAllRates = ref(false)
|
|
|
|
const endpointUrl = computed(() =>
|
|
showAllRates.value
|
|
? 'https://vat-api.eu/api/v1/rates'
|
|
: `https://vat-api.eu/api/v1/rates/${selectedCode.value}`,
|
|
)
|
|
|
|
function syntaxHighlight(json: string): string {
|
|
return json
|
|
.replace(/("(?:\\.|[^"\\])*")\s*:/g, '<span style="color:#79c0ff">$1</span>:')
|
|
.replace(/:\s*("(?:\\.|[^"\\])*")/g, ': <span style="color:#a5d6ff">$1</span>')
|
|
.replace(/:\s*(\d+\.?\d*)/g, ': <span style="color:#79c0ff">$1</span>')
|
|
.replace(/(\[|\])/g, '<span style="color:#ff7b72">$1</span>')
|
|
.replace(/^(\{|})/gm, '<span style="color:#ff7b72">$1</span>')
|
|
}
|
|
|
|
async function sendRequest() {
|
|
isLoading.value = true
|
|
response.value = ''
|
|
statusCode.value = null
|
|
|
|
try {
|
|
const url = showAllRates.value
|
|
? '/api/v1/rates'
|
|
: `/api/v1/rates/${selectedCode.value}`
|
|
const data = await $fetch(url)
|
|
statusCode.value = 200
|
|
response.value = syntaxHighlight(JSON.stringify(data, null, 2))
|
|
} catch (err: any) {
|
|
statusCode.value = err?.statusCode ?? 500
|
|
const errorBody = err?.data ?? { error: err?.statusMessage ?? 'Request failed' }
|
|
response.value = syntaxHighlight(JSON.stringify(errorBody, null, 2))
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Auto-send on mount
|
|
onMounted(() => sendRequest())
|
|
</script>
|
|
|
|
<template>
|
|
<section id="playground" class="section-padding bg-surface-soft">
|
|
<div class="section-container">
|
|
<div class="text-center mb-10 animate-on-scroll">
|
|
<h2 class="text-title md:text-display-sm text-ink">
|
|
Try it out
|
|
</h2>
|
|
<p class="mt-3 text-ink-muted max-w-lg mx-auto">
|
|
Build your request, send it, and see the response.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="animate-on-scroll max-w-3xl mx-auto">
|
|
<div class="bg-white rounded-2xl border border-surface-border shadow-card overflow-hidden">
|
|
<!-- Controls -->
|
|
<div class="p-5 border-b border-surface-border">
|
|
<div class="flex flex-wrap gap-3 items-end">
|
|
<!-- Endpoint toggle -->
|
|
<div class="flex-shrink-0">
|
|
<label class="block text-xs font-medium text-ink-muted mb-1.5">Endpoint</label>
|
|
<div class="flex rounded-lg border border-surface-border overflow-hidden text-sm">
|
|
<button
|
|
class="px-3 py-2 font-medium transition-colors"
|
|
:class="!showAllRates ? 'bg-eu-blue text-white' : 'bg-white text-ink-secondary hover:bg-surface-soft'"
|
|
@click="showAllRates = false"
|
|
>
|
|
Single
|
|
</button>
|
|
<button
|
|
class="px-3 py-2 font-medium transition-colors"
|
|
:class="showAllRates ? 'bg-eu-blue text-white' : 'bg-white text-ink-secondary hover:bg-surface-soft'"
|
|
@click="showAllRates = true"
|
|
>
|
|
All Rates
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Country picker -->
|
|
<div v-if="!showAllRates" class="flex-shrink-0">
|
|
<label class="block text-xs font-medium text-ink-muted mb-1.5">Country</label>
|
|
<select
|
|
v-model="selectedCode"
|
|
class="px-3 py-2 rounded-lg border border-surface-border bg-white text-sm text-ink focus:outline-none focus:ring-2 focus:ring-eu-blue/20 focus:border-eu-blue/40 transition-all"
|
|
>
|
|
<option v-for="rate in rates" :key="rate.code" :value="rate.code">
|
|
{{ rate.flag }} {{ rate.country }} ({{ rate.code }})
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Send button -->
|
|
<button
|
|
class="px-5 py-2 rounded-lg bg-eu-gold text-eu-blue-dark font-semibold text-sm hover:bg-eu-gold-dark transition-colors flex items-center gap-2"
|
|
@click="sendRequest"
|
|
:disabled="isLoading"
|
|
>
|
|
<svg v-if="isLoading" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
</svg>
|
|
<span>{{ isLoading ? 'Sending...' : 'Send Request' }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- URL display -->
|
|
<div class="mt-3.5 flex items-center gap-2 px-3.5 py-2.5 rounded-lg bg-surface-muted font-mono text-sm overflow-x-auto">
|
|
<span class="flex-shrink-0 text-xs font-semibold px-1.5 py-0.5 rounded bg-green-100 text-green-700">GET</span>
|
|
<span class="text-ink-secondary">{{ endpointUrl }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Response -->
|
|
<div class="relative">
|
|
<div class="flex items-center justify-between px-5 py-3 bg-[#0d1117] border-b border-white/5">
|
|
<span class="text-xs text-[#8b949e] font-mono">Response</span>
|
|
<span v-if="statusCode === 200" class="text-xs font-mono px-2 py-0.5 rounded bg-green-500/20 text-green-400">200 OK</span>
|
|
<span v-else-if="statusCode" class="text-xs font-mono px-2 py-0.5 rounded bg-red-500/20 text-red-400">{{ statusCode }} Error</span>
|
|
</div>
|
|
<div class="bg-[#0d1117] text-[#e6edf3] font-mono text-sm leading-relaxed p-5 max-h-80 overflow-y-auto">
|
|
<pre v-if="response" class="text-[13px] leading-6" v-html="response" />
|
|
<p v-else-if="isLoading" class="text-[#8b949e] text-sm">Loading...</p>
|
|
<p v-else class="text-[#8b949e] text-sm">Click "Send Request" to see a response.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|