Skip to content

Commit 2464a08

Browse files
committed
feat(admin): new page form
1 parent 69cc2f6 commit 2464a08

File tree

8 files changed

+174
-5
lines changed

8 files changed

+174
-5
lines changed

.env.example

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
NUXT_HUB_PROJECT_KEY=key-goes-here
1+
NUXT_HUB_PROJECT_KEY="key-goes-here"
22
NUXT_PUBLIC_HELLO_TEXT="FilDB goes here."
3+
# Comma seperated list of scrape groups to allow pages to be set to.
4+
NUXT_PUBLIC_SCRAPE_GROUPS="andromeda,butterfly,circinus,dusty,eye,fireworks"
5+
36
NUXT_PUBLIC_SUPABASE_URL="https://example.supabase.co"
47
NUXT_PUBLIC_SUPABASE_KEY="<your_key>"
5-
SUPABASE_PG_URL=postgresql://full-pg-url
8+
SUPABASE_PG_URL="postgresql://full-pg-url"
69

710
# For Playwright E2E tests
8-
PLAYWRIGHT_BASE_URL=http://localhost:3000
11+
PLAYWRIGHT_BASE_URL="http://localhost:3000"
912
TEST_SUPABASE_URL="https://example.supabase.co"
1013
TEST_SUPABASE_SECRET_KEY="<your_key>"
1114
TEST_ADMIN_1_PW=

app/app.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<UApp>
2+
<UApp :toaster="{ position: 'top-right' }">
33
<NuxtRouteAnnouncer />
44
<NuxtLoadingIndicator />
55
<NuxtLayout>

