feat(frontend): Clean-up schemas, remove dead code, move types
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<UForm :state="state" :schema="schema" class="space-y-4" @submit="onSubmit">
|
<UForm :state="state" :schema="organizationSchema" class="space-y-4" @submit="onCreateOrganizationSubmit">
|
||||||
<UFormField label="Name" name="name">
|
<UFormField label="Name" name="name">
|
||||||
<UInput v-model="state.name" class="w-full" />
|
<UInput v-model="state.name" class="w-full" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
@@ -33,25 +33,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as z from 'zod'
|
import { organizationSchema, type OrganizationSchema } from '~/types/schemas'
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'organizationCreated', id: string | undefined): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { createOrganization } = useOrganizationStore()
|
const { createOrganization } = useOrganizationStore()
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const isSlugEdited = ref(false)
|
const isSlugEdited = ref(false)
|
||||||
|
|
||||||
type Schema = z.output<typeof schema>
|
const state = reactive<Partial<OrganizationSchema>>({
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
name: z.string().min(2, 'Too short'),
|
|
||||||
slug: z.string().min(2, 'Too short'),
|
|
||||||
logo: z.string().optional()
|
|
||||||
})
|
|
||||||
|
|
||||||
const state = reactive<Partial<Schema>>({
|
|
||||||
name: undefined,
|
name: undefined,
|
||||||
slug: undefined,
|
slug: undefined,
|
||||||
logo: undefined
|
logo: undefined
|
||||||
@@ -87,13 +76,12 @@ function handleLogoChange(event: Event) {
|
|||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onCreateOrganizationSubmit() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
if (!state.name || !state.slug) return
|
if (!state.name || !state.slug) return
|
||||||
|
|
||||||
const organization = await createOrganization(state.name, state.slug, state.logo)
|
await createOrganization(state.name, state.slug, state.logo)
|
||||||
emit('organizationCreated', organization.data?.id)
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
open.value = false
|
open.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="form-container">
|
|
||||||
<h2>Login</h2>
|
|
||||||
<form @submit.prevent="handleLogin">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email-login">Email:</label>
|
|
||||||
<input id="email-login" v-model="email" type="email" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password-login">Passwort:</label>
|
|
||||||
<input id="password-login" v-model="password" type="password" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const email = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
|
|
||||||
const handleLogin = () => {
|
|
||||||
if (!email.value || !password.value) {
|
|
||||||
alert('Bitte alle Felder ausfüllen')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authClient.signIn.email(
|
|
||||||
{
|
|
||||||
email: email.value,
|
|
||||||
password: password.value
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onRequest: (ctx) => {
|
|
||||||
console.log('Sending login request', ctx)
|
|
||||||
},
|
|
||||||
onSuccess: (ctx) => {
|
|
||||||
console.log('Successfully logged in!')
|
|
||||||
},
|
|
||||||
onError: (ctx) => {
|
|
||||||
console.log(ctx.error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.form-container {
|
|
||||||
max-width: 300px;
|
|
||||||
margin: auto;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="form-container">
|
|
||||||
<h2>Registrieren</h2>
|
|
||||||
<form @submit.prevent="handleRegister">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="username">Benutzername:</label>
|
|
||||||
<input id="username" v-model="username" type="text" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email:</label>
|
|
||||||
<input id="email" v-model="email" type="email" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">Passwort:</label>
|
|
||||||
<input id="password" v-model="password" type="password" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirmPassword">Passwort bestätigen:</label>
|
|
||||||
<input id="confirmPassword" v-model="confirmPassword" type="password" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit">Registrieren</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const username = ref('')
|
|
||||||
const email = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
|
|
||||||
const handleRegister = () => {
|
|
||||||
if (!username.value || !email.value || !password.value || !confirmPassword.value) {
|
|
||||||
alert('Bitte alle Felder ausfüllen')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (password.value !== confirmPassword.value) {
|
|
||||||
alert('Passwörter stimmen nicht überein')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authClient.signUp.email(
|
|
||||||
{
|
|
||||||
email: email.value,
|
|
||||||
password: password.value,
|
|
||||||
name: username.value
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onRequest: (ctx) => {
|
|
||||||
console.log('Sending register request', ctx)
|
|
||||||
},
|
|
||||||
onSuccess: (ctx) => {
|
|
||||||
console.log('Successfully registered!')
|
|
||||||
},
|
|
||||||
onError: (ctx) => {
|
|
||||||
console.log(ctx.error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.form-container {
|
|
||||||
max-width: 300px;
|
|
||||||
margin: auto;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #218838;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { LegalRole } from '~/server/utils/permissions'
|
import type { LegalRole } from '~/server/utils/permissions'
|
||||||
import type { ListMembersOptions } from '~/composables/useAuth'
|
import type { ListMembersOptions } from '~/types/auth'
|
||||||
|
|
||||||
export function useOrganizationApi() {
|
export function useOrganizationApi() {
|
||||||
const { organization } = useAuth()
|
const { organization } = useAuth()
|
||||||
|
|||||||
@@ -2,47 +2,20 @@
|
|||||||
|
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { createAuthClient } from 'better-auth/vue'
|
import { createAuthClient } from 'better-auth/vue'
|
||||||
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
|
import type { ClientOptions, InferSessionFromClient, InferUserFromClient } from 'better-auth/client'
|
||||||
import { organizationClient, jwtClient } from 'better-auth/client/plugins'
|
import { jwtClient, organizationClient } from 'better-auth/client/plugins'
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
accessControl,
|
accessControl,
|
||||||
employerRole,
|
|
||||||
worksCouncilMemberRole,
|
|
||||||
employeeRole,
|
|
||||||
adminRole,
|
adminRole,
|
||||||
|
employeeRole,
|
||||||
|
employerRole,
|
||||||
ownerRole,
|
ownerRole,
|
||||||
ROLES
|
ROLES,
|
||||||
|
worksCouncilMemberRole
|
||||||
} from '~/server/utils/permissions'
|
} from '~/server/utils/permissions'
|
||||||
|
import type { RuntimeAuthConfig } from '~/types/auth'
|
||||||
|
|
||||||
interface RuntimeAuthConfig {
|
|
||||||
redirectUserTo: RouteLocationRaw | string
|
|
||||||
redirectGuestTo: RouteLocationRaw | string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types can be found here: https://github.com/better-auth/better-auth/blob/3f574ec70bb15c155a78673d42c5e25f7376ced3/packages/better-auth/src/plugins/organization/routes/crud-invites.ts#L531
|
|
||||||
type Client = ReturnType<typeof useAuth>['client']
|
|
||||||
|
|
||||||
export type Session = Client['$Infer']['Session']
|
|
||||||
export type User = Session['user']
|
|
||||||
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
|
||||||
export type Organization = Client['$Infer']['Organization']
|
|
||||||
export type Invitation = Client['$Infer']['Invitation']
|
|
||||||
export type Member = Client['$Infer']['Member']
|
|
||||||
export type ListMembersOptions = Parameters<Client['organization']['listMembers']>[0]
|
|
||||||
export type ListMembersResponse = Awaited<ReturnType<Client['organization']['listMembers']>>
|
|
||||||
export type ListMembersQuery = NonNullable<ListMembersOptions>['query']
|
|
||||||
|
|
||||||
// Extended invitation type with additional organization and inviter details
|
|
||||||
export type CustomInvitation =
|
|
||||||
| (Invitation & {
|
|
||||||
organizationName: string
|
|
||||||
organizationSlug: string
|
|
||||||
inviterEmail: string
|
|
||||||
})
|
|
||||||
| null
|
|
||||||
|
|
||||||
// TODO: Move into pinia store
|
|
||||||
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
|
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
|
||||||
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
|
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
|
||||||
const sessionFetching = import.meta.server ? ref(false) : ref(false)
|
const sessionFetching = import.meta.server ? ref(false) : ref(false)
|
||||||
@@ -195,7 +168,6 @@ export function useAuth() {
|
|||||||
user,
|
user,
|
||||||
loggedIn: computed(() => !!session.value),
|
loggedIn: computed(() => !!session.value),
|
||||||
signIn: client.signIn,
|
signIn: client.signIn,
|
||||||
signUp: client.signUp,
|
|
||||||
deleteUser: client.deleteUser,
|
deleteUser: client.deleteUser,
|
||||||
signOut,
|
signOut,
|
||||||
organization: client.organization,
|
organization: client.organization,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CustomInvitation } from '~/composables/useAuth'
|
import type { CustomInvitation } from '~/types/auth'
|
||||||
|
|
||||||
const invitationId = useRoute().params.id as string
|
const invitationId = useRoute().params.id as string
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<UAuthForm
|
<UAuthForm
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:schema="schema"
|
:schema="signInSchema"
|
||||||
:providers="providers"
|
:providers="providers"
|
||||||
title="Welcome back"
|
title="Welcome back"
|
||||||
icon="i-lucide-lock"
|
icon="i-lucide-lock"
|
||||||
@submit="onSubmit"
|
@submit="onLoginSubmit"
|
||||||
>
|
>
|
||||||
<template #description>
|
<template #description>
|
||||||
Don't have an account? <ULink to="/signup" class="text-primary-500 font-medium">Sign up</ULink>.
|
Don't have an account? <ULink to="/signup" class="text-primary-500 font-medium">Sign up</ULink>.
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as z from 'zod'
|
|
||||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||||
|
import { signInSchema, type SignInSchema } from '~/types/schemas'
|
||||||
|
|
||||||
definePageMeta({ layout: 'auth' })
|
definePageMeta({ layout: 'auth' })
|
||||||
|
|
||||||
useSeoMeta({ title: 'Login' })
|
useSeoMeta({ title: 'Login' })
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { signIn } = useAuth()
|
const { signIn } = useUserStore()
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
@@ -70,45 +70,11 @@ const providers = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const schema = z.object({
|
function onLoginSubmit(payload: FormSubmitEvent<SignInSchema>) {
|
||||||
email: z.string().email('Invalid email'),
|
|
||||||
password: z.string().min(8, 'Must be at least 8 characters')
|
|
||||||
})
|
|
||||||
|
|
||||||
type Schema = z.output<typeof schema>
|
|
||||||
|
|
||||||
function onSubmit(payload: FormSubmitEvent<Schema>) {
|
|
||||||
if (!payload.data.email || !payload.data.password) {
|
if (!payload.data.email || !payload.data.password) {
|
||||||
alert('Bitte alle Felder ausfüllen')
|
alert('Bitte alle Felder ausfüllen')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
signIn.email(
|
signIn(payload)
|
||||||
{
|
|
||||||
email: payload.data.email,
|
|
||||||
password: payload.data.password
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onRequest: () => {
|
|
||||||
// TODO: Show loading spinner
|
|
||||||
console.log('Sending login request')
|
|
||||||
},
|
|
||||||
onResponse: () => {
|
|
||||||
// TODO: Hide loading spinner
|
|
||||||
console.log('Receiving login response')
|
|
||||||
},
|
|
||||||
onSuccess: async (ctx) => {
|
|
||||||
console.log('Successfully logged in!', ctx)
|
|
||||||
await navigateTo('/')
|
|
||||||
},
|
|
||||||
onError: (ctx) => {
|
|
||||||
console.log(ctx.error.message)
|
|
||||||
useToast().add({
|
|
||||||
title: 'Fehler bei der Anmeldung',
|
|
||||||
description: ctx.error.message,
|
|
||||||
color: 'error'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<UAuthForm
|
<UAuthForm
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:schema="schema"
|
:schema="signUpSchema"
|
||||||
:providers="providers"
|
:providers="providers"
|
||||||
title="Create an account"
|
title="Create an account"
|
||||||
:submit="{ label: 'Create account' }"
|
:submit="{ label: 'Create account' }"
|
||||||
@submit="onSubmit"
|
@submit="onSignUpSubmit"
|
||||||
>
|
>
|
||||||
<template #description>
|
<template #description>
|
||||||
Already have an account? <ULink to="/login" class="text-primary-500 font-medium">Login</ULink>.
|
Already have an account? <ULink to="/login" class="text-primary-500 font-medium">Login</ULink>.
|
||||||
@@ -18,17 +18,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as z from 'zod'
|
|
||||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||||
import { UserRole } from '~/.api-client'
|
import { signUpSchema, type SignUpSchema } from '~/types/schemas'
|
||||||
|
|
||||||
definePageMeta({ layout: 'auth' })
|
definePageMeta({ layout: 'auth' })
|
||||||
|
|
||||||
useSeoMeta({ title: 'Sign up' })
|
useSeoMeta({ title: 'Sign up' })
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { signUp, deleteUser } = useAuth()
|
const { signUp } = useUserStore()
|
||||||
const { createUser } = useUser()
|
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
@@ -68,63 +66,7 @@ const providers = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const schema = z.object({
|
function onSignUpSubmit(payload: FormSubmitEvent<SignUpSchema>) {
|
||||||
name: z.string().min(1, 'Name is required'),
|
signUp(payload)
|
||||||
email: z.string().email('Invalid email'),
|
|
||||||
password: z.string().min(8, 'Must be at least 8 characters')
|
|
||||||
})
|
|
||||||
|
|
||||||
type Schema = z.output<typeof schema>
|
|
||||||
|
|
||||||
function onSubmit(payload: FormSubmitEvent<Schema>) {
|
|
||||||
signUp.email(
|
|
||||||
{
|
|
||||||
email: payload.data.email,
|
|
||||||
password: payload.data.password,
|
|
||||||
name: payload.data.name
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onRequest: () => {
|
|
||||||
// TODO: Show loading spinner
|
|
||||||
console.log('Sending register request')
|
|
||||||
},
|
|
||||||
onResponse: () => {
|
|
||||||
// TODO: Hide loading spinner
|
|
||||||
console.log('Receiving register response')
|
|
||||||
},
|
|
||||||
onSuccess: async (ctx) => {
|
|
||||||
console.log('Successfully registered!')
|
|
||||||
|
|
||||||
// Create user in backend after successful Better Auth registration
|
|
||||||
try {
|
|
||||||
console.log('Creating user in backend...', ctx.data)
|
|
||||||
await createUser({
|
|
||||||
id: ctx.data.user.id,
|
|
||||||
name: ctx.data.user.name,
|
|
||||||
status: 'ACTIVE',
|
|
||||||
role: UserRole.Employee
|
|
||||||
})
|
|
||||||
console.log('User created in backend successfully')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create user in backend:', error)
|
|
||||||
toast.add({
|
|
||||||
title: 'Warning',
|
|
||||||
description: 'Account created but there was an issue with backend setup. Please contact support.',
|
|
||||||
color: 'warning'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await navigateTo('/')
|
|
||||||
},
|
|
||||||
onError: async (ctx) => {
|
|
||||||
console.log(ctx.error.message)
|
|
||||||
useToast().add({
|
|
||||||
title: 'Fehler bei der Registrierung',
|
|
||||||
description: ctx.error.message,
|
|
||||||
color: 'error'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import type {
|
|
||||||
ActiveOrganization,
|
|
||||||
Organization,
|
|
||||||
Invitation,
|
|
||||||
ListMembersOptions,
|
|
||||||
ListMembersResponse,
|
|
||||||
ListMembersQuery
|
|
||||||
} from '~/composables/useAuth'
|
|
||||||
import { useOrganizationApi } from '~/composables/organization/useOrganizationApi'
|
import { useOrganizationApi } from '~/composables/organization/useOrganizationApi'
|
||||||
import type { LegalRole } from '~/server/utils/permissions'
|
import type { LegalRole } from '~/server/utils/permissions'
|
||||||
|
import type {
|
||||||
|
ActiveOrganization,
|
||||||
|
CustomInvitation,
|
||||||
|
Invitation,
|
||||||
|
ListMembersOptions,
|
||||||
|
ListMembersQuery,
|
||||||
|
ListMembersResponse,
|
||||||
|
Member,
|
||||||
|
Organization
|
||||||
|
} from '~/types/auth'
|
||||||
|
|
||||||
export const useOrganizationStore = defineStore('Organization', () => {
|
export const useOrganizationStore = defineStore('Organization', () => {
|
||||||
const activeOrganization = ref<ActiveOrganization | null>(null)
|
const activeOrganization = ref<ActiveOrganization | null>(null)
|
||||||
|
|||||||
@@ -1,39 +1,82 @@
|
|||||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||||
|
import { UserRole } from '~/.api-client'
|
||||||
|
import type { SignInSchema, SignUpSchema } from '~/types/schemas'
|
||||||
|
|
||||||
export const useUserStore = defineStore('User', () => {
|
export const useUserStore = defineStore('User', () => {
|
||||||
|
const { createUser } = useUser()
|
||||||
const name = ref('')
|
const name = ref('')
|
||||||
|
const toast = useToast()
|
||||||
|
const { client } = useAuth()
|
||||||
|
|
||||||
async function signUp(payload: FormSubmitEvent<Schema>) {
|
async function signUp(payload: FormSubmitEvent<SignUpSchema>) {
|
||||||
await authClient.signUp.email(
|
await client.signUp.email(
|
||||||
{
|
{
|
||||||
email: payload.data.email,
|
email: payload.data.email,
|
||||||
password: payload.data.password,
|
password: payload.data.password,
|
||||||
name: payload.data.name
|
name: payload.data.name
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onRequest: (ctx) => {
|
onRequest: () => {
|
||||||
// TODO: Show loading spinner
|
|
||||||
console.log('Sending register request')
|
console.log('Sending register request')
|
||||||
},
|
},
|
||||||
onResponse: () => {
|
onResponse: () => {
|
||||||
// TODO: Hide loading spinner
|
|
||||||
console.log('Receiving register response')
|
console.log('Receiving register response')
|
||||||
},
|
},
|
||||||
onSuccess: async (ctx) => {
|
onSuccess: async (ctx) => {
|
||||||
console.log('Successfully registered!')
|
console.log('Successfully registered!')
|
||||||
|
|
||||||
|
// Create user in backend after successful Better Auth registration
|
||||||
|
try {
|
||||||
|
console.log('Creating user in backend...', ctx.data)
|
||||||
|
await createUser({
|
||||||
|
id: ctx.data.user.id,
|
||||||
|
name: ctx.data.user.name,
|
||||||
|
status: 'ACTIVE',
|
||||||
|
role: UserRole.Employee
|
||||||
|
})
|
||||||
|
console.log('User created in backend successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create user in backend:', error)
|
||||||
|
toast.add({
|
||||||
|
title: 'Warning',
|
||||||
|
description: 'Account created but there was an issue with backend setup. Please contact support.',
|
||||||
|
color: 'warning'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
await navigateTo('/')
|
await navigateTo('/')
|
||||||
},
|
},
|
||||||
onError: (ctx) => {
|
onError: async (ctx) => {
|
||||||
console.log(ctx.error.message)
|
console.log(ctx.error.message)
|
||||||
useToast().add({
|
useToast().add({
|
||||||
title: 'Fehler bei der Registrierung',
|
title: 'Fehler bei der Registrierung',
|
||||||
description: ctx.error.message,
|
description: ctx.error.message,
|
||||||
color: 'success'
|
color: 'error'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { name, signUp }
|
async function signIn(payload: FormSubmitEvent<SignInSchema>) {
|
||||||
|
await client.signIn.email(
|
||||||
|
{
|
||||||
|
email: payload.data.email,
|
||||||
|
password: payload.data.password
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onRequest: () => {
|
||||||
|
console.log('Sending login request')
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
console.log('Successfully logged in!')
|
||||||
|
},
|
||||||
|
onError: (ctx) => {
|
||||||
|
console.log(ctx.error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name, signUp, signIn }
|
||||||
})
|
})
|
||||||
|
|||||||
27
legalconsenthub/types/auth.ts
Normal file
27
legalconsenthub/types/auth.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { RouteLocationRaw } from '#vue-router'
|
||||||
|
import type { useAuth } from '~/composables/useAuth'
|
||||||
|
|
||||||
|
export interface RuntimeAuthConfig {
|
||||||
|
redirectUserTo: RouteLocationRaw | string
|
||||||
|
redirectGuestTo: RouteLocationRaw | string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types can be found here: https://github.com/better-auth/better-auth/blob/3f574ec70bb15c155a78673d42c5e25f7376ced3/packages/better-auth/src/plugins/organization/routes/crud-invites.ts#L531
|
||||||
|
type Client = ReturnType<typeof useAuth>['client']
|
||||||
|
export type Session = Client['$Infer']['Session']
|
||||||
|
export type User = Session['user']
|
||||||
|
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
||||||
|
export type Organization = Client['$Infer']['Organization']
|
||||||
|
export type Invitation = Client['$Infer']['Invitation']
|
||||||
|
export type Member = Client['$Infer']['Member']
|
||||||
|
export type ListMembersOptions = Parameters<Client['organization']['listMembers']>[0]
|
||||||
|
export type ListMembersResponse = Awaited<ReturnType<Client['organization']['listMembers']>>
|
||||||
|
export type ListMembersQuery = NonNullable<ListMembersOptions>['query']
|
||||||
|
// Extended invitation type with additional organization and inviter details
|
||||||
|
export type CustomInvitation =
|
||||||
|
| (Invitation & {
|
||||||
|
organizationName: string
|
||||||
|
organizationSlug: string
|
||||||
|
inviterEmail: string
|
||||||
|
})
|
||||||
|
| null
|
||||||
22
legalconsenthub/types/schemas.ts
Normal file
22
legalconsenthub/types/schemas.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as z from 'zod'
|
||||||
|
|
||||||
|
export const signUpSchema = z.object({
|
||||||
|
name: z.string().min(1, 'Name is required'),
|
||||||
|
email: z.string().email('Invalid email'),
|
||||||
|
password: z.string().min(8, 'Must be at least 8 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
export const signInSchema = z.object({
|
||||||
|
email: z.string().email('Invalid email'),
|
||||||
|
password: z.string().min(8, 'Must be at least 8 characters')
|
||||||
|
})
|
||||||
|
|
||||||
|
export const organizationSchema = z.object({
|
||||||
|
name: z.string().min(2, 'Too short'),
|
||||||
|
slug: z.string().min(2, 'Too short'),
|
||||||
|
logo: z.string().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SignUpSchema = z.output<typeof signUpSchema>
|
||||||
|
export type SignInSchema = z.output<typeof signInSchema>
|
||||||
|
export type OrganizationSchema = z.output<typeof organizationSchema>
|
||||||
Reference in New Issue
Block a user