Adds the whole disclaimer feature

This commit is contained in:
2026-03-16 15:22:17 +01:00
parent 29a94899da
commit 7f380303ab
12 changed files with 499 additions and 27 deletions

View File

@@ -4,16 +4,29 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Services\Config as ConfigService;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
final class LandingController extends Controller final class LandingController extends Controller
{ {
/** /**
* Display the landing page. * Display the landing page with a flag indicating whether a disclaimer is configured.
*/ */
public function index(): Response public function index(ConfigService $config): Response
{ {
return Inertia::render('Landing'); return Inertia::render('Landing', [
'hasDisclaimer' => $config->contentDisclaimer !== '',
]);
}
/**
* Display the disclaimer page with the configured markdown content.
*/
public function disclaimer(ConfigService $config): Response
{
return Inertia::render('Disclaimer', [
'content' => $config->contentDisclaimer,
]);
} }
} }

View File

@@ -205,11 +205,14 @@ public function result(Session $session): InertiaResponse
{ {
$session->load('category'); $session->load('category');
$durationSeconds = $session->created_at->diffInSeconds($session->completed_at);
return Inertia::render('Session/Result', [ return Inertia::render('Session/Result', [
'session' => $session, 'session' => $session,
'score' => $session->score, 'score' => $session->score,
'result' => $session->result, 'result' => $session->result,
'categoryName' => $session->category->name, 'categoryName' => $session->category->name,
'durationSeconds' => $durationSeconds,
]); ]);
} }

View File

