Browse Source

better toast, better avatar

pull/1397/head
Bernd Storath 9 months ago
parent
commit
5bd33c91ff
  1. 13
      src/app/components/ClientCard/Avatar.vue
  2. 21
      src/app/components/base/Avatar.vue
  3. 4
      src/app/components/base/Switch.vue
  4. 23
      src/app/components/base/Toast.vue
  5. 17
      src/app/components/error/Toast.vue
  6. 8
      src/app/components/ui/ChooseLang.vue
  7. 0
      src/app/components/ui/Modal.vue
  8. 0
      src/app/components/ui/NavBar.vue
  9. 11
      src/app/components/ui/UserMenu.vue
  10. 8
      src/app/layouts/setup.vue
  11. 3
      src/app/pages/admin/interface.vue
  12. 2
      src/app/pages/index.vue
  13. 28
      src/app/pages/login.vue
  14. 14
      src/app/stores/setup.ts
  15. 3
      src/i18n/i18n.config.ts

13
src/app/components/ClientCard/Avatar.vue

@ -1,11 +1,8 @@
<template> <template>
<div class="relative mt-2 h-10 w-10 self-start rounded-full bg-gray-50"> <div class="relative mt-2 h-10 w-10 self-start rounded-full bg-gray-50">
<IconsAvatar class="m-2 w-6 text-gray-300" /> <BaseAvatar :img="client.avatar" class="h-10 w-10">
<img <IconsAvatar class="h-6 w-6 text-gray-300" />
v-if="client.avatar" </BaseAvatar>
:src="client.avatar"
class="absolute left-0 top-0 w-10 rounded-full"
/>
<div <div
v-if=" v-if="
@ -25,7 +22,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ const props = defineProps<{
client: LocalClient; client: LocalClient;
}>(); }>();
console.log(props.client.avatar);
</script> </script>

21
src/app/components/base/Avatar.vue

@ -0,0 +1,21 @@
<template>
<AvatarRoot
class="mr-2 inline-flex select-none items-center justify-center overflow-hidden rounded-full align-middle"
>
<AvatarImage
v-if="img"
class="h-full w-full rounded-[inherit] object-cover"
:src="img"
/>
<AvatarFallback
class="leading-1 flex h-full w-full items-center justify-center bg-white text-sm font-medium"
:delay-ms="600"
>
<slot />
</AvatarFallback>
</AvatarRoot>
</template>
<script lang="ts" setup>
defineProps<{ img?: string }>();
</script>

4
src/app/components/base/Switch.vue

@ -2,10 +2,10 @@
<SwitchRoot <SwitchRoot
:id="id" :id="id"
v-model:checked="data" v-model:checked="data"
class="relative flex h-[24px] w-[40px] cursor-default rounded-full bg-gray-200 shadow-sm focus-within:outline focus-within:outline-red-700 data-[state=checked]:bg-red-800 dark:bg-neutral-400" class="relative flex h-6 w-10 cursor-default rounded-full bg-gray-200 shadow-sm focus-within:outline focus-within:outline-red-700 data-[state=checked]:bg-red-800 dark:bg-neutral-400"
> >
<SwitchThumb <SwitchThumb
class="my-auto block h-[16px] w-[16px] translate-x-1 rounded-full bg-white shadow-sm transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[20px]" class="my-auto block h-4 w-4 translate-x-1 rounded-full bg-white shadow-sm transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[20px]"
/> />
</SwitchRoot> </SwitchRoot>
</template> </template>

23
src/app/components/ui/Toast.vue → src/app/components/base/Toast.vue

