Skip to content

Commit 2e0ef5c

Browse files
committed
generalise posting launcher
1 parent c9a3577 commit 2e0ef5c

File tree

4 files changed

+144
-68
lines changed

4 files changed

+144
-68
lines changed

app/components/admin/PostingCard.vue

+2-12
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,13 @@ if (props.posting && props.posting.tagsCSV) {
5858
Published
5959
</div>
6060
<div class="text-xs font-medium rounded-lg text-center px-2.5 py-1 bg-sky-100 text-sky-700" v-else>Draft</div>
61-
62-
<Modal class-override="p-4" :full-screen="true">
63-
<template #title-bar>
64-
<div class="flex items-center space-x-2">
65-
<Icon class="w-5 h-5 shrink-0 fill-current mr-2" name="iconamoon:edit" />
66-
<span>New Posting</span>
67-
</div>
68-
</template>
61+
<AdminPostingFormLauncher :posting :full-screen="true">
6962
<template #input="{ open }">
7063
<InputButton variant="outline" size="icon" @click.stop="open">
7164
<Icon name="iconamoon:edit" class="w-5 h-5" />
7265
</InputButton>
7366
</template>
74-
<template #content="{ close }">
75-
<LazyAdminPostingForm :id="posting.id" @done="close" />
76-
</template>
77-
</Modal>
67+
</AdminPostingFormLauncher>
7868
</div>
7969
</footer>
8070
</div>

app/components/admin/PostingForm.vue

+106-41
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script setup lang="ts">
22
import type { CalendarDate } from '@internationalized/date';
33
import { syncRef } from '@vueuse/core';
4-
import { employmentTypeIds } from '~~/shared/employment-types';
4+
import { employmentTypeIds, employmentTypes } from '~~/shared/employment-types';
55
import { createJobPostingSchema, updateJobPostingSchema } from '~~/shared/schemas/posting';
6+
import type { SelectableOption } from '~~/shared/types/general';
67
78
const props = defineProps<{
89
id?: string;
@@ -36,6 +37,7 @@ const [isPublished] = defineField('isPublished');
3637
const [validTill] = defineField('validTill');
3738
const [isRemote] = defineField('isRemote');
3839
const [employmentType] = defineField('employmentType');
40+
const [baseSalary] = defineField('baseSalary');
3941
4042
let onDelete = () => Promise.resolve();
4143
let onUpdate = () => Promise.resolve();
@@ -80,6 +82,7 @@ if (isEditing) {
8082
validTill.value = (data.value.validTill as string | null) || undefined;
8183
isRemote.value = data.value.isRemote || false;
8284
employmentType.value = data.value.employmentType || employmentTypeIds[0];
85+
baseSalary.value = data.value.baseSalary || {};
8386
8487
// Initialise Extra Refs
8588
if (validTill.value) {
@@ -101,6 +104,7 @@ if (isEditing) {
101104
} else {
102105
isRemote.value = false;
103106
employmentType.value = employmentTypeIds[0];
107+
baseSalary.value = {};
104108
}
105109
106110
const onSubmit = () => {
@@ -111,50 +115,31 @@ watch(validTillCalendarDate, (calendarDate) => {
111115
if (!calendarDate) return;
112116
validTill.value = calendarDateToDate(calendarDate).toISOString();
113117
});
118+
119+
const employmentTypeOptions = employmentTypes.map<SelectableOption>((e) => ({
120+
id: e.id,
121+
title: e.title,
122+
description: e.description,
123+
}));
124+
125+
defineExpose({
126+
delete: onDelete,
127+
submit: onSubmit,
128+
isEditing: isEditing,
129+
});
114130
</script>
115131

116132
<template>
117-
<div class="w-full max-w-8xl mx-auto">
133+
<div class="w-full h-full bg-zinc-100">
118134
<div
119135
class="flex justify-center items-center border-b border-zinc-200 bg-red-200 text-red-600 text-sm py-1"
120136
v-if="isExpired"
121137
>
122138
Posting has expired. Editing is disabled.
123139
</div>
124140
<!-- Input Section -->
125-
<div class="flex w-full justify-end space-x-3 mt-2">
126-
<AbstractConfirmationBox
127-
title="Delete Posting?"
128-
content="You won't be able to undo this action. You will loose access to applicant list."
129-
@confirm="onDelete"
130-
v-if="isEditing"
131-
>
132-
<template #input="{ open }">
133-
<InputButton class="w-22" variant="destructive" @click="open" :disabled="disableFields">
134-
<div class="flex items-center space-x-1 w-full">
135-
<Icon name="material-symbols:delete-outline" class="h-4 w-4" />
136-
<span>Delete</span>
137-
</div>
138-
</InputButton>
139-
</template>
140-
</AbstractConfirmationBox>
141-
<AbstractConfirmationBox
142-
title="Save Posting?"
143-
content="Are you sure you want to save the changes?"
144-
@confirm="onSubmit"
145-
>
146-
<template #input="{ open }">
147-
<InputButton class="w-22" :disabled="disableFields" @click="open">
148-
<div class="flex items-center space-x-1 w-full">
149-
<Icon name="lets-icons:save" class="h-4 w-4" />
150-
<span>Save</span>
151-
</div>
152-
</InputButton>
153-
</template>
154-
</AbstractConfirmationBox>
155-
</div>
156-
<form @submit="onSubmit" class="w-full flex items-start py-2 space-x-6">
157-
<section class="flex flex-col w-2/3 space-y-3">
141+
<form @submit="onSubmit" class="w-full flex items-start space-x-6 h-full">
142+
<section class="flex flex-col w-2/3 space-y-3 px-4 pt-4 h-full">
158143
<InputText
159144
placeholder="Senior Software Engineer"
160145
label="Title"
@@ -179,10 +164,48 @@ watch(validTillCalendarDate, (calendarDate) => {
179164
</template>
180165
</InputLabel>
181166
</section>
182-
<div class="flex flex-col items-start justify-start space-y-3 w-1/3">
167+
<section class="flex flex-col items-start justify-start space-y-3 w-1/3 px-4 pt-4 border-l-2 h-full">
168+
<div class="flex w-full justify-end space-x-3 px-4 pt-2">
169+
<AbstractConfirmationBox
170+
title="Delete Posting?"
171+
content="You won't be able to undo this action. You will loose access to applicant list."
172+
@confirm="onDelete"
173+
v-if="isEditing"
174+
>
175+
<template #input="{ open }">
176+
<InputButton class="w-22" variant="destructive" @click="open" :disabled="disableFields">
177+
<div class="flex items-center space-x-1 w-full">
178+
<Icon name="material-symbols:delete-outline" class="h-4 w-4" />
179+
<span>Delete</span>
180+
</div>
181+
</InputButton>
182+
</template>
183+
</AbstractConfirmationBox>
184+
<AbstractConfirmationBox
185+
title="Save Posting?"
186+
content="Are you sure you want to save the changes?"
187+
@confirm="onSubmit"
188+
>
189+
<template #input="{ open }">
190+
<InputButton class="w-22" :disabled="disableFields" @click="open">
191+
<div class="flex items-center space-x-1 w-full">
192+
<Icon name="lets-icons:save" class="h-4 w-4" />
193+
<span>Save</span>
194+
</div>
195+
</InputButton>
196+
</template>
197+
</AbstractConfirmationBox>
198+
</div>
199+
<InputLabel label="Publish?">
200+
<template #input>
201+
<div class="border p-[7px] w-full rounded-lg bg-white">
202+
<InputSwitch v-model="isPublished" :disabled="disableFields" />
203+
</div>
204+
</template>
205+
</InputLabel>
183206
<InputLabel label="Expiry Date">
184207
<template #input>
185-
<div class="border p-0.5 w-full rounded-lg">
208+
<div class="border p-0.5 w-full rounded-lg bg-white">
186209
<InputDatePicker
187210
class="w-full"
188211
label="Expiry Date"
@@ -192,14 +215,56 @@ watch(validTillCalendarDate, (calendarDate) => {
192215
</div>
193216
</template>
194217
</InputLabel>
195-
<InputLabel label="Publish?">
218+
<InputLabel label="Is Remote?">
196219
<template #input>
197-
<div class="border p-[7px] w-full rounded-lg">
198-
<InputSwitch v-model="isPublished" :disabled="disableFields" />
220+
<div class="border p-[7px] w-full rounded-lg bg-white">
221+
<InputSwitch v-model="isRemote" :disabled="disableFields" />
199222
</div>
200223
</template>
201224
</InputLabel>
202-
</div>
225+
<InputLabel label="Employment Type">
226+
<template #input>
227+
<InputCombobox class="bg-white rounded-lg" :options="employmentTypeOptions" v-model="employmentType" />
228+
</template>
229+
</InputLabel>
230+
<InputLabel label="Salary Details">
231+
<template #input>
232+
<div class="flex flex-col p-2 border rounded-lg space-y-2 bg-white">
233+
<div class="flex w-full items-center space-x-2">
234+
<InputLabel label="Unit">
235+
<template #input>
236+
<PickerSalaryUnit v-model="baseSalary!.unitText" />
237+
</template>
238+
</InputLabel>
239+
<InputLabel label="Currency">
240+
<template #input>
241+
<PickerCurrency v-model="baseSalary!.currency" />
242+
</template>
243+
</InputLabel>
244+
</div>
245+
<InputLabel label="Range">
246+
<template #input>
247+
<div class="flex w-full items-center space-x-2">
248+
<InputText
249+
class="w-1/2"
250+
type-override="number"
251+
v-model="baseSalary!.minValue"
252+
placeholder="Minimum"
253+
/>
254+
<span class="text-xs text-zinc-600">to</span>
255+
<InputText
256+
class="w-1/2"
257+
type-override="number"
258+
v-model="baseSalary!.maxValue"
259+
placeholder="Maximum"
260+
/>
261+
</div>
262+
</template>
263+
</InputLabel>
264+
</div>
265+
</template>
266+
</InputLabel>
267+
</section>
203268
</form>
204269
</div>
205270
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup lang="ts">
2+
import type { JobPosting } from '~~/server/db/schema';
3+
4+
defineProps<{
5+
posting?: JobPosting;
6+
}>();
7+
</script>
8+
9+
<template>
10+
<Modal class-override="p-4" :full-screen="true">
11+
<template #title-bar>
12+
<div class="flex items-center space-x-2">
13+
<Icon class="w-5 h-5 shrink-0 fill-current mr-2" name="iconamoon:edit" />
14+
<span v-if="!posting">New Posting</span>
15+
<span v-else>{{ posting.title }}</span>
16+
</div>
17+
</template>
18+
<template #action-bar> </template>
19+
<template #input="{ open }">
20+
<slot name="input" :open> </slot>
21+
</template>
22+
<template #content="{ close }">
23+
<LazyAdminPostingForm :id="posting ? posting.id : undefined" @done="close" />
24+
</template>
25+
</Modal>
26+
</template>

app/pages/admin/dashboard.vue

+10-15
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,14 @@ const { user: profile } = useUserSession();
1717
<div class="text-xl font-bold text-zinc-900 font-noto">👋🏽 Hello, {{ profile?.firstName }}</div>
1818
<div class="text-sm text-zinc-500">Track your hiring activities. You are almost there!</div>
1919
<div class="flex items-center mt-4">
20-
<Modal class-override="p-4" :full-screen="true">
21-
<template #title-bar>
22-
<div class="flex items-center space-x-2">
23-
<Icon class="w-5 h-5 shrink-0 fill-current mr-2" name="iconamoon:edit" />
24-
<span>New Posting</span>
25-
</div>
26-
</template>
20+
<AdminPostingFormLauncher>
2721
<template #input="{ open }">
2822
<InputButton variant="secondary" @click.stop="open">
2923
<span class="mr-2">Create Posting</span>
3024
<Icon name="ic:baseline-plus" class="w-4 h-4" />
3125
</InputButton>
3226
</template>
33-
<template #content="{ close }">
34-
<LazyAdminPostingForm @done="close" />
35-
</template>
36-
</Modal>
27+
</AdminPostingFormLauncher>
3728
</div>
3829
</section>
3930
<div class="mt-4">
@@ -80,10 +71,14 @@ const { user: profile } = useUserSession();
8071
</li>
8172
</ul>
8273
</div>
83-
<InputButton as="NuxtLink" to="/admin/postings/edit">
84-
<Icon name="ic:baseline-plus" class="w-5 h-5" />
85-
<span class="ml-1">Create First Posting</span>
86-
</InputButton>
74+
<AdminPostingFormLauncher>
75+
<template #input="{ open }">
76+
<InputButton @click="open">
77+
<Icon name="ic:baseline-plus" class="w-5 h-5" />
78+
<span class="ml-1">Create First Posting</span>
79+
</InputButton>
80+
</template>
81+
</AdminPostingFormLauncher>
8782
</div>
8883
</div>
8984
<div class="grid grid-cols-12 gap-6 p-4">

0 commit comments

Comments
 (0)