- Added page transition effects in app.vue for smoother navigation. - Updated nuxt.config.ts to include custom font styles and page transitions. - Improved loading indicators in MapPageWrapper.vue and login.vue for better user experience. - Enhanced MapView.vue with a collapsible control panel and improved styling. - Introduced new icons for various components to enhance visual consistency. - Updated Tailwind CSS configuration to extend font families and improve theme management. - Refined layout styles in default.vue and admin pages for better responsiveness and aesthetics. - Implemented error handling and loading states across various forms for improved user feedback.
118 lines
3.8 KiB
Vue
118 lines
3.8 KiB
Vue
<template>
|
|
<div class="container mx-auto p-4 max-w-2xl">
|
|
<h1 class="text-2xl font-bold mb-6">Profile</h1>
|
|
|
|
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200">
|
|
<div class="card-body">
|
|
<h2 class="card-title gap-2">
|
|
<icons-icon-key />
|
|
Upload tokens
|
|
</h2>
|
|
<p class="text-sm opacity-80">Tokens for upload API. Generate and copy as needed.</p>
|
|
<ul v-if="tokens?.length" class="list-disc list-inside mt-2 space-y-1">
|
|
<li v-for="t in tokens" :key="t" class="font-mono text-sm flex items-center gap-2">
|
|
<span class="break-all">{{ uploadTokenDisplay(t) }}</span>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-xs shrink-0"
|
|
aria-label="Copy token"
|
|
@click="copyToken(t)"
|
|
>
|
|
Copy
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
<p v-else class="text-sm mt-2">No tokens yet.</p>
|
|
<p v-if="tokenError" class="text-error text-sm mt-2">{{ tokenError }}</p>
|
|
<button class="btn btn-primary btn-sm mt-2 transition-all duration-200 hover:scale-[1.02]" :disabled="loadingTokens" @click="generateToken">
|
|
{{ loadingTokens ? '…' : 'Generate token' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200">
|
|
<div class="card-body">
|
|
<h2 class="card-title gap-2">
|
|
<icons-icon-settings />
|
|
Change password
|
|
</h2>
|
|
<form @submit.prevent="changePass" class="flex flex-col gap-2">
|
|
<PasswordInput
|
|
v-model="newPass"
|
|
placeholder="New password"
|
|
autocomplete="new-password"
|
|
/>
|
|
<p v-if="passMsg" class="text-sm" :class="passOk ? 'text-success' : 'text-error'">{{ passMsg }}</p>
|
|
<button type="submit" class="btn btn-sm transition-all duration-200 hover:scale-[1.02]" :disabled="loadingPass">Save password</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const api = useMapApi()
|
|
const tokens = ref<string[]>([])
|
|
const uploadPrefix = ref('')
|
|
const newPass = ref('')
|
|
const loadingTokens = ref(false)
|
|
const loadingPass = ref(false)
|
|
const passMsg = ref('')
|
|
const passOk = ref(false)
|
|
const tokenError = ref('')
|
|
|
|
function uploadTokenDisplay(token: string): string {
|
|
const base = (uploadPrefix.value ?? '').replace(/\/+$/, '')
|
|
return base ? `${base}/client/${token}` : `client/${token}`
|
|
}
|
|
|
|
function copyToken(token: string) {
|
|
navigator.clipboard.writeText(uploadTokenDisplay(token)).catch(() => {})
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const me = await api.me()
|
|
tokens.value = me.tokens ?? []
|
|
uploadPrefix.value = me.prefix ?? ''
|
|
} catch {
|
|
tokens.value = []
|
|
uploadPrefix.value = ''
|
|
}
|
|
})
|
|
|
|
async function generateToken() {
|
|
tokenError.value = ''
|
|
loadingTokens.value = true
|
|
try {
|
|
await api.meTokens()
|
|
const me = await api.me()
|
|
tokens.value = me.tokens ?? []
|
|
uploadPrefix.value = me.prefix ?? ''
|
|
} catch (e: unknown) {
|
|
const msg = e instanceof Error ? e.message : ''
|
|
tokenError.value = msg === 'Forbidden'
|
|
? 'You need "upload" permission to generate tokens. Ask an admin to add it to your account.'
|
|
: (msg || 'Failed to generate token')
|
|
} finally {
|
|
loadingTokens.value = false
|
|
}
|
|
}
|
|
|
|
async function changePass() {
|
|
passMsg.value = ''
|
|
loadingPass.value = true
|
|
try {
|
|
await api.mePassword(newPass.value)
|
|
passMsg.value = 'Password updated.'
|
|
passOk.value = true
|
|
newPass.value = ''
|
|
} catch (e: unknown) {
|
|
passMsg.value = e instanceof Error ? e.message : 'Failed'
|
|
passOk.value = false
|
|
} finally {
|
|
loadingPass.value = false
|
|
}
|
|
}
|
|
</script>
|