@ -7,25 +7,32 @@ import {
ToastTitle, ToastTitle,
} from 'radix-vue'; } from 'radix-vue';
defineProps<{ defineExpose({
title: string; publish,
content: string; });
}>();
const count = reactive<{ title: string; message: string }[]>([]);
function publish(e: { title: string; message: string }) {
count.push({ title: e.title, message: e.message });
console.log(count.length);
}
</script> </script>
<template> <template>
<ToastRoot <ToastRoot
class="data-[state=open]:animate-slideIn data-[state=closed]:animate-hide data-[swipe=end]:animate-swipeOut grid grid-cols-[auto_max-content] items-center gap-x-[15px] rounded-md bg-white p-[15px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] [grid-template-areas:_'title_action'_'description_action'] data-[swipe=cancel]:translate-x-0 data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:transition-[transform_200ms_ease-out]" v-for="(e, i) in count"
:key="i"
class="data-[state=open]:animate-slideIn data-[state=closed]:animate-hide data-[swipe=end]:animate-swipeOut grid grid-cols-[auto_max-content] items-center gap-x-[15px] rounded-md bg-red-800 p-[15px] text-neutral-200 shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] [grid-template-areas:_'title_action'_'description_action'] data-[swipe=cancel]:translate-x-0 data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:transition-[transform_200ms_ease-out]"
> >
<ToastTitle <ToastTitle
v-if="title"
class="text-slate12 mb-[5px] text-[15px] font-medium [grid-area:_title]" class="text-slate12 mb-[5px] text-[15px] font-medium [grid-area:_title]"
> >
{{ title }} {{ e.title }}
</ToastTitle> </ToastTitle>
<ToastDescription <ToastDescription
class="text-slate11 m-0 text-[13px] leading-[1.3] [grid-area:_description]" class="text-slate11 m-0 text-[13px] leading-[1.3] [grid-area:_description]"
>{{ content }}</ToastDescription >{{ e.message }}</ToastDescription
> >
<ToastAction as-child alt-text="toast" class="[grid-area:_action]"> <ToastAction as-child alt-text="toast" class="[grid-area:_action]">
<slot /> <slot />

17
src/app/components/error/Toast.vue

@ -1,17 +0,0 @@
<script setup lang="ts">
const { title, message, duration } = defineProps<{
title: string;
message: string;
duration?: number;
}>();
</script>
<template>
<ToastRoot
:duration="duration"
class="rounded-md bg-red-800 p-2 text-neutral-200"
>
<ToastTitle class="mb-4 text-lg font-medium">{{ title }} </ToastTitle>
<ToastDescription as-child>{{ message }}</ToastDescription>
</ToastRoot>
</template>

8
src/app/components/ui/ChooseLang.vue

@ -17,7 +17,7 @@
<SelectItem <SelectItem
v-for="(option, index) in langs" v-for="(option, index) in langs"
:key="index" :key="index"
:value="option.value" :value="option.code"
class="text-grass11 relative flex h-[25px] items-center rounded-[3px] pl-[25px] pr-[35px] text-[13px] leading-none hover:bg-red-800 hover:text-white dark:text-white" class="text-grass11 relative flex h-[25px] items-center rounded-[3px] pl-[25px] pr-[35px] text-[13px] leading-none hover:bg-red-800 hover:text-white dark:text-white"
> >
<SelectItemText> <SelectItemText>
@ -31,7 +31,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { locale, locales } = useI18n(); import { LOCALES } from '#shared/locales';
const { locale } = useI18n();
const emit = defineEmits(['update:lang']); const emit = defineEmits(['update:lang']);
const langProxy = ref(locale); const langProxy = ref(locale);
@ -40,5 +42,5 @@ watch(langProxy, (newVal) => {
emit('update:lang', newVal); emit('update:lang', newVal);
}); });
const langs = locales.value.sort((a, b) => a.value.localeCompare(b.value)); const langs = LOCALES.sort((a, b) => a.code.localeCompare(b.code));
</script> </script>

0
src/app/components/ui/Modal.vue

0
src/app/components/ui/NavBar.vue

11
src/app/components/ui/UserMenu.vue

@ -5,16 +5,9 @@
class="flex items-center rounded-full pe-1 text-sm font-medium text-gray-400 hover:text-red-800 focus:ring-4 focus:ring-gray-100 md:me-0 dark:text-neutral-400 dark:hover:text-red-800 dark:focus:ring-gray-700" class="flex items-center rounded-full pe-1 text-sm font-medium text-gray-400 hover:text-red-800 focus:ring-4 focus:ring-gray-100 md:me-0 dark:text-neutral-400 dark:hover:text-red-800 dark:focus:ring-gray-700"
type="button" type="button"
> >
<AvatarRoot <BaseAvatar class="h-8 w-8">
class="mr-2 inline-flex h-8 w-8 select-none items-center justify-center overflow-hidden rounded-full align-middle"
>
<AvatarFallback
class="leading-1 flex h-full w-full items-center justify-center bg-white text-[15px] font-medium"
:delay-ms="600"
>
{{ fallbackName }} {{ fallbackName }}
</AvatarFallback> </BaseAvatar>
</AvatarRoot>
{{ authStore.userData?.name }} {{ authStore.userData?.name }}
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>