@@ -183,7 +183,7 @@
*/ */
'actions' => [ 'actions' => [
'resource' => \Laravel\Nova\Actions\ActionResource::class, 'resource' => null,
], ],
/* /*

13
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"@heroicons/vue": "^2.2.0", "@heroicons/vue": "^2.2.0",
"@inertiajs/vue3": "^2.3.13", "@inertiajs/vue3": "^2.3.13",
"@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue": "^6.0.4",
"marked": "^17.0.4",
"vue": "^3.5.27" "vue": "^3.5.27"
}, },
"devDependencies": { "devDependencies": {
@@ -3257,6 +3258,18 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/marked": {
"version": "17.0.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz",
"integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",

View File

@@ -22,6 +22,7 @@
"@heroicons/vue": "^2.2.0", "@heroicons/vue": "^2.2.0",
"@inertiajs/vue3": "^2.3.13", "@inertiajs/vue3": "^2.3.13",
"@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue": "^6.0.4",
"marked": "^17.0.4",
"vue": "^3.5.27" "vue": "^3.5.27"
} }
} }

View File

@@ -0,0 +1,257 @@
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { Head } from '@inertiajs/vue3'
import { ArrowLeftIcon } from '@heroicons/vue/24/outline'
import { marked } from 'marked'
import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue'
defineOptions({ layout: AppLayout })
const props = defineProps({
content: String,
})
const parsedContent = computed(() => {
if (!props.content) return ''
return marked(props.content)
})
const bottomBackButtonRef = ref(null)
const showStickyBack = ref(false)
let observer = null
onMounted(() => {
if (!bottomBackButtonRef.value) return
observer = new IntersectionObserver(
([entry]) => { showStickyBack.value = !entry.isIntersecting },
{ threshold: 0 }
)
observer.observe(bottomBackButtonRef.value.$el || bottomBackButtonRef.value)
})
onUnmounted(() => {
observer?.disconnect()
})
</script>
<template>
<Head title="Disclaimer" />
<div class="max-w-3xl mx-auto px-4 py-10">
<!-- Sticky back button appears when bottom back button scrolls out of view -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 translate-x-4"
enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-150 ease-in"
leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 translate-x-4"
>
<div v-if="showStickyBack" class="fixed top-[88px] right-6 z-40">
<AppButton variant="ghost" size="lg" href="/">
<ArrowLeftIcon class="h-5 w-5" />
Back
</AppButton>
</div>
</Transition>
<!-- Page heading -->
<div class="mb-10">
<h1 class="text-2xl font-bold text-white">Disclaimer</h1>
<div class="h-px bg-gradient-to-r from-primary/40 via-primary/10 to-transparent mt-4"></div>
</div>
<!-- Rendered markdown content -->
<div class="prose-dark" v-html="parsedContent" />
<!-- Bottom back button -->
<div class="mt-12 pt-8 border-t border-white/[0.06]">
<AppButton ref="bottomBackButtonRef" variant="ghost" size="lg" href="/">
<ArrowLeftIcon class="h-5 w-5" />
Back
</AppButton>
</div>
</div>
</template>
<style scoped>
.prose-dark :deep(h1) {
color: #ffffff;
font-size: 1.875rem;
font-weight: 700;
line-height: 1.3;
margin-top: 2rem;
margin-bottom: 1rem;
}
.prose-dark :deep(h2) {
color: #ffffff;
font-size: 1.5rem;
font-weight: 700;
line-height: 1.35;
margin-top: 1.75rem;
margin-bottom: 0.875rem;
}
.prose-dark :deep(h3) {
color: #ffffff;
font-size: 1.25rem;
font-weight: 600;
line-height: 1.4;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.prose-dark :deep(h4) {
color: #ffffff;
font-size: 1.1rem;
font-weight: 600;
line-height: 1.4;
margin-top: 1.25rem;
margin-bottom: 0.625rem;
}
.prose-dark :deep(p) {
color: #d1d5db; /* text-gray-300 */
line-height: 1.75;
margin-bottom: 1rem;
}
.prose-dark :deep(a) {
color: #d1ec51; /* text-primary */
text-decoration: underline;
text-underline-offset: 2px;
transition: color 0.15s ease;
}
.prose-dark :deep(a:hover) {
color: #b5d136; /* text-primary-dark */
}
.prose-dark :deep(ul) {
color: #d1d5db;
list-style: none;
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.prose-dark :deep(ul li) {
position: relative;
margin-bottom: 0.5rem;
line-height: 1.75;
}
.prose-dark :deep(ul li::before) {
content: '—';
color: #d1ec51; /* text-primary */
position: absolute;
left: -1.5rem;
font-weight: 600;
}
.prose-dark :deep(ol) {
color: #d1d5db;
list-style: none;
padding-left: 2rem;
margin-bottom: 1rem;
counter-reset: ol-counter;
}
.prose-dark :deep(ol li) {
position: relative;
margin-bottom: 0.5rem;
line-height: 1.75;
counter-increment: ol-counter;
}
.prose-dark :deep(ol li::before) {
content: counter(ol-counter) '.';
color: #d1ec51; /* text-primary */
position: absolute;
left: -2rem;
font-weight: 600;
}
.prose-dark :deep(blockquote) {
border-left: 3px solid #d1ec51; /* border-primary */
padding-left: 1.25rem;
margin: 1.5rem 0;
font-style: italic;
color: #9ca3af; /* text-gray-400 */
}
.prose-dark :deep(blockquote p) {
color: #9ca3af;
margin-bottom: 0;
}
.prose-dark :deep(pre) {
background-color: rgba(31, 41, 55, 0.5); /* bg-gray-800/50 */
border: 1px solid rgba(75, 85, 99, 0.4);
border-radius: 0.5rem;
padding: 1.25rem;
overflow-x: auto;
margin-bottom: 1rem;
}
.prose-dark :deep(pre code) {
background-color: transparent;
padding: 0;
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, Consolas, monospace;
font-size: 0.875rem;
color: #d1d5db;
}
.prose-dark :deep(code) {
background-color: rgba(31, 41, 55, 0.5); /* bg-gray-800/50 */
border: 1px solid rgba(75, 85, 99, 0.3);
border-radius: 0.25rem;
padding: 0.125rem 0.375rem;
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, Consolas, monospace;
font-size: 0.875em;
color: #d1ec51; /* text-primary */
}
.prose-dark :deep(strong) {
color: #ffffff;
font-weight: 600;
}
.prose-dark :deep(em) {
color: #d1d5db;
font-style: italic;
}
.prose-dark :deep(hr) {
border: none;
border-top: 1px solid #4b5563; /* border-gray-600 */
margin: 2rem 0;
}
.prose-dark :deep(table) {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
font-size: 0.9rem;
}
.prose-dark :deep(th) {
color: #ffffff;
font-weight: 600;
text-align: left;
padding: 0.625rem 0.875rem;
border-bottom: 2px solid #4b5563;
}
.prose-dark :deep(td) {
color: #d1d5db;
padding: 0.625rem 0.875rem;
border-bottom: 1px solid rgba(75, 85, 99, 0.4);
}
.prose-dark :deep(tr:last-child td) {
border-bottom: none;
}
</style>

View File

@@ -1,17 +1,28 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed, ref } from 'vue'
import { Head, router, usePage } from '@inertiajs/vue3' import { Head, router, usePage } from '@inertiajs/vue3'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/20/solid'
import AppLayout from '@/Layouts/AppLayout.vue' import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue' import AppButton from '@/Components/AppButton.vue'
defineOptions({ layout: AppLayout }) defineOptions({ layout: AppLayout })
const props = defineProps({
hasDisclaimer: Boolean,
})
const page = usePage() const page = usePage()
const disclaimerAgreed = ref(false)
const isAuthenticated = computed(() => { const isAuthenticated = computed(() => {
return page.props.auth?.user != null return page.props.auth?.user != null
}) })
const canContinue = computed(() => {
return !props.hasDisclaimer || disclaimerAgreed.value
})
const userInfo = computed(() => { const userInfo = computed(() => {
const user = page.props.auth?.user const user = page.props.auth?.user
if (!user) return null if (!user) return null
@@ -51,7 +62,69 @@ const handleContinue = () => {
You will first complete a short pre-screening questionnaire, followed by a detailed category-specific checklist You will first complete a short pre-screening questionnaire, followed by a detailed category-specific checklist
to determine whether to pursue (Go), decline (No Go), or escalate (Consult Leadership) an opportunity. to determine whether to pursue (Go), decline (No Go), or escalate (Consult Leadership) an opportunity.
</p> </p>
<AppButton v-if="isAuthenticated" size="lg" @click="handleContinue" data-cy="start-screening">
<!-- Disclaimer checkbox only shown when authenticated and a disclaimer exists -->
<div v-if="isAuthenticated && hasDisclaimer" class="flex items-start justify-center gap-3 mb-6">
<label class="flex items-start gap-3 cursor-pointer select-none group">
<!-- Hidden native checkbox -->
<input
v-model="disclaimerAgreed"
type="checkbox"
class="sr-only peer"
/>
<!-- Custom checkbox -->
<span
class="
mt-0.5 flex-shrink-0
w-5 h-5 rounded
border-2 border-gray-500
bg-transparent
flex items-center justify-center
transition-all duration-200
peer-checked:bg-primary peer-checked:border-primary
group-hover:border-gray-300
"
aria-hidden="true"
>
<!-- Checkmark visible when checked via peer -->
<svg
class="w-3 h-3 text-gray-900 opacity-0 transition-opacity duration-200 peer-checked:opacity-100"
:class="{ 'opacity-100': disclaimerAgreed }"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 6l3 3 5-5"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<!-- Label text -->
<span class="text-sm text-gray-400 text-left leading-relaxed">
I have read and agree to the
<a
href="/disclaimer"
target="_blank"
class="text-primary underline underline-offset-2 inline-flex items-center gap-0.5 hover:text-primary-dark transition-colors duration-150"
>
disclaimer
<ArrowTopRightOnSquareIcon class="w-3.5 h-3.5 flex-shrink-0" />
</a>
</span>
</label>
</div>
<AppButton
v-if="isAuthenticated"
size="lg"
:disabled="!canContinue"
@click="handleContinue"
data-cy="start-screening"
>
Continue Continue
</AppButton> </AppButton>
<AppButton v-else size="lg" href="/login" external> <AppButton v-else size="lg" href="/login" external>

View File

@@ -1,5 +1,7 @@
<script setup> <script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { Head, router } from '@inertiajs/vue3' import { Head, router } from '@inertiajs/vue3'
import { ArrowLeftIcon } from '@heroicons/vue/24/outline'
import AppLayout from '@/Layouts/AppLayout.vue' import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue' import AppButton from '@/Components/AppButton.vue'
@@ -34,12 +36,47 @@ const handleStartCategory = (categoryId) => {
screening_id: props.screening.id, screening_id: props.screening.id,
}) })
} }
const bottomBackButtonRef = ref(null)
const showStickyBack = ref(false)
let observer = null
onMounted(() => {
if (!bottomBackButtonRef.value) return
observer = new IntersectionObserver(
([entry]) => { showStickyBack.value = !entry.isIntersecting },
{ threshold: 0 }
)
observer.observe(bottomBackButtonRef.value.$el || bottomBackButtonRef.value)
})
onUnmounted(() => {
observer?.disconnect()
})
</script> </script>
<template> <template>
<Head title="Screening Result" /> <Head title="Screening Result" />
<div class="max-w-4xl mx-auto px-4 py-8"> <div class="max-w-4xl mx-auto px-4 py-8">
<!-- Sticky back button -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 translate-x-4"
enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-150 ease-in"
leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 translate-x-4"
>
<div v-if="showStickyBack" class="fixed top-[88px] right-6 z-40">
<AppButton variant="ghost" size="lg" href="/" data-cy="sticky-back">
<ArrowLeftIcon class="h-5 w-5" />
Back
</AppButton>
</div>
</Transition>
<h1 class="text-3xl font-bold text-white mb-6">Pre-Screening Result</h1> <h1 class="text-3xl font-bold text-white mb-6">Pre-Screening Result</h1>
<!-- Score Display --> <!-- Score Display -->
@@ -57,15 +94,8 @@ const handleStartCategory = (categoryId) => {
</div> </div>
</div> </div>
<!-- Failed: Show Again button -->
<div v-if="!passed" class="flex justify-center">
<AppButton size="lg" href="/">
Again
</AppButton>
</div>
<!-- Passed: Show category picker --> <!-- Passed: Show category picker -->
<div v-else data-cy="category-select"> <div v-if="passed" data-cy="category-select">
<h2 class="text-2xl font-semibold text-white mb-4">Select a Category</h2> <h2 class="text-2xl font-semibold text-white mb-4">Select a Category</h2>
<div class="space-y-3"> <div class="space-y-3">
<div <div
@@ -80,5 +110,13 @@ const handleStartCategory = (categoryId) => {
</div> </div>
</div> </div>
</div> </div>
<!-- Bottom back button -->
<div class="mt-12 pt-8 border-t border-white/[0.06]">
<AppButton ref="bottomBackButtonRef" variant="ghost" size="lg" href="/" data-cy="back-to-landing">
<ArrowLeftIcon class="h-5 w-5" />
Back
</AppButton>
</div>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,7 @@
<script setup> <script setup>
import { computed, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { Head, useForm } from '@inertiajs/vue3' import { Head, useForm } from '@inertiajs/vue3'
import { ArrowLeftIcon } from '@heroicons/vue/24/outline'
import AppLayout from '@/Layouts/AppLayout.vue' import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue' import AppButton from '@/Components/AppButton.vue'
import RadioButtonGroup from '@/Components/RadioButtonGroup.vue' import RadioButtonGroup from '@/Components/RadioButtonGroup.vue'
@@ -47,14 +48,49 @@ watch(currentScore, (score) => {
const allAnswered = computed(() => { const allAnswered = computed(() => {
return Object.values(form.answers).every(v => v !== null) return Object.values(form.answers).every(v => v !== null)
}) })
const bottomBackButtonRef = ref(null)
const showStickyBack = ref(false)
let observer = null
onMounted(() => {
if (!bottomBackButtonRef.value) return
observer = new IntersectionObserver(
([entry]) => { showStickyBack.value = !entry.isIntersecting },
{ threshold: 0 }
)
observer.observe(bottomBackButtonRef.value.$el || bottomBackButtonRef.value)
})
onUnmounted(() => {
observer?.disconnect()
})
</script> </script>
<template> <template>
<Head title="Pre-Screening Questions" /> <Head title="Pre-Screening Questions" />
<div class="max-w-4xl mx-auto px-4 py-8"> <div class="max-w-4xl mx-auto px-4 py-8">
<!-- Sticky back button -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 translate-x-4"
enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-150 ease-in"
leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 translate-x-4"
>
<div v-if="showStickyBack" class="fixed top-[88px] right-6 z-40">
<AppButton variant="ghost" size="lg" href="/" data-cy="sticky-back">
<ArrowLeftIcon class="h-5 w-5" />
Back
</AppButton>
</div>
</Transition>
<h1 class="text-3xl font-bold text-white mb-2">Pre-Screening Questions</h1> <h1 class="text-3xl font-bold text-white mb-2">Pre-Screening Questions</h1>
<p class="text-gray-400 mb-8">Answer all 10 questions to proceed. Each "Yes" scores 1 point, "I don't know" scores half a point. You need at least 5 points to pass.</p> <p class="text-gray-400 mb-8">Please answer all questions to proceed.</p>
<div class="space-y-4 mb-8"> <div class="space-y-4 mb-8">
<div <div
@@ -84,10 +120,17 @@ const allAnswered = computed(() => {
</div> </div>
</div> </div>
<div class="flex justify-end"> <div class="mt-12 pt-8 border-t border-white/[0.06]">
<div class="flex justify-between items-center">
<AppButton ref="bottomBackButtonRef" variant="ghost" size="lg" href="/" data-cy="back-to-landing">
<ArrowLeftIcon class="h-5 w-5" />
Back
</AppButton>
<AppButton size="lg" @click="handleSubmit" :loading="form.processing" :disabled="!allAnswered || form.processing" data-cy="submit-screening"> <AppButton size="lg" @click="handleSubmit" :loading="form.processing" :disabled="!allAnswered || form.processing" data-cy="submit-screening">
Submit Submit
</AppButton> </AppButton>
</div> </div>
</div> </div>
</div>
</template> </template>

View File

@@ -24,6 +24,25 @@ const props = defineProps({
type: String, type: String,
required: true, required: true,
}, },
durationSeconds: {
type: Number,
default: 0,
},
})
const formattedDuration = computed(() => {
const total = props.durationSeconds
const hours = Math.floor(total / 3600)
const minutes = Math.floor((total % 3600) / 60)
const seconds = total % 60
if (hours > 0) {
return `${hours}h ${minutes}m`
}
if (minutes > 0) {
return `${minutes}m ${seconds}s`
}
return `${seconds}s`
}) })
const resultDisplay = computed(() => { const resultDisplay = computed(() => {
@@ -87,15 +106,23 @@ const resultDisplay = computed(() => {
<!-- Session Details --> <!-- Session Details -->
<div class="rounded-lg p-6 mb-8"> <div class="rounded-lg p-6 mb-8">
<h2 class="text-xl font-semibold text-white mb-4">Session Details</h2> <h2 class="text-xl font-semibold text-white mb-4">Session Details</h2>
<dl class="grid grid-cols-2 gap-4 text-sm"> <dl class="grid grid-cols-3 gap-4 text-sm">
<div> <div>
<dt class="text-gray-400">Category</dt> <dt class="text-gray-400">Category</dt>
<dd class="text-white font-medium">{{ categoryName }}</dd> <dd class="text-white font-medium">{{ categoryName }}</dd>
</div> </div>
<div class="flex justify-center">
<div> <div>
<dt class="text-gray-400">Completed</dt> <dt class="text-gray-400">Completed</dt>
<dd class="text-white font-medium">{{ new Date(session.completed_at).toLocaleDateString() }}</dd> <dd class="text-white font-medium">{{ new Date(session.completed_at).toLocaleDateString() }}</dd>
</div> </div>
</div>
<div class="flex justify-end">
<div>
<dt class="text-gray-400">Duration</dt>
<dd class="text-white font-medium">{{ formattedDuration }}</dd>
</div>
</div>
</dl> </dl>
</div> </div>

View File

@@ -76,6 +76,7 @@ const saveAnswer = (questionId) => {
}, { }, {
preserveScroll: true, preserveScroll: true,
preserveState: true, preserveState: true,
showProgress: false,
only: ['answers'], only: ['answers'],
onStart: () => { processing.value = true }, onStart: () => { processing.value = true },
onFinish: () => { processing.value = false }, onFinish: () => { processing.value = false },
@@ -100,6 +101,7 @@ const saveComments = () => {
additionalComments.put(`/sessions/${props.session.id}`, { additionalComments.put(`/sessions/${props.session.id}`, {
preserveScroll: true, preserveScroll: true,
preserveState: true, preserveState: true,
showProgress: false,
}) })
}, 1000) }, 1000)
} }
@@ -223,7 +225,7 @@ const completeSession = async () => {
<div class="space-y-8"> <div class="space-y-8">
<!-- User Info Section --> <!-- User Info Section -->
<div class="border border-white/[0.06] rounded-xl p-8"> <div class="bg-white/[0.03] border border-white/[0.06] rounded-xl p-8">
<h2 class="text-lg font-semibold text-white mb-5">Basic Information</h2> <h2 class="text-lg font-semibold text-white mb-5">Basic Information</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@@ -242,7 +244,7 @@ const completeSession = async () => {
<div <div
v-for="group in questionGroups" v-for="group in questionGroups"
:key="group.id" :key="group.id"
class="border border-white/[0.06] rounded-xl p-8" class="bg-white/[0.03] border border-white/[0.06] rounded-xl p-8"
> >
<div class="flex items-center gap-3 mb-1"> <div class="flex items-center gap-3 mb-1">
<div class="w-2 h-2 rounded-full bg-primary/60"></div> <div class="w-2 h-2 rounded-full bg-primary/60"></div>
@@ -269,7 +271,7 @@ const completeSession = async () => {
</div> </div>
<!-- Additional Comments --> <!-- Additional Comments -->
<div class="border border-white/[0.06] rounded-xl p-8"> <div class="bg-white/[0.03] border border-white/[0.06] rounded-xl p-8">
<h2 class="text-lg font-semibold text-white mb-5">Additional Comments</h2> <h2 class="text-lg font-semibold text-white mb-5">Additional Comments</h2>
<textarea <textarea
v-model="additionalComments.additional_comments" v-model="additionalComments.additional_comments"
@@ -301,6 +303,7 @@ const completeSession = async () => {
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<AppButton ref="bottomBackButtonRef" variant="ghost" size="lg" :href="`/screening/${session.screening_id}/result`" data-cy="back-to-screening"> <AppButton ref="bottomBackButtonRef" variant="ghost" size="lg" :href="`/screening/${session.screening_id}/result`" data-cy="back-to-screening">
<ArrowLeftIcon class="h-5 w-5" />
Back Back
</AppButton> </AppButton>

View File

@@ -10,6 +10,7 @@
// Landing page (public) // Landing page (public)
Route::get('/', [LandingController::class, 'index'])->name('landing'); Route::get('/', [LandingController::class, 'index'])->name('landing');
Route::get('/disclaimer', [LandingController::class, 'disclaimer'])->name('disclaimer');
// Authentication routes // Authentication routes
Route::get('/login', [SocialiteController::class, 'redirect'])->name('login'); Route::get('/login', [SocialiteController::class, 'redirect'])->name('login');