From 97436e086d7b0375cd1382dd41ce21e5083f61c8 Mon Sep 17 00:00:00 2001 From: Alex Dyakonov <115474788+AlexDyakonov@users.noreply.github.com> Date: Wed, 29 Jan 2025 20:45:45 +0300 Subject: [PATCH] Dev merging (#8) * Update cd-dev.yaml * updates lint errors * added test command * added nginx.conf * added better functionality * added map page * added place parsing funcitonality and massive refactor * implemented useQuery, and better state management * quick fixes and new ways to add palces * marker update * fixed ugly scrollbar * updated markers * added reordering images * feat(image-upload): Added image uploading * added id to places * fix: type casting place.location and price * fix(place.module.tsx): remove unnecessary refresh fields with useEffect * added delete place button * fixed add-place buttons on screen * update headers --------- Co-authored-by: Mike de Geofroy Co-authored-by: timur Co-authored-by: timur Co-authored-by: Timur Valeev <76071256+PriestFaria@users.noreply.github.com> --- package.json | 1 + public/layers-2x.png | Bin 0 -> 1259 bytes public/layers.png | Bin 0 -> 696 bytes public/marker-icon-2x.png | Bin 0 -> 2464 bytes public/marker-icon.png | Bin 0 -> 1466 bytes public/marker-shadow.png | Bin 0 -> 618 bytes src/index.css | 179 ++++++++++++----------- src/modules/dashboard-sidebar.module.tsx | 4 +- src/modules/place.module.tsx | 134 ++++++++++++----- src/pages/add-place.page.tsx | 94 ++++++++---- src/shared/api/parse.api.ts | 20 +++ src/shared/api/places.api.ts | 54 ++++++- yarn.lock | 76 ++++++++++ 13 files changed, 410 insertions(+), 152 deletions(-) create mode 100644 public/layers-2x.png create mode 100644 public/layers.png create mode 100644 public/marker-icon-2x.png create mode 100644 public/marker-icon.png create mode 100644 public/marker-shadow.png diff --git a/package.json b/package.json index d31565e..cfe0aaa 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "preview": "vite preview" }, "dependencies": { + "@hello-pangea/dnd": "^17.0.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-scroll-area": "^1.2.0", diff --git a/public/layers-2x.png b/public/layers-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..200c333dca9652ac4cba004d609e5af4eee168c1 GIT binary patch literal 1259 zcmVFhCYNy;#0irRPomHqW|G1C*;4?@4#E?jH>?v@U%cy?3dQAc-DchXVErpOh~ z-jbon+tNbnl6hoEb;)TVk+%hTDDi_G%i3*RZ&15!$Fjr^f;Ke&A@|?=`2&+{zr+3a z{D*=t(`AXyS%X7N z%a#RZw6vD^t_rnM`L4E>m=U&R!A-&}nZIi$BOPvkhrCuUe@BN~-lRD)f44;J%TwgE zcze8u!PQ_NR7?o(NylLXVTfDO zxs5=@|GsYEsNo4M#nT%N!UE(?dnS)t2+{ELYAFp*3=iF=|EQnTp`#vlSXuGVraYo? z+RCzXo6h3qA8{KG?S4nE(lM+;Eb4nT3XV;7gcAxUi5m)`k5tv}cPy()8ZR3TLW3I- zAS^}cq-IJvL7a4RgR!yk@~RT%$lA7{L5ES*hyx)M4(yxI$Ub(4f)K|^v1>zvwQY!_ zIrWw8q9GS^!Dp~}+?mbnB6jDF8mVlbQ!jFKDY;w=7;XO{9bq7>LXGK24WA`;rL)_Z z)&j}pbV(;6gY;VMhbxgvn`X;6x}VUEE-7 z%)7j-%t8S=ZL3yc)HbXDAqJZvBTPoiW_A-+a8m3_Z?v{DN7Tnr#O_VUMT0UBt$;p` zDh6JbGHN8JJ*JN%y2%msb97@_S>9!%Egwk;?PEkU9ntz&3uR}%Fj5d$JHQbQb3}a{ zSzFT^#n=VInPpcAS}CNxj?_ zVscANk5Cfz(51EI1pz};AWWb|kgbYNb4wCEGUn3+eMUMV?1-{=I4TlmLJMot@rd07 zZuo2hk1ccu{YmGkcYdWAVdk{Z4Nm?^cTD&}jGm+Q1SYIXMwmG*oO*83&#>l%nbR`G zhh=lZ%xIb7kU3#;TBbfECrnC9P=-XpL|TG2BoZdj61*XiFbW8?1Z_wp%#;>${SUIy V$8qr;L*)Pf002ovPDHLkV1hYLS~36t literal 0 HcmV?d00001 diff --git a/public/layers.png b/public/layers.png new file mode 100644 index 0000000000000000000000000000000000000000..1a72e5784b2b456eac5d7670738db80697af3377 GIT binary patch literal 696 zcmV;p0!RIcP)*@&l2<6p=!C&s@#ZL+%BQvF&b?w6S%wp=I>1QHj7AP5C)IWy#b znXXB;g;j=$a-tW89K%FbDceHVq&unY*Wx3L#=EGWH=rjqnp|4c_Ulec!ql3#G-5ZF zVlbBA@XP=)C8U&+Lrc)S4O5%1$&{(;7R^K(CSnvSr$v;+B$8q&7Bf|h$#PARo1^%M zf1H^nG-EiXVXr07OH(*8R)xa|FD;lXUlg_-%)~ZGsL2cX0NXaAzN2q%jqLRR6ruVk8`Jb7n#{`T;o@`F= z#3YcynIR^s83UNF3D!f5m#Mg)NJ24&Qfrqb&_z=yF;=B)#9Iq7u-@^O!(mW{D;qvr zPc)gVb%aowtS8m@ElL4A9G>w#ffQ~q{i&_i)*6f^)Sz|C?C>zb4Uo?H<-&Hz@a?J; z$ml@zGygWofb9$ZBj6aLjpLhsT2AzjOu=-*u_gSCUYnU^5s62$4H-fe}gSR(=wKRaTHh!@*b)YV6mo|a4Fn6Rgc&Rpk zvn_X|3VY?v=>nJ{slE^V1GaGWk}m@aIWGIpghbfPh8m@aIWEo_%AZI>==moIFVE^L=C zZJ91?mo03UEp3-BY?wBGur6$uD{Yr9Y?m%SHF8Fk1pc(Nva%QJ+{FLkalfypz3&M|||Fn`7|g3c~4(nXHKFmRnwn$J#_$xE8i z|Ns9!kC;(oC1qQk>LMp3_a2(odYyMT@>voX=UI)k>1cJdn;gjmJ-|6v4nb1Oryh)eQMwHP(i@!36%vGJyFK(JTj?Vb{{C=jx&)@1l zlFmnw%0`&bqruifkkHKC=vbiAM3&E`#Mv>2%tw;VK8?_|&E89cs{a1}$J*!f_xd-C z&F%B|oxRgPlh0F!txkxrQjNA`m9~?&&|jw4W0<`_iNHsX$VQXVK!B}Xkh4>av|f_8 zLY2?t?ejE=%(TnfV5iqOjm?d;&qI~ZGl|SzU77a)002XDQchC<95+*MjE@82?VLm= z3xf6%Vd@99z|q|-ua5l3kJxvZwan-8K1cPiwQAtlcNX~ZqLeoMB+a;7)WA|O#HOB% zg6SX;754xD1{Fy}K~#8Ntklac&zTpadXZ& zC*_=T&g7hfbI$R?v%9?sknIb97gJOJ=`-8YyS3ndqN+Jm+x33!p&Hc@@L$w))s2@N ztv~i}Emc?DykgwFWwma($8+~b>l?tqj$dh13R^nMZnva9 zn0Vflzv2Dvp`oVQw{Guby~i`JGbyBGTEC{y>yzCkg>K&CIeQ$u;lyQ+M{O~gEJ^)Z zrF3p)^>|uT;57}WY&IRwyOQ=dq%Az}_t=_hKowP!Z79q0;@Zu(SWEJJcHY+5T6I({ zw)wj*SNi4wrd+POUfZe4gF77vW?j zoFS}|r2n&$U9Y!S4VEOyN}OpZZi|?cr1VcE_tHsDQgp-ga(SwkBrkCm{|*-yb=}ZW zvcYvLvfA90TPn|!-TuYJV<6`}+RJeRgP3EA=qQcF9k0*#*{f&I_pjam%I6Dd#YE|G zqB!R}tW-K!wV1w+4JcFA_s6~=@9F&j8`u$-ifLN3vK;`lvaA-`jRn_}(8|)!3?-}I zvFi{H;@A$gEZYh?%|Qr_y#*UkOPjwiRCsJQ>mb6h5yGIk6C5_XA=8T?IBfm_?+P0; zhhUs)-(0R*H<&Kku(1>#cGtOpk&Z&kQcw&SJv-4VY<+;=8hYnoX zfNJMCa9)^5Z0;2dCUk;x-%#yS!I~Jr3pNuI!g_tHz!$hKwt1GL~sFvx)3u4TA zv>CLGdQtoZ7Du7ctJRfTqY;FPxs1G{ZJ?73D5J@OO{6BHcPbk{_mjg&p2QFeke%QI zlAJ-kvjuwy1<5D-6>su68A+i998aSZNnQX)+Q}6(GK-C%8G-!1bOJBONU{gT%IOOE z;Yk24YC@^lFW77>r6x7eS1Omc;8=GUp#&zLQ&L{ zv8$hGC`wp~$9pR>f%-_Ps3>YhzP(+vC(E*zr1CVO8ChN^MI-VGMX7+|(r!SGZ9gd5 zzO9sQd>sm|f1|X&oh=8lOzd6+ITvo zCXInR?>RZ#>Hb*PO=7dI!dZ(wY4O}ZGv zdfQFio7+0~PN*RFCZGM6@9-o~y*@?;k00NvOsw54t1^tt{*ATMs^2j}4Wp=4t3RH* z_+8b`F-{E=0sOgM<;VHTo!Ij3u zmmI`2?K7g(GOcGA)@h?$SW&pwHdtj1n57PLI8&6RHhx4R%Q7b z^JEqR)@06V!pbS*@D_ZyRMo_LlT}r{#sXOx4kM-V<_V{!5SSuM^SIVCA37|nY7LWQ zZA#B1h4l`6asz=Lvax_#GMRX|NF>=$=p{Qn0i@ExX1jGhy@B8a*_uR+ODEbVi8ObL zezG?azy>E~S~dl43&8<$(2H}P&*tuBdESUP83KQ?8B z?K(!uS>H1wlWQz;qOfB`T#TZ=EoSp~vZ5XtCvwm1h*Ex6mzTsn_y@_=xREIslV-%- zpdWkEzMjeNOGWrSM32gpBt27*O29NdhGzuDgYxcf`Jjjqw@B;Vmdb@fxdhCRi`Kg> zmUTr$=&@#i!%F4Q6mb&4QKfR^95KJ!<6~fqx-f^66AV!|ywG{6D^Vay-3b99>XOe# e-I|>x8~*?ZhF3snGbtJX0000cOl4 literal 0 HcmV?d00001 diff --git a/public/marker-icon.png b/public/marker-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..950edf24677ded147df13b26f91baa2b0fa70513 GIT binary patch literal 1466 zcmV;r1x5OaP)P001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Amke9$Lam@{1K@O ze*LXqlKQHiv=gx+V^Cbb2?z@ISBQ*3amF;9UJ3SBg(N|710TLamQmYZ&Qjn2LuO<* zCZlB4n%@pc&7NNnY1}x+NWpHlq`OJEo|`aYN9<`RBUB+79g;>dgb6YlfN#kGL?lO_ z!6~M^7sOnbsUkKk<@Ysie&`G>ruxH&Mgy&8;i=A zB9OO!xR{AyODw>DS-q5YM{0ExFEAzt zm>RdS+ssW(-8|?xr0(?$vBVB*%(xDLtq3Hf0I5yFm<_g=W2`QWAax{1rWVH=I!VrP zs(rTFX@W#t$hXNvbgX`gK&^w_YD;CQ!B@e0QbLIWaKAXQe2-kkloo;{iF#6}z!4=W zi$giRj1{ zt;2w`VSCF#WE&*ev7jpsC=6175@(~nTE2;7M-L((0bH@yG}-TB$R~WXd?tA$s3|%y zA`9$sA(>F%J3ioz<-LJl*^o1|w84l>HBR`>3l9c8$5Xr@xCiIQ7{x$fMCzOk_-M=% z+{a_Q#;42`#KfUte@$NT77uaTz?b-fBe)1s5XE$yA79fm?KqM^VgLXD07*qoM6N<$ Ef<_J(9smFU literal 0 HcmV?d00001 diff --git a/src/index.css b/src/index.css index f263c0f..a5f516c 100644 --- a/src/index.css +++ b/src/index.css @@ -8,100 +8,115 @@ } :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 500; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 500; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } body { - margin: auto; - min-width: 320px; - min-height: 100vh; + margin: auto; + min-width: 320px; + min-height: 100vh; } @media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + +@layer utilities { + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } } @layer base { - :root { - --background: 0 0% 100%; - --foreground: 0 0% 3.9%; - --card: 0 0% 100%; - --card-foreground: 0 0% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 0 0% 3.9%; - --primary: 0 0% 9%; - --primary-foreground: 0 0% 98%; - --secondary: 0 0% 96.1%; - --secondary-foreground: 0 0% 9%; - --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; - --accent: 0 0% 96.1%; - --accent-foreground: 0 0% 9%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 89.8%; - --input: 0 0% 89.8%; - --ring: 0 0% 3.9%; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - --radius: 0.5rem; - } - .dark { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --card: 0 0% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 0 0% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - --ring: 0 0% 83.1%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } } @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } } diff --git a/src/modules/dashboard-sidebar.module.tsx b/src/modules/dashboard-sidebar.module.tsx index 3a77b18..5697f76 100644 --- a/src/modules/dashboard-sidebar.module.tsx +++ b/src/modules/dashboard-sidebar.module.tsx @@ -24,13 +24,13 @@ export default function DashboardSidebar() { onChange={(e) => setFilter(e.target.value)} /> -
+
{filteredPlaces?.map((x, index) => (
{ navigate(`${x.id}`); }} className="cursor-pointer px-3 py-2 h-24 hover:bg-gray-100 w-full">

- {x.title} + {x.title} {x.id}

{x.address} diff --git a/src/modules/place.module.tsx b/src/modules/place.module.tsx index 2238e12..8cd462e 100644 --- a/src/modules/place.module.tsx +++ b/src/modules/place.module.tsx @@ -1,5 +1,12 @@ import { useCallback, useEffect, useState } from "react"; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, +} from "@hello-pangea/dnd"; + import { Place, Tag } from "@/interfaces/place.interface"; import { Plus, X } from "lucide-react"; import { Label } from "@/components/ui/label"; @@ -9,6 +16,9 @@ import { TagComponent } from "@/components/tag"; import { Button } from "@/components/ui/button"; import { useDashboardStore } from "@/shared/stores/places.store"; +import { uploadImageByUrl } from "@/shared/api/parse.api"; +import { deletePlace } from "@/shared/api/places.api"; + export const PlaceModule = ({ inputPlace, onSave, @@ -19,7 +29,7 @@ export const PlaceModule = ({ const [place, setPlace] = useState(inputPlace); useEffect(() => { - setPlace(inputPlace); + if (inputPlace.title !== "") setPlace(inputPlace); }, [inputPlace]); const { tags } = useDashboardStore(); @@ -42,14 +52,17 @@ export const PlaceModule = ({ [place] ); - const handleAddPhoto = () => { + const handleAddPhoto = async () => { if (!place) return; - const newPhotoUrl = prompt("Enter the URL of the new photo:"); - if (newPhotoUrl) { - setPlace({ - ...place, - images: [...place.images, newPhotoUrl], - }); + const rawPhotoUrl = prompt("Enter the URL of the new photo:"); + if (rawPhotoUrl) { + const newPhotoUrl = await uploadImageByUrl(rawPhotoUrl); + if (newPhotoUrl.url) { + setPlace({ + ...place, + images: [...place.images, newPhotoUrl.url], + }); + } } }; @@ -75,47 +88,81 @@ export const PlaceModule = ({ }; const handleSave = useCallback(() => { - place.location.lat = Number(place.location.lat); place.location.lon = Number(place.location.lon); - + place.location.lat = Number(place.location.lat); place.priceAvg = Number(place.priceAvg); - onSave(place); }, [place, onSave]); + const handleDelete = () => { + deletePlace(place); + }; + const handleDragEnd = (result: DropResult) => { + if (!result.destination) return; + + const reorderedImages = Array.from(place.images); + const [movedImage] = reorderedImages.splice(result.source.index, 1); + reorderedImages.splice(result.destination.index, 0, movedImage); + + setPlace((prev) => ({ ...prev, images: reorderedImages })); + + console.log(place); + }; + return place !== null ? (

-
-
- {place.images.map((img, index) => ( -
- Place +
+ + + {(provided) => (
handleRemovePhoto(index)} + {...provided.droppableProps} + ref={provided.innerRef} + className="whitespace-nowrap h-fit space-x-5 text-gray-300 flex" > - + {place.images.map((img, index) => ( + + {(provided) => ( +
+ Place +
handleRemovePhoto(index)} + > + +
+
+ )} +
+ ))} + {provided.placeholder} +
+
+ +
+
-
- ))} -
-
- -
-
-
+ )} + +
-
+
@@ -211,7 +258,16 @@ export const PlaceModule = ({
-
+ +
+ + diff --git a/src/pages/add-place.page.tsx b/src/pages/add-place.page.tsx index 6909d7d..a664e1d 100644 --- a/src/pages/add-place.page.tsx +++ b/src/pages/add-place.page.tsx @@ -1,31 +1,51 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; + +import { Textarea } from "@/components/ui/textarea"; + import { Place } from "@/interfaces/place.interface"; import { PlaceModule } from "@/modules/place.module"; import { parsePlace } from "@/shared/api/parse.api"; import { usePlaces } from "@/shared/hooks/usePlaces"; -import React, { useState } from 'react'; + +import React, { useState } from "react"; import toast from "react-hot-toast"; import { useNavigate } from "react-router-dom"; export const AddPlacePage = () => { - const [url, setUrl] = useState(''); - const [parsedPlace, setParsedPlace] = useState | null>(null); + const [url, setUrl] = useState(""); + const [jsonInput, setJsonInput] = useState(""); + const [parsedPlace, setParsedPlace] = useState>({ + address: "", + description: "", + images: [], + location: { lat: 0, lon: 0 }, + priceAvg: 0, + reviewCount: 0, + reviewRating: 0, + shortDescription: "", + tags: [], + title: "", + updatedAt: "" + }); const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - + const [error, setError] = useState(""); const navigate = useNavigate(); - const { savePlace } = usePlaces(); const handleInputChange = (e: React.ChangeEvent) => { setUrl(e.target.value); }; - const handleParse = async () => { - setError(''); + + const handleJsonChange = (e: React.ChangeEvent) => { + setJsonInput(e.target.value); + }; + + const handleParseUrl = async () => { + setError(""); if (!url) { - setError('URL cannot be empty.'); + setError("URL cannot be empty."); return; } @@ -34,41 +54,65 @@ export const AddPlacePage = () => { const place = await parsePlace(url); setParsedPlace(place); } catch { - setError('Failed to parse the place. Please check the URL or API key.'); + setError("Failed to parse the place. Please check the URL or API key."); } finally { setLoading(false); } }; + const handleParseJson = () => { + setError(""); + try { + const place = JSON.parse(jsonInput); + if (typeof place !== "object" || !place.title) { + throw new Error("Invalid JSON structure."); + } + setParsedPlace(place); + } catch { + setError("Invalid JSON format."); + } + }; + const handleSave = async (place: Place) => { const newPlace = await savePlace.mutateAsync(place); navigate(`/dashboard/${newPlace.id}`); - toast.success("Place updated"); - } + toast.success("Place saved successfully!"); + }; return ( -
-
-
+
+ {/* URL Input Section */} +
+
+ {loading ? "Parsing..." : "Parse URL"} + +
+
+