Skip to content

Commit

Permalink
chore(refactor): update dataflow for postings
Browse files Browse the repository at this point in the history
  • Loading branch information
amandesai01 committed Sep 6, 2024
1 parent 52ec789 commit 662d0b2
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 142 deletions.
Empty file.
69 changes: 69 additions & 0 deletions app/composables/postings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { z } from 'zod';
import type { JobPosting } from '~~/server/db/schema';
import type {
createJobPostingSchema,
deleteJobPostingSchema,
fetchJobPostingFilterSchema,
updateJobPostingSchema,
} from '~~/shared/schemas/posting';

export type PostingLite = { id: string; title: string };
export type PostingsLite = PostingLite[];
export function usePostingsLiteRepository(
options: { immediate?: boolean } = { immediate: true }
) {
return useObjectRepository<
PostingsLite,
never,
never,
never,
never,
never,
never
>({
key: 'postings-lite',
fetchURL: '/api/postings/lite',
initFn: () => [],
immediate: options.immediate,
});
}

export type Postings = JobPosting[];
export function usePostingsRepository() {
return useObjectRepository<
Postings,
never,
never,
never,
never,
never,
never
>({
key: 'postings',
fetchURL: '/api/postings',
initFn: () => [],
});
}

export type CreatePostingSchema = z.infer<typeof createJobPostingSchema>;
export type UpdatePostingSchema = z.infer<typeof updateJobPostingSchema>;
export type FetchPostingSchema = z.infer<typeof fetchJobPostingFilterSchema>;
export type DeletePostingSchema = z.infer<typeof deleteJobPostingSchema>;
export function usePostingRepository(query: FetchPostingSchema) {
return useObjectRepository<
JobPosting,
FetchPostingSchema,
UpdatePostingSchema,
never,
CreatePostingSchema,
never,
DeletePostingSchema
>({
key: `${query.id}-posting`,
fetchURL: '/api/posting',
fetchQuery: query,
postURL: '/api/posting',
updateURL: '/api/posting',
deleteURL: '/api/posting',
});
}
5 changes: 4 additions & 1 deletion app/composables/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type LookupRepositoryOptions<T, FQ extends RecordAny = RecordAny> = {
updateURL?: string;
deleteURL?: string;
initFn?: () => T;
immediate?: boolean;
};

