bstein-dev-home/frontend/src/views/RequestAccessView.vue

249 lines
8.3 KiB
Vue

<template>
<div class="page">
<section class="card hero glass">
<div>
<p class="eyebrow">Atlas</p>
<h1>Request Access</h1>
<p class="lede">
Request access to Atlas. Approved accounts are provisioned from this form only.
</p>
</div>
</section>
<section class="card module">
<div class="module-head">
<h2>Request form</h2>
<span class="pill mono" :class="submitted ? 'pill-ok' : 'pill-warn'">
{{ submitted ? "submitted" : "pending" }}
</span>
</div>
<p class="muted">
Requests require a verified external email so Keycloak can support account recovery. After verification, an admin can approve your account.
Your lab username becomes your Atlas identity (including your @{{ mailDomain }} mailbox).
</p>
<form class="form" @submit.prevent="submit" v-if="!submitted">
<label class="field">
<span class="label mono">Lab Name (username)</span>
<input
v-model="form.username"
class="input mono"
type="text"
autocomplete="username"
placeholder="e.g. alice"
:disabled="submitting"
required
/>
<div v-if="availability.label" class="availability">
<span class="pill mono" :class="availability.pillClass">{{ availability.label }}</span>
<span v-if="availability.detail" class="hint mono">{{ availability.detail }}</span>
</div>
</label>
<label class="field">
<span class="label mono">Last name</span>
<input
v-model="form.last_name"
class="input"
type="text"
autocomplete="family-name"
placeholder="e.g. Stein"
:disabled="submitting"
required
/>
<span class="hint mono">Required for account provisioning.</span>
</label>
<label class="field">
<span class="label mono">First name (optional)</span>
<input
v-model="form.first_name"
class="input"
type="text"
autocomplete="given-name"
placeholder="e.g. Brad"
:disabled="submitting"
/>
</label>
<label class="field">
<span class="label mono">Email</span>
<input
v-model="form.email"
class="input mono"
type="email"
autocomplete="email"
placeholder="you@example.com"
:disabled="submitting"
required
/>
<span class="hint mono">Must be an external address (not @{{ mailDomain }})</span>
</label>
<label class="field">
<span class="label mono">Note (optional)</span>
<textarea
v-model="form.note"
class="textarea"
rows="4"
placeholder="What do you want access to?"
:disabled="submitting"
/>
</label>
<div class="actions">
<button
class="primary"
type="submit"
:disabled="submitting || !form.username.trim() || !form.last_name.trim() || availability.blockSubmit"
>
{{ submitting ? "Submitting..." : "Submit request" }}
</button>
<span class="hint mono">Requests are rate-limited.</span>
</div>
</form>
<div v-else class="success-box">
<div class="mono">Request submitted.</div>
<div class="muted">
Save this request code. Check your email for a verification link, then use the code to track status. Once approved,
your status will provide an onboarding link to finish account setup.
</div>
<div class="request-code-row">
<span class="label mono">Request Code</span>
<button class="copy mono" type="button" @click="copyRequestCode">
{{ requestCode }}
<span v-if="copied" class="copied">copied</span>
</button>
</div>
</div>
<div class="card module status-module">
<div class="module-head">
<h2>Check status</h2>
<span class="pill mono" :class="statusPillClass(status)">
{{ statusLabel(status) }}
</span>
</div>
<p class="muted">
Enter your request code to see whether it is awaiting approval, building accounts, awaiting onboarding, ready, or rejected.
</p>
<div class="status-form">
<input
v-model="statusForm.request_code"
class="input mono"
type="text"
placeholder="username~XXXXXXXXXX"
:disabled="checking"
/>
<button class="primary" type="button" @click="checkStatus" :disabled="checking || !statusForm.request_code.trim()">
{{ checking ? "Checking..." : "Check" }}
</button>
</div>
<div v-if="verifying" class="muted" style="margin-top: 10px;">
Verifying email…
</div>
<div v-if="verifyBanner" class="verify-box">
<div class="verify-title mono">{{ verifyBanner.title }}</div>
<div class="verify-body">{{ verifyBanner.body }}</div>
</div>
<div v-if="status === 'pending_email_verification'" class="actions" style="margin-top: 10px;">
<button class="pill mono" type="button" :disabled="resending" @click="resendVerification">
{{ resending ? "Resending..." : "Resend verification email" }}
</button>
<span v-if="resendMessage" class="hint mono">{{ resendMessage }}</span>
</div>
<div v-if="tasks.length" class="task-box">
<div class="module-head" style="margin-bottom: 10px;">
<h2>Automation</h2>
<span class="pill mono" :class="blocked ? 'pill-bad' : 'pill-ok'">
{{ blocked ? "blocked" : "running" }}
</span>
</div>
<ul class="task-list">
<li v-for="item in tasks" :key="item.task" class="task-row">
<span class="mono task-name">{{ item.task }}</span>
<span class="pill mono" :class="taskPillClass(item.status)">{{ item.status }}</span>
<span v-if="item.detail" class="mono task-detail">{{ item.detail }}</span>
</li>
</ul>
<p v-if="blocked" class="muted" style="margin-top: 10px;">
One or more automation steps failed. Fix the error above, then check again.
</p>
<div v-if="blocked" class="actions" style="margin-top: 10px;">
<button class="pill mono" type="button" :disabled="retrying" @click="retryProvisioning">
{{ retrying ? "Retrying..." : "Retry failed steps" }}
</button>
<span v-if="retryMessage" class="hint mono">{{ retryMessage }}</span>
</div>
<p v-if="blocked" class="muted" style="margin-top: 8px;">
If the error mentions rate limiting or a temporary outage, wait a few minutes and retry. If it keeps failing,
contact an admin.
</p>
</div>
<div
v-if="onboardingUrl && (status === 'awaiting_onboarding' || status === 'ready')"
class="actions onboarding-actions"
>
<div class="onboarding-copy">
<p class="muted" style="margin: 0;">
Your accounts are ready. Continue onboarding to finish setup.
</p>
</div>
<a class="primary onboarding-cta" :href="onboardingUrl">Continue onboarding</a>
</div>
</div>
<div v-if="error" class="error-box">
<div class="mono">{{ error }}</div>
</div>
</section>
</div>
</template>
<script setup>
import { useRoute } from "vue-router";
import { useRequestAccessFlow } from "../request-access/useRequestAccessFlow";
const {
statusLabel,
statusPillClass,
form,
submitting,
submitted,
error,
requestCode,
copied,
verifying,
mailDomain,
availability,
statusForm,
checking,
status,
onboardingUrl,
tasks,
blocked,
retrying,
retryMessage,
resending,
resendMessage,
verifyBanner,
taskPillClass,
submit,
copyRequestCode,
checkStatus,
retryProvisioning,
resendVerification,
} = useRequestAccessFlow(useRoute());
</script>
<style scoped src="../styles/request-access.css"></style>