From ab832f851b340152a3edd1507e9184238d862339 Mon Sep 17 00:00:00 2001 From: Iliyan Angelov Date: Tue, 18 Nov 2025 18:35:46 +0200 Subject: [PATCH] updates --- .../163657e72e93_add_page_content_table.py | 62 + ...e93_add_page_content_table.cpython-312.pyc | Bin 0 -> 4833 bytes ...ty_room_size_view_to_rooms.cpython-312.pyc | Bin 1585 -> 1585 bytes ...dd_map_url_to_page_content.cpython-312.pyc | Bin 0 -> 1072 bytes ...ce764ef7a50_add_map_url_to_page_content.py | 28 + Backend/src/__pycache__/main.cpython-312.pyc | Bin 13744 -> 14253 bytes Backend/src/main.py | 14 +- Backend/src/models/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 1468 -> 1546 bytes .../__pycache__/page_content.cpython-312.pyc | Bin 0 -> 2600 bytes Backend/src/models/page_content.py | 60 + .../page_content_routes.cpython-312.pyc | Bin 0 -> 16205 bytes Backend/src/routes/page_content_routes.py | 413 +++ Frontend/src/App.tsx | 88 +- Frontend/src/components/layout/Footer.tsx | 269 +- .../src/components/layout/SidebarAdmin.tsx | 87 +- Frontend/src/pages/AboutPage.tsx | 193 +- Frontend/src/pages/ContactPage.tsx | 81 +- Frontend/src/pages/HomePage.tsx | 44 +- .../pages/admin/AnalyticsDashboardPage.tsx | 1186 +++++++ .../src/pages/admin/BusinessDashboardPage.tsx | 1179 +++++++ .../src/pages/admin/PageContentDashboard.tsx | 1458 ++++++++ .../pages/admin/ReceptionDashboardPage.tsx | 2961 +++++++++++++++++ Frontend/src/pages/admin/SettingsPage.tsx | 937 ++++++ Frontend/src/services/api/index.ts | 2 + .../src/services/api/pageContentService.ts | 168 + 26 files changed, 8878 insertions(+), 355 deletions(-) create mode 100644 Backend/alembic/versions/163657e72e93_add_page_content_table.py create mode 100644 Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc create mode 100644 Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc create mode 100644 Backend/alembic/versions/cce764ef7a50_add_map_url_to_page_content.py create mode 100644 Backend/src/models/__pycache__/page_content.cpython-312.pyc create mode 100644 Backend/src/models/page_content.py create mode 100644 Backend/src/routes/__pycache__/page_content_routes.cpython-312.pyc create mode 100644 Backend/src/routes/page_content_routes.py create mode 100644 Frontend/src/pages/admin/AnalyticsDashboardPage.tsx create mode 100644 Frontend/src/pages/admin/BusinessDashboardPage.tsx create mode 100644 Frontend/src/pages/admin/PageContentDashboard.tsx create mode 100644 Frontend/src/pages/admin/ReceptionDashboardPage.tsx create mode 100644 Frontend/src/pages/admin/SettingsPage.tsx create mode 100644 Frontend/src/services/api/pageContentService.ts diff --git a/Backend/alembic/versions/163657e72e93_add_page_content_table.py b/Backend/alembic/versions/163657e72e93_add_page_content_table.py new file mode 100644 index 00000000..08c3a32b --- /dev/null +++ b/Backend/alembic/versions/163657e72e93_add_page_content_table.py @@ -0,0 +1,62 @@ +"""add_page_content_table + +Revision ID: 163657e72e93 +Revises: 6a126cc5b23c +Create Date: 2025-11-18 18:02:03.480951 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '163657e72e93' +down_revision = '6a126cc5b23c' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + # Only create the page_contents table, skip other schema changes + op.create_table('page_contents', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('page_type', sa.Enum('home', 'contact', 'about', 'footer', 'seo', name='pagetype'), nullable=False), + sa.Column('title', sa.String(length=500), nullable=True), + sa.Column('subtitle', sa.String(length=1000), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('meta_title', sa.String(length=500), nullable=True), + sa.Column('meta_description', sa.Text(), nullable=True), + sa.Column('meta_keywords', sa.String(length=1000), nullable=True), + sa.Column('og_title', sa.String(length=500), nullable=True), + sa.Column('og_description', sa.Text(), nullable=True), + sa.Column('og_image', sa.String(length=1000), nullable=True), + sa.Column('canonical_url', sa.String(length=1000), nullable=True), + sa.Column('contact_info', sa.Text(), nullable=True), + sa.Column('social_links', sa.Text(), nullable=True), + sa.Column('footer_links', sa.Text(), nullable=True), + sa.Column('hero_title', sa.String(length=500), nullable=True), + sa.Column('hero_subtitle', sa.String(length=1000), nullable=True), + sa.Column('hero_image', sa.String(length=1000), nullable=True), + sa.Column('story_content', sa.Text(), nullable=True), + sa.Column('values', sa.Text(), nullable=True), + sa.Column('features', sa.Text(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_page_contents_id'), 'page_contents', ['id'], unique=False) + op.create_index(op.f('ix_page_contents_page_type'), 'page_contents', ['page_type'], unique=True) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_page_contents_page_type'), table_name='page_contents') + op.drop_index(op.f('ix_page_contents_id'), table_name='page_contents') + op.drop_table('page_contents') + op.execute("DROP TYPE IF EXISTS pagetype") + # ### end Alembic commands ### + diff --git a/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d09e68da1da928eb1c2c74ba98152022e795a745 GIT binary patch literal 4833 zcmcIoU2Gdw7WUZJW5BJhGUAuP^>abTtat_W_xp?{*812BeRY%B!gk$0}i7)v=hLnOt}ZKB(f zEzfEs61hmOtCni$vB~jtEIHIaG&oF#Q)INiA|X~fHY6ugLy9suo9b60r%WQ-BsLDW zbS%}E8th3Xdy*ru?X;5TyA+K8Lh53ac}!Of37>nHX=ZP^#< zArER(ZH}C(M)&dO*;;qRCq0Pui|11w)cSbd=0V(Tp8Lzt(|e%qZ#5nEpw`Fxb`N6x zyzlr^P&+-S_3{3hx+5N`JK|9f;%*t*-%5(@fx5p{|LmWF+T}s5Pu`!aJL0jrBOdo4 zHuIgmHdgoe$FHEzdk|Y{NSxXSV)q|HY~_|~BlhzeyVgjkSWbp)9ux zIYQB#ktH<5T18p0X;7Xu@-`K(7=}$u8nB2#!%Ukg0|wG%P!HOwtr2wx+7jT28qqWM z+%yea`Pqu(M`*3+0Zc5#RC6};oD~bujM7M!*s@esqs?X7s}?PD=1Jj(VPcDhjf|_> z1e8~iNz^P%j7Ak%H*{5zH7RdG+v&O@srnUzHd=Vky6|c)$p1{;0Pht!1! zT5?NLF^gPYSFhy>ZGT8yO;9{)vNjdmDkE2VdFr=hDxNa5d=?@*30-~$l7%Mpe3pur zY*W=U6it%_n})^=LnE?I!>o;;Rv~$<7ff)>ESw>QQ-*HYrmR9*IKHZ5vOt9^|MqRO z)%uH+U<)LX(H9cq#G1E_T;en=4Xp=&=fO^5OjhQJjuWy*va_m^xK2!V$gmRL_Fvll zOwyYx&`>^?F=b55zk<0#a9RI>zJJlTBl?1&chBEE|Ni-Jk@)W9&B^!h?Xf#kx28UL zeRa@5NzR>LPOS_VhyOLSI$FwXEZlo?^G(U=_{Zja&Oz6>Qs%aKr*NyV8gSZPEZGh^ z$pu%JyH?`G`09Yu-nTK}pjWvdyL@rwa`E!g+oe_q4RNlz+_KVEY+E|KI_;no=W5Gi zE0e{^)k{uia%0>bQpd+97KMj2p`naJ4_@lqoe`xlPw4g7~u4IatCA9?b!yM=La3AIDdyllq z-aQ#@bzYv>#`@%$Q^0nB+-zP6kUy4PZ80VgVdu0E6cS~Be+7o2 Bszv|+ literal 0 HcmV?d00001 diff --git a/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc b/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc index 6a578129ee117b9965473dc9d6318afd2cd6df0f..30060d3e11ee619781001427545496a1226d93d6 100644 GIT binary patch delta 18 YcmdnUvyq4MG%qg~0}w=Q03Q2Ympq$O;^2G^VpU`Kr`b=5Z%TxMfuB7LR1x5MB8c?~2 z_xwtf9=th958h~Z8;v0aRsij60qmlXi`UUwpOO9oSj>fqez+#}y>pBCfqe3Oz3*XR zU)+l;;KaVP$0~#D#kDxpLaJPeGJ}i-ksNbb{|ft|LCGO*HLTqQH|aJV$69rWnxO2m zNMYRDp3`i&5g%BQw9xaVJ0Y#R)i8 z&0F?5aj|AOq_JAJwGBeq&;o6cSMwi(p7dLh-1Mu|!h~LenoI)~pp8x?l#sWVH95ya_kyuv}l@@NYeOkh%&60c^e_TM^*e5i0BBAuv ecJg?5yq!579tFsZw3Ut|a1XfC3p}TFJNg9`*aBt% literal 0 HcmV?d00001 diff --git a/Backend/alembic/versions/cce764ef7a50_add_map_url_to_page_content.py b/Backend/alembic/versions/cce764ef7a50_add_map_url_to_page_content.py new file mode 100644 index 00000000..112cf9eb --- /dev/null +++ b/Backend/alembic/versions/cce764ef7a50_add_map_url_to_page_content.py @@ -0,0 +1,28 @@ +"""add_map_url_to_page_content + +Revision ID: cce764ef7a50 +Revises: 163657e72e93 +Create Date: 2025-11-18 18:11:41.071053 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'cce764ef7a50' +down_revision = '163657e72e93' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + # Only add the map_url column to page_contents table + op.add_column('page_contents', sa.Column('map_url', sa.String(length=1000), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('page_contents', 'map_url') + # ### end Alembic commands ### diff --git a/Backend/src/__pycache__/main.cpython-312.pyc b/Backend/src/__pycache__/main.cpython-312.pyc index bc96cf665ab74a191e185482f5a1d024a52e2eba..65d9cdf6682761477030d9b9c124d0f14024b9f8 100644 GIT binary patch delta 4721 zcmbtXeN0=|6~EV?&n1Qp;}Vk?9>owK(2y=6fu_m$3^1T>2vK+qGY;>@*zrg9{0vQ+ z#OW3-?KCY^_m4)ZGHr`gWyMpNELG7yq)D6BuB$ZYGMQP6_Rs!U_D4h#Rh>HR<6N7& z=QLQR80npJ?mg#s&OPV&JnmaO{gX)P@2%FS5d8JLz1=_F@@DB)-BQI*8=u$JziL5p zsazB63Uy1}n8{+WC)6wTX5`vnU#MT|r{{H!_%jQbc|Zsw(jE9-R?ek?H*;56nYiS- zglj>UT>-Lpvq(4T-z*f8Y6uR8Mx>EUQaeB%l}2f^-f3(kHGvbA} z?YlZ5ngyOIGRbv`=eZ;wNW8G1&*B*Z>UiH&3w682eQKOo$!^}2HQRP`E%fphmCUui z^)Z8F?_-!>YyP+-ACwP4AHK@%;1ugwb!DySFU*d3H)%ellY1^37uK9`E}hJS?v?v; zW61rN(f={ifcYIijmF7H;6rW2CHZh-;OqRTnKG;g9nA+Vh=ic5^&KDafErH>6huQ@ zhORGwhCv?;4%1G{{&*-R#X_^vY-moJ%bSjG)9K6iVjPq^Qk7OY_%PyPV!=QbaZ(t{ zBG53GMWA6ei$KG87J-HlEdmWw%Fr9?FHCAt{e@{Q;-oOKMWA75i$KHR7J-J@W$5?@ zW{AH|G*0F=J^7zQ(i3GC`?_bK_YJjWL;QA^`Pce@4wN^=&%#_k(9Xgod4$`_qd7OC zbL3U|#nX)lDR0S0wg%5j=Yf|5t3unPZ5VCj{>md{`2ohH3z=)^1=-Cmu{^MJ5q8f9 z{_{A1t>l+T%{&euD0zi$9@8I+71U+hS_ONCC#_-EO#dxb(Xh=Czv#p4V+5|{N8nmM zmN>c~WDyvhwVt)sAu{MfN{uu`b_U*h(wAIMhTcZkb^MGNO+Xo>p_hl|h3|flmFDmq zdPP5h=5W*aJ?5gxm^Sx&qiUCXFlnlIUne%7>b`I^ruf4?Q6b-m z`4#%Nr(IQz{a$~N$f6n%2~(3bC6SY2l1e z-*GJK@VDtJ+Xp(jX2Fs7WRQqnRXiC{XT&ul(HoY<9FTY_qKMOxaMT}#V@;o>Ki@t8 z-%AE(o9PIPGfF$1Kf`166K4$`r}pX&`v7dGt=W*CsOnW?QDuxySC3Vk0?ljiL=VF^ z@88isFQL^hB|ohGlWxq-T(vRs5+g4HX)-HMGg)HfWkyagQo+b^Mkav#7(D`t6apk| z4iQ!HPe+v?tQP6RS|9#$QhKHoThq2PP+1S80XZb6>Q?dbDTXlk!;qd9hDAsld;7Y_ z52vlr8PBoZ9`}*H{=wr)0949pMl!LikeH&z&UnZyG)uV*icjH*M(Fk()yd!1J8;Jo zGiK@L1o)hn(&$V~l_PWEH8)ql8sQsTcVK)u>1g~Wwofs9#z{^cl8a5Hc*qut$Rrqj zHYYVzX$JeW#p6*~Z#|y0X<8v(l_)2fADI#M9h^2Pk(f%NN(XD%ZTgoWIq!Qz=2~y za!~mL*uXCUMcXa({g#iaEXF>3S7^U0jNBFWtO%u7OWr71s@QW!*h}ASea^8W*iwRX zS#YKV*RtTc*>L;llHj@{bSMAPI-zS{!GWf`-1z7EmYc_Jcpji8 z?6|h?A!Cp9NY}(j{i*?$s}eoh_AdS;`QNsAedh|&r%?GaDqpfSq-?Frw${Z{OQ`$~ zdSM;SOsai&xqUdJAE8(G%saj_xni|nodi}^nJQ~oE^AmRuS%7>KBoKkwheepXp8+J z!jL0NZOa>5mMRbZ3g3<{*?Uq#@5e&#zgG23`&rZhHTg|-SEnDNd)Vl&*WWW$_(lCa Hk;(r9GVDhi delta 4285 zcmbtXeN0nV6u-A$6rlyRwG=GnqaX+fiioJ-K#=-X6`bHBT07sRQ2K$dMFh8W8co#b zWVyyH8Z)EG;zpNjc3GTTvi;${{X>^6Y7>+Fr7r%1k|oA1%g$}NYbO%WHhJfqcklU~ z&v#z$``GtMpY~^sW)^~<>U2);!-el^vt&Qb{j_AI%-ETVM6GCcH+q_cCd}kK_Zm;L z&`eh;jluj;%uonnM4DX3V{#&OolLxo$;2WSgbGJxu{%IEzbbMbgj3{R@7W-1h=x`S z@Q2NE`t<0m^QVkOp9yJ%j4EzCx>+Md)F0i zCC*(}=!jMy{4Bo0rQtM$gf7zM`hu^-IXqX(xm9?sY_2ckdt4Ihz$+5jG*9@b*=7)xJxe0%rQe(OoN7zZ-p@@0`YVU-SmUh<-sX)A6GM>b3` z2j1zS3`xI9)4y^H`)B5Z*E!C~-bCcfwNOfga+5P^zJCg&efmZOrhrb5gh@=|)d17T z(1p{y8ls&XOV#6Ke8E5_5h)C15@?vqB+xLMNuXgolR(3WCV_@2W#}387bZ2S{=&2- z5h+Y;5@;COB+xLpNuXhN8QL(#4AHNY=6WkXk3|1bAMw{poQvzYhPXcA6tWK{?-9+O zTN)fB`^8OtC2$5lEh16l9ukItr@6B|IYJIbGdcLhy>Uqxjvm*BMH@T(@d$(iusY;= z%C#6zkKKry;oSL*rHlV&l$PoAS+0K)HDmG!c3~>$s)Gldn4S0G5W58qv0FoJqw1Ij zMnjq*O(7z!79^PkD=Bl)$#z5wv7STcWjr>Fc0l%mwaYr9KG74C2Js-;FW-R%acTP> z%tWa&lCsv}4>WFB7f#oIFEb2VTD<-Nsn_ew+vD@~^^yvSxE%pHZm?t(uXXggiI^Ag zPTva45ls(qxC1?Qf4~tK@JnsB+9BOQ!%0{IY)14WVuB$DDG0jYP7U+RN9_(~{r z;>_^pg<2fGYk7z}?Mz3}>+SX>7-lvj%I21)*IOeRD4c!!V*8f1mbL3TrC!*QT#Q7m zYakUVFwk!&`-nFn9fFO=aQS`oWJzwgc7YMsF%J?;Fu}odR{6^iG&L0@}~e zrDG)+9}FKaJ%sgfBMF&@^UJijD%EZeINop*yFH@XBN0b{NDk(04Q36H3F%FZI#g74uaGndqT!RoG@n)IJ^z%Sk#y?^kTH{6Fkx>Qx?} zIN&A?(hbmI-{}4y)wE;L4MU1@4Zf#dc2B+Oj#_&v?O57P-HKn;D`|b@lJL>W9WwLX zqVmhzFWDcX5^Q|0_6cL-awIDnlYunWNexRrnl+A;ST>f4u!8q$)M%cv#6=s z7&cYc$mxk?BgSJp?`ZU=b^^;V+|DSzl~H_WPWJ6NmRq!@w#wR}Lh1S^2tzb1j`U;c zHx2by9ar0L>KkvXn|@I@{WT_MyU+Yai2izRqoKP2-EUAj3*-;vI;Tngz{KQ#0ZBs9 AK>z>% diff --git a/Backend/src/main.py b/Backend/src/main.py index be697f93..6cc3a81a 100644 --- a/Backend/src/main.py +++ b/Backend/src/main.py @@ -42,15 +42,17 @@ if settings.is_development: logger.info("Creating database tables (development mode)") Base.metadata.create_all(bind=engine) else: - # Ensure new cookie-related tables exist even if full migrations haven't been run yet. + # Ensure new tables exist even if full migrations haven't been run yet. try: from .models.cookie_policy import CookiePolicy from .models.cookie_integration_config import CookieIntegrationConfig - logger.info("Ensuring cookie-related tables exist") + from .models.page_content import PageContent + logger.info("Ensuring required tables exist") CookiePolicy.__table__.create(bind=engine, checkfirst=True) CookieIntegrationConfig.__table__.create(bind=engine, checkfirst=True) + PageContent.__table__.create(bind=engine, checkfirst=True) except Exception as e: - logger.error(f"Failed to ensure cookie tables exist: {e}") + logger.error(f"Failed to ensure required tables exist: {e}") from .routes import auth_routes from .routes import privacy_routes @@ -125,9 +127,11 @@ app.add_exception_handler(Exception, general_exception_handler) # Enhanced Health check with database connectivity @app.get("/health", tags=["health"]) +@app.get("/api/health", tags=["health"]) async def health_check(db: Session = Depends(get_db)): """ Enhanced health check endpoint with database connectivity test + Available at both /health and /api/health for consistency """ health_status = { "status": "healthy", @@ -196,7 +200,7 @@ from .routes import ( room_routes, booking_routes, payment_routes, invoice_routes, banner_routes, favorite_routes, service_routes, service_booking_routes, promotion_routes, report_routes, review_routes, user_routes, audit_routes, admin_privacy_routes, - system_settings_routes, contact_routes + system_settings_routes, contact_routes, page_content_routes ) # Legacy routes (maintain backward compatibility) @@ -234,6 +238,8 @@ app.include_router(audit_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(admin_privacy_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(system_settings_routes.router, prefix=settings.API_V1_PREFIX) app.include_router(contact_routes.router, prefix=settings.API_V1_PREFIX) +app.include_router(page_content_routes.router, prefix="/api") +app.include_router(page_content_routes.router, prefix=settings.API_V1_PREFIX) logger.info("All routes registered successfully") diff --git a/Backend/src/models/__init__.py b/Backend/src/models/__init__.py index edb1f882..27187c9a 100644 --- a/Backend/src/models/__init__.py +++ b/Backend/src/models/__init__.py @@ -19,6 +19,7 @@ from .cookie_policy import CookiePolicy from .cookie_integration_config import CookieIntegrationConfig from .system_settings import SystemSettings from .invoice import Invoice, InvoiceItem +from .page_content import PageContent, PageType __all__ = [ "Role", @@ -48,5 +49,7 @@ __all__ = [ "SystemSettings", "Invoice", "InvoiceItem", + "PageContent", + "PageType", ] diff --git a/Backend/src/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/models/__pycache__/__init__.cpython-312.pyc index b8a9853dec24ddc996d4401b7c6f99ab0254c7f2..97f64fe34adc7c96787f086fc7d3fd241b3c9cae 100644 GIT binary patch delta 167 zcmdnP-NnOunwOW00SL;b$z&d$$ScWcuu)x-Q6_~UMJ&f4*D%U3*C@(}ks+NSMSKxs zlyRkmrsQTH##xL~Ot-iL64O(i^Ycnl^Ga@UfH)zQ1*w{{lbUM_~ZoE0RH<7`kz?jxS1N+i!6Ym0JrBX A4gdfE delta 92 zcmeC;*~86ynwOW00SJCZN@o^KB$1j r?aY3fhMSi#cQa0QXN}@A2kK)4;^L6Wt62khKC#GfGc~donF56X0G|~J diff --git a/Backend/src/models/__pycache__/page_content.cpython-312.pyc b/Backend/src/models/__pycache__/page_content.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ba79360df11dd88c50ece75db8dd6934511450f GIT binary patch literal 2600 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 import('./pages/auth/ResetPasswordPage')); // Lazy load admin pages const AdminDashboardPage = lazy(() => import('./pages/admin/DashboardPage')); -const RoomManagementPage = lazy(() => import('./pages/admin/RoomManagementPage')); const UserManagementPage = lazy(() => import('./pages/admin/UserManagementPage')); -const BookingManagementPage = lazy(() => import('./pages/admin/BookingManagementPage')); -const PaymentManagementPage = lazy(() => import('./pages/admin/PaymentManagementPage')); -const InvoiceManagementPage = lazy(() => import('./pages/admin/InvoiceManagementPage')); -const ServiceManagementPage = lazy(() => import('./pages/admin/ServiceManagementPage')); -const ReviewManagementPage = lazy(() => import('./pages/admin/ReviewManagementPage')); -const PromotionManagementPage = lazy(() => import('./pages/admin/PromotionManagementPage')); -const BannerManagementPage = lazy(() => import('./pages/admin/BannerManagementPage')); -const ReportsPage = lazy(() => import('./pages/admin/ReportsPage')); -const CookieSettingsPage = lazy(() => import('./pages/admin/CookieSettingsPage')); -const CurrencySettingsPage = lazy(() => import('./pages/admin/CurrencySettingsPage')); -const StripeSettingsPage = lazy(() => import('./pages/admin/StripeSettingsPage')); -const AuditLogsPage = lazy(() => import('./pages/admin/AuditLogsPage')); -const CheckInPage = lazy(() => import('./pages/admin/CheckInPage')); -const CheckOutPage = lazy(() => import('./pages/admin/CheckOutPage')); +const PageContentDashboardPage = lazy(() => import('./pages/admin/PageContentDashboard')); +const AnalyticsDashboardPage = lazy(() => import('./pages/admin/AnalyticsDashboardPage')); +const BusinessDashboardPage = lazy(() => import('./pages/admin/BusinessDashboardPage')); +const SettingsPage = lazy(() => import('./pages/admin/SettingsPage')); +const ReceptionDashboardPage = lazy(() => import('./pages/admin/ReceptionDashboardPage')); // Demo component for pages not yet created const DemoPage: React.FC<{ title: string }> = ({ title }) => ( @@ -299,65 +289,25 @@ function App() { path="users" element={} /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> } + path="business" + element={} /> - } + } + /> + } + /> + } /> } - /> - } - /> - } + element={} /> diff --git a/Frontend/src/components/layout/Footer.tsx b/Frontend/src/components/layout/Footer.tsx index 20c502ab..166117fb 100644 --- a/Frontend/src/components/layout/Footer.tsx +++ b/Frontend/src/components/layout/Footer.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { Hotel, @@ -15,8 +15,51 @@ import { Star } from 'lucide-react'; import CookiePreferencesLink from '../common/CookiePreferencesLink'; +import { pageContentService } from '../../services/api'; +import type { PageContent } from '../../services/api/pageContentService'; const Footer: React.FC = () => { + const [pageContent, setPageContent] = useState(null); + + useEffect(() => { + const fetchPageContent = async () => { + try { + const response = await pageContentService.getPageContent('footer'); + if (response.status === 'success' && response.data?.page_content) { + setPageContent(response.data.page_content); + } + } catch (err: any) { + console.error('Error fetching footer content:', err); + // Silently fail - use default content + } + }; + + fetchPageContent(); + }, []); + + // Default links + const defaultQuickLinks = [ + { label: 'Home', url: '/' }, + { label: 'Rooms & Suites', url: '/rooms' }, + { label: 'My Bookings', url: '/bookings' }, + { label: 'About Us', url: '/about' } + ]; + + const defaultSupportLinks = [ + { label: 'FAQ', url: '/faq' }, + { label: 'Terms of Service', url: '/terms' }, + { label: 'Privacy Policy', url: '/privacy' }, + { label: 'Contact Us', url: '/contact' } + ]; + + const quickLinks = pageContent?.footer_links?.quick_links && pageContent.footer_links.quick_links.length > 0 + ? pageContent.footer_links.quick_links + : defaultQuickLinks; + + const supportLinks = pageContent?.footer_links?.support_links && pageContent.footer_links.support_links.length > 0 + ? pageContent.footer_links.support_links + : defaultSupportLinks; + return (