92 lines
2.5 KiB
Vue
92 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { Card, Button, Spin, message, Result } from 'ant-design-vue'
|
|
import { apiClient, isAxiosError, API } from '../router/api'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const token = route.params.token as string
|
|
const loading = ref(false)
|
|
const accepting = ref(false)
|
|
const accepted = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
const acceptInvite = async () => {
|
|
accepting.value = true
|
|
error.value = null
|
|
try {
|
|
const response = await apiClient.post<{ message: string; success: boolean; uuid: string }>(
|
|
API.organizationJoin(token),
|
|
)
|
|
message.success(response.data?.message || 'Successfully joined organization')
|
|
accepted.value = true
|
|
setTimeout(() => {
|
|
if (response.data?.uuid) router.push(`/organization/${response.data.uuid}`)
|
|
else router.push('/')
|
|
}, 1500)
|
|
} catch (err) {
|
|
console.error('Failed to accept invite:', err)
|
|
if (isAxiosError(err)) {
|
|
const respErr = err.response?.data?.error || err.response?.data?.detail
|
|
error.value = respErr ? String(respErr) : 'Failed to accept invite'
|
|
} else {
|
|
error.value = 'Failed to accept invite'
|
|
}
|
|
} finally {
|
|
accepting.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
acceptInvite()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page">
|
|
<Spin :spinning="loading" tip="Loading invite...">
|
|
<Card class="panel" :bordered="false">
|
|
<div v-if="error">
|
|
<Result status="error" :title="error">
|
|
<template #extra>
|
|
<Button type="primary" @click="router.push('/')">Go Home</Button>
|
|
</template>
|
|
</Result>
|
|
</div>
|
|
|
|
<div v-else-if="accepted">
|
|
<Result
|
|
status="success"
|
|
title="Successfully Joined Organization"
|
|
sub-title="Redirecting to organization page..."
|
|
/>
|
|
</div>
|
|
</Card>
|
|
</Spin>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page {
|
|
max-width: 800px;
|
|
padding: 2rem 1rem;
|
|
}
|
|
|
|
.invite-content {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.org-info {
|
|
background: #1f2937;
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
.actions {
|
|
margin-top: 2rem;
|
|
}
|
|
</style>
|