| 1 | <template>
|
|---|
| 2 | <main class="application-page">
|
|---|
| 3 | <section class="application-hero">
|
|---|
| 4 | <div class="container hero-inner">
|
|---|
| 5 | <div class="hero-copy">
|
|---|
| 6 | <p class="eyebrow">Petify clinics</p>
|
|---|
| 7 | <h1>Apply as a vet clinic</h1>
|
|---|
| 8 | <p>
|
|---|
| 9 | Send your clinic details to the Petify admins. Once approved, your clinic can manage appointments,
|
|---|
| 10 | availability, cancellations, and client visit outcomes from the clinic dashboard.
|
|---|
| 11 | </p>
|
|---|
| 12 | </div>
|
|---|
| 13 | <div class="review-note">
|
|---|
| 14 | <span class="note-label">Admin review</span>
|
|---|
| 15 | <strong>Applications start as pending</strong>
|
|---|
| 16 | <p>Admins can approve or deny them from the Clinics tab.</p>
|
|---|
| 17 | </div>
|
|---|
| 18 | </div>
|
|---|
| 19 | </section>
|
|---|
| 20 |
|
|---|
| 21 | <section class="container application-body">
|
|---|
| 22 | <div class="form-shell">
|
|---|
| 23 | <div class="form-heading">
|
|---|
| 24 | <h2>Clinic information</h2>
|
|---|
| 25 | <p>Use details patients can recognize. Email or phone is required.</p>
|
|---|
| 26 | </div>
|
|---|
| 27 |
|
|---|
| 28 | <form class="application-form" @submit.prevent="submit">
|
|---|
| 29 | <div class="form-grid">
|
|---|
| 30 | <div class="form-group wide">
|
|---|
| 31 | <label for="clinicName">Clinic name *</label>
|
|---|
| 32 | <input
|
|---|
| 33 | id="clinicName"
|
|---|
| 34 | v-model.trim="form.name"
|
|---|
| 35 | class="form-control"
|
|---|
| 36 | type="text"
|
|---|
| 37 | placeholder="Happy Paws Clinic"
|
|---|
| 38 | required
|
|---|
| 39 | />
|
|---|
| 40 | </div>
|
|---|
| 41 |
|
|---|
| 42 | <div class="form-group">
|
|---|
| 43 | <label for="clinicEmail">Email</label>
|
|---|
| 44 | <input
|
|---|
| 45 | id="clinicEmail"
|
|---|
| 46 | v-model.trim="form.email"
|
|---|
| 47 | class="form-control"
|
|---|
| 48 | type="email"
|
|---|
| 49 | placeholder="clinic@example.com"
|
|---|
| 50 | />
|
|---|
| 51 | </div>
|
|---|
| 52 |
|
|---|
| 53 | <div class="form-group">
|
|---|
| 54 | <label for="clinicPhone">Phone</label>
|
|---|
| 55 | <input
|
|---|
| 56 | id="clinicPhone"
|
|---|
| 57 | v-model.trim="form.phone"
|
|---|
| 58 | class="form-control"
|
|---|
| 59 | type="tel"
|
|---|
| 60 | placeholder="+389 70 000 000"
|
|---|
| 61 | />
|
|---|
| 62 | </div>
|
|---|
| 63 |
|
|---|
| 64 | <div class="form-group">
|
|---|
| 65 | <label for="clinicCity">City *</label>
|
|---|
| 66 | <input
|
|---|
| 67 | id="clinicCity"
|
|---|
| 68 | v-model.trim="form.city"
|
|---|
| 69 | class="form-control"
|
|---|
| 70 | type="text"
|
|---|
| 71 | placeholder="Skopje"
|
|---|
| 72 | required
|
|---|
| 73 | />
|
|---|
| 74 | </div>
|
|---|
| 75 |
|
|---|
| 76 | <div class="form-group">
|
|---|
| 77 | <label for="clinicAddress">Address *</label>
|
|---|
| 78 | <input
|
|---|
| 79 | id="clinicAddress"
|
|---|
| 80 | v-model.trim="form.address"
|
|---|
| 81 | class="form-control"
|
|---|
| 82 | type="text"
|
|---|
| 83 | placeholder="Partizanska 12"
|
|---|
| 84 | required
|
|---|
| 85 | />
|
|---|
| 86 | </div>
|
|---|
| 87 | </div>
|
|---|
| 88 |
|
|---|
| 89 | <div v-if="errorMessage" class="alert alert-danger">
|
|---|
| 90 | {{ errorMessage }}
|
|---|
| 91 | </div>
|
|---|
| 92 |
|
|---|
| 93 | <div v-if="successMessage" class="alert alert-success">
|
|---|
| 94 | {{ successMessage }}
|
|---|
| 95 | </div>
|
|---|
| 96 |
|
|---|
| 97 | <div class="form-actions">
|
|---|
| 98 | <button class="btn btn-primary" type="submit" :disabled="isSubmitting">
|
|---|
| 99 | <span v-if="isSubmitting" class="spinner-border spinner-border-sm me-2"></span>
|
|---|
| 100 | {{ isSubmitting ? 'Submitting...' : 'Submit application' }}
|
|---|
| 101 | </button>
|
|---|
| 102 | <button class="btn btn-outline-secondary" type="button" :disabled="isSubmitting" @click="resetForm">
|
|---|
| 103 | Reset
|
|---|
| 104 | </button>
|
|---|
| 105 | </div>
|
|---|
| 106 | </form>
|
|---|
| 107 | </div>
|
|---|
| 108 | </section>
|
|---|
| 109 | </main>
|
|---|
| 110 | </template>
|
|---|
| 111 |
|
|---|
| 112 | <script setup lang="ts">
|
|---|
| 113 | import { ref } from 'vue'
|
|---|
| 114 | import { submitClinicApplication } from '../api/clinicApplications'
|
|---|
| 115 |
|
|---|
| 116 | const emptyForm = {
|
|---|
| 117 | name: '',
|
|---|
| 118 | email: '',
|
|---|
| 119 | phone: '',
|
|---|
| 120 | city: '',
|
|---|
| 121 | address: '',
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | const form = ref({ ...emptyForm })
|
|---|
| 125 | const isSubmitting = ref(false)
|
|---|
| 126 | const errorMessage = ref('')
|
|---|
| 127 | const successMessage = ref('')
|
|---|
| 128 |
|
|---|
| 129 | function validateForm(): string {
|
|---|
| 130 | if (!form.value.name.trim()) return 'Clinic name is required'
|
|---|
| 131 | if (!form.value.city.trim()) return 'City is required'
|
|---|
| 132 | if (!form.value.address.trim()) return 'Address is required'
|
|---|
| 133 | if (!form.value.email.trim() && !form.value.phone.trim()) return 'Email or phone is required'
|
|---|
| 134 | return ''
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | async function submit() {
|
|---|
| 138 | const validationError = validateForm()
|
|---|
| 139 | if (validationError) {
|
|---|
| 140 | errorMessage.value = validationError
|
|---|
| 141 | successMessage.value = ''
|
|---|
| 142 | return
|
|---|
| 143 | }
|
|---|
| 144 |
|
|---|
| 145 | try {
|
|---|
| 146 | isSubmitting.value = true
|
|---|
| 147 | errorMessage.value = ''
|
|---|
| 148 | successMessage.value = ''
|
|---|
| 149 | const application = await submitClinicApplication(form.value)
|
|---|
| 150 | successMessage.value = `Application submitted. Your status is ${application.status || 'PENDING'}.`
|
|---|
| 151 | form.value = { ...emptyForm }
|
|---|
| 152 | } catch (error) {
|
|---|
| 153 | errorMessage.value = error instanceof Error ? error.message : 'Failed to submit application'
|
|---|
| 154 | } finally {
|
|---|
| 155 | isSubmitting.value = false
|
|---|
| 156 | }
|
|---|
| 157 | }
|
|---|
| 158 |
|
|---|
| 159 | function resetForm() {
|
|---|
| 160 | form.value = { ...emptyForm }
|
|---|
| 161 | errorMessage.value = ''
|
|---|
| 162 | successMessage.value = ''
|
|---|
| 163 | }
|
|---|
| 164 | </script>
|
|---|
| 165 |
|
|---|
| 166 | <style scoped>
|
|---|
| 167 | .application-page {
|
|---|
| 168 | min-height: calc(100vh - 72px);
|
|---|
| 169 | background: #f7f5f2;
|
|---|
| 170 | color: #1f2933;
|
|---|
| 171 | }
|
|---|
| 172 |
|
|---|
| 173 | .application-hero {
|
|---|
| 174 | background:
|
|---|
| 175 | linear-gradient(135deg, rgba(31, 41, 51, 0.82), rgba(60, 78, 88, 0.68)),
|
|---|
| 176 | url('https://images.unsplash.com/photo-1628009368231-7bb7cfcb0def?auto=format&fit=crop&w=1800&q=80');
|
|---|
| 177 | background-position: center;
|
|---|
| 178 | background-size: cover;
|
|---|
| 179 | color: #ffffff;
|
|---|
| 180 | }
|
|---|
| 181 |
|
|---|
| 182 | .hero-inner {
|
|---|
| 183 | min-height: 360px;
|
|---|
| 184 | display: grid;
|
|---|
| 185 | grid-template-columns: minmax(0, 1fr) 320px;
|
|---|
| 186 | gap: 32px;
|
|---|
| 187 | align-items: end;
|
|---|
| 188 | padding-top: 72px;
|
|---|
| 189 | padding-bottom: 46px;
|
|---|
| 190 | }
|
|---|
| 191 |
|
|---|
| 192 | .hero-copy {
|
|---|
| 193 | max-width: 720px;
|
|---|
| 194 | }
|
|---|
| 195 |
|
|---|
| 196 | .eyebrow {
|
|---|
| 197 | margin: 0 0 10px;
|
|---|
| 198 | font-size: 0.78rem;
|
|---|
| 199 | font-weight: 800;
|
|---|
| 200 | letter-spacing: 0.08em;
|
|---|
| 201 | text-transform: uppercase;
|
|---|
| 202 | color: #fed7aa;
|
|---|
| 203 | }
|
|---|
| 204 |
|
|---|
| 205 | .hero-copy h1 {
|
|---|
| 206 | margin: 0;
|
|---|
| 207 | font-size: clamp(2.35rem, 5vw, 4.7rem);
|
|---|
| 208 | line-height: 0.95;
|
|---|
| 209 | font-weight: 850;
|
|---|
| 210 | }
|
|---|
| 211 |
|
|---|
| 212 | .hero-copy p {
|
|---|
| 213 | margin: 20px 0 0;
|
|---|
| 214 | max-width: 620px;
|
|---|
| 215 | color: rgba(255, 255, 255, 0.9);
|
|---|
| 216 | font-size: 1.04rem;
|
|---|
| 217 | line-height: 1.65;
|
|---|
| 218 | }
|
|---|
| 219 |
|
|---|
| 220 | .review-note {
|
|---|
| 221 | align-self: end;
|
|---|
| 222 | padding: 18px;
|
|---|
| 223 | border: 1px solid rgba(255, 255, 255, 0.24);
|
|---|
| 224 | background: rgba(255, 255, 255, 0.11);
|
|---|
| 225 | backdrop-filter: blur(12px);
|
|---|
| 226 | border-radius: 8px;
|
|---|
| 227 | }
|
|---|
| 228 |
|
|---|
| 229 | .note-label {
|
|---|
| 230 | display: inline-block;
|
|---|
| 231 | margin-bottom: 10px;
|
|---|
| 232 | color: #fed7aa;
|
|---|
| 233 | font-size: 0.78rem;
|
|---|
| 234 | font-weight: 800;
|
|---|
| 235 | text-transform: uppercase;
|
|---|
| 236 | }
|
|---|
| 237 |
|
|---|
| 238 | .review-note strong {
|
|---|
| 239 | display: block;
|
|---|
| 240 | font-size: 1.05rem;
|
|---|
| 241 | }
|
|---|
| 242 |
|
|---|
| 243 | .review-note p {
|
|---|
| 244 | margin: 8px 0 0;
|
|---|
| 245 | color: rgba(255, 255, 255, 0.82);
|
|---|
| 246 | }
|
|---|
| 247 |
|
|---|
| 248 | .application-body {
|
|---|
| 249 | padding-top: 34px;
|
|---|
| 250 | padding-bottom: 64px;
|
|---|
| 251 | }
|
|---|
| 252 |
|
|---|
| 253 | .form-shell {
|
|---|
| 254 | max-width: 940px;
|
|---|
| 255 | margin: 0 auto;
|
|---|
| 256 | background: #ffffff;
|
|---|
| 257 | border: 1px solid #e5e7eb;
|
|---|
| 258 | border-radius: 8px;
|
|---|
| 259 | box-shadow: 0 18px 40px rgba(31, 41, 51, 0.08);
|
|---|
| 260 | padding: 28px;
|
|---|
| 261 | }
|
|---|
| 262 |
|
|---|
| 263 | .form-heading {
|
|---|
| 264 | display: flex;
|
|---|
| 265 | justify-content: space-between;
|
|---|
| 266 | gap: 24px;
|
|---|
| 267 | border-bottom: 1px solid #edf0f2;
|
|---|
| 268 | padding-bottom: 18px;
|
|---|
| 269 | margin-bottom: 22px;
|
|---|
| 270 | }
|
|---|
| 271 |
|
|---|
| 272 | .form-heading h2 {
|
|---|
| 273 | margin: 0;
|
|---|
| 274 | font-size: 1.45rem;
|
|---|
| 275 | font-weight: 800;
|
|---|
| 276 | }
|
|---|
| 277 |
|
|---|
| 278 | .form-heading p {
|
|---|
| 279 | max-width: 340px;
|
|---|
| 280 | margin: 0;
|
|---|
| 281 | color: #667085;
|
|---|
| 282 | }
|
|---|
| 283 |
|
|---|
| 284 | .form-grid {
|
|---|
| 285 | display: grid;
|
|---|
| 286 | grid-template-columns: repeat(2, minmax(0, 1fr));
|
|---|
| 287 | gap: 18px;
|
|---|
| 288 | }
|
|---|
| 289 |
|
|---|
| 290 | .form-group.wide {
|
|---|
| 291 | grid-column: 1 / -1;
|
|---|
| 292 | }
|
|---|
| 293 |
|
|---|
| 294 | .form-group label {
|
|---|
| 295 | display: block;
|
|---|
| 296 | margin-bottom: 7px;
|
|---|
| 297 | color: #344054;
|
|---|
| 298 | font-weight: 700;
|
|---|
| 299 | font-size: 0.92rem;
|
|---|
| 300 | }
|
|---|
| 301 |
|
|---|
| 302 | .form-control {
|
|---|
| 303 | min-height: 46px;
|
|---|
| 304 | border-radius: 8px;
|
|---|
| 305 | }
|
|---|
| 306 |
|
|---|
| 307 | .alert {
|
|---|
| 308 | margin-top: 18px;
|
|---|
| 309 | }
|
|---|
| 310 |
|
|---|
| 311 | .form-actions {
|
|---|
| 312 | display: flex;
|
|---|
| 313 | gap: 12px;
|
|---|
| 314 | margin-top: 22px;
|
|---|
| 315 | }
|
|---|
| 316 |
|
|---|
| 317 | .btn {
|
|---|
| 318 | border-radius: 8px;
|
|---|
| 319 | font-weight: 700;
|
|---|
| 320 | }
|
|---|
| 321 |
|
|---|
| 322 | .btn-primary {
|
|---|
| 323 | background: #f97316;
|
|---|
| 324 | border-color: #f97316;
|
|---|
| 325 | }
|
|---|
| 326 |
|
|---|
| 327 | .btn-primary:hover {
|
|---|
| 328 | background: #ea580c;
|
|---|
| 329 | border-color: #ea580c;
|
|---|
| 330 | }
|
|---|
| 331 |
|
|---|
| 332 | @media (max-width: 768px) {
|
|---|
| 333 | .hero-inner {
|
|---|
| 334 | grid-template-columns: 1fr;
|
|---|
| 335 | min-height: auto;
|
|---|
| 336 | padding-top: 48px;
|
|---|
| 337 | }
|
|---|
| 338 |
|
|---|
| 339 | .review-note {
|
|---|
| 340 | max-width: 420px;
|
|---|
| 341 | }
|
|---|
| 342 |
|
|---|
| 343 | .form-heading,
|
|---|
| 344 | .form-actions {
|
|---|
| 345 | flex-direction: column;
|
|---|
| 346 | }
|
|---|
| 347 |
|
|---|
| 348 | .form-grid {
|
|---|
| 349 | grid-template-columns: 1fr;
|
|---|
| 350 | }
|
|---|
| 351 | }
|
|---|
| 352 | </style>
|
|---|