Skip to content

Commit

Permalink
fix(auth): revamp auth (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
rjsamra authored Dec 21, 2024
1 parent d3ef5d7 commit 1bc7e2a
Show file tree
Hide file tree
Showing 86 changed files with 1,536 additions and 4,544 deletions.
7 changes: 7 additions & 0 deletions app/auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Admin } from '~~/server/db/schema';

declare module '#auth-utils' {
interface User extends Admin {}
}

export {};
26 changes: 0 additions & 26 deletions app/components/HandleFlexStrip.vue

This file was deleted.

6 changes: 3 additions & 3 deletions app/components/Onboarding/KeyInput.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
const emits = defineEmits<{
done: [string];
done: [string, boolean];
}>();
const key = ref('');
Expand All @@ -11,12 +11,12 @@ const verify = async () => {
if (!key.value) return;
try {
isVerifying.value = true;
const response = await $fetch<{ result: boolean }>('/api/onboarding/validate-key', {
const response = await $fetch<{ result: boolean; registrationNeeded: boolean }>('/api/onboarding/validate-key', {
method: 'POST',
body: { key: key.value },
});
if (response.result) {
emits('done', key.value);
emits('done', key.value, response.registrationNeeded);
} else {
error.value = 'Invalid Key. Try again.';
}
Expand Down
85 changes: 85 additions & 0 deletions app/components/Onboarding/Register.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
import { registerSchema, loginSchema } from '~~/shared/schemas/authentication';
const props = defineProps<{
register: boolean;
onboardingKey: string;
}>();
const formSchema = toTypedSchema(props.register ? registerSchema : loginSchema);
const { handleSubmit, errors, defineField } = useForm({
validationSchema: formSchema,
});
const loginError = ref<string>();
// @ts-expect-error
const [firstName] = defineField('firstName');
// @ts-expect-error
const [lastName] = defineField('lastName');
const [email] = defineField('email');
const [password] = defineField('password');
const emits = defineEmits<{
done: [];
}>();
const submit = handleSubmit(async (validatedData) => {
try {
await $fetch(`/api/${props.register ? 'register' : 'login'}`, {
method: 'POST',
body: validatedData,
headers: {
'x-onboarding-token': props.onboardingKey,
},
});
emits('done');
} catch (e: any) {
if (e.data.statusCode === 401) {
loginError.value = e.data.message;
} else {
loginError.value = 'Something went wrong.';
}
}
});
</script>

<template>
<div class="flex flex-col space-y-2">
<div class="flex flex-col justify-center">
<InputLabel class="text-center" label-class="text-xl" label="Personal Details" v-if="register" />
<InputLabel class="text-center" label-class="text-xl" label="Log back in" v-else />
<InputLabel
class="text-center"
label-class="!text-zinc-500"
label="Registering as super admin for this instance."
v-if="register"
/>
<InputLabel
class="text-center"
label-class="!text-zinc-500"
label="Super admin already registered, log back in."
v-else
/>
</div>
<div class="flex space-x-2 w-full" v-if="register">
<!-- @vue-expect-error -->
<InputText class="w-1/2" placeholder="First Name" v-model="firstName" :error="errors['firstName']" />
<!-- @vue-expect-error -->
<InputText class="w-1/2" placeholder="Last Name" v-model="lastName" :error="errors['lastName']" />
</div>
<InputText class="w-full" placeholder="Email" v-model="email" :error="errors['email']" />

<InputText
class="w-full"
placeholder="Password"
type-override="password"
v-model="password"
:error="errors['password']"
/>
<InputButton @click="submit">
{{ register ? 'Save' : 'Continue' }}
</InputButton>
<InputLabel :error="loginError" v-if="loginError" />
</div>
</template>
33 changes: 8 additions & 25 deletions app/components/admin/ApplicationRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,29 @@ const props = defineProps<{
application: Application;
}>();
const user = props.applicant.user;
const resume = props.applicant.handles.find((h) => h.key == 'resume')?.value;
const isResumeAvailable = Boolean(resume);
const candidate = props.applicant.candidate;
</script>

<template>
<div
class="flex bg-white border border-zinc-200 rounded-xl items-center justify-between py-2 px-4"
v-if="props.applicant && user"
v-if="props.applicant && candidate"
>
<div class="flex">
<div class="flex items-center">
<div class="w-10 h-10 mr-2">
<img
class="rounded-xl"
:src="user.picture || undefined"
width="36"
height="36"
:alt="`${user.firstName}'s Profile Picture'`"
/>
</div>
<div class="font-medium text-zinc-800">
<span class="font-bold">{{ user.firstName + ' ' + user.lastName }}</span
><br />
<span class="text-zinc-700">{{ user.email }}</span>
</div>
<div class="font-medium text-zinc-800">
<span class="font-bold">
{{ candidate.firstName + ' ' + candidate.lastName }}
</span>
<br />
<span class="text-zinc-700">{{ candidate.email }}</span>
</div>
</div>
<div class="flex items-center justify-between space-x-20">
<div class="whitespace-nowrap">
<HandleFlexStrip :handles="applicant.handles" />
</div>
<div class="whitespace-nowrap">
<div class="text-left">
{{ timeAgo(new Date(application.createdAt)) }}
</div>
</div>
<InputButton variant="secondary" as="a" target="_blank" :href="resume" :disabled="!isResumeAvailable">
View Resume
</InputButton>
</div>
</div>
</template>
6 changes: 3 additions & 3 deletions app/components/admin/MemberCard.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<script setup lang="ts">
import type { User } from '~~/server/db/schema';
import type { Admin } from '~~/server/db/schema';
const props = defineProps<{
member: User;
member: Admin;
}>();
const { deleteData } = await useMembersRepository();
const { profile } = useAuth();
const { user: profile } = useUserSession();
const onRemove = () => {
deleteData({ id: props.member.id });
Expand Down
46 changes: 25 additions & 21 deletions app/components/admin/MembersAddAction.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { User } from '~~/server/db/schema';
import type { Admin } from '~~/server/db/schema';
import { watchDebounced } from '@vueuse/core';
const { postData, changing } = await useMembersRepository();
const suggestedUsers = ref<User[]>();
const suggestedAdmins = ref<Admin[]>();
const submit = async (id: string, closeFn: () => void) => {
let success = false;
Expand All @@ -16,23 +16,23 @@ const submit = async (id: string, closeFn: () => void) => {
}
};
const userSearchQuery = ref<string>('');
const fetchingUserSuggestions = ref(false);
const adminSearchQuery = ref<string>('');
const fetchingAdminSuggestions = ref(false);
watchDebounced(
userSearchQuery,
adminSearchQuery,
async (q) => {
try {
fetchingUserSuggestions.value = true;
suggestedUsers.value = await $fetch<User[]>('/api/user/lookup', {
fetchingAdminSuggestions.value = true;
suggestedAdmins.value = await $fetch<Admin[]>('/api/admin/lookup', {
query: {
q,
},
});
} catch (error) {
console.error('error fetching user suggestions', error);
console.error('error fetching admin suggestions', error);
} finally {
fetchingUserSuggestions.value = false;
fetchingAdminSuggestions.value = false;
}
},
{ debounce: 500, maxWait: 1000 }
Expand All @@ -52,35 +52,39 @@ watchDebounced(
type="text"
class="input-custom"
placeholder="Start searching to add members"
v-model="userSearchQuery"
v-model="adminSearchQuery"
:disabled="changing"
/>
<div
class="flex flex-col space-y-3 overflow-y-scroll no-scrollbar h-64 mt-3"
v-if="suggestedUsers && suggestedUsers.length > 0"
v-if="suggestedAdmins && suggestedAdmins.length > 0"
>
<div class="flex w-full justify-between p-2 border rounded-lg" v-for="user in suggestedUsers" :key="user.id">
<div
class="flex w-full justify-between p-2 border rounded-lg"
v-for="admin in suggestedAdmins"
:key="admin.id"
>
<div class="flex space-x-1 items-center">
<img :src="user.picture" v-if="user.picture" class="w-10 h-10 rounded-full" />
<img :src="admin.picture" v-if="admin.picture" class="w-10 h-10 rounded-full" />
<div class="flex flex-col text-sm">
<span class="font-bold">{{ user.firstName }} {{ user.lastName }}</span>
<span>{{ user.email }}</span>
<span class="font-bold">{{ admin.firstName }} {{ admin.lastName }}</span>
<span>{{ admin.email }}</span>
</div>
</div>
<InputButton size="sm" variant="outline" @click="submit(user.id, close)" :loading="changing"
<InputButton size="sm" variant="outline" @click="submit(admin.id, close)" :loading="changing"
>Add</InputButton
>
</div>
</div>
<div class="h-64 flex w-full justify-center items-center" v-else>
<span class="text-center" v-if="userSearchQuery && !fetchingUserSuggestions">
<span class="font-bold">No User Found</span><br />
<span class="text-xs">User has to login atleast once to be added.</span>
<span class="text-center" v-if="adminSearchQuery && !fetchingAdminSuggestions">
<span class="font-bold">No Admin Found</span><br />
<span class="text-xs">Admin has to login atleast once to be added.</span>
</span>
<span class="text-center" v-else-if="!userSearchQuery">
<span class="text-center" v-else-if="!adminSearchQuery">
<span class="font-bold">Start typing to see suggestions.</span><br />
<span class="text-xs">
Any user added will be able to see / create job postings and see / manage all the applicants.
Any admin added will be able to see / create job postings and see / manage all the applicants.
</span>
</span>
</div>
Expand Down
25 changes: 25 additions & 0 deletions app/components/admin/ReviewTag/Picker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import type { SelectableOption } from '~~/shared/types/general';
const { data: reviewTags } = await useReviewTagsRepository();
const options: SelectableOption[] = reviewTagGroupVsIndex
.flatMap((_, i) => reviewTags.value.filter((rt) => rt.parent === i))
.map((rt) => ({
id: rt.id,
title: rt.title,
logo: 'material-symbols:circle',
logoClass: `text-${reviewTagColorVsIndex[rt.parent]}-200 rounded-full border border-${reviewTagColorVsIndex[rt.parent]}-600`,
}));
const props = defineProps<{
modelValue?: number;
}>();
const selectedTag = useVModel(props, 'modelValue');
</script>

<template>
<AbstractDropdownSingleSelect :options title="Select Tag" v-model="selectedTag" />
</template>
Loading

0 comments on commit 1bc7e2a

Please sign in to comment.