Skip to content

Commit 2dabf6a

Browse files
committed
looks good to me
1 parent ccdee0e commit 2dabf6a

File tree

11 files changed

+248
-44
lines changed

11 files changed

+248
-44
lines changed

app/composables/use-topic.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Supa } from '~/utils/supabase';
2+
import type { FullTopic } from '~/utils/topics';
3+
4+
async function createTopic(supa: Supa) {
5+
const { data } = await supa
6+
.from('topics')
7+
.insert({
8+
title: 'New Topic',
9+
description: '',
10+
})
11+
.select('id')
12+
.single()
13+
.throwOnError();
14+
15+
return data.id;
16+
}
17+
18+
export default async (id?: string): Promise<FullTopic> => {
19+
const supa = useSupabaseClient();
20+
if (!id)
21+
id = await createTopic(supa);
22+
23+
return (await useSupabaseClient()
24+
.from('topics')
25+
.select('*, tasks(*), timeline_nodes(*)')
26+
.eq('id', id!)
27+
.single()
28+
.throwOnError()
29+
).data;
30+
};

app/layouts/default.vue

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
<script setup lang="ts">
22
import type { NavigationMenuItem } from '@nuxt/ui';
33
4+
const supa = useSupabaseClient();
45
const user = useSupabaseUser();
56
6-
const open = ref(false);
7-
8-
const links: [NavigationMenuItem[], NavigationMenuItem[] ] = [[{
9-
label: 'Topic 1',
10-
to: '/topic/1',
11-
}], [{
7+
const links = ref<[NavigationMenuItem[], NavigationMenuItem[]]>([[], [{
128
label: 'New Topic',
139
icon: 'i-lucide-plus',
14-
onSelect: async () => navigateTo(`/topic/${await createTopic()}`),
15-
}/* , { label: 'Feedback', icon: 'i-lucide-message-circle', to: 'https://github.com/nuxt-ui-pro/dashboard', target: '_blank',} */]];
10+
onSelect: async () => navigateTo(`/topic/${await createTopic(supa)}`),
11+
}/* , { label: 'Feedback', icon: 'i-lucide-message-circle', to: 'https://github.com/nuxt-ui-pro/dashboard', target: '_blank',} */]]);
1612
1713
const groups = computed(() => [{
1814
id: 'links',
1915
label: 'Go to',
20-
items: links.flat().map((item) => {
16+
items: links.value.flat().map((item) => {
2117
return typeof item.to === 'string' && item.to.startsWith('/topic/')
2218
? { ...item, icon: 'i-lucide-target' }
2319
: item;
@@ -33,13 +29,28 @@ const groups = computed(() => [{
3329
target: '_blank',
3430
}],
3531
}]);
32+
33+
onMounted(async () => {
34+
if (!user.value)
35+
return;
36+
const { data } = await supa
37+
.from('topics')
38+
.select('id, title')
39+
.eq('owner', user.value.id)
40+
.order('created_at', { ascending: false })
41+
.throwOnError();
42+
links.value[0] = data.map((topic) => ({
43+
label: topic.title,
44+
icon: 'i-lucide-target',
45+
to: `/topic/${topic.id}`,
46+
}));
47+
})
3648
</script>
3749

3850
<template>
3951
<UDashboardGroup unit="rem">
4052
<UDashboardSidebar
4153
id="default"
42-
v-model:open="open"
4354
resizable
4455
class="bg-(--ui-bg-elevated)/25"
4556
:ui="{ footer: 'lg:border-t lg:border-(--ui-border)' }"

app/pages/topic/[id].vue

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue';
2+
const route = useRoute();
33
4-
const state = ref({
5-
title: 'Topic 1',
6-
description: '...',
7-
});
4+
const { data: topic } = useAsyncData(() => useTopic(route.params.id as string));
5+
6+
function generate() {
7+
$fetch('/api/request-tasks', {
8+
method: 'post',
9+
body: { id: topic.value?.id },
10+
});
11+
}
812
</script>
913

1014
<template>
1115
<UDashboardPanel id="topic">
1216
<template #header>
13-
<UDashboardNavbar :title="state.title">
17+
<UDashboardNavbar :title="topic?.title">
1418
<template #right>
1519
<UButton variant="outline" color="neutral">
1620
Delete Topic
@@ -20,25 +24,64 @@ const state = ref({
2024
</template>
2125

2226
<template #body>
23-
<UForm class="space-y-4" :state>
27+
<UForm v-if="topic" class="space-y-4" :state="topic">
2428
<UFormField label="Description" hint="Describe it..." size="xl">
25-
<UTextarea v-model="state.description" class="w-full" />
29+
<UTextarea v-model="topic.description" class="w-full" />
2630
</UFormField>
2731

2832
<div class="grid grid-cols-[1fr_auto_1fr] items-center w-full gap-3">
2933
<UFormField label="Timeline" size="xl" hint="What did you do...">
30-
todo
34+
todo list
3135
</UFormField>
3236

3337
<div class="h-8 border-l ring-inset" />
3438

35-
<UFormField label="Recommended Tasks" size="xl">
36-
todo
39+
<UFormField label="Recommended Tasks" size="xl" :ui="{ label: 'items-start' }">
40+
<ul>
41+
<template v-for="(task) in topic.tasks">
42+
<UCheckbox
43+
v-if="task.status === 'pending'"
44+
:key="task.id"
45+
class="w-full"
46+
:default-value="false"
47+
as="li"
48+
@update:model-value="task.status = 'accepted'"
49+
>
50+
<template #label>
51+
<UButton variant="soft" color="neutral" size="xs" icon="lucide:trash-2" />
52+
{{ ' ' }}
53+
<span class="align-top">{{ task.title }}</span>
54+
</template>
55+
</UCheckbox>
56+
</template>
57+
</ul>
58+
59+
<template #hint>
60+
<UButton @click="generate">
61+
Generate suggestions
62+
</UButton>
63+
</template>
3764
</UFormField>
3865
</div>
3966

4067
<UFormField label="Tasks" size="xl">
41-
todo
68+
<ul>
69+
<template v-for="(task) in topic.tasks">
70+
<UCheckbox
71+
v-if="task.status === 'accepted' || task.status === 'completed'"
72+
:key="task.id"
73+
:default-value="false"
74+
as="li"
75+
@update:model-value="(v) => task.status = v ? 'completed' : 'accepted'"
76+
>
77+
<template #label>
78+
<UButton variant="soft" color="neutral" size="xs" icon="lucide:trash-2" />
79+
{{ ' ' }}
80+
<span class="align-top">{{ task.title }}</span>
81+
</template>
82+
</UCheckbox>
83+
</template>
84+
</ul>
4285
</UFormField>
4386
</UForm>
4487
</template>

app/plugins/data-loader.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// https://uvr.esm.is/data-loaders/nuxt.html#nuxt
2+
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
3+
4+
export default defineNuxtPlugin({
5+
name: 'data-loaders',
6+
dependsOn: ['nuxt:router'],
7+
setup(nuxtApp) {
8+
nuxtApp.vueApp.use(DataLoaderPlugin, {
9+
router: nuxtApp.vueApp.config.globalProperties.$router,
10+
isSSR: import.meta.server,
11+
});
12+
},
13+
});

app/utils/supabase.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { SupabaseClient } from '@supabase/supabase-js';
2+
import type { Database } from '~/types/database.types';
3+
4+
export type Supa = SupabaseClient<Database, 'public', Database['public']>;

app/utils/topics.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import type { SupabaseClient } from "@supabase/supabase-js"
1+
import type { SupabaseClient } from '@supabase/supabase-js';
2+
import type { MergeDeep } from 'type-fest';
3+
import type { Tables } from '~/types/database.types';
4+
5+
export type FullTopic = MergeDeep<
6+
Tables<'topics'>,
7+
{
8+
tasks: Tables<'tasks'>[]
9+
timeline_nodes: Tables<'timeline_nodes'>[]
10+
}
11+
>;
212

313
export async function createTopic(supa: SupabaseClient) {
414
const { data } = await supa
@@ -9,7 +19,15 @@ export async function createTopic(supa: SupabaseClient) {
919
})
1020
.select('id')
1121
.single()
12-
.throwOnError()
22+
.throwOnError();
1323

1424
return data.id;
1525
}
26+
27+
export function deleteTopic(supa: SupabaseClient, id: string) {
28+
return supa
29+
.from('topics')
30+
.delete()
31+
.eq('id', id)
32+
.throwOnError();
33+
}

0 commit comments

Comments
 (0)