@meeovi/layer-lists 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/app/components/features/archived.vue +64 -0
  2. package/app/components/features/bookmarks.vue +64 -0
  3. package/app/components/features/lists.vue +61 -0
  4. package/app/components/features/starred.vue +64 -0
  5. package/app/components/lists/ListItemCard.vue +190 -0
  6. package/app/components/lists/add-bookmark.vue +52 -0
  7. package/app/components/lists/add-list-item.vue +88 -0
  8. package/app/components/lists/add-list.vue +57 -0
  9. package/app/components/lists/lists.vue +6 -0
  10. package/app/components/lists/listsettings.vue +145 -0
  11. package/app/components/lists/update-bookmark.vue +267 -0
  12. package/app/components/lists/update-list.vue +192 -0
  13. package/app/components/media/MediaPlayer.vue +302 -0
  14. package/app/components/partials/addtolist.vue +233 -0
  15. package/app/components/partials/createListBtn.vue +95 -0
  16. package/app/components/partials/listBtn.vue +35 -0
  17. package/app/components/related/list.vue +33 -0
  18. package/app/components/related/relatedlists.vue +43 -0
  19. package/app/components/tasks/TaskItem.vue +204 -0
  20. package/app/composables/bookmarks/createBookmark.js +30 -0
  21. package/app/composables/bookmarks/deleteBookmark.js +15 -0
  22. package/app/composables/bookmarks/updateBookmark.js +15 -0
  23. package/app/composables/config.ts +17 -0
  24. package/app/composables/content/uploadFiles.js +41 -0
  25. package/app/composables/globals/useDirectusForm.ts +1 -0
  26. package/app/composables/lists/createList.js +25 -0
  27. package/app/composables/lists/deleteList.js +14 -0
  28. package/app/composables/lists/updateList.js +20 -0
  29. package/app/composables/lists/useBookmarks.js +69 -0
  30. package/app/composables/lists/useLists.js +120 -0
  31. package/app/composables/lists/usePlaylist.js +64 -0
  32. package/app/composables/lists/useSaved.js +29 -0
  33. package/app/composables/lists/useTasks.js +86 -0
  34. package/app/composables/lists/useWishlist.js +51 -0
  35. package/app/composables/providers/atproto.ts +156 -0
  36. package/app/composables/providers/directus.ts +49 -0
  37. package/app/composables/providers/memory.ts +88 -0
  38. package/app/composables/registry.ts +13 -0
  39. package/app/composables/types.ts +35 -0
  40. package/app/composables/useLists.ts +20 -0
  41. package/app/composables/utils/transforms.ts +42 -0
  42. package/app/composables/utils/validation.ts +21 -0
  43. package/app/pages/lists/bookmark/[id].vue +76 -0
  44. package/app/pages/lists/index.vue +152 -0
  45. package/app/pages/lists/list/[...slug].vue +233 -0
  46. package/nuxt.config.ts +11 -0
  47. package/package.json +26 -0
