mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
Compare commits
1 Commits
copilot/fi
...
tonya/coll
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12d6b17318 |
130
frontend/components/App/OrgSelector.vue
Normal file
130
frontend/components/App/OrgSelector.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<template>
|
||||||
|
<Popover v-model:open="open">
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
:aria-expanded="open"
|
||||||
|
class="w-full justify-between"
|
||||||
|
>
|
||||||
|
{{ value && value.name ? value.name : "Select inventory" }}
|
||||||
|
<div class="flex items-center gap-2" v-if="value">
|
||||||
|
<Badge
|
||||||
|
class="whitespace-nowrap"
|
||||||
|
:variant="value.role === 'owner' ? 'default' : value.role === 'admin' ? 'secondary' : 'outline'"
|
||||||
|
>
|
||||||
|
{{ value.role }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-[--reka-popper-anchor-width] p-0">
|
||||||
|
<Command :ignore-filter="true">
|
||||||
|
<CommandInput v-model="search" placeholder="Search collections..." :display-value="(_) => ''" />
|
||||||
|
<CommandEmpty>No inventory found</CommandEmpty>
|
||||||
|
<CommandList>
|
||||||
|
<CommandGroup heading="Your Collections">
|
||||||
|
<CommandItem
|
||||||
|
v-for="org in filteredOrgs"
|
||||||
|
:key="org.id"
|
||||||
|
:value="org.id"
|
||||||
|
@select="selectOrg(org as unknown as OrgSummary)"
|
||||||
|
>
|
||||||
|
<Check :class="cn('mr-2 h-4 w-4', value?.id === org.id ? 'opacity-100' : 'opacity-0')" />
|
||||||
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
|
{{ org.name }}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Badge
|
||||||
|
class="whitespace-nowrap"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
{{ org.count }}
|
||||||
|
</Badge>
|
||||||
|
<Badge
|
||||||
|
class="whitespace-nowrap"
|
||||||
|
:variant="org.role === 'owner' ? 'default' : org.role === 'admin' ? 'secondary' : 'outline'"
|
||||||
|
>
|
||||||
|
{{ org.role }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem @select="() => {}">
|
||||||
|
<Plus class="mr-2 size-4" /> Create New Collection
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem @select="() => {}">
|
||||||
|
<Plus class="mr-2 size-4" /> Join Existing Collection
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Check, ChevronsUpDown, Lock, Users, Plus } from "lucide-vue-next";
|
||||||
|
import fuzzysort from "fuzzysort";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "~/components/ui/command";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
import { ref, computed, watch } from "vue";
|
||||||
|
import { useVModel } from "@vueuse/core";
|
||||||
|
|
||||||
|
type OrgSummary = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
role: "owner" | "admin" | "editor" | "viewer";
|
||||||
|
type: "personal" | "org";
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
modelValue?: OrgSummary | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const search = ref("");
|
||||||
|
const value = useVModel(props, "modelValue", emit);
|
||||||
|
|
||||||
|
// Mock data for demonstration purposes
|
||||||
|
const orgs = ref<OrgSummary[]>([
|
||||||
|
{ id: "1", name: "Personal Inventory", count: 1, role: "owner", type: "personal" },
|
||||||
|
{ id: "2", name: "Family Home", count: 4, role: "admin", type: "org" },
|
||||||
|
{ id: "3", name: "Office Equipment", count: 12, role: "editor", type: "org" },
|
||||||
|
{ id: "4", name: "Workshop Tools", count: 3, role: "viewer", type: "org" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
function selectOrg(org: OrgSummary) {
|
||||||
|
if (value.value?.id !== org.id) {
|
||||||
|
value.value = org;
|
||||||
|
} else {
|
||||||
|
value.value = null;
|
||||||
|
}
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredOrgs = computed(() => {
|
||||||
|
const filtered = fuzzysort.go(search.value, orgs.value, { key: "name", all: true }).map((i) => i.obj);
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset search when value is cleared
|
||||||
|
watch(
|
||||||
|
() => value.value,
|
||||||
|
() => {
|
||||||
|
if (!value.value) {
|
||||||
|
search.value = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from "vue";
|
import type { HTMLAttributes } from "vue";
|
||||||
import { Primitive, type PrimitiveProps } from "reka-ui";
|
import { Primitive, type PrimitiveProps } from "reka-ui";
|
||||||
import { type ButtonVariants, buttonVariants } from ".";
|
import { type ButtonVariants, buttonVariants } from ".";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
variant?: ButtonVariants["variant"];
|
variant?: ButtonVariants["variant"];
|
||||||
size?: ButtonVariants["size"];
|
size?: ButtonVariants["size"];
|
||||||
class?: HTMLAttributes["class"];
|
class?: HTMLAttributes["class"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
as: "button",
|
as: "button",
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
57
frontend/components/ui/calendar/Calendar.vue
Normal file
57
frontend/components/ui/calendar/Calendar.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarRoot, type CalendarRootEmits, type CalendarRootProps, useForwardPropsEmits } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from '.'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const emits = defineEmits<CalendarRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarRoot
|
||||||
|
v-slot="{ grid, weekDays }"
|
||||||
|
:class="cn('p-3', props.class)"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<CalendarHeader>
|
||||||
|
<CalendarPrevButton />
|
||||||
|
<CalendarHeading />
|
||||||
|
<CalendarNextButton />
|
||||||
|
</CalendarHeader>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
|
||||||
|
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
|
||||||
|
<CalendarGridHead>
|
||||||
|
<CalendarGridRow>
|
||||||
|
<CalendarHeadCell
|
||||||
|
v-for="day in weekDays" :key="day"
|
||||||
|
>
|
||||||
|
{{ day }}
|
||||||
|
</CalendarHeadCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridHead>
|
||||||
|
<CalendarGridBody>
|
||||||
|
<CalendarGridRow v-for="(weekDates, index) in month.rows" :key="`weekDate-${index}`" class="mt-2 w-full">
|
||||||
|
<CalendarCell
|
||||||
|
v-for="weekDate in weekDates"
|
||||||
|
:key="weekDate.toString()"
|
||||||
|
:date="weekDate"
|
||||||
|
>
|
||||||
|
<CalendarCellTrigger
|
||||||
|
:day="weekDate"
|
||||||
|
:month="month.value"
|
||||||
|
/>
|
||||||
|
</CalendarCell>
|
||||||
|
</CalendarGridRow>
|
||||||
|
</CalendarGridBody>
|
||||||
|
</CalendarGrid>
|
||||||
|
</div>
|
||||||
|
</CalendarRoot>
|
||||||
|
</template>
|
||||||
21
frontend/components/ui/calendar/CalendarCell.vue
Normal file
21
frontend/components/ui/calendar/CalendarCell.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarCell
|
||||||
|
:class="cn('relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-view])]:bg-accent/50', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarCell>
|
||||||
|
</template>
|
||||||
35
frontend/components/ui/calendar/CalendarCellTrigger.vue
Normal file
35
frontend/components/ui/calendar/CalendarCellTrigger.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/components/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarCellTrigger
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'ghost' }),
|
||||||
|
'h-9 w-9 p-0 font-normal',
|
||||||
|
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
|
||||||
|
// Selected
|
||||||
|
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground',
|
||||||
|
// Disabled
|
||||||
|
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||||
|
// Unavailable
|
||||||
|
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
|
||||||
|
// Outside months
|
||||||
|
'data-[outside-view]:text-muted-foreground data-[outside-view]:opacity-50 [&[data-outside-view][data-selected]]:bg-accent/50 [&[data-outside-view][data-selected]]:text-muted-foreground [&[data-outside-view][data-selected]]:opacity-30',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarCellTrigger>
|
||||||
|
</template>
|
||||||
21
frontend/components/ui/calendar/CalendarGrid.vue
Normal file
21
frontend/components/ui/calendar/CalendarGrid.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarGrid, type CalendarGridProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGrid
|
||||||
|
:class="cn('w-full border-collapse space-y-1', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarGrid>
|
||||||
|
</template>
|
||||||
11
frontend/components/ui/calendar/CalendarGridBody.vue
Normal file
11
frontend/components/ui/calendar/CalendarGridBody.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { CalendarGridBody, type CalendarGridBodyProps } from 'reka-ui'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridBodyProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridBody v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridBody>
|
||||||
|
</template>
|
||||||
11
frontend/components/ui/calendar/CalendarGridHead.vue
Normal file
11
frontend/components/ui/calendar/CalendarGridHead.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { CalendarGridHead, type CalendarGridHeadProps } from 'reka-ui'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridHeadProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridHead v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridHead>
|
||||||
|
</template>
|
||||||
18
frontend/components/ui/calendar/CalendarGridRow.vue
Normal file
18
frontend/components/ui/calendar/CalendarGridRow.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarGridRow, type CalendarGridRowProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarGridRow :class="cn('flex', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarGridRow>
|
||||||
|
</template>
|
||||||
18
frontend/components/ui/calendar/CalendarHeadCell.vue
Normal file
18
frontend/components/ui/calendar/CalendarHeadCell.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarHeadCell, type CalendarHeadCellProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeadCell :class="cn('w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarHeadCell>
|
||||||
|
</template>
|
||||||
18
frontend/components/ui/calendar/CalendarHeader.vue
Normal file
18
frontend/components/ui/calendar/CalendarHeader.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarHeader, type CalendarHeaderProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeader :class="cn('relative flex w-full items-center justify-between pt-1', props.class)" v-bind="forwardedProps">
|
||||||
|
<slot />
|
||||||
|
</CalendarHeader>
|
||||||
|
</template>
|
||||||
28
frontend/components/ui/calendar/CalendarHeading.vue
Normal file
28
frontend/components/ui/calendar/CalendarHeading.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { CalendarHeading, type CalendarHeadingProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
default: (props: { headingValue: string }) => any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarHeading
|
||||||
|
v-slot="{ headingValue }"
|
||||||
|
:class="cn('text-sm font-medium', props.class)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot :heading-value>
|
||||||
|
{{ headingValue }}
|
||||||
|
</slot>
|
||||||
|
</CalendarHeading>
|
||||||
|
</template>
|
||||||
29
frontend/components/ui/calendar/CalendarNextButton.vue
Normal file
29
frontend/components/ui/calendar/CalendarNextButton.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { ChevronRight } from 'lucide-vue-next'
|
||||||
|
import { CalendarNext, type CalendarNextProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/components/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarNextProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarNext
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRight class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarNext>
|
||||||
|
</template>
|
||||||
29
frontend/components/ui/calendar/CalendarPrevButton.vue
Normal file
29
frontend/components/ui/calendar/CalendarPrevButton.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import { ChevronLeft } from 'lucide-vue-next'
|
||||||
|
import { CalendarPrev, type CalendarPrevProps, useForwardProps } from 'reka-ui'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { buttonVariants } from '@/components/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<CalendarPrevProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CalendarPrev
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronLeft class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarPrev>
|
||||||
|
</template>
|
||||||
12
frontend/components/ui/calendar/index.ts
Normal file
12
frontend/components/ui/calendar/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export { default as Calendar } from './Calendar.vue'
|
||||||
|
export { default as CalendarCell } from './CalendarCell.vue'
|
||||||
|
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
|
||||||
|
export { default as CalendarGrid } from './CalendarGrid.vue'
|
||||||
|
export { default as CalendarGridBody } from './CalendarGridBody.vue'
|
||||||
|
export { default as CalendarGridHead } from './CalendarGridHead.vue'
|
||||||
|
export { default as CalendarGridRow } from './CalendarGridRow.vue'
|
||||||
|
export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
|
||||||
|
export { default as CalendarHeader } from './CalendarHeader.vue'
|
||||||
|
export { default as CalendarHeading } from './CalendarHeading.vue'
|
||||||
|
export { default as CalendarNextButton } from './CalendarNextButton.vue'
|
||||||
|
export { default as CalendarPrevButton } from './CalendarPrevButton.vue'
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<AppLogo />
|
<AppLogo />
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<!-- <AppOrgSelector v-model:model-value="selectedOrg" /> -->
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger as-child>
|
<DropdownMenuTrigger as-child>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
@@ -84,7 +85,20 @@
|
|||||||
<span>{{ $t("menu.scanner") }}</span>
|
<span>{{ $t("menu.scanner") }}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton
|
||||||
|
:class="{
|
||||||
|
'text-nowrap': typeof locale === 'string' && locale.startsWith('zh-'),
|
||||||
|
}"
|
||||||
|
:tooltip="$t('menu.scanner')"
|
||||||
|
@click.prevent="openDialog('scanner')"
|
||||||
|
>
|
||||||
|
<MdiAccount />
|
||||||
|
<span>Collection Settings</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
||||||
@@ -111,18 +125,29 @@
|
|||||||
<SidebarTrigger class="absolute left-2 top-2 hidden lg:flex" variant="default" />
|
<SidebarTrigger class="absolute left-2 top-2 hidden lg:flex" variant="default" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-20 flex h-28 translate-y-[-0.5px] flex-col bg-secondary p-2 shadow-md sm:h-16 sm:flex-row"
|
class="sticky top-0 z-20 flex h-28 translate-y-[-0.5px] flex-col bg-secondary p-2 shadow-md sm:h-16 sm:flex-row justify-between"
|
||||||
:class="{
|
:class="{
|
||||||
'lg:hidden': preferences.displayLegacyHeader,
|
'lg:hidden': preferences.displayLegacyHeader,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="flex h-1/2 items-center gap-2 sm:h-auto">
|
<div class="flex h-1/2 items-center gap-2 sm:h-auto">
|
||||||
|
<div>
|
||||||
<SidebarTrigger variant="default" />
|
<SidebarTrigger variant="default" />
|
||||||
|
</div>
|
||||||
|
<!-- <div>
|
||||||
|
<Button size="icon">
|
||||||
|
<AppLogo class="size-8" />
|
||||||
|
</Button>
|
||||||
|
</div> -->
|
||||||
<NuxtLink to="/home">
|
<NuxtLink to="/home">
|
||||||
<AppHeaderText class="h-6" />
|
<AppHeaderText class="h-6" />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
<!-- <AppOrgSelector v-model:model-value="selectedOrg" /> -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:grow"></div>
|
<!-- <div class="flex items-center">
|
||||||
|
<AppOrgSelector v-model:model-value="selectedOrg" />
|
||||||
|
</div> -->
|
||||||
<div class="flex h-1/2 grow items-center justify-end gap-2 sm:h-auto">
|
<div class="flex h-1/2 grow items-center justify-end gap-2 sm:h-auto">
|
||||||
<Input
|
<Input
|
||||||
v-model:model-value="search"
|
v-model:model-value="search"
|
||||||
@@ -209,6 +234,8 @@
|
|||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { toast } from "@/components/ui/sonner";
|
import { toast } from "@/components/ui/sonner";
|
||||||
|
|
||||||
|
const selectedOrg = ref<any>();
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const username = computed(() => authCtx.user?.name || "User");
|
const username = computed(() => authCtx.user?.name || "User");
|
||||||
|
|
||||||
@@ -314,13 +341,13 @@
|
|||||||
name: computed(() => t("menu.maintenance")),
|
name: computed(() => t("menu.maintenance")),
|
||||||
to: "/maintenance",
|
to: "/maintenance",
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
icon: MdiAccount,
|
// icon: MdiAccount,
|
||||||
id: 5,
|
// id: 5,
|
||||||
active: computed(() => route.path === "/profile"),
|
// active: computed(() => route.path === "/profile"),
|
||||||
name: computed(() => t("menu.profile")),
|
// name: computed(() => t("menu.profile")),
|
||||||
to: "/profile",
|
// to: "/profile",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
icon: MdiCog,
|
icon: MdiCog,
|
||||||
id: 6,
|
id: 6,
|
||||||
@@ -368,6 +395,36 @@
|
|||||||
const authCtx = useAuthContext();
|
const authCtx = useAuthContext();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
|
const checkAuth = async () => {
|
||||||
|
try {
|
||||||
|
// await api.user.self();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
// if (!authCtx.isAuthorized()) {
|
||||||
|
// console.log("Not authorised, redirecting to login");
|
||||||
|
// await navigateTo("/");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkAuth();
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
checkAuth();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await authCtx.logout(api);
|
await authCtx.logout(api);
|
||||||
navigateTo("/");
|
navigateTo("/");
|
||||||
|
|||||||
362
frontend/pages/collection.vue
Normal file
362
frontend/pages/collection.vue
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { v4 as uuidv4 } from 'uuid'; // For generating unique invite IDs
|
||||||
|
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Card } from '@/components/ui/card'; // Assuming you have a Card component
|
||||||
|
import { Badge } from '@/components/ui/badge'; // Assuming you have a Badge component
|
||||||
|
import {
|
||||||
|
Calendar as CalendarIcon,
|
||||||
|
PlusCircle,
|
||||||
|
Trash,
|
||||||
|
} from 'lucide-vue-next'; // Icons
|
||||||
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ['auth'],
|
||||||
|
});
|
||||||
|
useHead({
|
||||||
|
title: 'HomeBox | ' + t('menu.maintenance'),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
username: string;
|
||||||
|
id: string;
|
||||||
|
role: 'owner' | 'admin' | 'editor' | 'viewer';
|
||||||
|
lastActive: string;
|
||||||
|
added: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Invite {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
expiresAt: Date | null;
|
||||||
|
maxUses: number | null;
|
||||||
|
uses: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = ref<User[]>([
|
||||||
|
{
|
||||||
|
username: 'tonya',
|
||||||
|
id: '1',
|
||||||
|
role: 'owner',
|
||||||
|
lastActive: '12 hours ago',
|
||||||
|
added: '13 hours ago',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'steve',
|
||||||
|
id: '2',
|
||||||
|
role: 'admin',
|
||||||
|
lastActive: '1 day ago',
|
||||||
|
added: '2 days ago',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'bob',
|
||||||
|
id: '3',
|
||||||
|
role: 'editor',
|
||||||
|
lastActive: '30 minutes ago',
|
||||||
|
added: '5 hours ago',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'john',
|
||||||
|
id: '4',
|
||||||
|
role: 'viewer',
|
||||||
|
lastActive: '2 hours ago',
|
||||||
|
added: '1 day ago',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const invites = ref<Invite[]>([
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
code: 'ABCDEF',
|
||||||
|
expiresAt: null,
|
||||||
|
maxUses: null,
|
||||||
|
uses: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
code: 'GHIJKL',
|
||||||
|
expiresAt: new Date(new Date().setDate(new Date().getDate() + 7)), // Expires in 7 days
|
||||||
|
maxUses: 5,
|
||||||
|
uses: 2,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const newInviteExpiresAt = ref<Date | null>(null);
|
||||||
|
const newInviteMaxUses = ref<number | null>(null);
|
||||||
|
|
||||||
|
const page = ref(1);
|
||||||
|
|
||||||
|
const roles = ['owner', 'admin', 'editor', 'viewer'];
|
||||||
|
|
||||||
|
function handleRoleChange(userId: string, newRole: string) {
|
||||||
|
const userIndex = users.value.findIndex((user) => user.id === userId);
|
||||||
|
if (userIndex !== -1) {
|
||||||
|
users.value[userIndex].role = newRole as
|
||||||
|
| 'owner'
|
||||||
|
| 'admin'
|
||||||
|
| 'editor'
|
||||||
|
| 'viewer';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveUser(userId: string) {
|
||||||
|
users.value = users.value.filter((user) => user.id !== userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateInviteCode() {
|
||||||
|
return Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewInvite() {
|
||||||
|
const newInvite: Invite = {
|
||||||
|
id: uuidv4(),
|
||||||
|
code: generateInviteCode(),
|
||||||
|
expiresAt: newInviteExpiresAt.value,
|
||||||
|
maxUses: newInviteMaxUses.value,
|
||||||
|
uses: 0,
|
||||||
|
};
|
||||||
|
invites.value.push(newInvite);
|
||||||
|
newInviteExpiresAt.value = null;
|
||||||
|
newInviteMaxUses.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteInvite(inviteId: string) {
|
||||||
|
invites.value = invites.value.filter((invite) => invite.id !== inviteId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<BaseContainer class="flex flex-col gap-4">
|
||||||
|
<BaseSectionHeader> Collection Settings </BaseSectionHeader>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
:variant="page == 1 ? 'default' : 'outline'"
|
||||||
|
@click="page = 1"
|
||||||
|
>
|
||||||
|
Users
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
:variant="page == 2 ? 'default' : 'outline'"
|
||||||
|
@click="page = 2"
|
||||||
|
>
|
||||||
|
Invites
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
:variant="page == 3 ? 'default' : 'outline'"
|
||||||
|
@click="page = 3"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Card v-if="page == 1" class="p-4 m-4">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Username</TableHead>
|
||||||
|
<TableHead>Role</TableHead>
|
||||||
|
<TableHead>Last Active</TableHead>
|
||||||
|
<TableHead>Added</TableHead>
|
||||||
|
<TableHead class="text-right">Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow v-for="user in users" :key="user.id">
|
||||||
|
<TableCell class="font-medium">
|
||||||
|
{{ user.username }}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge
|
||||||
|
:variant="
|
||||||
|
user.role === 'owner'
|
||||||
|
? 'default'
|
||||||
|
: user.role === 'admin'
|
||||||
|
? 'secondary'
|
||||||
|
: 'outline'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ user.role }}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{{ user.lastActive }}</TableCell>
|
||||||
|
<TableCell>{{ user.added }}</TableCell>
|
||||||
|
<TableCell class="text-right">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button size="sm" variant="outline"> Edit </Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-48">
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h4 class="font-medium leading-none">Edit User</h4>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{{ user.username }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="role">Role</Label>
|
||||||
|
<Select
|
||||||
|
:model-value="user.role"
|
||||||
|
@update:model-value="
|
||||||
|
(newRole) => handleRoleChange(user.id, newRole)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem
|
||||||
|
v-for="role in roles"
|
||||||
|
:key="role"
|
||||||
|
:value="role"
|
||||||
|
>
|
||||||
|
{{ role }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
@click="handleRemoveUser(user.id)"
|
||||||
|
>
|
||||||
|
Remove User
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card v-if="page == 2" class="p-4 m-4">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<h3 class="text-lg font-semibold">Existing Invites</h3>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Code</TableHead>
|
||||||
|
<TableHead>Expires</TableHead>
|
||||||
|
<TableHead>Max Uses</TableHead>
|
||||||
|
<TableHead>Uses</TableHead>
|
||||||
|
<TableHead class="text-right">Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow v-for="invite in invites" :key="invite.id">
|
||||||
|
<TableCell class="font-medium">
|
||||||
|
{{ invite.code }}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{{
|
||||||
|
invite.expiresAt
|
||||||
|
? format(invite.expiresAt, 'PPP')
|
||||||
|
: 'Never'
|
||||||
|
}}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{{ invite.maxUses !== null ? invite.maxUses : 'Unlimited' }}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{{ invite.uses }}</TableCell>
|
||||||
|
<TableCell class="text-right">
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="icon"
|
||||||
|
@click="deleteInvite(invite.id)"
|
||||||
|
>
|
||||||
|
<Trash class="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<hr class="my-4" />
|
||||||
|
|
||||||
|
<h3 class="text-lg font-semibold">Create New Invite</h3>
|
||||||
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="new-invite-max-uses">Max Uses (optional)</Label>
|
||||||
|
<Input
|
||||||
|
id="new-invite-max-uses"
|
||||||
|
type="number"
|
||||||
|
v-model.number="newInviteMaxUses"
|
||||||
|
placeholder="Unlimited"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="new-invite-expires-at">Expires At (optional)</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="w-full justify-start text-left font-normal"
|
||||||
|
:class="
|
||||||
|
!newInviteExpiresAt && 'text-muted-foreground'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
|
{{
|
||||||
|
newInviteExpiresAt
|
||||||
|
? format(newInviteExpiresAt, 'PPP')
|
||||||
|
: 'Pick a date'
|
||||||
|
}}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar v-model:model-value="newInviteExpiresAt" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-end">
|
||||||
|
<Button @click="createNewInvite" class="w-full">
|
||||||
|
<PlusCircle class="mr-2 w-4 h-4" /> Generate Invite
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card v-if="page == 3" class="p-4 m-4">
|
||||||
|
<h3 class="text-lg font-semibold">Collection Settings</h3>
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
This is where you would configure general collection settings.
|
||||||
|
</p>
|
||||||
|
<!-- Add your settings forms/components here -->
|
||||||
|
</Card>
|
||||||
|
</BaseContainer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
28
frontend/pnpm-lock.yaml
generated
28
frontend/pnpm-lock.yaml
generated
@@ -8551,12 +8551,12 @@ snapshots:
|
|||||||
|
|
||||||
'@nuxtjs/eslint-config-typescript@12.1.0(eslint@8.57.1)(typescript@5.6.2)':
|
'@nuxtjs/eslint-config-typescript@12.1.0(eslint@8.57.1)(typescript@5.6.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)
|
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)
|
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)
|
||||||
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.2)
|
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.2)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-vue: 9.33.0(eslint@8.57.1)
|
eslint-plugin-vue: 9.33.0(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- eslint-import-resolver-webpack
|
- eslint-import-resolver-webpack
|
||||||
@@ -8564,11 +8564,11 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)':
|
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-config-standard: 17.1.0(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1)
|
eslint-config-standard: 17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-n: 15.7.0(eslint@8.57.1)
|
eslint-plugin-n: 15.7.0(eslint@8.57.1)
|
||||||
eslint-plugin-node: 11.1.0(eslint@8.57.1)
|
eslint-plugin-node: 11.1.0(eslint@8.57.1)
|
||||||
eslint-plugin-promise: 6.6.0(eslint@8.57.1)
|
eslint-plugin-promise: 6.6.0(eslint@8.57.1)
|
||||||
@@ -10655,10 +10655,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
|
|
||||||
eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0)(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1):
|
eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1))(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-n: 15.7.0(eslint@8.57.1)
|
eslint-plugin-n: 15.7.0(eslint@8.57.1)
|
||||||
eslint-plugin-promise: 6.6.0(eslint@8.57.1)
|
eslint-plugin-promise: 6.6.0(eslint@8.57.1)
|
||||||
|
|
||||||
@@ -10670,7 +10670,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.1):
|
eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
@@ -10681,18 +10681,18 @@ snapshots:
|
|||||||
tinyglobby: 0.2.12
|
tinyglobby: 0.2.12
|
||||||
unrs-resolver: 1.5.0
|
unrs-resolver: 1.5.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1):
|
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.2)
|
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.2)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -10708,7 +10708,7 @@ snapshots:
|
|||||||
eslint-utils: 2.1.0
|
eslint-utils: 2.1.0
|
||||||
regexpp: 3.2.0
|
regexpp: 3.2.0
|
||||||
|
|
||||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1):
|
eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.8
|
||||||
@@ -10719,7 +10719,7 @@ snapshots:
|
|||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.1)
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|||||||
Reference in New Issue
Block a user