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>
<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" />
<img
v-if="client.avatar"
:src="client.avatar"
class="absolute left-0 top-0 w-10 rounded-full"
/>
<BaseAvatar :img="client.avatar" class="h-10 w-10">
<IconsAvatar class="h-6 w-6 text-gray-300" />
</BaseAvatar>
<div
v-if="
@ -25,7 +22,9 @@
</template>
<script setup lang="ts">
defineProps<{
const props = defineProps<{
client: LocalClient;
}>();
console.log(props.client.avatar);
</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
:id="id"
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
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>
</template>

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

@ -7,25 +7,32 @@ import {
ToastTitle,
} from 'radix-vue';
defineProps<{
title: string;
content: string;
}>();
defineExpose({
publish,
});
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>
<template>
<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
v-if="title"
class="text-slate12 mb-[5px] text-[15px] font-medium [grid-area:_title]"
>
{{ title }}
{{ e.title }}
</ToastTitle>
<ToastDescription
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]">
<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
v-for="(option, index) in langs"
: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"
>
<SelectItemText>
@ -31,7 +31,9 @@
</template>
<script setup lang="ts">
const { locale, locales } = useI18n();
import { LOCALES } from '#shared/locales';
const { locale } = useI18n();
const emit = defineEmits(['update:lang']);
const langProxy = ref(locale);
@ -40,5 +42,5 @@ watch(langProxy, (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>

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"
type="button"
>
<AvatarRoot
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"
>
<BaseAvatar class="h-8 w-8">
{{ fallbackName }}
</AvatarFallback>
</AvatarRoot>
</BaseAvatar>
{{ authStore.userData?.name }}
</button>
</DropdownMenuTrigger>

8
src/app/layouts/setup.vue

@ -18,14 +18,12 @@
</PanelBody>
</Panel>
<ErrorToast
v-if="setupStore.error"
:title="setupStore.error.title"
:message="setupStore.error.message"
/>
<BaseToast ref="toast" />
</main>
</template>
<script lang="ts" setup>
const setupStore = useSetupStore();
const savedRef = useTemplateRef('toast');
setupStore.setErrorRef(savedRef);
</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>
<PanelHeadTitle :text="$t('pages.clients')" />
<PanelHeadBoat>
<ClientsRestoreConfig />
<ClientsBackupConfig />
<ClientsSort />
<ClientsNew />
</PanelHeadBoat>

28
src/app/pages/login.vue

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

14
src/app/stores/setup.ts

@ -50,10 +50,18 @@ export const useSetupStore = defineStore('Setup', () => {
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) {
error.value = e;
errorRef.value?.value?.publish(e);
}
const step = ref(1);
@ -67,7 +75,7 @@ export const useSetupStore = defineStore('Setup', () => {
step4,
step5,
runMigration,
error,
setErrorRef,
handleError,
step,
totalSteps,

3
src/i18n/i18n.config.ts

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

Loading…
Cancel
Save