@@ -0,0 +1,29 @@
1
+ export const useSaved = () => {
2
+ const { $directus } = useNuxtApp();
3
+ const user = useSupabaseUser();
4
+
5
+ const isSaved = async (productId) => {
6
+ if (!user.value) return false;
7
+
8
+ const lists = await $directus.items('lists').readItems({
9
+ filter: { user: { _eq: user.value.id } },
10
+ fields: ['id']
11
+ });
12
+
13
+ const listIds = lists.map((l) => l.id);
14
+
15
+ if (listIds.length === 0) return false;
16
+
17
+ const saved = await $directus.items('list_items').readItems({
18
+ filter: {
19
+ list: { _in: listIds },
20
+ product: { _eq: productId }
21
+ }
22
+ });
23
+
24
+ return saved.length > 0;
25
+ };
26
+
27
+ return { isSaved };
28
+ };
29
+
@@ -0,0 +1,86 @@
1
+ export const useTasks = () => {
2
+ const { createList, addToList, getUserLists, updateListItem } = useLists()
3
+
4
+ const createTaskList = async (name, description = '') => {
5
+ return await createList({
6
+ name,
7
+ description,
8
+ type: 'tasks',
9
+ visibility: 'private'
10
+ })
11
+ }
12
+
13
+ const addTask = async (listId, taskData) => {
14
+ const { title, description, priority, due_date, labels } = taskData
15
+
16
+ return await addToList(listId, {
17
+ type: 'task',
18
+ title,
19
+ description,
20
+ priority: priority || 'medium',
21
+ due_date,
22
+ labels: labels || [],
23
+ completed: false,
24
+ date_created: new Date().toISOString(),
25
+ subtasks: []
26
+ })
27
+ }
28
+
29
+ const completeTask = async (itemId) => {
30
+ return await updateListItem(itemId, {
31
+ 'content.completed': true,
32
+ 'content.date_completed': new Date().toISOString()
33
+ })
34
+ }
35
+
36
+ const uncompleteTask = async (itemId) => {
37
+ return await updateListItem(itemId, {
38
+ 'content.completed': false,
39
+ 'content.date_completed': null
40
+ })
41
+ }
42
+
43
+ const updateTaskPriority = async (itemId, priority) => {
44
+ return await updateListItem(itemId, {
45
+ 'content.priority': priority
46
+ })
47
+ }
48
+
49
+ const updateTaskDueDate = async (itemId, dueDate) => {
50
+ return await updateListItem(itemId, {
51
+ 'content.due_date': dueDate
52
+ })
53
+ }
54
+
55
+ const addSubtask = async (itemId, subtaskTitle) => {
56
+ const { getListById } = useLists()
57
+ const item = await getListById(itemId)
58
+ const subtasks = item.content.subtasks || []
59
+
60
+ subtasks.push({
61
+ id: Date.now(),
62
+ title: subtaskTitle,
63
+ completed: false,
64
+ date_created: new Date().toISOString()
65
+ })
66
+
67
+ return await updateListItem(itemId, {
68
+ 'content.subtasks': subtasks
69
+ })
70
+ }
71
+
72
+ const getUserTaskLists = async () => {
73
+ return await getUserLists('tasks')
74
+ }
75
+
76
+ return {
77
+ createTaskList,
78
+ addTask,
79
+ completeTask,
80
+ uncompleteTask,
81
+ updateTaskPriority,
82
+ updateTaskDueDate,
83
+ addSubtask,
84
+ getUserTaskLists
85
+ }
86
+ }
@@ -0,0 +1,51 @@
1
+ export const useWishlist = () => {
2
+ const { createList, addToList, getUserLists, removeFromList } = useLists()
3
+
4
+ const createWishlist = async (name = 'My Wishlist', description = '') => {
5
+ return await createList({
6
+ name,
7
+ description,
8
+ type: 'wishlist',
9
+ visibility: 'private'
10
+ })
11
+ }
12
+
13
+ const addToWishlist = async (wishlistId, itemData) => {
14
+ const { title, url, price, image, description, category } = itemData
15
+
16
+ return await addToList(wishlistId, {
17
+ type: 'product',
18
+ title,
19
+ url,
20
+ price,
21
+ image,
22
+ description,
23
+ category,
24
+ date_added: new Date().toISOString(),
25
+ priority: itemData.priority || 'medium'
26
+ })
27
+ }
28
+
29
+ const getUserWishlists = async () => {
30
+ return await getUserLists('wishlist')
31
+ }
32
+
33
+ const removeFromWishlist = async (itemId) => {
34
+ return await removeFromList(itemId)
35
+ }
36
+
37
+ const updateWishlistItemPriority = async (itemId, priority) => {
38
+ const { updateListItem } = useLists()
39
+ return await updateListItem(itemId, {
40
+ 'content.priority': priority
41
+ })
42
+ }
43
+
44
+ return {
45
+ createWishlist,
46
+ addToWishlist,
47
+ getUserWishlists,
48
+ removeFromWishlist,
49
+ updateWishlistItemPriority
50
+ }
51
+ }
@@ -0,0 +1,156 @@
1
+ import { registerListsProvider } from '../registry'
2
+ import type { ListsProvider, List, ListItem } from '../types'
3
+ import { wrapSocialRequest } from '@meeovi/social'
4
+ import { transformList, transformItem } from '../utils/transforms'
5
+ import { validateListInput, validateItemInput } from '../utils/validation'
6
+ import { getListsConfig } from '../config'
7
+
8
+ async function atprotoFetch(path: string, options: RequestInit = {}) {
9
+ const { baseUrl, apiKey } = getListsConfig()
10
+
11
+ const res = await fetch(`${baseUrl}${path}`, {
12
+ ...options,
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
16
+ ...(options.headers || {})
17
+ }
18
+ })
19
+
20
+ if (!res.ok) {
21
+ const error: any = new Error(`ATProto error: ${res.status}`)
22
+ error.status = res.status
23
+ error.response = res
24
+ throw error
25
+ }
26
+
27
+ return res.json()
28
+ }
29
+
30
+ const AtprotoListsProvider: ListsProvider = {
31
+ async getList(id) {
32
+ return wrapSocialRequest('atproto', async () => {
33
+ const data = await atprotoFetch(`/xrpc/app.bsky.graph.getList?list=${id}`)
34
+ return transformList(data.list)
35
+ }, {
36
+ cacheKey: `atproto:list:${id}`,
37
+ ttlMs: 1000 * 30,
38
+ retry: true,
39
+ swr: true
40
+ })
41
+ },
42
+
43
+ async listLists() {
44
+ return wrapSocialRequest('atproto', async () => {
45
+ const data = await atprotoFetch(`/xrpc/app.bsky.graph.getLists`)
46
+ return data.lists.map(transformList)
47
+ }, {
48
+ cacheKey: `atproto:lists`,
49
+ ttlMs: 1000 * 30,
50
+ retry: true,
51
+ swr: true
52
+ })
53
+ },
54
+
55
+ async createList(data) {
56
+ validateListInput(data)
57
+
58
+ return wrapSocialRequest('atproto', async () => {
59
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.createList`, {
60
+ method: 'POST',
61
+ body: JSON.stringify({
62
+ name: data.title,
63
+ purpose: data.type ?? 'list',
64
+ description: data.metadata?.description ?? ''
65
+ })
66
+ })
67
+
68
+ return transformList(result)
69
+ })
70
+ },
71
+
72
+ async updateList(id, data) {
73
+ validateListInput(data)
74
+
75
+ return wrapSocialRequest('atproto', async () => {
76
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.updateList`, {
77
+ method: 'POST',
78
+ body: JSON.stringify({
79
+ list: id,
80
+ name: data.title,
81
+ description: data.metadata?.description
82
+ })
83
+ })
84
+
85
+ return transformList(result)
86
+ })
87
+ },
88
+
89
+ async deleteList(id) {
90
+ return wrapSocialRequest('atproto', async () => {
91
+ await atprotoFetch(`/xrpc/app.bsky.graph.deleteList`, {
92
+ method: 'POST',
93
+ body: JSON.stringify({ list: id })
94
+ })
95
+ })
96
+ },
97
+
98
+ async addItem(listId, item) {
99
+ validateItemInput(item)
100
+
101
+ return wrapSocialRequest('atproto', async () => {
102
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.addListItem`, {
103
+ method: 'POST',
104
+ body: JSON.stringify({
105
+ list: listId,
106
+ subject: item.title // ATProto uses "subject" for list entries
107
+ })
108
+ })
109
+
110
+ return transformItem(result)
111
+ })
112
+ },
113
+
114
+ async updateItem(listId, itemId, data) {
115
+ validateItemInput(data)
116
+
117
+ return wrapSocialRequest('atproto', async () => {
118
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.updateListItem`, {
119
+ method: 'POST',
120
+ body: JSON.stringify({
121
+ list: listId,
122
+ item: itemId,
123
+ ...data
124
+ })
125
+ })
126
+
127
+ return transformItem(result)
128
+ })
129
+ },
130
+
131
+ async deleteItem(listId, itemId) {
132
+ return wrapSocialRequest('atproto', async () => {
133
+ await atprotoFetch(`/xrpc/app.bsky.graph.deleteListItem`, {
134
+ method: 'POST',
135
+ body: JSON.stringify({
136
+ list: listId,
137
+ item: itemId
138
+ })
139
+ })
140
+ })
141
+ },
142
+
143
+ async reorderItems(listId, itemIds) {
144
+ return wrapSocialRequest('atproto', async () => {
145
+ await atprotoFetch(`/xrpc/app.bsky.graph.reorderListItems`, {
146
+ method: 'POST',
147
+ body: JSON.stringify({
148
+ list: listId,
149
+ items: itemIds
150
+ })
151
+ })
152
+ })
153
+ }
154
+ }
155
+
156
+ registerListsProvider('atproto', AtprotoListsProvider)
@@ -0,0 +1,49 @@
1
+ import { registerListsProvider } from '../registry'
2
+ import type { ListsProvider } from '../types'
3
+ import { fetcher } from '@meeovi/api'
4
+
5
+ const DirectusListsProvider: ListsProvider = {
6
+ async getList(id) {
7
+ const { data } = await fetcher('lists.GET_LIST', { id })
8
+ return data.list
9
+ },
10
+
11
+ async listLists() {
12
+ const { data } = await fetcher('lists.LIST_LISTS')
13
+ return data.lists
14
+ },
15
+
16
+ async createList(data) {
17
+ const { data: result } = await fetcher('lists.CREATE_LIST', { data })
18
+ return result.list
19
+ },
20
+
21
+ async updateList(id, data) {
22
+ const { data: result } = await fetcher('lists.UPDATE_LIST', { id, data })
23
+ return result.list
24
+ },
25
+
26
+ async deleteList(id) {
27
+ await fetcher('lists.DELETE_LIST', { id })
28
+ },
29
+
30
+ async addItem(listId, item) {
31
+ const { data } = await fetcher('lists.ADD_ITEM', { listId, item })
32
+ return data.item
33
+ },
34
+
35
+ async updateItem(listId, itemId, data) {
36
+ const { data: result } = await fetcher('lists.UPDATE_ITEM', { listId, itemId, data })
37
+ return result.item
38
+ },
39
+
40
+ async deleteItem(listId, itemId) {
41
+ await fetcher('lists.DELETE_ITEM', { listId, itemId })
42
+ },
43
+
44
+ async reorderItems(listId, itemIds) {
45
+ await fetcher('lists.REORDER_ITEMS', { listId, itemIds })
46
+ }
47
+ }
48
+
49
+ registerListsProvider('directus', DirectusListsProvider)
@@ -0,0 +1,88 @@
1
+ import { registerListsProvider } from '../registry'
2
+ import type { List, ListItem, ListsProvider } from '../types'
3
+ import { nanoid } from 'nanoid'
4
+
5
+ const lists = new Map<string, List>()
6
+
7
+ const MemoryListsProvider: ListsProvider = {
8
+ async getList(id) {
9
+ const list = lists.get(id)
10
+ if (!list) throw new Error(`List ${id} not found`)
11
+ return list
12
+ },
13
+
14
+ async listLists() {
15
+ return Array.from(lists.values())
16
+ },
17
+
18
+ async createList(data) {
19
+ const id = nanoid()
20
+ const list: List = {
21
+ id,
22
+ title: data.title || 'Untitled List',
23
+ type: data.type || 'list',
24
+ items: [],
25
+ metadata: data.metadata || {},
26
+ createdAt: new Date().toISOString(),
27
+ updatedAt: new Date().toISOString()
28
+ }
29
+ lists.set(id, list)
30
+ return list
31
+ },
32
+
33
+ async updateList(id, data) {
34
+ const list = await this.getList(id)
35
+ const updated = {
36
+ ...list,
37
+ ...data,
38
+ updatedAt: new Date().toISOString()
39
+ }
40
+ lists.set(id, updated)
41
+ return updated
42
+ },
43
+
44
+ async deleteList(id) {
45
+ lists.delete(id)
46
+ },
47
+
48
+ async addItem(listId, item) {
49
+ const list = await this.getList(listId)
50
+ const newItem: ListItem = {
51
+ id: nanoid(),
52
+ title: item.title || '',
53
+ description: item.description,
54
+ completed: item.completed || false,
55
+ position: list.items.length,
56
+ parentId: item.parentId,
57
+ metadata: item.metadata || {},
58
+ createdAt: new Date().toISOString(),
59
+ updatedAt: new Date().toISOString()
60
+ }
61
+ list.items.push(newItem)
62
+ return newItem
63
+ },
64
+
65
+ async updateItem(listId, itemId, data) {
66
+ const list = await this.getList(listId)
67
+ const item = list.items.find(i => i.id === itemId)
68
+ if (!item) throw new Error(`Item ${itemId} not found`)
69
+ Object.assign(item, data, { updatedAt: new Date().toISOString() })
70
+ return item
71
+ },
72
+
73
+ async deleteItem(listId, itemId) {
74
+ const list = await this.getList(listId)
75
+ list.items = list.items.filter(i => i.id !== itemId)
76
+ },
77
+
78
+ async reorderItems(listId, itemIds) {
79
+ const list = await this.getList(listId)
80
+ const newOrder = itemIds.map(id => list.items.find(i => i.id === id)!)
81
+ list.items = newOrder.map((item, index) => ({
82
+ ...item,
83
+ position: index
84
+ }))
85
+ }
86
+ }
87
+
88
+ registerListsProvider('memory', MemoryListsProvider)
@@ -0,0 +1,13 @@
1
+ import type { ListsProvider } from './types'
2
+
3
+ const providers: Record<string, ListsProvider> = {}
4
+
5
+ export function registerListsProvider(name: string, provider: ListsProvider) {
6
+ providers[name] = provider
7
+ }
8
+
9
+ export function getListsProvider(name: string): ListsProvider {
10
+ const provider = providers[name]
11
+ if (!provider) throw new Error(`Lists provider "${name}" not found`)
12
+ return provider
13
+ }
@@ -0,0 +1,35 @@
1
+ export interface ListItem {
2
+ id: string
3
+ title: string
4
+ description?: string
5
+ completed?: boolean
6
+ position?: number
7
+ parentId?: string
8
+ metadata?: Record<string, any>
9
+ createdAt?: string
10
+ updatedAt?: string
11
+ }
12
+
13
+ export interface List {
14
+ id: string
15
+ title: string
16
+ type: 'checklist' | 'kanban' | 'list' | string
17
+ items: ListItem[]
18
+ metadata?: Record<string, any>
19
+ createdAt?: string
20
+ updatedAt?: string
21
+ }
22
+
23
+ export interface ListsProvider {
24
+ getList(id: string): Promise<List>
25
+ listLists(params?: Record<string, any>): Promise<List[]>
26
+ createList(data: Partial<List>): Promise<List>
27
+ updateList(id: string, data: Partial<List>): Promise<List>
28
+ deleteList(id: string): Promise<void>
29
+
30
+ addItem(listId: string, item: Partial<ListItem>): Promise<ListItem>
31
+ updateItem(listId: string, itemId: string, data: Partial<ListItem>): Promise<ListItem>
32
+ deleteItem(listId: string, itemId: string): Promise<void>
33
+
34
+ reorderItems?(listId: string, itemIds: string[]): Promise<void>
35
+ }
@@ -0,0 +1,20 @@
1
+ import { getListsConfig } from './config'
2
+ import { getListsProvider } from './registry'
3
+
4
+ export function useLists() {
5
+ const { provider } = getListsConfig()
6
+ const lists = getListsProvider(provider)
7
+
8
+ return {
9
+ getList: lists.getList,
10
+ listLists: lists.listLists,
11
+ createList: lists.createList,
12
+ updateList: lists.updateList,
13
+ deleteList: lists.deleteList,
14
+
15
+ addItem: lists.addItem,
16
+ updateItem: lists.updateItem,
17
+ deleteItem: lists.deleteItem,
18
+ reorderItems: lists.reorderItems
19
+ }
20
+ }
@@ -0,0 +1,42 @@
1
+ import type { List, ListItem } from '../types'
2
+
3
+ /**
4
+ * Normalize a raw provider list into Meeovi's List shape.
5
+ */
6
+ export function transformList(raw: any): List {
7
+ return {
8
+ id: raw.id,
9
+ title: raw.title ?? raw.name ?? 'Untitled',
10
+ type: raw.type ?? 'list',
11
+ items: Array.isArray(raw.items)
12
+ ? raw.items.map(transformItem)
13
+ : [],
14
+ metadata: raw.metadata ?? {},
15
+ createdAt: raw.createdAt ?? raw.created_at ?? null,
16
+ updatedAt: raw.updatedAt ?? raw.updated_at ?? null
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Normalize a raw provider item into Meeovi's ListItem shape.
22
+ */
23
+ export function transformItem(raw: any): ListItem {
24
+ return {
25
+ id: raw.id,
26
+ title: raw.title ?? raw.name ?? '',
27
+ description: raw.description ?? raw.body ?? '',
28
+ completed: raw.completed ?? raw.done ?? false,
29
+ position: raw.position ?? raw.order ?? 0,
30
+ parentId: raw.parentId ?? raw.parent_id ?? null,
31
+ metadata: raw.metadata ?? {},
32
+ createdAt: raw.createdAt ?? raw.created_at ?? null,
33
+ updatedAt: raw.updatedAt ?? raw.updated_at ?? null
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Normalize arrays safely.
39
+ */
40
+ export function transformListArray(raw: any[]): List[] {
41
+ return raw.map(transformList)
42
+ }
@@ -0,0 +1,21 @@
1
+ import type { List, ListItem } from '../types'
2
+
3
+ export function validateListInput(data: Partial<List>) {
4
+ if (!data.title || typeof data.title !== 'string') {
5
+ throw new Error('List title is required and must be a string')
6
+ }
7
+
8
+ if (data.type && typeof data.type !== 'string') {
9
+ throw new Error('List type must be a string')
10
+ }
11
+ }
12
+
13
+ export function validateItemInput(data: Partial<ListItem>) {
14
+ if (!data.title || typeof data.title !== 'string') {
15
+ throw new Error('Item title is required and must be a string')
16
+ }
17
+
18
+ if (data.completed !== undefined && typeof data.completed !== 'boolean') {
19
+ throw new Error('Item completed must be a boolean')
20
+ }
21
+ }
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <v-row class="contentPage">
3
+ <v-col cols="12">
4
+ <v-card class="mx-auto" max-width="800px" elevation="0">
5
+ <NuxtImg loading="lazy" class="align-end text-white" height="200" :src="`${$directus.url}/assets/${website?.image?.filename_disk}`" :alt="website?.name" cover />
6
+ <v-card-title>{{ website?.name }}</v-card-title>
7
+
8
+ <v-card-subtitle class="pt-4">
9
+ Created: {{ new Date(website?.created_at).toLocaleDateString() }}
10
+ </v-card-subtitle>
11
+
12
+ <v-card-text>
13
+ <div>Type: {{ website?.type }}</div>
14
+
15
+ <div>{{ website?.note }}</div>
16
+ </v-card-text>
17
+
18
+ <v-card-actions>
19
+ <updatebookmark />
20
+
21
+ <v-spacer></v-spacer>
22
+ <v-btn color="orange" text="Visit" :href="website?.url"></v-btn>
23
+ </v-card-actions>
24
+ </v-card>
25
+ </v-col>
26
+
27
+ <v-divider></v-divider>
28
+ <v-col cols="12">
29
+ <comments />
30
+ </v-col>
31
+ </v-row>
32
+ </template>
33
+
34
+ <script setup>
35
+ import {
36
+ ref,
37
+ computed
38
+ } from 'vue'
39
+ import updatebookmark from '#lists/app/components/lists/update-bookmark.vue'
40
+ import createListBtn from '#lists/app/components/partials/createListBtn.vue'
41
+ import comments from '#social/app/components/comments.vue'
42
+
43
+ const route = useRoute();
44
+
45
+ const {
46
+ $directus,
47
+ $readItem
48
+ } = useNuxtApp()
49
+
50
+ const {
51
+ data: website
52
+ } = await useAsyncData('website', () => {
53
+ return $directus.request($readItem('websites', route.params.id, {
54
+ fields: ['*', {
55
+ '*': ['*']
56
+ }]
57
+ }))
58
+ })
59
+
60
+
61
+ // Add this debug log
62
+ watchEffect(() => {
63
+ if (website.value) {
64
+ console.log('Fetched website data:', website.value)
65
+ }
66
+ })
67
+
68
+
69
+ useHead({
70
+ title: computed(() => website?.value?.name || 'Bookmark Page')
71
+ })
72
+
73
+ definePageMeta({
74
+ middleware: ['authenticated'],
75
+ })
76
+ </script>