app/components/NewPageForm.vue

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<script setup lang="ts">
2+
import type { FormSubmitEvent } from '@nuxt/ui';
3+
import { objectToSnake } from 'ts-case-convert';
4+
import type { Reactive } from 'vue';
5+
import { z } from 'zod';
6+
7+
import { useScrapers } from '~/queries/scrapers';
8+
import { useSites } from '~/queries/sites';
9+
import type { ScrapeStatus, ThemeColor } from '~/utils/utils';
10+
import { icons } from '~/utils/utils';
11+
12+
const runtimeConfig = useRuntimeConfig();
13+
const supabase = useSupabaseClient();
14+
const toast = useToast();
15+
16+
const { sites } = useSites();
17+
const { scrapers } = useScrapers();
18+
19+
if (sites.value.error) {
20+
toast.add({
21+
title: 'Error loading sites.',
22+
description: sites.value.error.message,
23+
icon: icons.error,
24+
color: 'error',
25+
duration: -1,
26+
});
27+
}
28+
29+
const scrapeGroups = (runtimeConfig.public.scrapeGroups ?? '')
30+
.split(',')
31+
.map((g) => ({ name: g }));
32+
33+
const pageSchema = z.object({
34+
url: z.string().url(),
35+
scrapeUrl: z.string().url(),
36+
siteId: z.string().uuid(),
37+
scraperId: z.string().uuid(),
38+
scrapeGroup: z.string(),
39+
scrapeStatus: z.enum(['pending', 'active', 'paused', 'archived']),
40+
});
41+
type PageSchema = z.output<typeof pageSchema>;
42+
43+
const newPage = reactive({
44+
url: '',
45+
scrapeUrl: '',
46+
siteId: '',
47+
scraperId: '',
48+
scrapeGroup: '',
49+
scrapeStatus: 'pending' as ScrapeStatus,
50+
});
51+
52+
const status: Reactive<{
53+
sending: boolean;
54+
showAlert: boolean;
55+
alertText: string;
56+
alertColor: ThemeColor;
57+
}> = reactive({
58+
sending: false,
59+
showAlert: false,
60+
alertText: '',
61+
alertColor: 'neutral',
62+
});
63+
64+
const addPage = async (event: FormSubmitEvent<PageSchema>) => {
65+
status.showAlert = false;
66+
status.sending = true;
67+
const { error } = await supabase
68+
.from('pages')
69+
.insert(objectToSnake({ id: crypto.randomUUID(), ...event.data }));
70+
if (error) {
71+
status.alertColor = 'error';
72+
status.alertText = `Error: ${error.message}`;
73+
status.showAlert = true;
74+
console.log(error);
75+
} else {
76+
status.alertColor = 'success';
77+
status.alertText = 'Created!';
78+
status.showAlert = true;
79+
newPage.url = '';
80+
newPage.scrapeUrl = '';
81+
newPage.siteId = '';
82+
newPage.scraperId = '';
83+
newPage.scrapeGroup = '';
84+
newPage.scrapeStatus = 'pending';
85+
}
86+
status.sending = false;
87+
};
88+
</script>
89+
<template>
90+
<UForm
91+
:schema="pageSchema"
92+
:state="newPage"
93+
class="my-2 flex flex-wrap items-stretch gap-2 rounded-xl border border-slate-400 p-3"
94+
@submit="addPage"
95+
>
96+
<UFormField label="URL" name="url">
97+
<UInput v-model="newPage.url" class="min-w-48" />
98+
</UFormField>
99+
<UFormField label="Scrape URL" name="scrapeUrl">
100+
<UInput v-model="newPage.scrapeUrl" class="min-w-48" />
101+
</UFormField>
102+
<UFormField label="Site" name="siteId">
103+
<USelectMenu
104+
v-model="newPage.siteId"
105+
value-key="id"
106+
label-key="name"
107+
:items="sites.data"
108+
class="min-w-48"
109+
/>
110+
</UFormField>
111+
<UFormField label="Scraper" name="scraperId">
112+
<USelectMenu
113+
v-model="newPage.scraperId"
114+
value-key="id"
115+
label-key="name"
116+
:items="scrapers.data"
117+
class="min-w-48"
118+
/>
119+
</UFormField>
120+
<UFormField label="Scrape Group" name="scrapeGroup">
121+
<USelectMenu
122+
v-model="newPage.scrapeGroup"
123+
value-key="name"
124+
label-key="name"
125+
:items="scrapeGroups"
126+
class="min-w-48"
127+
/>
128+
</UFormField>
129+
<UFormField label="Scrape Status" name="scrapeStatus">
130+
<USelect
131+
v-model="newPage.scrapeStatus"
132+
:items="['pending', 'active', 'paused', 'archived']"
133+
class="min-w-48"
134+
/>
135+
</UFormField>
136+
<UButton type="submit" :loading="status.sending" class="h-fit self-center">
137+
Create
138+
</UButton>
139+
<UAlert
140+
v-if="status.showAlert"
141+
:color="status.alertColor"
142+
variant="subtle"
143+
:title="status.alertText"
144+
:icon="status.alertColor === 'success' ? icons.success : icons.error"
145+
class="h-fit w-fit self-center"
146+
/>
147+
</UForm>
148+
</template>

app/pages/admin/pages.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ definePageMeta({ layout: 'admin' });
44
<template>
55
<div>
66
<div class="text-xl italic">Pages</div>
7+
<NewPageForm />
78
<PagesTable />
89
</div>
910
</template>

app/utils/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ export type ThemeColor =
88
| 'neutral';
99

1010
export type ScrapeStatus = 'pending' | 'active' | 'paused' | 'archived';
11+
12+
export const icons = {
13+
error: 'solar:danger-circle-linear',
14+
success: 'solar:check-circle-linear',
15+
};

nuxt.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default defineNuxtConfig({
2121
public: {
2222
// Can be overridden by NUXT_PUBLIC_HELLO_TEXT environment variable
2323
helloText: 'FilDB goes here.',
24+
scrapeGroups: '',
2425
supabaseKey: '',
2526
supabaseUrl: '',
2627
},
@@ -55,4 +56,4 @@ export default defineNuxtConfig({
5556
},
5657
types: './types/database.types.ts',
5758
},
58-
});
59+
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"e2e": "playwright test"
1515
},
1616
"dependencies": {
17+
"@iconify-json/lucide": "^1.2.30",
1718
"@iconify-json/mdi": "^1.2.3",
1819
"@iconify-json/solar": "^1.2.2",
1920
"@nuxt/eslint": "^0.7.5",

pnpm-lock.yaml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)