Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ RewriteRule ^api/v1/(.*)$ http://localhost:8080/api/v1/$1 [P,L]
# Handle root API endpoint
RewriteRule ^api/v1/?$ http://localhost:8080/api/v1/ [P,L]

# Serve Swagger UI static files (no proxy needed)
# Static files will be served directly by web server
# =============================================================================
# SWAGGER UI STATIC FILES CONFIGURATION
# =============================================================================

# Serve Swagger UI
RewriteRule ^swagger/?$ /dist/index.html [L]
RewriteRule ^swagger/(.*)$ /dist/$1 [L]

# Serve docs directory (swagger.json, swagger.yaml, swagger.html)
RewriteRule ^docs/(.*)$ /docs/$1 [L]


# Handle docs route for Vue documentation site
RewriteRule ^docs/?$ /index.html [L]

# Optional: API documentation redirect
RewriteRule ^api/?$ /api/v1/health [R=302,L]
# Redirect /api/ to Swagger UI documentation
RewriteRule ^api/?$ /swagger/ [R=302,L]

# =============================================================================
# CORS HEADERS CONFIGURATION
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"author": "",
"license": "ISC",
"type": "module",
"packageManager": "pnpm@10.13.1",
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
"devDependencies": {
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.16",
Expand All @@ -26,10 +26,13 @@
"vue-tsc": "^3.0.6"
},
"dependencies": {
"@types/prismjs": "^1.26.5",
"aos": "^2.3.4",
"katex": "^0.16.22",
"prismjs": "^1.30.0",
"vue": "^3.5.21",
"vue-i18n": "9",
"vue-prism-component": "^2.0.0",
"vue-router": "^4.5.1"
}
}
2 changes: 2 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
overrides:
vite@>=7.1.0 <=7.1.4: '>=7.1.5'
195 changes: 195 additions & 0 deletions src/components/CodeBlock.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<template>
<div class="code-block-wrapper">
<div v-if="title || language" class="code-header">
<span v-if="title" class="code-title">{{ title }}</span>
<span v-if="language" class="code-language">{{ language }}</span>
<button
@click="copyToClipboard"
class="copy-button"
:class="{ 'copied': copied }"
:title="copied ? 'Copied!' : 'Copy code'"
>
<svg v-if="!copied" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</button>
</div>
<div class="code-container" :class="`language-${language}`">
<pre><code ref="codeElement" :class="`language-${language}`">{{ code }}</code></pre>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import Prism from 'prismjs'

// Import commonly used languages
import 'prismjs/components/prism-bash'
import 'prismjs/components/prism-json'
import 'prismjs/components/prism-javascript'
import 'prismjs/components/prism-typescript'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-go'

// Import Prism themes (we'll use a custom theme)
import 'prismjs/themes/prism-tomorrow.css'

interface Props {
code: string
language?: string
title?: string
}

const props = withDefaults(defineProps<Props>(), {
language: 'bash'
})

const codeElement = ref<HTMLElement>()
const copied = ref(false)

const highlightCode = () => {
if (codeElement.value) {
Prism.highlightElement(codeElement.value)
}
}

const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(props.code)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
} catch (err) {
console.error('Failed to copy:', err)
}
}

onMounted(() => {
highlightCode()
})

watch(() => props.code, () => {
highlightCode()
})
</script>

<style scoped>
.code-block-wrapper {
@apply rounded-lg overflow-hidden border border-gray-700 bg-gray-900 my-4;
}

.code-header {
@apply flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700;
}

.code-title {
@apply text-sm font-medium text-gray-300;
}

.code-language {
@apply text-xs font-mono text-gray-500 ml-auto mr-2;
}

.copy-button {
@apply p-1.5 rounded-md bg-gray-700 hover:bg-gray-600 text-gray-300 hover:text-white transition-all duration-200;
}

.copy-button.copied {
@apply bg-green-600 text-white;
}

.code-container {
@apply relative overflow-x-auto;
}

.code-container pre {
@apply m-0 p-4 text-xs sm:text-sm font-mono;
background: transparent !important;
}

.code-container code {
@apply text-gray-300;
background: transparent !important;
font-family: 'Fira Code', 'JetBrains Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
}

/* Customize syntax highlighting colors for better contrast */
:deep(.token.comment),
:deep(.token.prolog),
:deep(.token.doctype),
:deep(.token.cdata) {
@apply text-gray-500;
}

:deep(.token.punctuation) {
@apply text-gray-400;
}

:deep(.token.property),
:deep(.token.tag),
:deep(.token.boolean),
:deep(.token.number),
:deep(.token.constant),
:deep(.token.symbol),
:deep(.token.deleted) {
@apply text-red-400;
}

:deep(.token.selector),
:deep(.token.attr-name),
:deep(.token.string),
:deep(.token.char),
:deep(.token.builtin),
:deep(.token.inserted) {
@apply text-green-400;
}

:deep(.token.operator),
:deep(.token.entity),
:deep(.token.url),
:deep(.language-css .token.string),
:deep(.style .token.string) {
@apply text-blue-400;
}

:deep(.token.atrule),
:deep(.token.attr-value),
:deep(.token.keyword) {
@apply text-purple-400;
}

:deep(.token.function),
:deep(.token.class-name) {
@apply text-yellow-400;
}

:deep(.token.regex),
:deep(.token.important),
:deep(.token.variable) {
@apply text-orange-400;
}

/* Mobile responsive adjustments */
@media (max-width: 640px) {
.code-container pre {
@apply text-[10px] p-3;
}

.code-header {
@apply px-3 py-1.5;
}

.code-title {
@apply text-xs;
}

.copy-button {
@apply p-1;
}
}
</style>
29 changes: 15 additions & 14 deletions src/components/documentation/ErrorHandlingSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,11 @@
<div class="grid lg:grid-cols-2 gap-6">
<div>
<h4 class="font-semibold text-gray-900 mb-3">{{ t('documentation.errorHandling.errorResponse') }}</h4>
<div class="bg-gray-900 rounded-lg p-4 font-mono text-sm">
<div class="text-blue-400">{</div>
<div class="ml-2">
<div><span class="text-yellow-400">"status"</span>: <span class="text-red-300">"error"</span>,</div>
<div><span class="text-yellow-400">"error"</span>: {</div>
<div class="ml-2">
<div><span class="text-yellow-400">"code"</span>: <span class="text-orange-400">400</span>,</div>
<div><span class="text-yellow-400">"message"</span>: <span class="text-green-300">"Invalid page parameter"</span>,</div>
<div><span class="text-yellow-400">"details"</span>: <span class="text-green-300">"Page must be a positive integer"</span></div>
</div>
<div>}</div>
</div>
<div class="text-blue-400">}</div>
</div>
<CodeBlock
:code="errorResponseExample"
language="json"
title="Error Response"
/>
</div>

<div>
Expand Down Expand Up @@ -112,11 +103,21 @@

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import CodeBlock from '@/components/CodeBlock.vue'

interface Props {
isActive: boolean
}

defineProps<Props>()
const { t } = useI18n()

const errorResponseExample = `{
"status": "error",
"error": {
"code": 400,
"message": "Invalid page parameter",
"details": "Page must be a positive integer"
}
}`
</script>
Loading