made billboards dashboard page + general updates
This commit is contained in:
parent
4c25f8ca6c
commit
af5294a6f8
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
|
||||
5317
package-lock.json
generated
5317
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,10 @@
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@googlemaps/js-api-loader": "^1.16.6",
|
||||
"@nivo/bar": "^0.87.0",
|
||||
"@nivo/core": "^0.87.0",
|
||||
"@nivo/line": "^0.87.0",
|
||||
"@nivo/pie": "^0.87.0",
|
||||
"@reduxjs/toolkit": "^2.2.3",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@stripe/stripe-js": "^3.0.10",
|
||||
@ -26,10 +30,12 @@
|
||||
"@types/react-redux": "^7.1.23",
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chart.js": "^4.4.4",
|
||||
"colorthief": "^2.4.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"cookie": "^0.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"d3": "^7.9.0",
|
||||
"dompurify": "^3.1.0",
|
||||
"embla-carousel-autoplay": "^8.0.0-rc14",
|
||||
"embla-carousel-react": "^8.0.0-rc22",
|
||||
@ -46,6 +52,7 @@
|
||||
"next-sitemap": "^4.2.3",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-infinite-scroll-hook": "^4.1.1",
|
||||
"react-player": "^2.16.0",
|
||||
|
||||
183
public/pics/billboard/no-data.svg
Normal file
183
public/pics/billboard/no-data.svg
Normal file
@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
|
||||
<g id="BACKGROUND">
|
||||
|
||||
<ellipse transform="matrix(0.9475 -0.3196 0.3196 0.9475 -67.2231 78.8269)" style="fill:#F5F5FF;" cx="206.549" cy="244.221" rx="142.424" ry="160.232"/>
|
||||
</g>
|
||||
<g id="OBJECTS">
|
||||
<ellipse style="fill:#DFDFEB;" cx="349.605" cy="437.787" rx="60.92" ry="11.298"/>
|
||||
<g>
|
||||
<path style="fill:#D3DDF7;" d="M287.494,313.87H58.911c-5.61,0-10.158-4.548-10.158-10.158V160.014
|
||||
c0-5.61,4.548-10.158,10.158-10.158h228.583c5.61,0,10.158,4.548,10.158,10.158v143.698
|
||||
C297.652,309.322,293.104,313.87,287.494,313.87z"/>
|
||||
<path style="fill:#7584F1;" d="M287.494,149.856H58.911c-5.61,0-10.158,4.548-10.158,10.158v9.984h248.898v-9.984
|
||||
C297.652,154.404,293.104,149.856,287.494,149.856z"/>
|
||||
<g>
|
||||
<circle style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-miterlimit:10;" cx="256.366" cy="160.247" r="3.597"/>
|
||||
<circle style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-miterlimit:10;" cx="269.815" cy="160.247" r="3.597"/>
|
||||
<circle style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-miterlimit:10;" cx="283.265" cy="160.247" r="3.597"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#7584F1;" d="M150.813,200.125c-2.837,0-5.583-1.895-7.761-3.713l-2.652-2.213
|
||||
c-1.64-1.369-3.589-2.102-5.586-2.102h-22.625c-2.053,0-3.717,1.664-3.717,3.717v4.311v79.022c0,2.756,2.234,4.989,4.989,4.989
|
||||
h114.208c2.756,0,4.989-2.234,4.989-4.989v-74.032c0-2.756-2.234-4.989-4.989-4.989H150.813z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-miterlimit:10;" d="M223.563,216.111h-32.308c-1.44,0-2.608-1.168-2.608-2.608l0,0
|
||||
c0-1.44,1.168-2.608,2.608-2.608h32.308c1.44,0,2.608,1.168,2.608,2.608l0,0C226.171,214.944,225.003,216.111,223.563,216.111z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#BA6F4C;" d="M416.122,193.9c0,0,5.736,13.49,6.476,17.404c2.267,11.98,13.033,44.926,16.675,49.055
|
||||
c0,0,6.8,0.729,10.199-1.214c0,0-8.5-53.912-12.142-61.197c-3.643-7.285-6.071-19.185-6.071-19.185L416.122,193.9z"/>
|
||||
<g>
|
||||
<path style="fill:#BA6F4C;" d="M448.643,257.27c-3.761-3.129-11.35-1.271-12.673,3.439c-1.077,3.834-1.115,5.994-1.213,6.648
|
||||
c-0.191,1.279-4.845,9.195-3.953,10.819c0.645,1.176,3.936-2.52,5.583-5.114c0.339-0.534,1.158-0.33,1.214,0.3
|
||||
c0.312,3.522,1.004,10.567,1.611,11.443c0.813,1.173,3.77,6.141,4.647,5.603c0.877-0.539-0.107-3.998-0.877-6.313
|
||||
c-0.77-2.315,0.178-7.466,0.178-7.466s3.905,11.755,5.559,13.878c2.464,3.162,3.284-1.999,3.284-1.999
|
||||
c1.478,1.617,2.914-2.232,2.658-3.441c-0.505-2.389-4.206-10.852-3.551-10.542c0.655,0.31,4.83,9.25,6.725,9.511
|
||||
c1.895,0.262,0.977-2.965-0.086-5.101c-1.063-2.135-3.942-8.283-3.942-8.283s-0.964-5.823-4.542-12.08
|
||||
C449.235,258.522,448.687,257.307,448.643,257.27z"/>
|
||||
|
||||
<line style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" x1="451.925" y1="288.224" x2="446.798" y2="276.303"/>
|
||||
</g>
|
||||
<polygon points="413.418,171.436 359.745,169.244 359.745,90.049 387.996,85.97 "/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M387.158,85.297
|
||||
c0,0,10.489,24.656,19.479,57.893"/>
|
||||
<path style="fill:#BA6F4C;" d="M292.214,183.714c0,0,12.629,5.613,41.922-16.488l-12.454-13.857L292.214,183.714z"/>
|
||||
<path style="fill:#9EC68A;" d="M384.214,127.936c0.534-0.093,3.816,2.98,4.397,3.335c5.588,3.413,10.221,3.909,16.528,5.108
|
||||
c12.44,2.365,21.211,22.194,22.476,26.336c1.495,4.895,4.09,13.313,5.585,18.208c0,0-2.452,13.622-18.203,13.316l-7.893-16.839
|
||||
c0,0-8.946,20.171-9.472,21.75c-0.526,1.579,5.438,21.224,5.438,21.224s-47.008,18.067-74.371,8.332
|
||||
c0,0,9.281-35.462,6.787-36.613c-2.361-1.09-7.265-5.358-1.35-24.866c0,0-9.91-5.788-12.278-21.049
|
||||
c0,0,12.892-11.927,21.048-12.191C351.061,133.724,384.201,127.938,384.214,127.936z"/>
|
||||
<path style="fill:#FFFFFF;" d="M364.481,141.486c0,0,3.42,9.077,13.813,3.947c8.561-4.226,11.403-11.118,12.141-13.421
|
||||
c-3.572-2.039-6.222-4.076-6.222-4.076s-14.568,2.543-26.766,4.335C356.787,133.64,350.898,146.47,364.481,141.486z"/>
|
||||
<path style="fill:#BA6F4C;" d="M383.293,110.966l0.921,16.97c0,0-7.893,11.971-17.365,10.656l-1.842-10.787L383.293,110.966z"/>
|
||||
<path style="fill:#BA6F4C;" d="M367.254,307.55l5.642,71.039l1.231,46.393c0,0,10.245,6.626,15.524-3.082
|
||||
c0,0,9.468-48.149,8.405-64.702c-1.063-16.552,0.804-36.037,0.804-36.037L367.254,307.55z"/>
|
||||
<path d="M327.186,417.879c0,0-28.935,24.907-26.331,24.907c2.604,0,14.902-1.754,22.859-3.683
|
||||
c7.957-1.929,11.14-7.016,11.14-7.016l0.579,4.911l5.498-0.702c0,0,6.221-13.155,1.881-20.522
|
||||
C338.471,408.407,327.186,417.879,327.186,417.879z"/>
|
||||
<polygon points="331.237,433.315 335.433,436.998 337.458,430.771 334.203,429.982 "/>
|
||||
<path d="M374.089,418.844c0,0-10.808,32.342-7.161,32.757c6.944,0.789,15.372-6.885,17.976-11.796
|
||||
c2.604-4.911,2.315-12.629,2.315-12.629l-2.46,5.788h5.064c0,0,2.749-2.631,0.579-15.26
|
||||
C390.402,417.703,374.089,433.709,374.089,418.844z"/>
|
||||
<path style="fill:#BA6F4C;" d="M329.356,305.27l-2.17,112.609c0,0,9.983,6.841,15.625-2.105c0,0,11.285-45.254,10.851-61.041
|
||||
c-0.434-15.786,2.17-34.204,2.17-34.204L329.356,305.27z"/>
|
||||
<path d="M333.434,207.394c0,0,12.629,23.153,66.829,3.157c0,0,21.048,54.726,4.736,115.767c0,0-46.307,21.575-90.508-4.21
|
||||
C314.491,322.108,321.858,246.334,333.434,207.394z"/>
|
||||
<path style="fill:#BA6F4C;" d="M334.136,130.041c0,0-40.08,28.24-43.062,33.151c-2.982,4.911-9.209,16.137-0.439,20.347
|
||||
s43.588-34.116,52.007-47.096L334.136,130.041z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M298.003,182.136
|
||||
c0,0,12.498-6.797,27.407-23.811"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M336.212,192.546
|
||||
c0,0,33.18,15.9,53.702-1.64"/>
|
||||
|
||||
<line style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" x1="407.104" y1="177.4" x2="399.963" y2="166.211"/>
|
||||
|
||||
<line style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" x1="334.136" y1="167.227" x2="338.17" y2="159.158"/>
|
||||
<path style="fill:#BA6F4C;" d="M355.721,121.064c-2.254-0.215-4.401,2.147-5.26,1.878c-0.684-0.214-1.606-3.456-1.952-4.766
|
||||
c1.302-7.606,0.408-13.507-1.375-11.926c-0.095,0.085-0.207,0.262-0.324,0.49c-0.263-2.413-0.948-3.62-1.876-2.797
|
||||
c-0.254,0.225-0.615,1.069-0.938,1.943c-0.279-2.273-0.952-3.385-1.853-2.587c-0.444,0.393-1.208,2.663-1.539,3.693
|
||||
c-0.178-0.217-0.439-0.309-0.769-0.419c-1.288-0.429-4.258,10.698-4.401,11.271c-0.143,0.573-3.292,13.024-3.292,13.024
|
||||
c-1.145,4.508,7.853,6.577,10.501,5.576c2.648-1.002,8.677-7.508,8.677-7.508c-0.007-0.012-0.017-0.023-0.024-0.035
|
||||
c0.208-0.301,0.512-0.76,0.936-1.45c0.912-1.485,5.528-5.206,5.635-5.528S357.975,121.278,355.721,121.064z"/>
|
||||
<path d="M357.868,121.922c0.107-0.322,0.107-0.644-2.147-0.859c-2.254-0.215-4.401,2.147-5.26,1.878
|
||||
c-0.684-0.214-1.606-3.456-1.952-4.766c1.302-7.606,0.408-13.507-1.375-11.926c-0.095,0.085-0.207,0.262-0.324,0.49
|
||||
c-0.263-2.413-0.948-3.62-1.876-2.797c-0.254,0.225-0.615,1.069-0.938,1.943c-0.131-1.063-0.35-1.859-0.636-2.338
|
||||
c-0.294,7.765,0.566,19.538,7.459,25.919c0.312-0.327,0.502-0.531,0.502-0.531c-0.007-0.012-0.017-0.023-0.024-0.035
|
||||
c0.208-0.301,0.512-0.76,0.936-1.45C353.145,125.966,357.761,122.244,357.868,121.922z"/>
|
||||
<g>
|
||||
<path d="M381.254,114.189l2.039-3.223l-18.286,16.839l1.395,8.171C373.995,134.735,377.55,122.467,381.254,114.189z"/>
|
||||
<path style="fill:#BA6F4C;" d="M346.315,95.132c0,0-5.408,30.74,12.426,36.949c17.835,6.209,24.108-23.125,24.146-33.408
|
||||
c0.039-10.283-4.747-15.674-17.446-16.959C346.655,79.812,346.315,95.132,346.315,95.132z"/>
|
||||
<path d="M386.849,83.268c-3.649-5.279-9.009-8.183-22.449-7.939c-10.23,0.186-16.651,7.017-18.833,10.563
|
||||
c-0.707,1.149-2.454,4.396,0.91,7.882c0,0,0.112-2.465,2.387-5.21c0,0,21.288,11.538,29.794,9.844l2.028,7.977
|
||||
c0,0,2.878-4.084,5.835-5.405C386.521,100.98,390.498,88.548,386.849,83.268z"/>
|
||||
<path style="fill:#BA6F4C;" d="M390.227,108.652c-1.385,4.013-4.774,6.505-7.57,5.566c-2.796-0.938-3.939-4.952-2.554-8.965
|
||||
c1.385-4.013,4.774-6.505,7.57-5.566C390.468,100.625,391.612,104.639,390.227,108.652z"/>
|
||||
<path d="M363.953,108.317c-0.017,1.055-0.574,1.904-1.245,1.894c-0.671-0.009-1.202-0.873-1.185-1.928
|
||||
c0.017-1.056,0.574-1.904,1.245-1.894C363.439,106.398,363.969,107.261,363.953,108.317z"/>
|
||||
<path d="M349.889,108.273c0.039,0.993-0.42,1.818-1.025,1.843c-0.606,0.025-1.129-0.759-1.168-1.752
|
||||
c-0.039-0.993,0.42-1.818,1.025-1.843C349.326,106.495,349.849,107.28,349.889,108.273z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M368.492,100.242
|
||||
c0,0-4.877,0.719-4.902-3.865"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M352.217,98.81
|
||||
c0,0-1.501,4.161-4.958,1.471"/>
|
||||
<path d="M363.692,125.333c0,0,2.006-6.359,6.688-2.335L363.692,125.333z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;" d="M387.603,104.909
|
||||
c0,0-3.021-2.96-5.096,1.96c0,0,1.741,2.105,0.46,3.585c0,0,1.815,0.538,2.83-1.028"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;" d="M386.093,106.745
|
||||
c0,0-0.105-1.13-1.265-2.487"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M351.727,112.201
|
||||
c-0.651,0.724-0.835,1.626-0.595,2.629c0.237,0.991,1.188,1.645,2.192,1.69"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M384.871,114.221c0,1.508-1.222,2.73-2.73,2.73s-2.73-1.222-2.73-2.73
|
||||
c0-1.508,1.222-2.73,2.73-2.73S384.871,112.714,384.871,114.221z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M339.047,134.601
|
||||
c0,0,1.052,2.28,3.596,1.842"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M340.604,106.992
|
||||
c0,0,1.907,1.014-1.184,11.933c0,0,1.908,6.907,1.381,8.222c-0.526,1.315-3.157,3.508-3.947,3.596"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M345.976,123.814
|
||||
c0,0-2.631,2.982-2.719,7.104"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M343.995,105.886
|
||||
c0,0,0.621,8.369-0.695,12.907"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#DFDFEB;" d="M260.514,455.211c0,3.885-9.053,7.035-20.22,7.035c-11.167,0-20.22-3.15-20.22-7.035
|
||||
c0-3.885,9.053-7.035,20.22-7.035C251.462,448.176,260.514,451.326,260.514,455.211z"/>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#7E9E6E;" d="M239.841,350.569c0,0-6.115,0.572-4.034-15.26c1.977-15.04,9.559-26.004,10.138-26.048
|
||||
c0,0,10.904,15.641,4.069,29.205C243.18,352.03,239.841,350.569,239.841,350.569z"/>
|
||||
<path style="fill:#7E9E6E;" d="M289.89,327.68c0,0-14.006,16.787-48.095,28.614c0,0-0.21-4.475,11.579-15.306
|
||||
C265.162,330.158,291.521,322.542,289.89,327.68z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M289.89,327.68
|
||||
c0,0-26.707,4.192-48.095,28.614c0,0-2.101,2.671-2.362,8.417"/>
|
||||
<path style="fill:#7E9E6E;" d="M189.383,360.174c0,0,14.006,16.787,48.095,28.614c0,0,0.21-4.475-11.579-15.306
|
||||
C214.111,362.651,187.752,355.035,189.383,360.174z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M189.383,360.174
|
||||
c0,0,26.707,4.192,48.095,28.614c0,0,2.101,2.671,2.362,8.417"/>
|
||||
<path style="fill:#7E9E6E;" d="M208.302,394.539c0,0,9.316,7.616,29.532,1.52c0,0-0.261-3.896-7.822-7.508
|
||||
C222.45,384.94,206.947,390.951,208.302,394.539z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M208.302,394.539
|
||||
c0,0,15.408-9.081,29.532,1.52c0,0,1.41,1.272,2.044,6.024"/>
|
||||
<path style="fill:#7E9E6E;" d="M286.289,354.49c0,0-9.787,26.34-43.877,38.166c0,0-1.174-14.868,10.614-25.699
|
||||
C264.815,356.127,287.92,349.352,286.289,354.49z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M286.289,354.49
|
||||
c0,0-24.875,5.318-43.877,38.166c0,0-2.181,3.949-2.442,9.695"/>
|
||||
<path style="fill:#7E9E6E;" d="M234.006,358.642c0,0-2.867,4.662-14.162,3.21c-11.295-1.452-23.507-14.878-31.133-12.441
|
||||
C188.711,349.411,207.466,342.688,234.006,358.642z"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M241.873,422.615
|
||||
c-2.797-23.384-3.986-51.511-0.712-81.881c0,0,0.435-8.056,4.784-31.473"/>
|
||||
<path style="fill:none;stroke:#000000;stroke-linecap:round;stroke-miterlimit:10;" d="M191.042,348.805
|
||||
c0,0,18.964-4.598,42.964,9.837c5.814,3.497,5.55,10.09,5.18,15.228"/>
|
||||
</g>
|
||||
<path style="fill:#545FAC;" d="M218.155,412.943l6.576,40.481c0,2.481,7.015,4.491,15.668,4.491s15.669-2.011,15.669-4.491
|
||||
l6.576-40.481H218.155z"/>
|
||||
<ellipse cx="240.399" cy="412.944" rx="22.244" ry="4.491"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#D3DDF7;" d="M264.859,50.713c17.001,0.463,31.067,14.08,32.045,31.059c0.575,9.984-3.294,19.069-9.805,25.478
|
||||
c-0.962,0.947-1.409,2.297-1.184,3.628l2.445,14.47l-15.045-8.766c-0.857-0.499-1.867-0.683-2.839-0.486
|
||||
c-3.678,0.742-7.575,0.878-11.591,0.27c-14.863-2.252-26.459-14.556-27.83-29.527C229.228,66.887,245.165,50.176,264.859,50.713z
|
||||
"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M263.031,98.473c-0.457,0-0.807-0.145-1.05-0.434c-0.244-0.289-0.365-0.692-0.365-1.21c0-0.487,0.129-0.883,0.388-1.187
|
||||
c0.258-0.304,0.601-0.457,1.027-0.457c0.486,0,0.845,0.145,1.073,0.434c0.229,0.29,0.343,0.693,0.343,1.21
|
||||
c0,0.457-0.122,0.845-0.366,1.165C263.837,98.313,263.488,98.473,263.031,98.473z M262.75,87.101
|
||||
c-0.035-0.943-0.069-1.91-0.104-2.9c-0.035-0.989-0.061-1.979-0.078-2.969c-0.018-0.989-0.01-2.002,0.026-3.037V66.138h1.146
|
||||
l0.052,12.057c0,1.035-0.009,2.048-0.026,3.037c-0.018,0.99-0.044,1.98-0.078,2.969c-0.036,0.99-0.07,1.957-0.105,2.9H262.75z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="TEXTS">
|
||||
<text transform="matrix(1 0 0 1 132.677 250.9341)" style="font-family:'Lexend-Light'; font-size:18.1198px;">No Data</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
1055
public/pics/billboard/store.svg
Normal file
1055
public/pics/billboard/store.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 110 KiB |
@ -38,9 +38,9 @@ const LoginNeeded = ({ wrapperClass, itemClass, isVisible = false, onClose }:Log
|
||||
|
||||
return (
|
||||
<ClickOutside wrapperClass="login-needed" handleClass="login-needed-handle" className={`!p-0 !shadow-none`} onClickOutside={handleClickOutside}>
|
||||
<div className={`${show ? "visible -translate-y-4" : "invisible"} select-none block p-2 transition-transform duration-500 [transition-timing-function:cubic-bezier(0.2,1,0.3,1)] bottom-12 bg-market-input w-full sm:w-96 absolute shadow-lg left-1/2 -translate-x-1/2 rounded-xl z-20`}>
|
||||
<div className="flex flex-col space-y-6 items-center justify-center p-4 bg-white rounded-lg">
|
||||
<span className="block text-sm text-center">{translate("login-needed-text")}</span>
|
||||
<div className={`${show ? "visible -translate-y-4" : "invisible"} select-none block p-2 transition-transform duration-500 [transition-timing-function:cubic-bezier(0.2,1,0.3,1)] bottom-12 bg-market-input w-80 sm:w-96 absolute shadow-lg left-1/2 -translate-x-1/2 rounded-xl z-20`}>
|
||||
<div className="flex flex-col space-y-4 sm:space-y-6 items-center justify-center p-4 bg-white rounded-lg">
|
||||
<span className="block text-xs/6 sm:text-sm/7 text-center">{translate("login-needed-text")}</span>
|
||||
<Button
|
||||
type='button'
|
||||
text={translate("user-login-text")}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
interface IconProps {
|
||||
export type IconProps = {
|
||||
className?: string;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
@ -796,6 +796,18 @@ export const ArrowUpRightFromSquareSolid: React.FC<IconProps> = ({ className, on
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const UpRightFromSquareSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M352 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9L370.7 96 201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L416 141.3l41.4 41.4c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V32c0-17.7-14.3-32-32-32H352zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const SquareArrowUpRightSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M384 32c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96C0 60.7 28.7 32 64 32H384zM160 144c-13.3 0-24 10.7-24 24s10.7 24 24 24h94.1L119 327c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l135-135V328c0 13.3 10.7 24 24 24s24-10.7 24-24V168c0-13.3-10.7-24-24-24H160z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const TireLight: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M96 256C96 167.6 167.6 96 256 96C344.4 96 416 167.6 416 256C416 344.4 344.4 416 256 416C167.6 416 96 344.4 96 256zM381.7 280.2C383.2 272.3 384 264.3 384 256C384 219.2 368.5 186.1 343.7 162.7L305.5 215.4C314.5 226.4 320 240.6 320 256C320 257.4 319.1 258.7 319.9 260L381.7 280.2zM309.9 290.4C301.3 303.9 287.8 313.9 271.9 317.1L271.1 383C316.2 377.5 353.5 349.4 371.8 310.6L309.9 290.4zM239.1 383L239.9 317.1C224.1 313.9 210.6 303.9 202.1 290.4L140.2 310.6C158.5 349.4 195.7 377.5 239.1 383V383zM192.1 260C192 258.7 192 257.4 192 256C192 240.6 197.5 226.4 206.5 215.4L168.3 162.7C143.5 186.1 128 219.2 128 255.1C128 264.3 128.8 272.3 130.3 280.2L192.1 260zM232.4 196.5C239.7 193.6 247.7 192 256 192C264.3 192 272.3 193.6 279.6 196.5L317.8 143.9C299.5 133.8 278.4 128 256 128C233.6 128 212.5 133.8 194.2 143.9L232.4 196.5zM256 224C238.3 224 224 238.3 224 256C224 273.7 238.3 288 256 288C273.7 288 288 273.7 288 256C288 238.3 273.7 224 256 224zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 32C132.3 32 32 132.3 32 256C32 379.7 132.3 480 256 480C379.7 480 480 379.7 480 256C480 132.3 379.7 32 256 32z" />
|
||||
@ -1096,6 +1108,12 @@ export const MessagesSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const MessageSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64h96v80c0 6.1 3.4 11.6 8.8 14.3s11.9 2.1 16.8-1.5L309.3 416H448c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const InboxLight: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M508.3 304.9l-61.25-248.7C443.5 42 430.7 31.1 416 31.1H96c-14.69 0-27.47 10-31.03 24.25L3.715 304.9C1.248 314.9 0 325.2 0 335.5v96.47c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48v-96.47C512 325.2 510.8 314.9 508.3 304.9zM96 64h319.1l55.18 224H368c-6.062 0-11.59 3.438-14.31 8.844L326.1 352H185.9L158.3 296.8C155.6 291.4 150.1 288 144 288H40.84L96 64zM480 432c0 8.822-7.178 16-16 16h-416C39.18 448 32 440.8 32 432v-96.47C32 330.3 33.04 325.2 33.88 320h100.2l27.58 55.16C164.4 380.6 169.9 384 176 384h160c6.062 0 11.59-3.438 14.31-8.844L377.9 320h100.2C478.1 325.2 480 330.3 480 335.5V432z" />
|
||||
@ -1560,19 +1578,25 @@ export const WaterLight: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
}
|
||||
export const ArrowRotateLeftLight: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path xmlns="http://www.w3.org/2000/svg" d="M480 256c0 123.5-100.4 223.9-223.9 223.9c-69.41 0-133.9-31.3-176.7-86.05c-5.438-6.938-4.203-17 2.75-22.44c6.984-5.531 17.03-4.25 22.47 2.75C141.3 421.1 196.5 448 256 448c105.9 0 192-86.13 192-192s-86.13-192-192-192C187.1 64 124.5 100.7 90.15 160H176C184.8 160 192 167.2 192 176S184.8 192 176 192h-128C39.16 192 32 184.8 32 176v-128C32 39.16 39.16 32 48 32S64 39.16 64 48v93.56C104.4 73.87 176.6 32.11 256.1 32.11C379.6 32.11 480 132.5 480 256z" />
|
||||
<path d="M480 256c0 123.5-100.4 223.9-223.9 223.9c-69.41 0-133.9-31.3-176.7-86.05c-5.438-6.938-4.203-17 2.75-22.44c6.984-5.531 17.03-4.25 22.47 2.75C141.3 421.1 196.5 448 256 448c105.9 0 192-86.13 192-192s-86.13-192-192-192C187.1 64 124.5 100.7 90.15 160H176C184.8 160 192 167.2 192 176S184.8 192 176 192h-128C39.16 192 32 184.8 32 176v-128C32 39.16 39.16 32 48 32S64 39.16 64 48v93.56C104.4 73.87 176.6 32.11 256.1 32.11C379.6 32.11 480 132.5 480 256z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const ArrowRotateLeftRegular: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path xmlns="http://www.w3.org/2000/svg" d="M40 16C53.25 16 64 26.75 64 40v102.1C103.7 75.57 176.3 32.11 256.1 32.11C379.6 32.11 480 132.5 480 256s-100.4 223.9-223.9 223.9c-52.31 0-103.3-18.33-143.5-51.77c-10.19-8.5-11.56-23.62-3.062-33.81c8.5-10.22 23.66-11.56 33.81-3.062C174.9 417.5 214.9 432 256 432c97.03 0 176-78.97 176-176S353 80 256 80c-66.54 0-126.8 38.28-156.5 96H200C213.3 176 224 186.8 224 200S213.3 224 200 224h-160C26.75 224 16 213.3 16 200v-160C16 26.75 26.75 16 40 16z" />
|
||||
<path d="M40 16C53.25 16 64 26.75 64 40v102.1C103.7 75.57 176.3 32.11 256.1 32.11C379.6 32.11 480 132.5 480 256s-100.4 223.9-223.9 223.9c-52.31 0-103.3-18.33-143.5-51.77c-10.19-8.5-11.56-23.62-3.062-33.81c8.5-10.22 23.66-11.56 33.81-3.062C174.9 417.5 214.9 432 256 432c97.03 0 176-78.97 176-176S353 80 256 80c-66.54 0-126.8 38.28-156.5 96H200C213.3 176 224 186.8 224 200S213.3 224 200 224h-160C26.75 224 16 213.3 16 200v-160C16 26.75 26.75 16 40 16z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const RotateRightSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M463.5 224H472c13.3 0 24-10.7 24-24V72c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8H463.5z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const WavePulseSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
|
||||
<path xmlns="http://www.w3.org/2000/svg" d="M319.1 0c14.8-.4 27.9 9.3 31.8 23.6l74 271.2 17.7-35.4c10.8-21.7 33-35.4 57.2-35.4H608c17.7 0 32 14.3 32 32s-14.3 32-32 32H499.8L444.6 398.3c-5.9 11.9-18.6 18.8-31.8 17.5s-24.2-10.6-27.7-23.4L323.7 167.3 255.3 486.7c-3.1 14.4-15.5 24.8-30.2 25.3s-27.8-9.1-31.8-23.2L135.9 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H135.9c28.6 0 53.7 18.9 61.5 46.4L219.6 348 288.7 25.3C291.8 10.9 304.4 .4 319.1 0z" />
|
||||
<path d="M319.1 0c14.8-.4 27.9 9.3 31.8 23.6l74 271.2 17.7-35.4c10.8-21.7 33-35.4 57.2-35.4H608c17.7 0 32 14.3 32 32s-14.3 32-32 32H499.8L444.6 398.3c-5.9 11.9-18.6 18.8-31.8 17.5s-24.2-10.6-27.7-23.4L323.7 167.3 255.3 486.7c-3.1 14.4-15.5 24.8-30.2 25.3s-27.8-9.1-31.8-23.2L135.9 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H135.9c28.6 0 53.7 18.9 61.5 46.4L219.6 348 288.7 25.3C291.8 10.9 304.4 .4 319.1 0z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
@ -1852,4 +1876,28 @@ export const UnlockSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const ShopLockSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
|
||||
<path d="M36.8 192H449.6c20.2-19.8 47.9-32 78.4-32c30.5 0 58.1 12.2 78.3 31.9c18.9-1.6 33.7-17.4 33.7-36.7c0-7.3-2.2-14.4-6.2-20.4L558.2 21.4C549.3 8 534.4 0 518.3 0H121.7c-16 0-31 8-39.9 21.4L6.2 134.7c-4 6.1-6.2 13.2-6.2 20.4C0 175.5 16.5 192 36.8 192zM384 224H320V384H128V224H64V384v80c0 26.5 21.5 48 48 48H336c26.5 0 48-21.5 48-48V384 352 224zm144 16c17.7 0 32 14.3 32 32v48H496V272c0-17.7 14.3-32 32-32zm-80 32v48c-17.7 0-32 14.3-32 32V480c0 17.7 14.3 32 32 32H608c17.7 0 32-14.3 32-32V352c0-17.7-14.3-32-32-32V272c0-44.2-35.8-80-80-80s-80 35.8-80 80z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const PaletteSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M512 256c0 .9 0 1.8 0 2.7c-.4 36.5-33.6 61.3-70.1 61.3H344c-26.5 0-48 21.5-48 48c0 3.4 .4 6.7 1 9.9c2.1 10.2 6.5 20 10.8 29.9c6.1 13.8 12.1 27.5 12.1 42c0 31.8-21.6 60.7-53.4 62c-3.5 .1-7 .2-10.6 .2C114.6 512 0 397.4 0 256S114.6 0 256 0S512 114.6 512 256zM128 288c0-17.7-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32s32-14.3 32-32zm0-96c17.7 0 32-14.3 32-32s-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32zM288 96c0-17.7-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32s32-14.3 32-32zm96 96c17.7 0 32-14.3 32-32s-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const BagShoppingSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M160 112c0-35.3 28.7-64 64-64s64 28.7 64 64v48H160V112zm-48 48H48c-26.5 0-48 21.5-48 48V416c0 53 43 96 96 96H352c53 0 96-43 96-96V208c0-26.5-21.5-48-48-48H336V112C336 50.1 285.9 0 224 0S112 50.1 112 112v48zm24 96c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24zm200-24c0 13.3-10.7 24-24 24s-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
export const ChartSimpleSolid: React.FC<IconProps> = ({ className, onClick }) => {
|
||||
const Icon = <svg onClick={(e) => onClick && onClick(e)} className={`${className}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48H80c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H368c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z" />
|
||||
</svg>;
|
||||
return Icon;
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
||||
|
||||
const cmsAddress = () => {
|
||||
const serverAddress = process.env.NODE_ENV === 'production' ? `https://backend.flierland.ca` : `http://192.168.1.105:8055`;
|
||||
// const serverAddress = process.env.NODE_ENV === 'production' ? `http://192.168.1.105:8055` : `http://192.168.1.105:8055`;
|
||||
// const serverAddress = process.env.NODE_ENV === 'production' ? `https://backend.flierland.ca` : `http://192.168.1.105:8055`;
|
||||
const serverAddress = process.env.NODE_ENV === 'production' ? `http://192.168.1.105:8055` : `http://192.168.1.105:8055`;
|
||||
return serverAddress;
|
||||
}
|
||||
const defaultOptions = {
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
import { hasValue } from "services/general/general";
|
||||
import { BasketShoppingSolid, BrushSolid, CarMirrorsSolid, GamepadModernSolid, GraduationCapSolid, HandHoldingHeartSolid, HouseChimneySolid, PopcornSolid, ScaleBalancedSolid, StethoscopeSolid, UtensilsSolid } from "components/icons";
|
||||
import { Billboard } from "common/types/billboard";
|
||||
|
||||
interface GetBillboardCatIconProps {
|
||||
catId: number;
|
||||
billboard: Billboard;
|
||||
catIconClass: string;
|
||||
}
|
||||
interface CatIconProps {
|
||||
id: number;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
export const getHours = ({ billboard }: any) => {
|
||||
const date = new Date();
|
||||
@ -59,3 +71,22 @@ export const getHours = ({ billboard }: any) => {
|
||||
|
||||
return { hours: finalWeekDays, today: finalWeekDays.indexOf(today), isOpen: isOpen, nextOpenDay: nextOpenDay, daysTillNextOpenDay: Math.abs(daysTillNextOpenDay) <= 1 ? Math.abs(daysTillNextOpenDay) : 7 - Math.abs(daysTillNextOpenDay) };
|
||||
}
|
||||
|
||||
export const getBillboardCatIcon = ({ catId, billboard, catIconClass }: GetBillboardCatIconProps) => {
|
||||
const catIcons: CatIconProps[] = [
|
||||
{ id: 8, icon: <BasketShoppingSolid className={catIconClass} /> },
|
||||
{ id: 9, icon: <HouseChimneySolid className={catIconClass} /> },
|
||||
{ id: 10, icon: <StethoscopeSolid className={catIconClass} /> },
|
||||
{ id: 11, icon: <CarMirrorsSolid className={catIconClass} /> },
|
||||
{ id: 12, icon: <ScaleBalancedSolid className={catIconClass} /> },
|
||||
{ id: 13, icon: <UtensilsSolid className={catIconClass} /> },
|
||||
{ id: 14, icon: <BrushSolid className={catIconClass} /> },
|
||||
{ id: 15, icon: <GamepadModernSolid className={catIconClass} /> },
|
||||
{ id: 16, icon: <GraduationCapSolid className={catIconClass} /> },
|
||||
{ id: 17, icon: <PopcornSolid className={catIconClass} /> },
|
||||
{ id: 18, icon: <HandHoldingHeartSolid className={catIconClass} /> }
|
||||
];
|
||||
|
||||
const currecntCatIcon = catIcons.filter(x => (x.id === Number(catId) || x.id === Number(billboard.billboard_categories[0].billboard_categories_id.parent[0]?.related_billboard_categories_id.id)))[0].icon
|
||||
return currecntCatIcon;
|
||||
}
|
||||
@ -67,22 +67,22 @@ export const iranianLandlineNumberValidationRegex = new RegExp("^\\d{11}$");
|
||||
// Website general base path generator for both local and production
|
||||
// 167.99.189.252 live server's ip address
|
||||
export const basePath = () => {
|
||||
const basePath = process.env.NODE_ENV === 'production' ? `https://flierland.ca` : `http://localhost:3000`;
|
||||
// const basePath = process.env.NODE_ENV === 'production' ? `http://localhost:3000` : `http://localhost:3000`;
|
||||
// const basePath = process.env.NODE_ENV === 'production' ? `https://flierland.ca` : `http://192.168.1.105:3000`;
|
||||
const basePath = process.env.NODE_ENV === 'production' ? `http://localhost:3000` : `http://localhost:3000`;
|
||||
return basePath;
|
||||
}
|
||||
|
||||
// Website server address generator for both local and production
|
||||
export const serverAddress = () => {
|
||||
const serverAddress = process.env.NODE_ENV === 'production' ? `https://cdn.flierland.ca/assets/` : `http://192.168.1.105:8055/assets/`;
|
||||
// const serverAddress = process.env.NODE_ENV === 'production' ? `http://192.168.1.105:8055/assets/` : `http://192.168.1.105:8055/assets/`;
|
||||
// const serverAddress = process.env.NODE_ENV === 'production' ? `https://cdn.flierland.ca/assets/` : `http://192.168.1.105:8055/assets/`;
|
||||
const serverAddress = process.env.NODE_ENV === 'production' ? `http://192.168.1.105:8055/assets/` : `http://192.168.1.105:8055/assets/`;
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
// Website server address generator for both local and production
|
||||
export const cmsAddress = () => {
|
||||
const serverAddress = process.env.NODE_ENV === 'production' ? `https://backend.flierland.ca` : `http://192.168.1.105:8055`;
|
||||
// const serverAddress = process.env.NODE_ENV === 'production' ? `http://192.168.1.105:8055` : `http://192.168.1.105:8055`;
|
||||
// const serverAddress = process.env.NODE_ENV === 'production' ? `https://backend.flierland.ca` : `http://192.168.1.105:8055`;
|
||||
const serverAddress = process.env.NODE_ENV === 'production' ? `http://192.168.1.105:8055` : `http://192.168.1.105:8055`;
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ export const adminAccessToken = process.env.NODE_ENV === 'production' ? process.
|
||||
|
||||
// Strips html tags from text
|
||||
export const stripHtml = (text) => {
|
||||
const strippedText = text.replace(/(<([^>]+)>)/gi, '').replace(' ', '').replace(/‌/gi, ' ');
|
||||
const strippedText = text.replace(/(<([^>]+)>)/gi, '').replace(/ /gi, ' ').replace(/‌/gi, ' ').replace(/’/gi, "’");
|
||||
return strippedText;
|
||||
}
|
||||
// Strips html tags from text
|
||||
@ -124,6 +124,16 @@ export const hasValue = (value) => {
|
||||
return checkedValue;
|
||||
}
|
||||
|
||||
// one day to milliseconds
|
||||
export const oneDay = () => {
|
||||
let milliseconds = 86400000; // 24 * 60 * 60 * 1000 milliseconds in a day
|
||||
let seconds = 86400; // 24 * 60 * 60 mseconds in a day
|
||||
let minutes = 1440; // 24 * 60 minutes in a day
|
||||
let hours = 24; // 24 hours in a day
|
||||
|
||||
return { milliseconds: milliseconds, seconds: seconds, minutes: minutes, hours: hours };
|
||||
}
|
||||
|
||||
// get weekday
|
||||
export const getWeekDay = (date) => {
|
||||
let weekday = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
@ -173,6 +183,21 @@ export const getDatesBetween = (start, end) => {
|
||||
return datesBetween;
|
||||
}
|
||||
|
||||
// get dates weeks two dates
|
||||
export const getWeeksBetween = (start, end) => {
|
||||
const datesBetween = [];
|
||||
let startDate = new Date(mtd(start));
|
||||
let endDate = new Date(mtd(end));
|
||||
const oneDay = 86400000; // 24 * 60 * 60 * 1000 milliseconds in a day
|
||||
|
||||
while (endDate >= startDate) {
|
||||
datesBetween.push(new Date(endDate));
|
||||
endDate = new Date(endDate.getTime() - (7 * oneDay));
|
||||
}
|
||||
|
||||
return datesBetween.reverse();
|
||||
}
|
||||
|
||||
// get milliseconds & returns HH:MM string
|
||||
export const getDateHours = (milliseconds) => {
|
||||
const date = new Date(milliseconds);
|
||||
@ -773,7 +798,7 @@ export const getCatData = (cat) => {
|
||||
// Get the array of visited page IDs from local storage
|
||||
export const getVisitedPages = () => {
|
||||
const visitedPages = localStorage.getItem('visitedPages');
|
||||
let finalVisitedObject = "";
|
||||
let finalVisitedObject = { ads: [], billboards: [], billboard_news: [], articles: [], wiki: [] };
|
||||
if (hasValue(visitedPages)) {
|
||||
// ads
|
||||
if (!hasValue(JSON.parse(visitedPages).ads) || (hasValue(JSON.parse(visitedPages).ads) && JSON.parse(visitedPages).ads.length > 1000)) {
|
||||
@ -786,7 +811,10 @@ export const getVisitedPages = () => {
|
||||
}));
|
||||
}
|
||||
// billboards
|
||||
if (!hasValue(JSON.parse(visitedPages).billboards) || (hasValue(JSON.parse(visitedPages).billboards) && JSON.parse(visitedPages).billboards.length > 1000)) {
|
||||
if (
|
||||
!hasValue(JSON.parse(visitedPages).billboards) ||
|
||||
(hasValue(JSON.parse(visitedPages).billboards) && JSON.parse(visitedPages).billboards.length > 1000)
|
||||
) {
|
||||
localStorage.setItem('visitedPages', JSON.stringify({
|
||||
ads: JSON.parse(visitedPages).ads ?? [],
|
||||
billboards: [],
|
||||
@ -836,7 +864,7 @@ export const getVisitedPages = () => {
|
||||
// Add a new page ID to the array and update local storage
|
||||
export const addVisitedPage = (pageId, pageType) => {
|
||||
const visitedPages = getVisitedPages();
|
||||
visitedPages[pageType].push(pageId);
|
||||
visitedPages[pageType].push({ id: pageId, time: Date.now()});
|
||||
localStorage.setItem('visitedPages', JSON.stringify(visitedPages));
|
||||
};
|
||||
|
||||
|
||||
275
src/common/services/queries/directus/billboard.ts
Normal file
275
src/common/services/queries/directus/billboard.ts
Normal file
@ -0,0 +1,275 @@
|
||||
import { gql } from "@apollo/client";
|
||||
import { UUID } from "crypto";
|
||||
import { basePath } from "services/general/general";
|
||||
|
||||
|
||||
export const getBillboardFullDataQuery = (id: string | string[] | undefined, userId: string) => `
|
||||
Billboards (
|
||||
filter: {
|
||||
user_created: { id: {_eq: "${userId}"} },
|
||||
id : { _eq: "${id}" }
|
||||
},
|
||||
sort: ["sort", "-date_created"],
|
||||
limit: -1
|
||||
)
|
||||
{
|
||||
id
|
||||
date_created
|
||||
status
|
||||
visits
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
title
|
||||
body
|
||||
meta_description
|
||||
slogan
|
||||
faq
|
||||
}
|
||||
city {
|
||||
name
|
||||
English_name
|
||||
state {
|
||||
name
|
||||
English_name
|
||||
Initials
|
||||
}
|
||||
country {
|
||||
name
|
||||
}
|
||||
}
|
||||
address
|
||||
phone_number
|
||||
email
|
||||
website
|
||||
instagram
|
||||
facebook
|
||||
twitter
|
||||
youtube
|
||||
working_hours
|
||||
temporarily_closed
|
||||
brand_color
|
||||
dashboard_theme
|
||||
dashboard_color
|
||||
billboard_categories {
|
||||
billboard_categories_id {
|
||||
id
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
name
|
||||
}
|
||||
parent {
|
||||
related_billboard_categories_id {
|
||||
id
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cover_photo {
|
||||
id
|
||||
title
|
||||
description
|
||||
filename_download
|
||||
width
|
||||
height
|
||||
type
|
||||
}
|
||||
logo {
|
||||
id
|
||||
title
|
||||
description
|
||||
filename_download
|
||||
width
|
||||
height
|
||||
type
|
||||
}
|
||||
gallery {
|
||||
directus_files_id {
|
||||
id
|
||||
description
|
||||
filename_download
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
seats_data {
|
||||
id
|
||||
directus_files_id {
|
||||
id
|
||||
description
|
||||
filename_download
|
||||
width
|
||||
height
|
||||
}
|
||||
persian_name
|
||||
english_name
|
||||
types
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const getBillboardRatingsQuery = (id: string | string[] | undefined) => `
|
||||
billboard_ratings_aggregated(filter: { status: { _eq: "published" }, billboard_id: { id: { _eq: "${id}" } } }) {
|
||||
avg {
|
||||
service_quality
|
||||
}
|
||||
countDistinct {
|
||||
id
|
||||
}
|
||||
}
|
||||
billboard_ratings(filter: { status: { _eq: "published" }, billboard_id: { id: { _eq: "${id}" } } }) {
|
||||
id
|
||||
date_created
|
||||
user {
|
||||
id
|
||||
first_name
|
||||
last_name
|
||||
city {
|
||||
name
|
||||
English_name
|
||||
state {
|
||||
name
|
||||
English_name
|
||||
}
|
||||
}
|
||||
avatar {
|
||||
id
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
billboard_id {
|
||||
id
|
||||
}
|
||||
service_quality
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
title
|
||||
review
|
||||
}
|
||||
pics {
|
||||
directus_files_id {
|
||||
id
|
||||
title
|
||||
description
|
||||
filename_download
|
||||
width
|
||||
height
|
||||
type
|
||||
}
|
||||
}
|
||||
recommend
|
||||
useful {
|
||||
directus_users_id {
|
||||
id
|
||||
}
|
||||
}
|
||||
not_useful {
|
||||
directus_users_id {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const billboardProductsAggregatedQuery = (id: string | string[] | undefined) => `
|
||||
billboard_products_aggregated (
|
||||
filter: { status: { _eq: "published" }, billboard: { Billboards_id: { id: { _eq: "${id}" }}} }
|
||||
)
|
||||
{
|
||||
countDistinct {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const billboardOrdersQuery = (id: string | string[] | undefined) => `
|
||||
billboard_orders (
|
||||
filter: { billboard_id: { _eq: ${id} } },
|
||||
sort: ["sort", "-date_created"]
|
||||
)
|
||||
{
|
||||
id
|
||||
status
|
||||
date_created
|
||||
order_status
|
||||
billboard_id
|
||||
billboard_order_id
|
||||
user_id
|
||||
payment_id
|
||||
reservation_id
|
||||
ordered_products
|
||||
total_price
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
note
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const billboardNewsAggregatedQuery = (id: string | string[] | undefined) => `
|
||||
billboard_news_aggregated (
|
||||
filter: { status: { _eq: "published" }, billboards: { Billboards_id: { id: { _eq: "${id}" }}} }
|
||||
)
|
||||
{
|
||||
countDistinct {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// update billboard item
|
||||
export const updateBillboardItem = (id: string | string[] | undefined, payload: any) => gql`
|
||||
mutation {
|
||||
update_Billboards_item (
|
||||
id: ${id},
|
||||
data: ${payload}
|
||||
)
|
||||
{
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
// update billboard post request
|
||||
export const updateBillboard = async ({ id, payload }: { id: string | string[] | undefined, payload: any }) => {
|
||||
try {
|
||||
const response = await fetch(`${basePath()}/api/billboard/update-billboard`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: id,
|
||||
payload: payload
|
||||
}),
|
||||
});
|
||||
|
||||
// Check if the response is successful
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to update billboard: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const updateResponse = await response.json();
|
||||
return updateResponse; // Return the successful response
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error updating billboard:", error);
|
||||
// You can return a custom error object or just re-throw the error
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
19
src/common/services/queries/directus/general.ts
Normal file
19
src/common/services/queries/directus/general.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
// get currencies
|
||||
export const getGlobalCurrencies = `
|
||||
currencies
|
||||
(
|
||||
filter: {
|
||||
status: { _eq: "published" }
|
||||
},
|
||||
),
|
||||
{
|
||||
id
|
||||
persian_name
|
||||
english_name
|
||||
abbreviation
|
||||
sign
|
||||
}
|
||||
`
|
||||
|
||||
@ -27,11 +27,11 @@ export const updateAdViews = (views_count: number, id: string | string[] | undef
|
||||
`
|
||||
|
||||
// update billboard views
|
||||
export const updateBillboardViews = (views_count: number, id: string | string[] | undefined) => gql`
|
||||
export const updateBillboardViews = (visits: string[], id: string | string[] | undefined) => gql`
|
||||
mutation {
|
||||
update_Billboards_item (
|
||||
id: ${id},
|
||||
data: { views_count: ${views_count + 1} }
|
||||
data: { visits: [${visits}] }
|
||||
)
|
||||
{
|
||||
id
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import Link from "components/link/link"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { addVisitedPage, adminAccessToken, adminFetchPrivateData, basePath, fetchPrivateData, fetchPublicData, getLocaleTr, getSmartTime, getVisitedPages, hasValue, useGetRouter } from "services/general/general";
|
||||
import { addVisitedPage, adminAccessToken, adminFetchPrivateData, basePath, fetchPrivateData, fetchPublicData, getLocaleTr, getSmartTime, getVisitedPages, hasValue, oneDay, useGetRouter } from "services/general/general";
|
||||
import { CalendarDaysSolid, CopySolid, EllipsisSolid, EmptyHeart, EyeSolid, FilledHeart, ImageSharpSolid, PercentageSolid, ReplySolid, ShareNodesSolid, SplitSolid, Telegram, Whatsapp, XMark, XTwitter } from "components/icons";
|
||||
import Gallery from "components/gallery/gallery";
|
||||
import Image from "components/image/image";
|
||||
@ -313,7 +313,7 @@ const NewsDetails: React.FunctionComponent<NewsDetailsProps> = ({ billboardId, b
|
||||
const pageType: any = "billboard_news";
|
||||
const visitedPages = getVisitedPages();
|
||||
|
||||
if (hasValue(newsData) && !visitedPages[pageType].includes(String(newsData.id))) {
|
||||
if (hasValue(newsData) && !(visitedPages["billboard_news"]).some((x: { id: string, time: number }) => (x.id === String(newsData.id)) && (x.time > (Date.now() - oneDay().milliseconds * 30)))) {
|
||||
// If not, update the page views count and mark the page as visited
|
||||
addVisitedPage(newsData.id, pageType);
|
||||
updateViews();
|
||||
|
||||
@ -0,0 +1,169 @@
|
||||
import { Billboard } from 'common/types/billboard';
|
||||
import { getLocaleTr, hasValue, mtd, serverAddress, stripHtml, url, useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import Image from 'components/image/image';
|
||||
import { ArrowUpRightFromSquareSolid, BasketShoppingSolid, BellSolid, BoxArchiveSolid, ImageSharpSolid, MessagesSolid, PenSolid, SquareArrowUpRightSolid, UpRightFromSquareSolid } from 'components/icons';
|
||||
import StarRating from 'components/rating/star-rating';
|
||||
import { useMemo } from 'react';
|
||||
import InnerLoading from 'components/loading/inner-loading';
|
||||
import { getBillboardCatIcon } from 'services/billboard/general';
|
||||
import Button from 'components/button/button';
|
||||
import Link from 'components/link/link';
|
||||
|
||||
interface BillboardOverviewProps {
|
||||
data: Billboard;
|
||||
ratings: any;
|
||||
products: number;
|
||||
news: number;
|
||||
}
|
||||
|
||||
const BillboardOverview: React.FunctionComponent<BillboardOverviewProps> = ({ data, ratings, products, news }) => {
|
||||
|
||||
// states
|
||||
// const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
const billboardTitle = data ? getLocaleTr(data, locale).title : "";
|
||||
const highlighIconClass = "inline-block size-9 fill-current text-[var(--brand-color)] shrink-0 bg-[var(--light-brand-color)] rounded-full p-2";
|
||||
const highlights = [
|
||||
{ id: 1, title: translate("billboard-tabs-products"), icon: <BasketShoppingSolid className={highlighIconClass} />, value: products, link: "products" },
|
||||
{ id: 2, title: translate("billboard-tabs-updates"), icon: <BellSolid className={highlighIconClass} />, value: news, link: "news" },
|
||||
{ id: 3, title: translate("billboard-reviews-title"), icon: <MessagesSolid className={highlighIconClass} />, value: ratings.count, link: "reviews" },
|
||||
]
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, []);
|
||||
|
||||
// console.log(data);
|
||||
|
||||
// return
|
||||
return (
|
||||
<section
|
||||
style={{
|
||||
"--image-bg": hasValue(data.cover_photo) ? `url("${serverAddress()}${data.cover_photo?.id}/${data.cover_photo?.filename_download}")` : `url("${serverAddress()}${data.gallery[0].directus_files_id?.id}/${data.gallery[0].directus_files_id?.filename_download}")`,
|
||||
} as React.CSSProperties}
|
||||
className="block w-full px-0 rounded-b-[2.25rem] md:col-span-2"
|
||||
>
|
||||
{/* cover photo */}
|
||||
<div
|
||||
className={`block w-full relative lg:mt-[5px] h-48 sm:h-80 lg:h-[375px] bg-white
|
||||
${(data.cover_photo) ?
|
||||
"[background-image:var(--image-bg)] [background-size:cover] [background-position:50%_50%] md:[background-position:50%_55%]"
|
||||
:
|
||||
"[background-image:var(--image-bg)] md:[background-image:url('/pics/patterns/greyzz.webp')] max-md:[background-size:cover] [background-position:50%_50%] md:[background-position:50%_50%] md:bg-blend-multiply" }
|
||||
`}
|
||||
>
|
||||
<ImageSharpSolid className={`${(data.cover_photo) ? "hidden" : "hidden md:inline-block"} absolute top-16 sm:top-32 lg:top-36 left-1/2 -translate-x-1/2 -translate-y-1/2 w-12 sm:w-24 md:w-36 fill-gray-200 rounded-lg md:rounded-xl bg-white px-1 md:px-2`} />
|
||||
{/* Link to billboard */}
|
||||
<Link
|
||||
href={`/billboard/${data.id}/${url(getLocaleTr(data, locale).title)}`}
|
||||
target="_blank"
|
||||
className="reactive-button flex items-center justify-center w-max absolute top-4 rtl:right-4 ltr:left-4 mx-auto rounded-lg text-xs text-[var(--brand-color)] capitalize border-2 border-transparent ring-2 ring-white"
|
||||
>
|
||||
<div className="bg-white py-[6px] px-3 rounded-md">
|
||||
<SquareArrowUpRightSolid className="inline-block size-4 fill-current rtl:ml-2 ltr:mr-2" />
|
||||
{translate("visit-billboard")}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="block md:flex md:flex-col w-[calc(100%_-_2rem)] relative bg-white rounded-[2.25rem] pt-14 pb-2 px-4 -mt-10 mx-4 sm:pt-20 md:pt-10 md:pb-8 md:px-8 shadow-bot">
|
||||
<div className="flex items-start md:space-x-12 rtl:space-x-reverse">
|
||||
{/* logo */}
|
||||
<div className={`block max-md:absolute relative max-md:left-1/2 max-md:-translate-x-1/2 max-md:-top-10 size-20 sm:size-24 md:size-48 bg-white rounded-full ring-[6px] ring-white md:ring-[var(--light-brand-color)] md:border-2 md:border-white max-md:mx-auto sm:mt-0 shrink-0 overflow-hidden ${data.temporarily_closed ? "grayscale" : ""}`}>
|
||||
{hasValue(data.logo) ?
|
||||
<Image
|
||||
src={`${data.logo.id}/${data.logo.filename_download}`}
|
||||
alt={billboardTitle + " logo"}
|
||||
width={data.logo.width}
|
||||
height={data.logo.height}
|
||||
quality={75}
|
||||
ar={[1 / 1, 1 / 1, 1 / 1, 1 / 1]}
|
||||
imageSizes={[250, 250, 250, 250]}
|
||||
priority={true}
|
||||
noPreload={true}
|
||||
fetchPriority="low"
|
||||
className="rounded-full"
|
||||
figureClass="rounded-full w-full"
|
||||
/>
|
||||
:
|
||||
<span className="block size-full aspect-1/1 md:aspect-1/1 bg-gray-100 lg:bg-gray-50 rounded-full sm:border-[6px] border-gray-100">
|
||||
<ImageSharpSolid className="inline-block absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-12 xl:size-24 fill-gray-200" />
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col md:items-start">
|
||||
{/* title */}
|
||||
<h1 className="block max-md:text-center text-xl/9 md:text-2xl/10 xl:text-3xl px-2 md:px-0 mb-3 capitalize">{billboardTitle}</h1>
|
||||
|
||||
{/* rating */}
|
||||
<div className="flex items-center justify-center max-md:mx-auto mt-2 md:mt-2 space-x-2 rtl:space-x-reverse">
|
||||
<StarRating rating={ratings.totalRating} className="text-primary -mt-[2px]" starClass="size-4 shrink-0" />
|
||||
</div>
|
||||
|
||||
{/* mobile category */}
|
||||
<span className="flex md:hidden items-center justify-center w-max py-[6px] px-3 mt-5 mb-5 max-md:mx-auto rounded-lg text-xs text-[var(--brand-color)] bg-[var(--light-brand-color)] capitalize">
|
||||
{getBillboardCatIcon({ catId: Number(data.billboard_categories[0].billboard_categories_id.id), billboard: data, catIconClass: "inline-block size-4 fill-current text-current shrink-0 rtl:ml-2 ltr:mr-2"})}
|
||||
{getLocaleTr(data.billboard_categories[0].billboard_categories_id, locale).name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr className="block md:hidden w-full mb-1 pb-2 border-t border-dashed border-gray-200/75" />
|
||||
|
||||
{/* mobile description */}
|
||||
<p className="md:hidden w-full text-xs/7 text-center line-clamp-2 px-2 first-letter:capitalize">{stripHtml(getLocaleTr(data, locale).body)}</p>
|
||||
|
||||
{/* highlights */}
|
||||
<div className="grid grid-cols-3 md:w-80 xl:w-96 divide-x rtl:divide-x-reverse divide-[var(--light-brand-color)] max-md:px-2 py-3 rounded-lg mt-3 rtl:md:-mr-10 ltr:md:-ml-10 md:mt-5">
|
||||
{highlights.map(x => (
|
||||
<Link key={x.id} href={`/billboard/${data.id}/${x.link}`} className="reactive-button flex flex-col items-center space-y-3 px-2">
|
||||
{x.icon}
|
||||
<span className="inline-block text-xl font-extrabold text-gray-500">{x.value}</span>
|
||||
<span className="inline-block text-xs text-gray-500 capitalize">{x.title}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between md:mt-2 px-2 md:space-x-8 md:rtl:space-x-reverse">
|
||||
<div className="flex flex-col md:max-w-lg">
|
||||
{/* desktop category */}
|
||||
<span className="hidden md:flex items-center justify-center w-max py-[6px] px-3 mt-5 mb-4 max-md:mx-auto rounded-lg text-xs text-[var(--brand-color)] bg-[var(--light-brand-color)] capitalize">
|
||||
{getBillboardCatIcon({ catId: Number(data.billboard_categories[0].billboard_categories_id.id), billboard: data, catIconClass: "inline-block size-4 fill-current text-current shrink-0 rtl:ml-2 ltr:mr-2" })}
|
||||
{getLocaleTr(data.billboard_categories[0].billboard_categories_id, locale).name}
|
||||
</span>
|
||||
|
||||
{/* desktop description */}
|
||||
<p className="max-md:hidden w-full text-xs/7 line-clamp-2 first-letter:capitalize">{stripHtml(getLocaleTr(data, locale).body)}</p>
|
||||
</div>
|
||||
|
||||
{/* CTAs */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-1 gap-4 w-full py-3 mt-3 md:pb-0 md:w-64 xl:w-80 shrink-0">
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("edit")}
|
||||
className="rounded-xl bg-[var(--brand-color)] text-white text-sm py-2 px-2 shadow-none capitalize"
|
||||
leftIcon={<PenSolid className="inline-block size-4 fill-current rtl:ml-3 ltr:mr-3" />}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("archive")}
|
||||
className="rounded-xl bg-white ring-1 ring-[var(--brand-color)] text-[var(--brand-color)] text-sm py-2 px-2 shadow-none capitalize"
|
||||
leftIcon={<BoxArchiveSolid className="inline-block size-4 fill-current rtl:ml-3 ltr:mr-3" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default BillboardOverview
|
||||
@ -0,0 +1,155 @@
|
||||
import { hasValue, useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { CircleQuestionSolid, DropletSolid, PaletteSolid, RotateRightSolid, ShopLockSolid } from 'components/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Button from 'components/button/button';
|
||||
import BillboardSection from './section';
|
||||
import Modal from 'components/modal/modal';
|
||||
import { updateBillboard } from 'services/queries/directus/billboard';
|
||||
import Input from 'components/input/text';
|
||||
import Label from 'components/label/label';
|
||||
|
||||
interface BrandColorProps {
|
||||
themeColor: {
|
||||
light: string;
|
||||
full: string;
|
||||
};
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
const BrandColor: React.FunctionComponent<BrandColorProps> = ({ themeColor, brandColor }) => {
|
||||
|
||||
const defaultColor = hasValue(brandColor) ? brandColor : "#516ec2";
|
||||
// states
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [selectedColor, setSelectedColor] = useState(defaultColor);
|
||||
const [customColor, setCustomColor] = useState("");
|
||||
const [showColorError, setShowColorError] = useState(false);
|
||||
|
||||
// variables
|
||||
const { locale, query, router } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
const colors = [
|
||||
{ id: 1, value: "#4f46e5" },
|
||||
{ id: 2, value: "#7939ef" },
|
||||
{ id: 3, value: "#165ef0" },
|
||||
{ id: 4, value: "#2e90fb" },
|
||||
{ id: 5, value: "#006F46" },
|
||||
{ id: 6, value: "#009688" },
|
||||
{ id: 7, value: "#ed3224" },
|
||||
{ id: 8, value: "#e91e63" },
|
||||
{ id: 9, value: "#f26722" },
|
||||
{ id: 10, value: "#f0bb4b" },
|
||||
{ id: 11, value: "#000000" },
|
||||
{ id: 12, value: "#4d4d4d" },
|
||||
{ id: 13, value: "#795548" },
|
||||
];
|
||||
|
||||
const payload = `{
|
||||
brand_color: "${selectedColor}"
|
||||
}`
|
||||
|
||||
// methods
|
||||
const validateHexColor = (input: string): boolean => {
|
||||
const hexColorPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
||||
return hexColorPattern.test(input);
|
||||
};
|
||||
const handleCustomColorCode = (code: string) => {
|
||||
if (validateHexColor(code)) {
|
||||
setCustomColor(code);
|
||||
setSelectedColor(code);
|
||||
setShowColorError(false);
|
||||
} else {
|
||||
(showColorError && code.length === 0) ? setShowColorError(false) : setShowColorError(true);
|
||||
}
|
||||
}
|
||||
|
||||
const handleColorSelection = async () => {
|
||||
setUpdating(true);
|
||||
try {
|
||||
const result = await updateBillboard({ id: query.id, payload: payload });
|
||||
setUpdating(false);
|
||||
setCustomColor("");
|
||||
router.reload();
|
||||
} catch (error) {
|
||||
console.error("Update failed:", error);
|
||||
setUpdating(false);
|
||||
}
|
||||
}
|
||||
|
||||
// useEffects
|
||||
useEffect(() => {
|
||||
(hasValue(brandColor) && brandColor !== selectedColor) && setSelectedColor(brandColor);
|
||||
}, [brandColor]);
|
||||
|
||||
|
||||
// return
|
||||
return (
|
||||
<BillboardSection
|
||||
title={translate("dashboard-billboard-page-brand-color-title")}
|
||||
icon={<PaletteSolid className="inline-block size-4 text-[var(--brand-color)] fill-current" />}
|
||||
style={{
|
||||
"--selected-color": selectedColor
|
||||
} as React.CSSProperties}
|
||||
wrapperClass="md:col-span-2"
|
||||
>
|
||||
<p className="block text-xs/6 border-b border-dashed border-gray-200/75 pb-3">{translate("dashboard-billboard-page-brand-color-text")}</p>
|
||||
<span className="block text-sm font-semibold mt-4 pb-1 px-1">{translate("dashboard-billboard-page-brand-color-suggested-title")}</span>
|
||||
<div className="flex flex-col md:flex-row md:items-center md:space-x-8 md:rtl:space-x-reverse">
|
||||
<div className="grid grid-cols-7 gap-x-2 gap-y-4 w-80 max-w-full mt-3 relative mb-6 shrink-0">
|
||||
{colors.map(x => (
|
||||
<span
|
||||
key={x.id}
|
||||
style={{ "--item-color": x.value } as React.CSSProperties}
|
||||
className={`reactive-button inline-block size-9 shrink-0 bg-[var(--item-color)] border-[3px] border-white ring-[3px] ${selectedColor === x.value ? "ring-[var(--item-color)]" : "ring-white"} lg:hover:ring-[var(--item-color)] rounded-full`}
|
||||
onClick={() => selectedColor === x.value ? setSelectedColor(defaultColor) : setSelectedColor(x.value)}
|
||||
></span>
|
||||
))}
|
||||
<span
|
||||
key={14}
|
||||
className={`reactive-button flex items-center justify-center size-9 shrink-0 bg-[#516ec2] border-[3px] border-white ring-[3px] ${selectedColor === "#516ec2" ? "ring-[#516ec2]" : "ring-white"} rounded-full`}
|
||||
onClick={() => selectedColor === "#516ec2" ? setSelectedColor(defaultColor) : setSelectedColor("#516ec2")}
|
||||
>
|
||||
<RotateRightSolid className="inline-block size-4 fill-current text-white" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col md:space-y-6">
|
||||
<div className="flex items-center max-md:w-full space-x-4 rtl:space-x-reverse shrink-0">
|
||||
<Label forId="custom-brand-color-code" className="text-sm font-semibold px-1">{translate("dashboard-billboard-page-brand-color-custom-color-code")} :</Label>
|
||||
<Input
|
||||
id="custom-brand-color-code"
|
||||
type="text"
|
||||
placeholder="#000000"
|
||||
value={customColor}
|
||||
className="block w-24 bg-neutral-50 shadow-inner py-[6px] px-2 rounded-xl [direction:ltr] outline-none text-center"
|
||||
onInput={handleCustomColorCode}
|
||||
/>
|
||||
</div>
|
||||
{showColorError && <span className="block max-md:w-full text-xs/6 py-1 px-4 rounded-lg bg-error-bg text-error-text mt-4 shrink-0">- {translate("dashboard-billboard-page-brand-color-error")}</span>}
|
||||
<div className="flex items-center max-md:w-full space-x-4 rtl:space-x-reverse max-md:mt-3 max-md:pt-3 shrink-0">
|
||||
<span className="text-sm font-semibold px-1">{translate("dashboard-billboard-page-brand-color-selected-color")} :</span>
|
||||
<span className="inline-block size-8 rounded-full bg-[var(--selected-color)] border-[3px] border-white ring-[3px] ring-[var(--selected-color)]"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 w-full border-t border-dashed border-gray-200/75 pt-4 mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("apply-changes")}
|
||||
className="rounded-xl bg-[var(--brand-color)] text-white text-sm py-2 px-2 shadow-none capitalize"
|
||||
leftIcon={<DropletSolid className="inline-block size-4 fill-current rtl:ml-3 ltr:mr-3" />}
|
||||
loading={updating}
|
||||
onClick={handleColorSelection}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("reset")}
|
||||
className="rounded-xl bg-white ring-1 ring-[var(--brand-color)] text-[var(--brand-color)] text-sm py-2 px-2 shadow-none capitalize"
|
||||
leftIcon={<RotateRightSolid className="inline-block size-4 fill-current rtl:ml-3 ltr:mr-3" />}
|
||||
onClick={() => setSelectedColor(defaultColor)}
|
||||
/>
|
||||
</div>
|
||||
</BillboardSection>
|
||||
)
|
||||
}
|
||||
export default BrandColor
|
||||
@ -0,0 +1,77 @@
|
||||
import { useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { BlockBrickSolid, BrushSolid, ShopLockSolid } from 'components/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Button from 'components/button/button';
|
||||
import BillboardSection from './section';
|
||||
import Modal from 'components/modal/modal';
|
||||
import { updateBillboard } from 'services/queries/directus/billboard';
|
||||
import Select from 'components/select/select';
|
||||
|
||||
type Colors = "brand" | "system" | "custom";
|
||||
|
||||
interface DashboardColorProps {
|
||||
dashboardTheme: Colors;
|
||||
themeColor: {
|
||||
light: string;
|
||||
full: string;
|
||||
};
|
||||
}
|
||||
|
||||
const DashboardColor: React.FunctionComponent<DashboardColorProps> = ({ themeColor, dashboardTheme }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const dashboardThemeOptions = [
|
||||
{ code: "brand", label: translate("dashboard-billboard-page-dashboard-color-brand-label") },
|
||||
{ code: "system", label: translate("dashboard-billboard-page-dashboard-color-system-label") }
|
||||
];
|
||||
const getTheme = dashboardThemeOptions.filter(x => x.code === dashboardTheme)[0].label;
|
||||
|
||||
// states
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [dashboardColor, setDashboardColor] = useState(getTheme);
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
|
||||
// methods
|
||||
const handleThemeSelection = async (theme: Colors) => {
|
||||
const payload = `{
|
||||
dashboard_theme: "${dashboardThemeOptions.filter(x => x.label === theme)[0].code}"
|
||||
}`
|
||||
|
||||
try {
|
||||
const result = await updateBillboard({ id: query.id, payload: payload });
|
||||
setDashboardColor(theme);
|
||||
} catch (error) {
|
||||
console.error("Update failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// useEffects
|
||||
useEffect(() => {
|
||||
getTheme !== dashboardColor && setDashboardColor(getTheme);
|
||||
}, [dashboardTheme]);
|
||||
|
||||
// return
|
||||
return (
|
||||
<BillboardSection
|
||||
title={translate("dashboard-billboard-page-dashboard-color-title")}
|
||||
icon={<BrushSolid className="inline-block size-4 text-[var(--brand-color)] fill-current" />}
|
||||
>
|
||||
<p className="block text-xs/6">{translate("dashboard-billboard-page-dashboard-color-text")}</p>
|
||||
<Select
|
||||
id="news-cats-list"
|
||||
noLabel
|
||||
label=""
|
||||
value={dashboardColor}
|
||||
options={dashboardThemeOptions.map(x => x.label)}
|
||||
onChange={(theme) => handleThemeSelection(theme as any)}
|
||||
labelClass="text-sm px-1 font-semibold text-secondary-light capitalize"
|
||||
className="inline-block w-full px-2 rounded-lg bg-white capitalize text-sm py-1"
|
||||
wrapperClass="block w-52 bg-gray-100/75 rounded-xl py-2 px-2 mt-4"
|
||||
/>
|
||||
</BillboardSection>
|
||||
)
|
||||
}
|
||||
export default DashboardColor
|
||||
@ -0,0 +1,53 @@
|
||||
import { useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { ArrowLeftSolid, BellSolid, BoxOpenSolid, CircleDollarSolid, CircleQuestionSolid, MessagesSolid, UserTieHairSolid} from 'components/icons';
|
||||
import Link from 'components/link/link';
|
||||
|
||||
interface BillboardProductsNewsProps {
|
||||
|
||||
}
|
||||
|
||||
const BillboardProductsNews: React.FunctionComponent<BillboardProductsNewsProps> = ({ }) => {
|
||||
|
||||
// states
|
||||
// const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
const highlighIconClass = "inline-block size-9 fill-current text-[var(--brand-color)] shrink-0 bg-[var(--light-brand-color)] rounded-xl p-2";
|
||||
const parts = [
|
||||
{ id: 1, title: translate("dashboard-billboard-page-products-management"), icon: <BoxOpenSolid className={highlighIconClass} />, value: `/dashboard/billboards/${query.id}/products` },
|
||||
{ id: 2, title: translate("dashboard-billboard-page-news-management"), icon: <BellSolid className={highlighIconClass} />, value: `/dashboard/billboards/${query.id}/news` },
|
||||
{ id: 3, title: translate("dashboard-billboard-page-faq-management"), icon: <CircleQuestionSolid className={highlighIconClass} />, value: `/dashboard/billboards/${query.id}/faqs` },
|
||||
{ id: 4, title: translate("dashboard-billboard-page-service-providers-management"), icon: <UserTieHairSolid className={highlighIconClass} />, value: `/dashboard/billboards/${query.id}/service-providers` },
|
||||
{ id: 5, title: translate("dashboard-billboard-page-billing-management"), icon: <CircleDollarSolid className={highlighIconClass} />, value: `/dashboard/billboards/${query.id}/billing` },
|
||||
{ id: 6, title: translate("dashboard-billboard-page-reviews-management"), icon: <MessagesSolid className={highlighIconClass} />, value: `/dashboard/billboards/${query.id}/reviews` },
|
||||
]
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, []);
|
||||
|
||||
// console.log(data);
|
||||
|
||||
// return
|
||||
return (
|
||||
<section className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 w-full mt-4 px-4 md:col-span-2">
|
||||
{/* products & news management CTAs */}
|
||||
{parts.map(x => (
|
||||
<Link key={x.id} href={x.value} className="reactive-button bg-white flex items-center justify-between px-4 py-3 shadow-bot rounded-xl">
|
||||
<div className="flex items-center space-x-4 rtl:space-x-reverse">
|
||||
{x.icon}
|
||||
<span className="inline-block text-sm text-text-color capitalize">{x.title}</span>
|
||||
</div>
|
||||
<ArrowLeftSolid className="inline-block size-4 text-[var(--brand-color)] fill-current ltr:rotate-180" />
|
||||
</Link>
|
||||
))}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default BillboardProductsNews
|
||||
@ -0,0 +1,94 @@
|
||||
import { adminAccessToken, adminFetchPrivateData, hasValue, mtd, useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { ArrowLeftSolid, BagShoppingSolid, BasketShoppingSolid, BlockBrickSolid, ReceiptSolid, ShopLockSolid } from 'components/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Button from 'components/button/button';
|
||||
import BillboardSection from './section';
|
||||
import { BillboardOrdersItem } from 'common/types/billboard';
|
||||
import useSWR from 'swr';
|
||||
import { billboardOrdersQuery } from 'services/queries/directus/billboard';
|
||||
import InnerLoading from 'components/loading/inner-loading';
|
||||
import { UUID } from 'crypto';
|
||||
|
||||
interface SalesProps {
|
||||
|
||||
}
|
||||
|
||||
const Sales: React.FunctionComponent<SalesProps> = ({ }) => {
|
||||
|
||||
// states
|
||||
const [updating, setUpdating] = useState(false);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const { locale, query } = useGetRouter();
|
||||
const { data: orders, error: ordersError } = useSWR([billboardOrdersQuery(query.id), "", adminAccessToken], ([query, route, token]) => adminFetchPrivateData(route, token, query));
|
||||
ordersError && console.log(ordersError);
|
||||
const ordersList: BillboardOrdersItem[] = orders?.data.billboard_orders;
|
||||
|
||||
let customers: UUID[] = [];
|
||||
ordersList?.map(x => x.user_id).map(x => customers.indexOf(x) === -1 && customers.push(x));
|
||||
|
||||
const highlights = [
|
||||
{ id: 1, label: translate("dashboard-billboard-page-sales-total-sales"), value: hasValue(ordersList) ? ordersList.length : "-" },
|
||||
{ id: 2, label: translate("dashboard-billboard-page-sales-total-earnings"), value: hasValue(ordersList) && ordersList.length > 0 ? ordersList.map(x => x.total_price).reduceRight((total, x) => (total + x)).toFixed(2) : "0" },
|
||||
{ id: 3, label: translate("dashboard-billboard-page-sales-total-customers"), value: hasValue(ordersList) ? customers.length : "-" }
|
||||
]
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
// getTheme !== Sales && setSales(getTheme);
|
||||
// }, [dashboardTheme]);
|
||||
|
||||
// return
|
||||
return (
|
||||
<BillboardSection
|
||||
title={translate("dashboard-billboard-page-sales-title")}
|
||||
icon={<BagShoppingSolid className="inline-block size-4 text-[var(--brand-color)] fill-current" />}
|
||||
>
|
||||
{ordersList ?
|
||||
<>
|
||||
<p className="block text-xs/6 mb-4">{translate("dashboard-billboard-page-sales-text")}</p>
|
||||
<div className="grid grid-cols-3 w-full divide-x rtl:divide-x-reverse divide-[var(--light-brand-color)]">
|
||||
{highlights.map(x => (
|
||||
<div key={x.id} className="flex flex-col items-center space-y-2">
|
||||
<span className="inline-block text-center text-xl text-[var(--brand-color)] font-extrabold">{x.value}</span>
|
||||
<span className="inline-block text-center text-xs text-gray-500">{x.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<ul className="block w-full pt-4 mt-4 border-t border-dashed border-gray-200/75">
|
||||
<li className="text-sm font-semibold px-1 pb-4">{translate("dashboard-billboard-page-sales-latest-orders")}</li>
|
||||
{ordersList.length > 0 ?
|
||||
ordersList.slice(0, 3).map(x => (
|
||||
<li key={x.id} className="flex items-center justify-between w-full py-2 px-1 space-x-2 rtl:space-x-reverse border-b border-dashed border-neutral-100">
|
||||
{/* <span className="text-xs text-center text-text-gray">-</span> */}
|
||||
<span className="text-[0.7rem]/4 font-semibold w-60 rtl:pr-1 ltr:pl-1 line-clamp-1">- {(locale === "fa" ? x.ordered_products[0].persian_title : x.ordered_products[0].english_title)}</span>
|
||||
<span className="text-[0.7rem]/4 w-14 text-center font-extrabold py-1 px-2 rounded-md bg-success-bg text-success-text">${x.total_price}</span>
|
||||
{/* <span className="text-[0.7rem]/4 py-1 px-2 rounded-md bg-warning-bg text-warning-text">{translate(`billboard-orders-status-${x.order_status}`)}</span> */}
|
||||
</li>
|
||||
))
|
||||
:
|
||||
<div className="">
|
||||
<span className="block text-xs/6 mb-4 px-1">{translate("dashboard-billboard-page-no-sales-text")}</span>
|
||||
<img src="/pics/billboard/no-data.svg" alt="no data to show" className="block w-3/5 md:w-2/5 mx-auto mb-2" />
|
||||
</div>
|
||||
}
|
||||
</ul>
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("dashboard-billboard-page-sales-see-all")}
|
||||
className="block w-full py-2 px-4 rounded-xl text-sm bg-[var(--brand-color)] text-white mt-4"
|
||||
rightIcon={<ArrowLeftSolid className="inline-block size-4 text-white fill-current rtl:mr-3 ltr:ml-3" />}
|
||||
/>
|
||||
</>
|
||||
:
|
||||
<InnerLoading loadingText={""} width={"200"} height={"100"} />
|
||||
}
|
||||
</BillboardSection>
|
||||
)
|
||||
}
|
||||
export default Sales
|
||||
@ -0,0 +1,46 @@
|
||||
import { useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { ArrowLeftSolid } from 'components/icons';
|
||||
import Link from 'components/link/link';
|
||||
|
||||
interface BillboardSectionProps {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
linkText?: string;
|
||||
linkAddress?: string;
|
||||
showLink?: boolean;
|
||||
wrapperClass?: string;
|
||||
contentClass?: string;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const BillboardSection: React.FunctionComponent<BillboardSectionProps> = ({ title, icon, linkText, linkAddress, showLink = true, children, wrapperClass, contentClass, style }) => {
|
||||
|
||||
// states
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
|
||||
// return
|
||||
return (
|
||||
<section style={style} className={`block w-[calc(100%_-_2rem)] md:w-full pt-2 mx-auto shadow-bot bg-white rounded-xl ${wrapperClass}`}>
|
||||
<div className="flex items-center justify-between w-[calc(100%_-_1rem)] mx-auto py-2 px-3 rounded-t-lg rounded-b-sm bg-neutral-50">
|
||||
<div className="flex items-center space-x-2 rtl:space-x-reverse">
|
||||
{icon}
|
||||
<span className="text-sm font-semibold text-text-color capitalize">{title}</span>
|
||||
</div>
|
||||
{linkAddress && linkText && showLink && <Link href={linkAddress} className="">{linkText}</Link>}
|
||||
</div>
|
||||
<div className={`block w-full px-4 pt-2 pb-4 ${contentClass}`}>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default BillboardSection
|
||||
@ -0,0 +1,126 @@
|
||||
import { adminAccessToken, adminFetchPrivateData, getWeeksBetween, hasValue, mtd, useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { ArrowLeftSolid, ChartSimpleSolid, CircleSolid, EyeSolid, SackDollarSolid, StarSolid } from 'components/icons';
|
||||
import Link from 'components/link/link';
|
||||
import BillboardSection from './section';
|
||||
import useSWR from 'swr';
|
||||
import { billboardOrdersQuery } from 'services/queries/directus/billboard';
|
||||
import { BillboardOrdersItem } from 'common/types/billboard';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { Chart as ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Title, Tooltip, Legend, ChartOptions, } from 'chart.js';
|
||||
import InnerLoading from 'components/loading/inner-loading';
|
||||
import Button from 'components/button/button';
|
||||
|
||||
interface StatisticsOverviewProps {
|
||||
themeColor: {
|
||||
light: string;
|
||||
full: string;
|
||||
}
|
||||
visits: number[];
|
||||
ratings: any;
|
||||
}
|
||||
|
||||
const StatisticsOverview: React.FunctionComponent<StatisticsOverviewProps> = ({ themeColor, visits, ratings }) => {
|
||||
|
||||
// states
|
||||
// const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
|
||||
const today = new Date();
|
||||
const oneDay = 86400000; // 24 * 60 * 60 * 1000 milliseconds in a day
|
||||
const numberOfDaysToShow = 30 + 7; // 7 here is to count one week before the chart as well for better representation
|
||||
const pastMonthWeeks = getWeeksBetween(new Date(today.getTime() - (numberOfDaysToShow * oneDay)), today);
|
||||
const pastMonthWeeksLabels = pastMonthWeeks.map(x => mtd(x.getTime()).slice(5)).slice(1);
|
||||
const visitsAggregated = () => {
|
||||
let agrVisits = [];
|
||||
for (let i = 0; i < pastMonthWeeks.length - 1; i++) {
|
||||
agrVisits.push(visits?.filter(y => new Date(mtd(y)).getTime() > new Date(mtd(pastMonthWeeks[i].getTime())).getTime() && new Date(mtd(y)).getTime() <= new Date(mtd(pastMonthWeeks[i + 1].getTime())).getTime()));
|
||||
}
|
||||
return agrVisits.map(x => x.length);
|
||||
}
|
||||
|
||||
const { data: orders, error: ordersError } = useSWR([billboardOrdersQuery(query.id), "", adminAccessToken], ([query, route, token]) => adminFetchPrivateData(route, token, query));
|
||||
ordersError && console.log(ordersError);
|
||||
const ordersList: BillboardOrdersItem[] = orders?.data.billboard_orders;
|
||||
|
||||
const highlighIconClass = "inline-block size-2 fill-current text-[var(--brand-color)] shrink-0";
|
||||
const highlights = [
|
||||
{ id: 1, icon: <EyeSolid className={highlighIconClass} />, label: translate("dashboard-billboard-page-statistics-total-visits"), value: hasValue(visits) ? visits.length : "0" },
|
||||
{ id: 2, icon: <StarSolid className={`${highlighIconClass}`} />, label: translate("dashboard-billboard-page-statistics-total-ratings"), value: hasValue(ratings) ? `${ratings.totalRating} / 5` : "-" },
|
||||
{ id: 3, icon: <SackDollarSolid className={highlighIconClass} />, label: translate("dashboard-billboard-page-statistics-total-sales"), value: hasValue(ordersList) ? ordersList.length : "-" },
|
||||
]
|
||||
|
||||
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Title, Tooltip, Legend);
|
||||
|
||||
const data = {
|
||||
labels: pastMonthWeeksLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "",
|
||||
data: hasValue(visits) ? visitsAggregated() : pastMonthWeeksLabels.map(x => 0),
|
||||
fill: false,
|
||||
backgroundColor: themeColor.full,
|
||||
borderColor: themeColor.full,
|
||||
tension: 0.1,
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
const options: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'top' as const, // Ensure TypeScript understands this is a literal
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Line Chart Example',
|
||||
},
|
||||
},
|
||||
};
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, []);
|
||||
|
||||
// return
|
||||
return (
|
||||
<BillboardSection
|
||||
title={translate("dashboard-billboard-page-statistics-title")}
|
||||
icon={<ChartSimpleSolid className="inline-block size-4 text-[var(--brand-color)] fill-current" />}
|
||||
>
|
||||
{hasValue(ordersList) ?
|
||||
<>
|
||||
<div className="block space-y-3 mt-2">
|
||||
{highlights.map(x => (
|
||||
<div key={x.id} className="flex items-center space-x-2 rtl:space-x-reverse px-1">
|
||||
<CircleSolid className={highlighIconClass} />
|
||||
<span className="text-sm">{x.label} :</span>
|
||||
<span className="text-sm font-extrabold [direction:ltr]">{x.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="block pt-2 pb-4 mt-6 border-t border-dashed border-gray-200/75">
|
||||
<span className="block text-xs px-1 text-center py-2">{translate("dashboard-billboard-page-statistics-last-months-visits")}</span>
|
||||
<Line data={data} options={options} />
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("dashboard-billboard-page-statistics-see-all")}
|
||||
className="block w-full py-2 px-4 rounded-xl text-sm bg-[var(--brand-color)] text-white mt-4"
|
||||
rightIcon={<ArrowLeftSolid className="inline-block size-4 text-white fill-current rtl:mr-3 ltr:ml-3" />}
|
||||
/>
|
||||
</>
|
||||
:
|
||||
<InnerLoading loadingText={""} width={"200"} height={"100"} color={themeColor.full} />
|
||||
}
|
||||
</BillboardSection>
|
||||
)
|
||||
}
|
||||
export default StatisticsOverview
|
||||
@ -0,0 +1,109 @@
|
||||
import { useGetRouter } from 'services/general/general';
|
||||
import useTranslate from 'services/translation/translation';
|
||||
import { ShopLockSolid } from 'components/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Button from 'components/button/button';
|
||||
import BillboardSection from './section';
|
||||
import Modal from 'components/modal/modal';
|
||||
import { updateBillboard } from 'services/queries/directus/billboard';
|
||||
|
||||
interface TemporarilyClosedProps {
|
||||
isClosed: boolean;
|
||||
themeColor: {
|
||||
light: string;
|
||||
full: string;
|
||||
};
|
||||
}
|
||||
|
||||
const TemporarilyClosed: React.FunctionComponent<TemporarilyClosedProps> = ({ isClosed = false, themeColor }) => {
|
||||
|
||||
// states
|
||||
const [closed, setClosed] = useState(isClosed);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [popupOpen, setPopupOpen] = useState(false);
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
|
||||
const payload = `{
|
||||
temporarily_closed: ${!isClosed}
|
||||
}`
|
||||
|
||||
// methods
|
||||
const handleTemporarilyClose = () => {
|
||||
setPopupOpen(true);
|
||||
}
|
||||
const handleCloseModal = () => {
|
||||
setPopupOpen(false);
|
||||
}
|
||||
const handleTemporailyClose = async () => {
|
||||
setUpdating(true);
|
||||
try {
|
||||
const result = await updateBillboard({ id: query.id, payload: payload });
|
||||
setClosed(!closed);
|
||||
setUpdating(false);
|
||||
setPopupOpen(false);
|
||||
} catch (error) {
|
||||
console.error("Update failed:", error);
|
||||
setUpdating(false);
|
||||
}
|
||||
}
|
||||
|
||||
// useEffects
|
||||
useEffect(() => {
|
||||
isClosed !== closed && setClosed(isClosed);
|
||||
}, [isClosed]);
|
||||
|
||||
// return
|
||||
return (
|
||||
<BillboardSection
|
||||
title={translate("dashboard-billboard-page-temporarily-closed-title")}
|
||||
icon={<ShopLockSolid className="inline-block size-4 text-[var(--brand-color)] fill-current" />}
|
||||
>
|
||||
<Modal
|
||||
header={true}
|
||||
wrapperId={`temporarily-closed-modal`}
|
||||
open={popupOpen}
|
||||
onClose={handleCloseModal}
|
||||
title={translate("dashboard-billboard-page-temporarily-closed-title")}
|
||||
className="flex flex-col bg-white w-[90vw] h-auto max-h-[80vh] lg:w-auto lg:h-auto lg:min-w-[500px] lg:max-w-[90vw] lg:max-h-[90vh] rounded"
|
||||
childrenClass="lg:flierland-scrollbar"
|
||||
headerClass="!h-12"
|
||||
titleClass="text-base"
|
||||
closeClass="!size-6"
|
||||
style={{
|
||||
"--brand-color": themeColor.full,
|
||||
"--light-brand-color": themeColor.light,
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<div className="block w-full sm:w-[500px] p-gi">
|
||||
<p className="text-sm/7 sm:text-base/8">{closed ? translate("dashboard-billboard-page-temporarily-closed-modal-open-text") : translate("dashboard-billboard-page-temporarily-closed-modal-close-text")}</p>
|
||||
<div className="grid grid-cols-2 gap-4 px-2 pt-8">
|
||||
<Button
|
||||
type="button"
|
||||
text={closed ? translate("dashboard-billboard-page-temporarily-closed-false-label") : translate("dashboard-billboard-page-temporarily-closed-title")}
|
||||
className="bg-[var(--brand-color)] text-white text-sm md:text-base capitalize shadow-none w-full px-[10px] pt-[6px] pb-[6px] md:px-3 md:py-2 inline-flex justify-center items-center rounded-xl rtl:ml-2 ltr:mr-2 rtl:lg:ml-3 ltr:lg:mr-3"
|
||||
onClick={handleTemporailyClose}
|
||||
loading={updating}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
text={translate("cancel")}
|
||||
className="bg-white text-[var(--brand-color)] ring-1 ring-[var(--brand-color)] text-sm md:text-base capitalize shadow-none w-full px-[10px] pt-[6px] pb-[6px] md:px-3 md:py-2 inline-flex justify-center items-center rounded-xl"
|
||||
onClick={handleCloseModal}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<p className="block text-xs/6">{translate("dashboard-billboard-page-temporarily-closed-text")}</p>
|
||||
<div className="flex items-center justify-between mt-5">
|
||||
<div onClick={handleTemporarilyClose} className={`h-8 w-20 rounded-full p-1 transition duration-200 ease-in-out xl:cursor-pointer shadow-inner ${!closed ? "bg-neutral-100/75" : "bg-[var(--brand-color)]"} `}>
|
||||
<span className={`h-6 w-6 rounded-full inline-block pointer-events-none bg-white shadow-sm ring-1 ring-neutral-400/10 transition duration-200 ease-in-out ${!closed ? "rtl:-translate-x-12 ltr:translate-x-12" : "translate-x-0"}`}></span>
|
||||
</div>
|
||||
<span className={`text-sm font-semibold 2xl:text-base capitalize py-1 px-3 rounded-lg ltr:mr-2 rtl:ml-2 ${closed ? "bg-error-bg text-error-text" : "bg-success-bg text-success-text"}`}>{closed ? translate("dashboard-billboard-page-temporarily-closed-true-label") : translate("dashboard-billboard-page-temporarily-closed-false-label")}</span>
|
||||
</div>
|
||||
</BillboardSection>
|
||||
)
|
||||
}
|
||||
export default TemporarilyClosed
|
||||
@ -0,0 +1,29 @@
|
||||
import { hasValue } from "services/general/general";
|
||||
|
||||
interface ThemeColorProps {
|
||||
brandColor: string;
|
||||
dashboardTheme: "brand" | "system" | "custom";
|
||||
dashboardColor: string;
|
||||
}
|
||||
|
||||
export const ThemeColor = ({ brandColor, dashboardTheme, dashboardColor }: ThemeColorProps) => {
|
||||
|
||||
let finalColor = { light: "", full: "" };
|
||||
|
||||
switch (dashboardTheme) {
|
||||
case "brand":
|
||||
finalColor = { light: (hasValue(brandColor) ? `${brandColor}10` : "#f3f5fb"), full: (hasValue(brandColor) ? brandColor : "#516ec2") };
|
||||
break;
|
||||
case "system":
|
||||
finalColor = { light: "#f3f5fb", full: "#516ec2" };
|
||||
break;
|
||||
case "custom":
|
||||
finalColor = { light: `${dashboardColor}10`, full: dashboardColor };
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return finalColor
|
||||
}
|
||||
export default ThemeColor
|
||||
@ -1,31 +0,0 @@
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
|
||||
interface Billboards {
|
||||
|
||||
}
|
||||
|
||||
const Billboards: React.FunctionComponent<Billboards> = ({ }) => {
|
||||
|
||||
// states
|
||||
// const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, []);
|
||||
|
||||
// return
|
||||
return (
|
||||
<section className="block w-full px-6 py-6">
|
||||
Billboards
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default Billboards
|
||||
@ -6,19 +6,22 @@ import Header from "./header";
|
||||
import { setUserData, userData, userIsDataReady } from "common/redux/slices/user";
|
||||
import { userObject } from "services/user/user";
|
||||
import Loading from "components/loading/loading";
|
||||
import { adminAccessToken, adminFetchPrivateData, hasValue, useGetRouter } from "services/general/general";
|
||||
import { adminAccessToken, adminFetchPrivateData, fetchPublicData, hasValue, useGetRouter } from "services/general/general";
|
||||
import { getDictionary, setDictionary, setMenu } from "common/redux/slices/global";
|
||||
import useSWR from "swr";
|
||||
import { useAuth } from "services/user/AuthContext";
|
||||
import ProtectedRoute from "components/protected-route";
|
||||
import { useCachedGlobalData } from "services/general/global-data";
|
||||
import { globalDataQuery, useCachedGlobalData } from "services/general/global-data";
|
||||
import useSWRImmutable from "swr";
|
||||
|
||||
interface GeneralLayout {
|
||||
children: any,
|
||||
style?: React.CSSProperties;
|
||||
bodyClass?: string;
|
||||
}
|
||||
|
||||
// general layout
|
||||
const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children }) => {
|
||||
const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children, bodyClass, style }) => {
|
||||
|
||||
const [navOpen, setNavOpen] = useState(false);
|
||||
|
||||
@ -40,7 +43,7 @@ const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children }) =>
|
||||
const isUserDataAvailable = useAppSelector(userIsDataReady);
|
||||
const { data: messagesData, error: messageFetchError } = useSWR(isUserDataAvailable ? [adminAccessToken, messagesDataQuery, ""] : null, ([token, query, route]) => adminFetchPrivateData(route, token, query));
|
||||
const { data: globalData, error: globalDataError } = useCachedGlobalData();
|
||||
// const { data: translations, error: error } = useSWRImmutable(["", translationsQuery], ([route, query]) => fetchPublicData(route, query));
|
||||
// const { data: globalData, error: globalDataError } = process.env.NODE_ENV === 'production' ? useCachedGlobalData() : useSWRImmutable([globalDataQuery, ""], ([query, route]) => fetchPublicData(route, query));
|
||||
const loadingText = translate("general-loading");
|
||||
|
||||
if (messageFetchError) {
|
||||
@ -95,17 +98,18 @@ const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children }) =>
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
{trData.length > 0 ?
|
||||
<div className="flex w-full bg-market-input relative 2xl:px-[calc((100%_-_var(--max-width))/2)]">
|
||||
<div style={style} className="flex w-full bg-market-input relative 2xl:px-[calc((100%_-_var(--max-width))/2)]">
|
||||
<SideNavigation open={navOpen} onClose={toggleNav} />
|
||||
<div className="block w-full h-screen lg:w-[calc(100%_-_300px)] bg-white lg:bg-white">
|
||||
<Header open={navOpen} onClose={toggleNav} />
|
||||
<main className="block overflow-y-scroll h-[calc(100vh_-_74px)] lg:h-[calc(100vh_-_100px)] hide-scrollbar bg-gray-50">
|
||||
<main className={`block overflow-y-scroll h-[calc(100vh_-_74px)] lg:h-[calc(100vh_-_100px)] hide-scrollbar bg-gray-50 ${bodyClass}`}>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<Loading loadingText={loadingText} />
|
||||
// <Loading loadingText={loadingText} />
|
||||
""
|
||||
}
|
||||
</ProtectedRoute>
|
||||
)
|
||||
|
||||
@ -13,18 +13,22 @@ interface Header {
|
||||
const Header: React.FunctionComponent<Header> = ({ open, onClose }) => {
|
||||
|
||||
const user = useAppSelector(userData);
|
||||
const { asPath, router } = useGetRouter();
|
||||
const { asPath, router, pathname } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
const isSubPage = asPath.lastIndexOf("/") > 10
|
||||
|
||||
const pageTitle = () => {
|
||||
let title = "";
|
||||
|
||||
// ads
|
||||
// dashboard
|
||||
if (asPath === "/dashboard") title = translate("user-profile-menu-dashboard");
|
||||
// ads
|
||||
if (asPath === "/dashboard/ads") title = translate("user-profile-menu-ads");
|
||||
if (asPath === "/dashboard/ads/new-ad") title = translate("profile-create-new-ad");
|
||||
if (asPath.includes("/dashboard/ads/new-ad?id=")) title = translate("edit-ad");
|
||||
// billboards
|
||||
if (asPath === "/dashboard/billboards") title = translate("user-profile-menu-billboards");
|
||||
if (pathname === "/dashboard/billboards/[id]") title = translate("dashboard-billboard-page-title");
|
||||
// account
|
||||
if (asPath === "/dashboard/account") title = translate("user-profile-menu-account");
|
||||
if (asPath.includes("/dashboard/account/edit-details?id=")) title = translate("edit-user-details");
|
||||
|
||||
@ -7,7 +7,7 @@ import Skeleton from "components/skeleton/skeleton";
|
||||
import { resetUserData, userData } from "common/redux/slices/user";
|
||||
import { basePath, hasValue, useGetRouter } from "services/general/general";
|
||||
import LanguageSelection from "common/templates/general/header/language-selection";
|
||||
import { AdvertiseLight, AdvertiseSolid, Grid2Light, Grid2Solid, InboxFullSolid, InboxLight, SignOutLight, UserVneckHairLight, UserVneckHairSolid, WalletLight, WalletSolid } from "components/icons";
|
||||
import { AdvertiseLight, AdvertiseSolid, Grid2Light, Grid2Solid, InboxFullSolid, InboxLight, SignOutLight, SignsPostLight, SignsPostSolid, UserVneckHairLight, UserVneckHairSolid, WalletLight, WalletSolid } from "components/icons";
|
||||
import { useAuth } from "services/user/AuthContext";
|
||||
|
||||
interface SideNavigation {
|
||||
@ -56,6 +56,12 @@ const MenuItem: React.FunctionComponent<MenuItem> = ({ href, icon, activeIcon, t
|
||||
case "AdvertiseSolid":
|
||||
finalIcon = <AdvertiseSolid className={getIconClass} />
|
||||
break;
|
||||
case "SignsPostLight":
|
||||
finalIcon = <SignsPostLight className={getIconClass} />
|
||||
break;
|
||||
case "SignsPostSolid":
|
||||
finalIcon = <SignsPostSolid className={getIconClass} />
|
||||
break;
|
||||
case "UserVneckHairLight":
|
||||
finalIcon = <UserVneckHairLight className={getIconClass} />
|
||||
break;
|
||||
@ -144,10 +150,9 @@ const SideNavigation: React.FunctionComponent<SideNavigation> = ({ open, onClose
|
||||
<nav className="block w-full pt-0 pb-3 lg:py-4">
|
||||
<MenuItem href="" icon="Grid2Light" activeIcon="Grid2Solid" text="user-profile-menu-dashboard" />
|
||||
<MenuItem href="/ads" icon="AdvertiseLight" activeIcon="AdvertiseSolid" text="user-profile-menu-ads" />
|
||||
{/* <MenuItem href="/billboards" icon="SignsPostLight" activeIcon="SignsPostSolid" text="user-profile-menu-billboards" /> */}
|
||||
<MenuItem href="/account" icon="UserVneckHairLight" activeIcon="UserVneckHairSolid" text="user-profile-menu-account" />
|
||||
<MenuItem href="/wallet" icon="WalletLight" activeIcon="WalletSolid" text="user-profile-menu-wallet" />
|
||||
{/* <MenuItem href="/billboards" icon="signs_post_light" activeIcon="signs_post_solid" text="user-profile-menu-billboards" />
|
||||
<MenuItem href="/events" icon="calendar_star_light" activeIcon="calendar_star_solid" text="user-profile-menu-events" /> */}
|
||||
<MenuItem href="/inbox" icon="InboxLight" activeIcon="InboxFullSolid" text="user-profile-menu-inbox" notifications={user.inbox.new_messages} />
|
||||
{/* <MenuItem href="/comments" icon="messages_light" activeIcon="messages_solid" text="user-profile-menu-comments" /> */}
|
||||
</nav>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { UUID } from "crypto";
|
||||
import { currencies } from "./general";
|
||||
import { PaymentData } from "./payments";
|
||||
import { ServiceProvider } from "./service-provider";
|
||||
@ -5,6 +6,8 @@ import { UserProps } from "./user";
|
||||
|
||||
export type Billboard = {
|
||||
id: string;
|
||||
date_created: string;
|
||||
status: "draft" | "published" | "archived";
|
||||
translations: {
|
||||
languages_code: {
|
||||
code: string;
|
||||
@ -32,8 +35,11 @@ export type Billboard = {
|
||||
type: string;
|
||||
}
|
||||
brand_color: string;
|
||||
dashboard_theme: "brand" | "system" | "custom";
|
||||
dashboard_color: string;
|
||||
billboard_categories: {
|
||||
billboard_categories_id: {
|
||||
id: string;
|
||||
translations: {
|
||||
languages_code: {
|
||||
code: string;
|
||||
@ -114,6 +120,7 @@ export type Billboard = {
|
||||
seats_numbers: string[];
|
||||
}[];
|
||||
}[];
|
||||
visits: number[];
|
||||
}
|
||||
|
||||
export type BillboardReview = {
|
||||
@ -541,4 +548,42 @@ export type BillboardNewsItem = {
|
||||
pinned: boolean;
|
||||
likes: number;
|
||||
seen_by: number;
|
||||
}
|
||||
|
||||
export type BillboardOrdersItem = {
|
||||
id: string;
|
||||
status: "draft" | "published" | "archived";
|
||||
date_created: string;
|
||||
order_status: "pending" | "confirmed" | "processing" | "on-hold" | "shipped" | "delivered" | "completed" | "canceled" | "failed" | "returned" | "refunded";
|
||||
billboard_id: number;
|
||||
billboard_order_id: number;
|
||||
user_id: UUID;
|
||||
payment_id: UUID;
|
||||
reservation_id: number;
|
||||
ordered_products: {
|
||||
id: number;
|
||||
product_id: number;
|
||||
persian_title: string;
|
||||
english_title: string;
|
||||
original_price: number;
|
||||
final_price: number;
|
||||
price_unit: string;
|
||||
ordered_amount: string;
|
||||
ordered_size: string;
|
||||
ordered_color: {
|
||||
key: number,
|
||||
collection: "Colors"
|
||||
}
|
||||
currency: {
|
||||
key: number;
|
||||
collection: "currencies";
|
||||
}
|
||||
}[];
|
||||
total_price: number;
|
||||
translations: {
|
||||
languages_code: {
|
||||
code: string;
|
||||
};
|
||||
note: string;
|
||||
}[];
|
||||
}
|
||||
25
src/pages/api/billboard/update-billboard.ts
Normal file
25
src/pages/api/billboard/update-billboard.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { adminDataFetch } from 'common/data/apollo-client';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { adminAccessToken } from 'services/general/general';
|
||||
import { updateBillboardItem } from 'services/queries/directus/billboard';
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === 'POST') {
|
||||
|
||||
let id = req.body.id;
|
||||
let payload = req.body.payload;
|
||||
|
||||
try {
|
||||
const { data } = await adminDataFetch("", adminAccessToken).mutate({
|
||||
mutation: updateBillboardItem(id, payload),
|
||||
});
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
return res.status(400).json(error);
|
||||
}
|
||||
|
||||
} else {
|
||||
res.setHeader('Allow', 'POST');
|
||||
res.status(405).end('Method Not Allowed');
|
||||
}
|
||||
};
|
||||
@ -4,7 +4,7 @@ import client, { adminDataFetch } from "../../../common/data/apollo-client";
|
||||
import { useDispatch } from 'react-redux'
|
||||
import Parser from "components/parser/parser";
|
||||
import Meta from "components/meta/meta"
|
||||
import { url, basePath, getLocaleTr, useGetRouter, adminAccessToken, getVisitedPages, addVisitedPage, AdvancedURIDecoder, hasValue } from 'services/general/general'
|
||||
import { url, basePath, getLocaleTr, useGetRouter, adminAccessToken, getVisitedPages, addVisitedPage, AdvancedURIDecoder, hasValue, mtd, oneDay } from 'services/general/general'
|
||||
import useTranslate from 'services/translation/translation'
|
||||
import { BillboardSchema } from "components/schema/schema";
|
||||
import Gallery from "components/gallery/gallery";
|
||||
@ -34,7 +34,6 @@ import { updateBillboardViews } from "services/queries/directus/item-viewed";
|
||||
import ProductPlaceHolder from "common/templates/billboard/page/products/placeholder";
|
||||
const BillboardContact = dynamic(() => import("common/templates/billboard/page/contact"), { ssr: false })
|
||||
|
||||
|
||||
const BillboardPage = ({ data }: any) => {
|
||||
|
||||
// states
|
||||
@ -84,7 +83,14 @@ const BillboardPage = ({ data }: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const [updateViews, { data: billboardViewsMutationData, error: billboardViewsMutationErrors, loading: mutating }] = useMutation(updateBillboardViews(billboard.views_count, billboard.id), {
|
||||
const userViews = () => {
|
||||
let localUserViews: string[] = hasValue(billboard.visits) ? billboard.visits.map((x: string) => x) : [];
|
||||
localUserViews.push(`${Date.now()}`);
|
||||
|
||||
return localUserViews.map(x => String(x));
|
||||
};
|
||||
|
||||
const [updateViews, { data: billboardViewsMutationData, error: billboardViewsMutationErrors, loading: mutating }] = useMutation(updateBillboardViews(userViews(), billboard.id), {
|
||||
client: adminDataFetch("", adminAccessToken),
|
||||
onError: (err) => {
|
||||
console.log(err.message);
|
||||
@ -129,7 +135,7 @@ const BillboardPage = ({ data }: any) => {
|
||||
const pageType: any = "billboards";
|
||||
const visitedPages = getVisitedPages();
|
||||
|
||||
if (!visitedPages[pageType].includes(billboard.id)) {
|
||||
if (!(visitedPages["billboards"]).some((x: { id: string, time: number }) => (x.id === billboard.id) && (x.time > (Date.now() - oneDay().milliseconds * 30)) )) {
|
||||
// If not, update the page views count and mark the page as visited
|
||||
addVisitedPage(billboard.id, pageType);
|
||||
updateViews();
|
||||
@ -453,7 +459,7 @@ export async function getStaticProps({ locale, params }: any) {
|
||||
}
|
||||
Billboards(filter: { status: { _eq: "published" }, id: { _eq: "${params.id}" } }) {
|
||||
id
|
||||
views_count
|
||||
visits
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
|
||||
@ -86,8 +86,6 @@ const Ads: React.FunctionComponent<Ads> = ({ }) => {
|
||||
`
|
||||
const { data: userAds, error: userDataError } = useSWR([dataQuery, ""], ([query, route]) => fetchPublicData(route, query));
|
||||
|
||||
// variables
|
||||
|
||||
// methods
|
||||
// const handleArchive = () => {
|
||||
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import GeneralLayout from "common/templates/dashboard/navigation/general-layout";
|
||||
|
||||
interface Billboards {
|
||||
|
||||
}
|
||||
|
||||
const Billboards: React.FunctionComponent<Billboards> = ({ }) => {
|
||||
|
||||
// states
|
||||
|
||||
// variables
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
|
||||
// return
|
||||
return <GeneralLayout>
|
||||
ads page!
|
||||
</GeneralLayout>
|
||||
}
|
||||
export default Billboards;
|
||||
95
src/pages/dashboard/billboards/[id]/index.tsx
Normal file
95
src/pages/dashboard/billboards/[id]/index.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { useAppSelector } from "common/redux/hooks";
|
||||
import { userData } from "common/redux/slices/user";
|
||||
import BillboardOverview from "common/templates/dashboard/billboards/billboard-page/billboard-overview";
|
||||
import BillboardProductsNews from "common/templates/dashboard/billboards/billboard-page/products-news";
|
||||
import TemporarilyClosed from "common/templates/dashboard/billboards/billboard-page/temporarily-closed";
|
||||
import ThemeColor from "common/templates/dashboard/billboards/billboard-page/theme-color";
|
||||
import GeneralLayout from "common/templates/dashboard/navigation/general-layout";
|
||||
import { Billboard } from "common/types/billboard";
|
||||
import InnerLoading from "components/loading/inner-loading";
|
||||
import Link from "next/link";
|
||||
import { fetchPublicData, useGetRouter } from "services/general/general";
|
||||
import { billboardNewsAggregatedQuery, billboardProductsAggregatedQuery, getBillboardFullDataQuery, getBillboardRatingsQuery } from "services/queries/directus/billboard";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import useSWR from "swr";
|
||||
import BrandColor from "common/templates/dashboard/billboards/billboard-page/brand-color";
|
||||
import DashboardColor from "common/templates/dashboard/billboards/billboard-page/dashboard-color";
|
||||
import Sales from "common/templates/dashboard/billboards/billboard-page/sales";
|
||||
import { UUID } from "crypto";
|
||||
import StatisticsOverview from "common/templates/dashboard/billboards/billboard-page/statistics";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface BillboardPageProps {
|
||||
|
||||
}
|
||||
|
||||
const BillboardPage: React.FunctionComponent<BillboardPageProps> = ({ }) => {
|
||||
|
||||
// states
|
||||
|
||||
// variables
|
||||
const { locale, query } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
const user = useAppSelector(userData);
|
||||
|
||||
const { data: billboardData, error: billboardDataError } = useSWR([getBillboardFullDataQuery(query.id, user.generalDetails.id), ""], ([query, route]) => fetchPublicData(route, query));
|
||||
const { data: billboardRatings, error: billboardRatingsError } = useSWR([getBillboardRatingsQuery(query.id), ""], ([query, route]) => fetchPublicData(route, query));
|
||||
const { data: billboardGeneral, error: billboardGeneralError } = useSWR(
|
||||
[`${
|
||||
billboardProductsAggregatedQuery(query.id) +
|
||||
billboardNewsAggregatedQuery(query.id)
|
||||
}`, ""],
|
||||
([query, route]) => fetchPublicData(route, query)
|
||||
);
|
||||
|
||||
billboardDataError && console.log(billboardDataError);
|
||||
billboardRatingsError && console.log(billboardRatingsError);
|
||||
billboardGeneralError && console.log(billboardGeneralError);
|
||||
|
||||
const billboard: Billboard = billboardData?.data.Billboards[0];
|
||||
const overallRatings: any= billboardRatings?.data.billboard_ratings_aggregated;
|
||||
const billboardGeneralData: any = billboardGeneral?.data;
|
||||
|
||||
const theme_color = billboard ? ThemeColor({ brandColor: billboard.brand_color, dashboardTheme: billboard.dashboard_theme, dashboardColor: billboard.dashboard_color }) : { light: "#f3f5fb", full: "#516ec2" };
|
||||
|
||||
const getRating = useMemo(() => {
|
||||
const serviceQuality = overallRatings ? overallRatings[0].avg.service_quality : 0;
|
||||
const totalRating = (serviceQuality) / 1;
|
||||
return { totalRating: totalRating, count: overallRatings ? overallRatings[0].countDistinct.id : 0, serviceQuality: serviceQuality };
|
||||
}, [overallRatings]);
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
|
||||
// return
|
||||
return <GeneralLayout
|
||||
style={{
|
||||
"--brand-color": theme_color.full,
|
||||
"--light-brand-color": theme_color.light,
|
||||
} as React.CSSProperties}
|
||||
bodyClass=""
|
||||
>
|
||||
{(billboard && overallRatings && billboardGeneralData) ?
|
||||
<div className="block pb-8">
|
||||
<BillboardOverview
|
||||
data={billboard}
|
||||
ratings={getRating}
|
||||
products={billboardGeneralData.billboard_products_aggregated[0].countDistinct.id}
|
||||
news={billboardGeneralData.billboard_news_aggregated[0].countDistinct.id}
|
||||
/>
|
||||
<BillboardProductsNews />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-y-2 md:gap-y-4 gap-x-4 mt-4 md:px-4">
|
||||
<StatisticsOverview visits={billboard.visits} themeColor={theme_color} ratings={getRating} />
|
||||
<Sales />
|
||||
<BrandColor themeColor={theme_color} brandColor={billboard.brand_color} />
|
||||
<DashboardColor themeColor={theme_color} dashboardTheme={billboard.dashboard_theme} />
|
||||
<TemporarilyClosed isClosed={billboard.temporarily_closed} themeColor={theme_color} />
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<InnerLoading loadingText={""} width={"200"} height={"100"} />
|
||||
}
|
||||
</GeneralLayout>
|
||||
}
|
||||
export default BillboardPage;
|
||||
95
src/pages/dashboard/billboards/index.tsx
Normal file
95
src/pages/dashboard/billboards/index.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { useAppSelector } from "common/redux/hooks";
|
||||
import { userData } from "common/redux/slices/user";
|
||||
import GeneralLayout from "common/templates/dashboard/navigation/general-layout";
|
||||
import { Billboard } from "common/types/billboard";
|
||||
import Link from "next/link";
|
||||
import { fetchPublicData, getLocaleTr, useGetRouter } from "services/general/general";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import useSWR from "swr";
|
||||
|
||||
interface Billboards {
|
||||
|
||||
}
|
||||
|
||||
const Billboards: React.FunctionComponent<Billboards> = ({ }) => {
|
||||
|
||||
// states
|
||||
|
||||
// variables
|
||||
const { locale } = useGetRouter();
|
||||
const translate = useTranslate();
|
||||
const user = useAppSelector(userData);
|
||||
const userBillboardsQuery = `
|
||||
Billboards (
|
||||
filter: {
|
||||
user_created: { id: {_eq: "${user.generalDetails.id}"} },
|
||||
},
|
||||
sort: ["sort", "-date_created"],
|
||||
limit: -1
|
||||
)
|
||||
{
|
||||
id
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
title
|
||||
body
|
||||
meta_description
|
||||
slogan
|
||||
}
|
||||
city {
|
||||
name
|
||||
}
|
||||
address
|
||||
phone_number
|
||||
email
|
||||
website
|
||||
instagram
|
||||
facebook
|
||||
twitter
|
||||
youtube
|
||||
working_hours
|
||||
billboard_categories {
|
||||
billboard_categories_id {
|
||||
id
|
||||
translations {
|
||||
languages_code {
|
||||
code
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
gallery {
|
||||
directus_files_id {
|
||||
id
|
||||
description
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const { data: userBillboards, error: userBillboardsError } = useSWR([userBillboardsQuery, ""], ([query, route]) => fetchPublicData(route, query));
|
||||
|
||||
userBillboardsError && console.log(userBillboardsError);
|
||||
|
||||
const billboards: Billboard[] = userBillboards?.data.Billboards;
|
||||
|
||||
// methods
|
||||
|
||||
// useEffects
|
||||
|
||||
// return
|
||||
return <GeneralLayout>
|
||||
{billboards && <ul className="block w-full px-4">
|
||||
{billboards.map(x => (
|
||||
<li key={x.id}>
|
||||
<Link href={`/dashboard/billboards/${x.id}`} className="block w-full py-2">{getLocaleTr(x, locale).title}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>}
|
||||
</GeneralLayout>
|
||||
}
|
||||
export default Billboards;
|
||||
@ -9,7 +9,7 @@ import Button from "components/button/button"
|
||||
import Popover from "components/popover/popover"
|
||||
import Breadcrumb from "components/breadcrumb/breadcrumb"
|
||||
import Meta from "components/meta/meta"
|
||||
import { url, basePath, getVisitedPages, addVisitedPage, getLocaleTr, useGetRouter, AdvancedURIDecoder, adminAccessToken, getCatTr } from 'services/general/general'
|
||||
import { url, basePath, getVisitedPages, addVisitedPage, getLocaleTr, useGetRouter, AdvancedURIDecoder, adminAccessToken, getCatTr, oneDay } from 'services/general/general'
|
||||
import useTranslate from 'services/translation/translation'
|
||||
import { ArticleSchema } from "components/schema/schema";
|
||||
import Layout from "common/templates/general/layout/layout";
|
||||
@ -136,8 +136,8 @@ const ArticlePage = ({ data }: any) => {
|
||||
// Check if the current page has been visited
|
||||
const pageType: any = "articles";
|
||||
const visitedPages = getVisitedPages();
|
||||
|
||||
if (!visitedPages[pageType].includes(articleRouterId)) {
|
||||
|
||||
if (!(visitedPages["articles"]).some((x: { id: string, time: number }) => (x.id === articleRouterId) && (x.time > (Date.now() - oneDay().milliseconds * 30)))) {
|
||||
// If not, update the page views count and mark the page as visited
|
||||
addVisitedPage(articleRouterId, pageType);
|
||||
updateViews();
|
||||
|
||||
@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux'
|
||||
import { getGlobalMenu } from '../../../common/redux/slices/global'
|
||||
import Breadcrumb from "components/breadcrumb/breadcrumb"
|
||||
import Meta from "components/meta/meta"
|
||||
import { url, basePath, hasValue, getCatTr, useGetRouter, stripHtmlWithSpace, getVisitedPages, addVisitedPage, adminAccessToken, AdvancedURIDecoder } from 'services/general/general'
|
||||
import { url, basePath, hasValue, getCatTr, useGetRouter, stripHtmlWithSpace, getVisitedPages, addVisitedPage, adminAccessToken, AdvancedURIDecoder, oneDay } from 'services/general/general'
|
||||
import PicsGallery from "common/templates/market/ad-page/gallery";
|
||||
import Overview from "common/templates/market/ad-page/overview";
|
||||
import Contract from "common/templates/market/ad-page/contract";
|
||||
@ -93,8 +93,8 @@ const AdPage = ({data}:any) => {
|
||||
// Check if the current page has been visited
|
||||
const pageType:any = "ads";
|
||||
const visitedPages = getVisitedPages();
|
||||
|
||||
if (!visitedPages[pageType].includes(ad.id)) {
|
||||
|
||||
if (!(visitedPages["ads"]).some((x: { id: string, time: number }) => (x.id === ad.id) && (x.time > (Date.now() - oneDay().milliseconds * 30)))) {
|
||||
// If not, update the page views count and mark the page as visited
|
||||
addVisitedPage(ad.id, pageType);
|
||||
updateViews();
|
||||
|
||||
@ -3,7 +3,7 @@ import { gql, useMutation } from "@apollo/client";
|
||||
import client, { adminDataFetch } from "../../../common/data/apollo-client";
|
||||
import { useDispatch } from 'react-redux'
|
||||
import Meta from "components/meta/meta"
|
||||
import { url, basePath, hasValue, useGetRouter, URIDecoder, stripHtmlWithSpace, getVisitedPages, addVisitedPage, adminAccessToken, getLocaleTr } from 'services/general/general'
|
||||
import { url, basePath, hasValue, useGetRouter, URIDecoder, stripHtmlWithSpace, getVisitedPages, addVisitedPage, adminAccessToken, getLocaleTr, oneDay } from 'services/general/general'
|
||||
import Layout from "common/templates/general/layout/layout";
|
||||
import { WikiSchema } from "components/schema/schema";
|
||||
import InnerLoading from "components/loading/inner-loading";
|
||||
@ -49,7 +49,7 @@ const GuidePage = ({data}:any) => {
|
||||
const pageType:any = "wiki";
|
||||
const visitedPages = getVisitedPages();
|
||||
|
||||
if (hasValue(visitedPages[pageType]) && !visitedPages[pageType].includes(wiki.id)) {
|
||||
if (hasValue(visitedPages["wiki"]) && !(visitedPages["wiki"]).some((x: { id: string, time: number }) => (x.id === wiki.id) && (x.time > (Date.now() - oneDay().milliseconds * 30)))) {
|
||||
// If not, update the page views count and mark the page as visited
|
||||
addVisitedPage(wiki.id, pageType);
|
||||
updateViews();
|
||||
|
||||
@ -32,6 +32,8 @@ module.exports = {
|
||||
'light-gray': '#f5f7fa',
|
||||
'dark-gray': '#e6e6ea',
|
||||
'cool-gray': '#b8b5c3',
|
||||
'title-gray': '#7e849c',
|
||||
'text-gray': '#8789a2',
|
||||
'wheat': 'hsl(34deg 78% 91%)',
|
||||
'market-primary': '#2A2E3C',
|
||||
'market-sup': 'hsl(0deg 0% 95%)',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user