diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index ad7bee37..ce172fdf 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,8 +1,6 @@ import { query } from '@/api/query'; import { MetadataRoute } from 'next'; -export const dynamic = 'force-dynamic'; - const sitemap = async (): Promise => { const pages = [ { diff --git a/src/components/AddOrEditResource/AddOrEditResourceSheet.tsx b/src/components/AddOrEditResource/AddOrEditResourceSheet.tsx index 205f8765..2a55afc1 100644 --- a/src/components/AddOrEditResource/AddOrEditResourceSheet.tsx +++ b/src/components/AddOrEditResource/AddOrEditResourceSheet.tsx @@ -38,14 +38,12 @@ import { SheetTrigger, } from '@/ui/sheet'; import { Textarea } from '@/ui/textarea'; -import { ToastAction } from '@/ui/toast'; import { useToast } from '@/ui/use-toast'; -import { AlertTriangle, Loader2, Plus, WandSparkles } from 'lucide-react'; +import { AlertTriangle, Info, Loader2, Plus, WandSparkles } from 'lucide-react'; import { ReactNode, useRef, useState } from 'react'; import { AddCategorySheet } from './AddCategorySheet'; import { AddTopicSheet } from './AddTopicSheet'; import { AddTypeSheet } from './AddTypeSheet'; -import { Info } from 'lucide-react'; interface Props { children: ReactNode; @@ -65,24 +63,100 @@ export const AddOrEditResourceSheet = ({ const [open, setOpen] = useState(false); const { toast } = useToast(); + // Inputs + const [link, setLink] = useState(''); + const [name, setName] = useState(''); + const [slug, setSlug] = useState(''); + const [typeId, setTypeId] = useState(''); + const [categoryId, setCategoryId] = useState(''); + const [topicIds, setTopicIds] = useState>([]); + const [shortDescription, setShortDescription] = useState(''); + const [details, setDetails] = useState(''); + const [description, setDescription] = useState(''); + const [date, setDate] = useState(); + const [datePlain, setDatePlain] = useState(''); + const [relatedResourceIds, setRelatedrelatedResourceIds] = useState< + Array + >([]); + const [relatedResourcesPlain, setRelatedResourcesPlain] = useState(''); + const [suggestion, setSuggestion] = useState( + false, + ); + const [note, setNote] = useState(''); + + // Actions + + // getTypes const { data: types, runAction: fetchTypes } = useAction( action.types.getTypes, + { + onError: ({ error }) => { + toast({ + title: '❌ Error fetching types', + description: error, + variant: 'destructive', + }); + }, + }, ); + + // getCategories const { data: categories, runAction: fetchCategories } = useAction( action.categories.getCategories, + { + onError: ({ error }) => { + toast({ + title: '❌ Error fetching categories', + description: error, + variant: 'destructive', + }); + }, + }, ); + + // getTopics const { data: topics, runAction: fetchTopics } = useAction( action.topics.getTopics, + { + onError: ({ error }) => { + toast({ + title: '❌ Error fetching topics', + description: error, + variant: 'destructive', + }); + }, + }, ); + + // getResources const { data: resources, runAction: fetchResources } = useAction( action.resources.getResources, + { + onError: ({ error }) => { + toast({ + title: '❌ Error fetching resources', + description: error, + variant: 'destructive', + }); + }, + }, ); + + // analizeLink const { runAction: analyzeLink, isRunning: isAnalyzeLinkRunning } = useAction( action.resources.analizeLink, { onSuccess: (data) => { if (!data) return; - const { name, type, category, topics, description } = data; + const { + name, + type, + category, + topics, + shortDescription, + description, + date, + } = data; setName(name); setSlug(sluggify(name)); @@ -91,6 +165,14 @@ export const AddOrEditResourceSheet = ({ setTopicIds(topics.map(String)); setDescription(description); + if (shortDescription) { + setShortDescription(shortDescription); + } + + if (date) { + setDate(date); + } + toast({ title: '✅ Succesfully analized link', }); @@ -100,20 +182,12 @@ export const AddOrEditResourceSheet = ({ title: '❌ Error analizing link', description: error, variant: 'destructive', - action: ( - { - analyzeLink({ link }); - }} - > - Try again - - ), }); }, }, ); + + // addResource const { runAction: addResource, isRunning: isAddResourceRunning, @@ -121,11 +195,23 @@ export const AddOrEditResourceSheet = ({ validationErrors: addResourceValidationErrors, } = useAction(action.resources.addResource, { onSuccess: () => { + toast({ + title: '✅ Succesfully added resource', + }); onAdd?.(); onOpenChange(false); resetForm(); }, + onError: ({ error }) => { + toast({ + title: '❌ Error adding resource', + description: error, + variant: 'destructive', + }); + }, }); + + // editResource const { runAction: editResource, isRunning: isEditResourceRunning, @@ -133,23 +219,41 @@ export const AddOrEditResourceSheet = ({ validationErrors: editResourceValidationErrors, } = useAction(action.resources.editResource, { onSuccess: () => { + toast({ + title: '✅ Succesfully edited resource', + }); onOpenChange(false); resetForm(); }, + onError: ({ error }) => { + toast({ + title: '❌ Error editing resource', + description: error, + variant: 'destructive', + }); + }, }); + + // deleteResource const { runAction: deleteResource, isRunning: isDeleteResourceRunning } = useAction(action.resources.deleteResource, { onSuccess: () => { + toast({ + title: '✅ Succesfully deleted resource', + }); onOpenChange(false); resetForm(); }, onError: ({ error }) => { toast({ - title: `❌ ${error}`, + title: '❌ Error deleting resource', + description: error, variant: 'destructive', }); }, }); + + // revalidateCache const { runAction: revalidateCache, isRunning: isRevalidateCacheRunning } = useAction(action.cache.revalidateCache, { onSuccess: () => { @@ -159,44 +263,13 @@ export const AddOrEditResourceSheet = ({ }, onError: ({ error }) => { toast({ - title: '❌ Error revalidateCache', + title: '❌ Error revalidating cache', description: error, variant: 'destructive', - action: ( - { - analyzeLink({ link }); - }} - > - Try again - - ), }); }, }); - // Controlled inputs - const [link, setLink] = useState(''); - const [name, setName] = useState(''); - const [slug, setSlug] = useState(''); - const [typeId, setTypeId] = useState(''); - const [categoryId, setCategoryId] = useState(''); - const [topicIds, setTopicIds] = useState>([]); - const [shortDescription, setShortDescription] = useState(''); - const [details, setDetails] = useState(''); - const [description, setDescription] = useState(''); - const [date, setDate] = useState(); - const [datePlain, setDatePlain] = useState(''); - const [relatedResourceIds, setRelatedrelatedResourceIds] = useState< - Array - >([]); - const [relatedResourcesPlain, setRelatedResourcesPlain] = useState(''); - const [suggestion, setSuggestion] = useState( - false, - ); - const [note, setNote] = useState(''); - const onOpenChange = (open: boolean) => { if (open) { if (isEditMode) { @@ -385,7 +458,7 @@ export const AddOrEditResourceSheet = ({ {isEditMode && ( }> - Add redirect when changing the slug. + Consider adding a redirect when changing the slug! )} diff --git a/src/data/resources/action.ts b/src/data/resources/action.ts index 82619868..9787cabd 100644 --- a/src/data/resources/action.ts +++ b/src/data/resources/action.ts @@ -23,7 +23,7 @@ import { revalidateTag } from '../tags'; import { selectTopics } from '../topics/topics'; import { selectTypes } from '../types/types'; -const { SUGGESTION_MAIL_PASSWORD } = process.env; +const { SUGGESTION_MAIL_PASSWORD, OPENAI_API_KEY } = process.env; export const getResources = createAction({ action: async ({ ctx }) => { @@ -471,7 +471,13 @@ const analizeLinkSchema = z.object({ type: z.number(), category: z.number(), topics: z.array(z.number()), + shortDescription: z.string().nullable(), description: z.string(), + date: z + .string() + .date() + .transform((date) => new Date(date)) + .nullable(), }); export const analizeLink = createAdminAction({ @@ -550,16 +556,27 @@ export const analizeLink = createAdminAction({ }); const prompt = ` - I am going to give you the content of a website i am also goinf to give you input data that you are going to use to categorize the website. These are your instructions: - + I am going to give you the content of a website that you are going to use to categorize the website. + + These are your instructions: - Choose which type, categroy and topcis are the most relevant for the website. - - You are going to answer in JSON format. This is the format you are going to use: + - If the website is of a single person the type is most likely "Thoughtleader". + - If the type is "Thoughtleader" the name should be the name of the person. + - If the type is "Thoughtleader" the short description should be their job title or profession otherwise it should be null. + - Don't set more than 3 topics. Only set the most relevant ones that you are very confident with (7 or higher in a scale from 0 to 10). + - Answer in english only. + - If the website content is of a peace of media (book, article, podcast etc.) fill the date with the date of the publication (ISO date string) otherwise it should be null. + - Keep the description short and to the point (3 sentences max). + + You are going to answer in JSON format. This is the format you are going to use: { name: 'Name of the website', type: 1, category: 1, topics: [1, 2, 3], + shortDescription: 'UX Designer', description: 'Description of the website', + date: '2022-01-01' } TYPES: ${JSON.stringify(types)} @@ -576,7 +593,7 @@ export const analizeLink = createAdminAction({ `; const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY!, + apiKey: OPENAI_API_KEY, }); const aiResponse = await openai.chat.completions @@ -588,7 +605,7 @@ export const analizeLink = createAdminAction({ { role: 'system', content: - 'You are my AI web scraper. Your job is to make sense of the text content of a website and put it into a category.', + 'You are my AI web scraper. Your job is to make sense of the text content of a website and categorize it.', }, { role: 'user',