replace alerts with notifications using DaisyUI toasts

This commit is contained in:
Michael Dausmann
2023-05-12 23:46:27 +10:00
parent c6b48f05c1
commit 0d2db65f74
7 changed files with 76 additions and 8 deletions

View File

@@ -69,7 +69,7 @@ Demo site [here](https://nuxt3-saas-boilerplate.netlify.app/)
### Look and Feel, Design System and Customisation ### Look and Feel, Design System and Customisation
- [x] Default UI isn't too crap - [x] Default UI isn't too crap
- [x] Integrated Design system including theming (Tailwind + daisyUI) - [x] Integrated Design system including theming (Tailwind + daisyUI)
- [ ] Toasts for things like reset email sent - [x] Toasts for things like reset email sent
- [ ] Modals, just because people like modals - [ ] Modals, just because people like modals
### Demo Software (Notes) ### Demo Software (Notes)

View File

@@ -5,6 +5,8 @@
const user = useSupabaseUser(); const user = useSupabaseUser();
const accountStore = useAccountStore() const accountStore = useAccountStore()
const { dbUser, activeAccountId } = storeToRefs(accountStore); const { dbUser, activeAccountId } = storeToRefs(accountStore);
const notifyStore = useNotifyStore();
const { notifications } = storeToRefs(notifyStore);
onMounted(async () => { onMounted(async () => {
await accountStore.init() await accountStore.init()
@@ -21,6 +23,21 @@
<template> <template>
<div class="navbar bg-base-100"> <div class="navbar bg-base-100">
<div class="toast toast-end toast-top">
<div v-for="notification in notifications" :class="notification.type">
<div>
<button
@click.prevent="notifyStore.removeNotification(notification)"
type="button"
class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
aria-label="Close">
<span class="sr-only">Close</span>
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</button>
<span>&nbsp;{{notification.message}}</span>
</div>
</div>
</div>
<div class="navbar-start"> <div class="navbar-start">
<div class="dropdown"> <div class="dropdown">
<label tabindex="0" class="btn btn-ghost lg:hidden"> <label tabindex="0" class="btn btn-ghost lg:hidden">

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
const supabase = useSupabaseAuthClient(); const supabase = useSupabaseAuthClient();
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const notifyStore = useNotifyStore();
const loading = ref(false) const loading = ref(false)
const email = ref('') const email = ref('')
@@ -12,9 +13,9 @@
redirectTo: `${config.public.siteRootUrl}/resetpassword`, redirectTo: `${config.public.siteRootUrl}/resetpassword`,
}) })
if (error) throw error if (error) throw error
else alert('Password Reset link sent, check your email.'); else notifyStore.notify("Password Reset link sent, check your email.", NotificationType.Success);
} catch (error) { } catch (error) {
alert(error) notifyStore.notify(error, NotificationType.Error);
} finally { } finally {
loading.value = false loading.value = false
} }

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
const supabase = useSupabaseAuthClient(); const supabase = useSupabaseAuthClient();
const notifyStore = useNotifyStore();
const loading = ref(false) const loading = ref(false)
const password = ref('') const password = ref('')
const confirmPassword = ref('') const confirmPassword = ref('')
@@ -13,11 +15,11 @@
}); });
if (error) throw error if (error) throw error
else { else {
alert('password changed'); notifyStore.notify("password changed", NotificationType.Success);
navigateTo('/signin', {replace: true}); // navigate to signin because it is best practice although the auth session seems to be valid so it immediately redirects to dashboard navigateTo('/signin', {replace: true}); // navigate to signin because it is best practice although the auth session seems to be valid so it immediately redirects to dashboard
} }
} catch (error) { } catch (error) {
alert(error) notifyStore.notify(error, NotificationType.Error);
} finally { } finally {
loading.value = false loading.value = false
} }

View File

@@ -3,6 +3,7 @@
const supabase = useSupabaseAuthClient(); const supabase = useSupabaseAuthClient();
const accountStore = useAccountStore() const accountStore = useAccountStore()
const notifyStore = useNotifyStore();
const loading = ref(false) const loading = ref(false)
const email = ref('') const email = ref('')
@@ -15,7 +16,7 @@
const { error } = await supabase.auth.signInWithPassword({ email: email.value, password: password.value }) const { error } = await supabase.auth.signInWithPassword({ email: email.value, password: password.value })
if (error) throw error if (error) throw error
} catch (error) { } catch (error) {
alert(error) notifyStore.notify(error, NotificationType.Error);
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -28,7 +29,7 @@
const { error } = await supabase.auth.signInWithOAuth({ provider: 'google' }) const { error } = await supabase.auth.signInWithOAuth({ provider: 'google' })
if (error) throw error if (error) throw error
} catch (error) { } catch (error) {
alert(error) notifyStore.notify(error, NotificationType.Error);
} finally { } finally {
loading.value = false loading.value = false
} }

View File

@@ -2,6 +2,8 @@
const user = useSupabaseUser() const user = useSupabaseUser()
const supabase = useSupabaseAuthClient(); const supabase = useSupabaseAuthClient();
const notifyStore = useNotifyStore();
const loading = ref(false) const loading = ref(false)
const email = ref('') const email = ref('')
const password = ref('') const password = ref('')
@@ -19,7 +21,7 @@
signUpOk.value = true; signUpOk.value = true;
} }
} catch (error) { } catch (error) {
alert(error) notifyStore.notify(error, NotificationType.Error);
} finally { } finally {
loading.value = false loading.value = false
} }

45
stores/notify.store.ts Normal file
View File

@@ -0,0 +1,45 @@
import { defineStore } from "pinia"
/*
This store manages User and Account state including the ActiveAccount
It is used in the Account administration page and the header due to it's account switching features.
*/
export interface Notification{
message: string;
type: NotificationType;
notifyTime: number;
}
export enum NotificationType{
Info = "alert alert-info",
Success = "alert alert-success",
Warning = "alert alert-warning",
Error = "alert alert-error",
}
interface State {
notifications: Notification[],
notificationsArchive: Notification[],
}
export const useNotifyStore = defineStore('notify', {
state: (): State => {
return {
notifications: [],
notificationsArchive: [],
}
},
actions: {
notify(messageOrError: unknown, type:NotificationType){
let message: string = "";
if (messageOrError instanceof Error) message = messageOrError.message;
if (typeof messageOrError === "string") message = messageOrError;
const notification: Notification = {message, type, notifyTime: Date.now()};
this.notifications.push(notification);
setTimeout(this.removeNotification.bind(this), 5000, notification);
},
removeNotification(notification: Notification){
this.notifications = this.notifications.filter(n => n.notifyTime != notification.notifyTime);
},
}
});