/**
Expand All @@ -68,6 +69,8 @@ export async function useObjectRepository<
>(
options: LookupRepositoryOptions<T, FQ>
): Promise<LookupRepository<T, UB, UQ, PB, PQ, DQ>> {
const immediate =
typeof options.immediate === 'undefined' ? true : options.immediate;
const { data, setData, firstFetched, fetching, changing } = useObjectState(
options.key,
options.initFn
Expand All @@ -91,7 +94,7 @@ export async function useObjectRepository<
setFetchData();
};

if (!firstFetched.value) {
if (!firstFetched.value && immediate) {
firstFetched.value = true;
await fetchExecute();
setFetchData();
Expand Down
7 changes: 5 additions & 2 deletions app/pages/admin/applications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ const selectedPostings = ref<string[]>([]);
const postingsById = ref<Record<string, string>>({}); // id <> title;
const { applicants, applications, fetch: fetchApplicants } = useApplications();
const { data: postings } =
useFetch<{ id: string; title: string }[]>('/api/postings');
const { data: postings, refresh } = await usePostingsLiteRepository({
immediate: false,
});
onMounted(refresh);
if (postingIdsQuery) {
try {
Expand Down
117 changes: 28 additions & 89 deletions app/pages/admin/postings/edit.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<script setup lang="ts">
import {
createJobPostingSchema,
updateJobPostingSchema,
} from '~~/shared/schemas/posting';
import type { JobPosting } from '~~/server/db/schema';
import { updateJobPostingSchema } from '~~/shared/schemas/posting';
definePageMeta({
layout: 'admin',
Expand All @@ -12,95 +8,48 @@ definePageMeta({
const route = useRoute();
const postingId = route.query.id as string | undefined;
const isUpdating = !!postingId;
if (!route.query.id) {
await navigateTo('/admin/postings/new');
}
// @ts-expect-error
let posting: JobPosting = null;
const { refresh } = await usePostingsRepository();
if (isUpdating) {
posting = (
await useRequestFetch()('/api/posting', { query: { id: postingId } })
)[0] as unknown as JobPosting;
if (!posting) {
throw createError({
statusCode: 404,
message: 'Job Posting Not Found',
});
}
}
const q = { id: route.query.id as string };
const { data, updateData, deleteData, changing } =
await usePostingRepository(q);
useHead({
title: `${isUpdating ? 'Edit - ' : 'New Posting'} ${(isUpdating && posting.title) || ''}`,
title: `Edit - ${data.value.title}`,
});
const formSchema = toTypedSchema(
isUpdating ? updateJobPostingSchema : createJobPostingSchema
);
const { handleSubmit, errors, defineField, errorBag } = useForm({
const formSchema = toTypedSchema(updateJobPostingSchema);
const { handleSubmit, errors, defineField } = useForm({
validationSchema: formSchema,
});
// @ts-expect-error
const [id] = defineField('id');
const [title] = defineField('title');
const [contents] = defineField('contents');
const [tagsCSV] = defineField('tagsCSV');
const [isPublished] = defineField('isPublished');
if (isUpdating && posting) {
id.value = posting.id;
title.value = posting.title;
contents.value = posting.contents || undefined;
tagsCSV.value = posting.tagsCSV || undefined;
isPublished.value = posting.isPublished;
} else {
isPublished.value = false;
}
const isSubmitting = ref(false);
id.value = data.value.id;
title.value = data.value.title;
contents.value = data.value.contents || undefined;
tagsCSV.value = data.value.tagsCSV || undefined;
isPublished.value = data.value.isPublished;
const onSubmit = handleSubmit(async (values) => {
try {
isSubmitting.value = true;
await $fetch('/api/posting', {
method: isUpdating ? 'PUT' : 'POST',
body: {
...values,
},
});
await navigateTo('/admin/postings');
} catch (e) {
console.error(e);
} finally {
isSubmitting.value = false;
}
await updateData(values);
refresh();
await navigateTo('/admin/postings');
});
const isDeleting = ref(false);
const onDelete = async () => {
if (!isUpdating) {
return;
}
try {
if (errorBag.value) {
console.log(errorBag.value);
}
isDeleting.value = true;
await $fetch('/api/posting', {
method: 'DELETE',
query: {
id: postingId,
},
});
await navigateTo('/admin/postings');
} catch (e) {
console.error(e);
} finally {
isDeleting.value = false;
}
await deleteData(q);
refresh();
await navigateTo('/admin/postings');
};
</script>

Expand All @@ -118,7 +67,8 @@ const onDelete = async () => {
<Icon
class="w-5 h-5 shrink-0 fill-current mr-2"
name="iconamoon:edit"
/>{{ isUpdating ? posting.title : 'New Posting' }}
/>
{{ data.title }}
</h2>
</div>
<!-- Right: Actions -->
Expand All @@ -127,37 +77,26 @@ const onDelete = async () => {
title="Delete Posting?"
content="You won't be able to undo this action. You will loose access to applicant list."
@confirm="onDelete"
v-if="isUpdating"
>
<template #input="{ open }">
<InputButton
variant="destructive"
size="icon"
@click="open"
:disabled="isSubmitting"
:disabled="changing"
>
<Icon name="material-symbols:delete-outline" class="h-4 w-4" />
</InputButton>
</template>
</AbstractConfirmationBox>
<!-- <div class="flex space-x-1 items-center border bg-zinc-100 p-2 rounded-xl">
<span class="text-sm">Publish?</span>
<div class="form-switch">
<input type="checkbox" id="toggle1" name="toggle1" class="sr-only" v-model="isPublished" :disabled="isSubmitting">
<label class="bg-zinc-400" for="toggle1">
<span class="bg-white shadow-sm" aria-hidden="true"></span>
<span class="sr-only">Publish/Draft</span>
</label>
</div>
</div> -->
<InputSwitch label="Publish?" v-model="isPublished" />
<AbstractConfirmationBox
title="Save Posting?"
content="Are you sure you want to save the changes?"
@confirm="onSubmit"
>
<template #input="{ open }">
<InputButton :disabled="isSubmitting" @click="open">
<InputButton :disabled="changing" @click="open">
<div class="flex spece-x-2 items-center">
<Icon name="lets-icons:save" class="w-3 h-3 mr-1" />
<span>Save</span>
Expand All @@ -174,15 +113,15 @@ const onDelete = async () => {
<InputText
placeholder="Senior Software Engineer"
label="Title"
:disabled="isSubmitting"
:disabled="changing"
:error="errors.title"
v-model="title"
/>
<InputText
class="mt-4"
placeholder="Remote, Full Time, San Fransisco"
label="Tags (CSV)"
:disabled="isSubmitting"
:disabled="changing"
v-model="tagsCSV"
/>
<div class="mt-4">
Expand Down
11 changes: 3 additions & 8 deletions app/pages/admin/postings/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { JobPosting } from '~~/server/db/schema';
definePageMeta({
layout: 'admin',
middleware: 'admin-auth',
Expand All @@ -10,22 +8,19 @@ useHead({
title: 'Postings | Admin Panel',
});
const postings = await useFetch<JobPosting[]>('/api/posting');
const { data: postings } = await usePostingsRepository();
</script>

<template>
<div class="w-full max-w-9xl mx-auto">
<!-- Page header -->
<AdminPostingsHeader />

<div class="mt-4">
<div class="max-w-2xl mx-auto mt-12" v-if="!postings.data.value?.length">
<div class="max-w-2xl mx-auto mt-12" v-if="!postings.length">
<AdminPostingsEmptyState />
</div>
<!-- <AdminPostingsTable :postings="postings.data.value" v-else/> -->
<div class="grid grid-cols-12 gap-6 p-4">
<AdminPostingsCard
v-for="posting in postings.data.value"
v-for="posting in postings"
:key="posting.id"
:posting="posting"
/>
Expand Down
Loading

0 comments on commit 662d0b2

Please sign in to comment.