8
src/app/layouts/setup.vue

@ -18,14 +18,12 @@
</PanelBody> </PanelBody>
</Panel> </Panel>
<ErrorToast <BaseToast ref="toast" />
v-if="setupStore.error"
:title="setupStore.error.title"
:message="setupStore.error.message"
/>
</main> </main>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const setupStore = useSetupStore(); const setupStore = useSetupStore();
const savedRef = useTemplateRef('toast');
setupStore.setErrorRef(savedRef);
</script> </script>

3
src/app/pages/admin/interface.vue

@ -0,0 +1,3 @@
<template><div></div></template>
<script setup lang="ts"></script>

2
src/app/pages/index.vue

@ -4,8 +4,6 @@
<PanelHead> <PanelHead>
<PanelHeadTitle :text="$t('pages.clients')" /> <PanelHeadTitle :text="$t('pages.clients')" />
<PanelHeadBoat> <PanelHeadBoat>
<ClientsRestoreConfig />
<ClientsBackupConfig />
<ClientsSort /> <ClientsSort />
<ClientsNew /> <ClientsNew />
</PanelHeadBoat> </PanelHeadBoat>

28
src/app/pages/login.vue

@ -75,12 +75,7 @@
/> />
</form> </form>
<ErrorToast <BaseToast ref="toast" />
v-if="setupError"
:title="setupError.title"
:message="setupError.message"
:duration="12000"
/>
</main> </main>
</template> </template>
@ -94,22 +89,7 @@ const remember = ref(false);
const username = ref<null | string>(null); const username = ref<null | string>(null);
const password = ref<null | string>(null); const password = ref<null | string>(null);
const authStore = useAuthStore(); const authStore = useAuthStore();
const toast = useTemplateRef('toast');
type SetupError = {
title: string;
message: string;
};
const setupError = ref<null | SetupError>(null);
// TODO: check if needed
watch(setupError, (value) => {
if (value) {
setTimeout(() => {
setupError.value = null;
}, 13000);
}
});
async function login(e: Event) { async function login(e: Event) {
e.preventDefault(); e.preventDefault();
@ -128,10 +108,10 @@ async function login(e: Event) {
} }
} catch (error) { } catch (error) {
if (error instanceof FetchError) { if (error instanceof FetchError) {
setupError.value = { toast.value?.publish({
title: t('error.login'), title: t('error.login'),
message: error.data.message, message: error.data.message,
}; });
} }
} }
authenticating.value = false; authenticating.value = false;

14
src/app/stores/setup.ts

@ -50,10 +50,18 @@ export const useSetupStore = defineStore('Setup', () => {
message: string; message: string;
}; };
const error = ref<null | SetupError>(null); type ErrorRef = {
value: { publish: (e: SetupError) => void } | null;
};
const errorRef = ref<null | ErrorRef>(null);
function setErrorRef(a: ErrorRef | null) {
errorRef.value = a;
}
function handleError(e: SetupError) { function handleError(e: SetupError) {
error.value = e; errorRef.value?.value?.publish(e);
} }
const step = ref(1); const step = ref(1);
@ -67,7 +75,7 @@ export const useSetupStore = defineStore('Setup', () => {
step4, step4,
step5, step5,
runMigration, runMigration,
error, setErrorRef,
handleError, handleError,
step, step,
totalSteps, totalSteps,

3
src/i18n/i18n.config.ts

@ -19,13 +19,10 @@ import it from './locales/it.json';
import th from './locales/th.json'; import th from './locales/th.json';
import hi from './locales/hi.json'; import hi from './locales/hi.json';
import { LOCALES } from '~~/shared/locales';
export default defineI18nConfig(() => ({ export default defineI18nConfig(() => ({
fallbackLocale: 'en', fallbackLocale: 'en',
legacy: false, legacy: false,
locale: 'en', locale: 'en',
locales: LOCALES,
messages: { messages: {
en, en,
ua, ua,

Loading…
Cancel
Save