From b3b7b7cd38054c648b18f6b9e62e1ab84ad87d64 Mon Sep 17 00:00:00 2001 From: Michael Liendo Date: Fri, 16 Aug 2024 12:44:13 -0400 Subject: [PATCH 1/4] chore: create the service for the creation of links --- client/src/services/Link.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/client/src/services/Link.ts b/client/src/services/Link.ts index 2d1b50a..bccedcb 100644 --- a/client/src/services/Link.ts +++ b/client/src/services/Link.ts @@ -1,18 +1,34 @@ -import { LinkSchema } from '@linx/shared'; import { z } from 'zod'; +import { LinkSchema } from '@linx/shared'; import fetch from '../utils/fetch'; +import type { ILinkForCreate } from '@linx/shared'; + export default class Link { static async getAll() { try { const request = await fetch('/links/getAll'); - const response = await request.json(); + const body = await request.json(); - return z.array(LinkSchema).parse(response.data.links); + return z.array(LinkSchema).parse(body.data.links); } catch (error) { console.error('LinkService', error); throw error; } } + + static async create(link: ILinkForCreate) { + try { + const request = await fetch('/links/create', { + body: JSON.stringify(link), + }); + const body = await request.json(); + + return LinkSchema.parse(body.data.link); + } catch (e) { + console.log('LinkService', e); + throw e; + } + } } From 4558547a286f2201918cb6c6afa62e38f3e35377 Mon Sep 17 00:00:00 2001 From: Michael Liendo Date: Fri, 16 Aug 2024 14:03:15 -0400 Subject: [PATCH 2/4] chore: create links --- client/src/App.tsx | 9 +- client/src/Routes.tsx | 2 +- .../datagrids/links/links-table-toolbar.tsx | 6 +- .../datagrids/links/modal-create.tsx | 82 +++++++++++++++++++ client/src/components/user-nav.tsx | 2 +- client/src/context/LinxContext.tsx | 40 --------- client/src/hooks/{Auth.tsx => useAuth.tsx} | 0 client/src/hooks/useLinks.tsx | 43 ++++++++-- client/src/pages/(app)/Home.tsx | 2 +- client/src/pages/(auth)/Login.tsx | 2 +- client/src/pages/(auth)/Signup.tsx | 2 +- client/src/services/Link.ts | 3 +- server/src/controllers/Link/create.ts | 2 +- server/src/repository/Link.ts | 17 ++-- server/src/services/Link.ts | 4 +- shared/src/interfaces/link.ts | 2 +- 16 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 client/src/components/datagrids/links/modal-create.tsx delete mode 100644 client/src/context/LinxContext.tsx rename client/src/hooks/{Auth.tsx => useAuth.tsx} (100%) diff --git a/client/src/App.tsx b/client/src/App.tsx index adbbe43..fb758dc 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,17 +1,14 @@ import { Routes } from './Routes'; import { AuthProvider } from './context/AuthContext'; -import { LinksProvider } from './context/LinxContext'; import { ThemeProvider } from './context/ThemeContext'; function App() { return ( <> - - - - - + + + ); diff --git a/client/src/Routes.tsx b/client/src/Routes.tsx index 0cf9652..655ea1f 100644 --- a/client/src/Routes.tsx +++ b/client/src/Routes.tsx @@ -1,5 +1,5 @@ import { Routes as ReactRoutes, Route } from 'react-router'; -import useAuth from './hooks/Auth'; +import useAuth from './hooks/useAuth'; import HomeApp from './pages/(app)/Home'; import Login from './pages/(auth)/Login'; import Signup from './pages/(auth)/Signup'; diff --git a/client/src/components/datagrids/links/links-table-toolbar.tsx b/client/src/components/datagrids/links/links-table-toolbar.tsx index 94931d4..bc86f59 100644 --- a/client/src/components/datagrids/links/links-table-toolbar.tsx +++ b/client/src/components/datagrids/links/links-table-toolbar.tsx @@ -1,9 +1,10 @@ -import { Cross2Icon } from '@radix-ui/react-icons'; +import { Cross2Icon, Link1Icon } from '@radix-ui/react-icons'; import type { Table } from '@tanstack/react-table'; import { Button } from '../../ui/button'; import { Input } from '../../ui/input'; import { DataTableViewOptions } from './links-table-view-options'; +import { LinkModalCreate } from './modal-create'; interface DataTableToolbarProps { table: Table; @@ -29,7 +30,7 @@ export function DataTableToolbar({ /> {/* todo: for enums of data - {table.getColumn('shorter_name') && ( + {table.getColumn('shorter_name') && ( ({ )} + ); } diff --git a/client/src/components/datagrids/links/modal-create.tsx b/client/src/components/datagrids/links/modal-create.tsx new file mode 100644 index 0000000..cc8d575 --- /dev/null +++ b/client/src/components/datagrids/links/modal-create.tsx @@ -0,0 +1,82 @@ +import { TextField } from '@/components/text-field'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { useLinks } from '@/hooks/useLinks'; +import { LinkForCreateSchema } from '@linx/shared'; +import { Link1Icon } from '@radix-ui/react-icons'; +import { useState } from 'react'; + +import type { FormEvent } from 'react'; + +export function LinkModalCreate() { + const [shorterName, setShorterName] = useState(''); + const [url, setUrl] = useState(''); + const { create } = useLinks(); + + async function handleSubmit(event: FormEvent) { + event.preventDefault(); + const dto = LinkForCreateSchema.parse({ + shorter_name: shorterName, + url, + }); + console.log; + + create(dto); + } + + return ( + + + + + + + Create link + + Make changes to your profile here. Click save when you're done. + + +
+
+ + + + + +
+
+ ); +} diff --git a/client/src/components/user-nav.tsx b/client/src/components/user-nav.tsx index 5844d03..16ed87b 100644 --- a/client/src/components/user-nav.tsx +++ b/client/src/components/user-nav.tsx @@ -1,4 +1,4 @@ -import useAuth from '@/hooks/Auth'; +import useAuth from '@/hooks/useAuth'; import { Avatar, AvatarFallback, AvatarImage } from '@radix-ui/react-avatar'; import { Button } from './ui/button'; import { diff --git a/client/src/context/LinxContext.tsx b/client/src/context/LinxContext.tsx deleted file mode 100644 index 661c8c0..0000000 --- a/client/src/context/LinxContext.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { ILink } from '@linx/shared'; -import { createContext, useEffect, useState } from 'react'; -import Services from '../services'; -import { AuthContext } from './AuthContext'; - -export interface LinksContextProps { - isLoading: boolean; - links: ILink[] | []; -} - -export const LinksContext = createContext( - undefined, -); - -export const LinksProvider = ({ children }: { children?: React.ReactNode }) => { - const [links, setLinks] = useState([]); - const [loading, setLoading] = useState(true); - - const getLinks = async () => { - setLoading(true); - try { - const links = await Services.link.getAll(); - setLinks(links); - } catch (e) { - console.log(e); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - getLinks(); - }, []); - - return ( - - {children} - - ); -}; diff --git a/client/src/hooks/Auth.tsx b/client/src/hooks/useAuth.tsx similarity index 100% rename from client/src/hooks/Auth.tsx rename to client/src/hooks/useAuth.tsx diff --git a/client/src/hooks/useLinks.tsx b/client/src/hooks/useLinks.tsx index 69e4101..9d86bac 100644 --- a/client/src/hooks/useLinks.tsx +++ b/client/src/hooks/useLinks.tsx @@ -1,10 +1,39 @@ -import { LinksContext } from '@/context/LinxContext'; import { useContext } from 'react'; -export default function useLinks() { - const context = useContext(LinksContext); - if (context === undefined) { - throw new Error('useLinks must be used in LinksProvider'); - } - return context; +import type { ILink, ILinkForCreate } from '@linx/shared'; +import { createContext, useEffect, useState } from 'react'; +import Services from '../services'; + +export interface LinksContextProps { + isLoading: boolean; + links: ILink[] | []; + create: (link: ILinkForCreate) => void; } + +export const useLinks = () => { + const [links, setLinks] = useState([]); + const [loading, setLoading] = useState(true); + + const getLinks = async () => { + setLoading(true); + try { + const links = await Services.link.getAll(); + setLinks(links); + } catch (e) { + console.log(e); + } finally { + setLoading(false); + } + }; + + const create = async (linkDTO: ILinkForCreate) => { + const link = await Services.link.create(linkDTO); + setLinks((oldLinks) => [link, ...oldLinks]); + }; + + useEffect(() => { + getLinks(); + }, []); + + return { isLoading: loading, links, create }; +}; diff --git a/client/src/pages/(app)/Home.tsx b/client/src/pages/(app)/Home.tsx index dc49a4f..4c91be5 100644 --- a/client/src/pages/(app)/Home.tsx +++ b/client/src/pages/(app)/Home.tsx @@ -3,7 +3,7 @@ import { linksColumns } from '@/components/datagrids/links/links-columns'; import { MainNav } from '@/components/main-nav'; import { UserNav } from '@/components/user-nav'; -import useLinks from '@/hooks/useLinks'; +import { useLinks } from '@/hooks/useLinks'; export default function HomeApp() { const { links } = useLinks(); diff --git a/client/src/pages/(auth)/Login.tsx b/client/src/pages/(auth)/Login.tsx index efb5bf7..795648d 100644 --- a/client/src/pages/(auth)/Login.tsx +++ b/client/src/pages/(auth)/Login.tsx @@ -10,7 +10,7 @@ import { CardHeader, CardTitle, } from '@/components/ui/card'; -import useAuth from '../../hooks/Auth'; +import useAuth from '../../hooks/useAuth'; import Services from '../../services'; export default function Login() { diff --git a/client/src/pages/(auth)/Signup.tsx b/client/src/pages/(auth)/Signup.tsx index 50c5a19..d319f98 100644 --- a/client/src/pages/(auth)/Signup.tsx +++ b/client/src/pages/(auth)/Signup.tsx @@ -9,7 +9,7 @@ import { import { type FormEvent, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { TextField } from '../../components/text-field'; -import useAuth from '../../hooks/Auth'; +import useAuth from '../../hooks/useAuth'; import Services from '../../services'; export default function Signup() { diff --git a/client/src/services/Link.ts b/client/src/services/Link.ts index bccedcb..3dfbab3 100644 --- a/client/src/services/Link.ts +++ b/client/src/services/Link.ts @@ -1,5 +1,5 @@ -import { z } from 'zod'; import { LinkSchema } from '@linx/shared'; +import { z } from 'zod'; import fetch from '../utils/fetch'; import type { ILinkForCreate } from '@linx/shared'; @@ -21,6 +21,7 @@ export default class Link { static async create(link: ILinkForCreate) { try { const request = await fetch('/links/create', { + method: 'POST', body: JSON.stringify(link), }); const body = await request.json(); diff --git a/server/src/controllers/Link/create.ts b/server/src/controllers/Link/create.ts index 868fc06..8037a34 100644 --- a/server/src/controllers/Link/create.ts +++ b/server/src/controllers/Link/create.ts @@ -20,5 +20,5 @@ export default async function create(request: Request, reply: Reply) { return reply .code(201) - .send({ success: true, message: 'Link created', data: { id: link } }); + .send({ success: true, message: 'Link created', data: { link: link } }); } diff --git a/server/src/repository/Link.ts b/server/src/repository/Link.ts index 4f42496..2f7e8b8 100644 --- a/server/src/repository/Link.ts +++ b/server/src/repository/Link.ts @@ -3,9 +3,12 @@ import database from './database'; import type { ILink, ILinkForCreate } from '@linx/shared'; export class Link { - static async create(link: ILinkForCreate): Promise { - const [id] = await database('links').insert(link).returning('id'); - return id.id; + static async create(linkDTO: ILinkForCreate): Promise { + const [link] = await database('links') + .insert(linkDTO) + .returning('*') + .orderBy('created_at'); + return link; } static async getByShorterName(shorter_name: string): Promise { @@ -16,16 +19,16 @@ export class Link { } static async getByID(ID: string): Promise { - const link = await database('links') - .select('*') - .where({ user_id: ID }); + const link = await database('links').select('*').where({ id: ID }); return link; } static async getUserLinks(userID: string): Promise { const links = await database('links') .select('*') - .where({ user_id: userID }); + .where({ user_id: userID }) + .orderBy('created_at', 'desc'); + return links; } } diff --git a/server/src/services/Link.ts b/server/src/services/Link.ts index c7e37f5..1263c9a 100644 --- a/server/src/services/Link.ts +++ b/server/src/services/Link.ts @@ -4,7 +4,7 @@ import { BadRequestError } from '../utils/errorHandler'; import type { ILink, ILinkForCreate } from '@linx/shared'; export default class Link { - static async create(link_dto: ILinkForCreate): Promise { + static async create(link_dto: ILinkForCreate): Promise { const check = await Repository.link.getByShorterName(link_dto.shorter_name); if (check) throw new BadRequestError('Exists the shorter name, please select other'); @@ -15,7 +15,7 @@ export default class Link { } static async getAllByUser(user_id: string): Promise { - const links = await Repository.link.getByID(user_id); + const links = await Repository.link.getUserLinks(user_id); return links; } } diff --git a/shared/src/interfaces/link.ts b/shared/src/interfaces/link.ts index 53caee0..3be4cb7 100644 --- a/shared/src/interfaces/link.ts +++ b/shared/src/interfaces/link.ts @@ -10,5 +10,5 @@ export interface ILink { export interface ILinkForCreate { url: string; shorter_name: string; - user_id: string; + user_id?: string; } From 1bc4712f8ddef29fd8c4de55f84c0c5a954aa2ef Mon Sep 17 00:00:00 2001 From: Michael Liendo Date: Fri, 16 Aug 2024 14:35:33 -0400 Subject: [PATCH 3/4] chore: create the links --- client/src/components/datagrids/links/modal-create.tsx | 1 + client/src/hooks/useLinks.tsx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/components/datagrids/links/modal-create.tsx b/client/src/components/datagrids/links/modal-create.tsx index cc8d575..7721e8a 100644 --- a/client/src/components/datagrids/links/modal-create.tsx +++ b/client/src/components/datagrids/links/modal-create.tsx @@ -55,6 +55,7 @@ export function LinkModalCreate() { id="shorter_name" name="shorter_name" placeholder="linx-short-url" + autoComplete="off" onValue={setShorterName} required /> diff --git a/client/src/hooks/useLinks.tsx b/client/src/hooks/useLinks.tsx index 9d86bac..60e3f22 100644 --- a/client/src/hooks/useLinks.tsx +++ b/client/src/hooks/useLinks.tsx @@ -28,7 +28,11 @@ export const useLinks = () => { const create = async (linkDTO: ILinkForCreate) => { const link = await Services.link.create(linkDTO); - setLinks((oldLinks) => [link, ...oldLinks]); + setLinks((oldLinks) => { + const newList = [...oldLinks]; + newList.unshift(link); + return newList; + }); }; useEffect(() => { From 7ad5ce58cf0890aaa590ec3aa9f8e29e60f6eb52 Mon Sep 17 00:00:00 2001 From: Michael Liendo Date: Fri, 16 Aug 2024 15:01:50 -0400 Subject: [PATCH 4/4] chore: fix and --- client/src/App.tsx | 9 ++-- .../datagrids/links/modal-create.tsx | 2 +- client/src/context/LinksContext.tsx | 51 +++++++++++++++++++ client/src/hooks/useLinks.tsx | 47 +++-------------- client/src/pages/(app)/Home.tsx | 3 +- 5 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 client/src/context/LinksContext.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index fb758dc..fa89b7e 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,14 +1,17 @@ import { Routes } from './Routes'; import { AuthProvider } from './context/AuthContext'; +import { LinksProvider } from './context/LinksContext'; import { ThemeProvider } from './context/ThemeContext'; function App() { return ( <> - - - + + + + + ); diff --git a/client/src/components/datagrids/links/modal-create.tsx b/client/src/components/datagrids/links/modal-create.tsx index 7721e8a..5b152bd 100644 --- a/client/src/components/datagrids/links/modal-create.tsx +++ b/client/src/components/datagrids/links/modal-create.tsx @@ -9,7 +9,7 @@ import { DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; -import { useLinks } from '@/hooks/useLinks'; +import useLinks from '@/hooks/useLinks'; import { LinkForCreateSchema } from '@linx/shared'; import { Link1Icon } from '@radix-ui/react-icons'; import { useState } from 'react'; diff --git a/client/src/context/LinksContext.tsx b/client/src/context/LinksContext.tsx new file mode 100644 index 0000000..399f6e6 --- /dev/null +++ b/client/src/context/LinksContext.tsx @@ -0,0 +1,51 @@ +import { useContext } from 'react'; + +import type { ILink, ILinkForCreate } from '@linx/shared'; +import { createContext, useEffect, useState } from 'react'; +import Services from '../services'; + +export interface LinksContextProps { + isLoading: boolean; + links: ILink[] | []; + create: (link: ILinkForCreate) => void; +} + +export const LinksContext = createContext( + undefined, +); + +export const LinksProvider = ({ children }: { children?: React.ReactNode }) => { + const [links, setLinks] = useState([]); + const [loading, setLoading] = useState(true); + + const getLinks = async () => { + setLoading(true); + try { + const links = await Services.link.getAll(); + setLinks(links); + } catch (e) { + console.log(e); + } finally { + setLoading(false); + } + }; + + const create = async (linkDTO: ILinkForCreate) => { + const link = await Services.link.create(linkDTO); + setLinks((oldLinks) => { + const newList = [...oldLinks]; + newList.unshift(link); + return newList; + }); + }; + + useEffect(() => { + getLinks(); + }, []); + + return ( + + {children} + + ); +}; diff --git a/client/src/hooks/useLinks.tsx b/client/src/hooks/useLinks.tsx index 60e3f22..59e1795 100644 --- a/client/src/hooks/useLinks.tsx +++ b/client/src/hooks/useLinks.tsx @@ -1,43 +1,10 @@ +import { LinksContext } from '@/context/LinksContext'; import { useContext } from 'react'; -import type { ILink, ILinkForCreate } from '@linx/shared'; -import { createContext, useEffect, useState } from 'react'; -import Services from '../services'; - -export interface LinksContextProps { - isLoading: boolean; - links: ILink[] | []; - create: (link: ILinkForCreate) => void; +export default function useLinks() { + const context = useContext(LinksContext); + if (context === undefined) { + throw new Error('useLinks must be used in AuthProvider'); + } + return context; } - -export const useLinks = () => { - const [links, setLinks] = useState([]); - const [loading, setLoading] = useState(true); - - const getLinks = async () => { - setLoading(true); - try { - const links = await Services.link.getAll(); - setLinks(links); - } catch (e) { - console.log(e); - } finally { - setLoading(false); - } - }; - - const create = async (linkDTO: ILinkForCreate) => { - const link = await Services.link.create(linkDTO); - setLinks((oldLinks) => { - const newList = [...oldLinks]; - newList.unshift(link); - return newList; - }); - }; - - useEffect(() => { - getLinks(); - }, []); - - return { isLoading: loading, links, create }; -}; diff --git a/client/src/pages/(app)/Home.tsx b/client/src/pages/(app)/Home.tsx index 4c91be5..dc311bf 100644 --- a/client/src/pages/(app)/Home.tsx +++ b/client/src/pages/(app)/Home.tsx @@ -2,8 +2,7 @@ import { DataTable } from '@/components/data-table'; import { linksColumns } from '@/components/datagrids/links/links-columns'; import { MainNav } from '@/components/main-nav'; import { UserNav } from '@/components/user-nav'; - -import { useLinks } from '@/hooks/useLinks'; +import useLinks from '@/hooks/useLinks'; export default function HomeApp() { const { links } = useLinks();