Huge update, mostly to profile + translations
# translations system updated # profile ad form ready (almost) # general bug fixes
This commit is contained in:
parent
e39638ad46
commit
2b0aaa8a30
3
.gitignore
vendored
3
.gitignore
vendored
@ -32,3 +32,6 @@ yarn-error.log*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# tinymce
|
||||
/public/tinymce/
|
||||
|
||||
2696
package-lock.json
generated
2696
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -7,15 +7,21 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"test": "jest --watch",
|
||||
"postbuild": "next-sitemap"
|
||||
"postbuild": "next-sitemap",
|
||||
"postinstall": "node ./postinstall.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.5.9",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@tinymce/tinymce-react": "^4.3.0",
|
||||
"@types/react-beautiful-dnd": "^13.1.4",
|
||||
"@types/react-redux": "^7.1.23",
|
||||
"concurrently": "^7.6.0",
|
||||
"embla-carousel-autoplay": "^7.0.7",
|
||||
"embla-carousel-react": "^7.0.3",
|
||||
"fs-extra": "^11.1.1",
|
||||
"graphql": "^16.3.0",
|
||||
"html-react-parser": "^3.0.1",
|
||||
"next": "^13.1.6",
|
||||
@ -23,11 +29,13 @@
|
||||
"next-pwa": "^5.6.0",
|
||||
"next-sitemap": "^3.1.22",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-infinite-scroll-hook": "^4.1.1",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-virtuoso": "^4.1.0",
|
||||
"swr": "^1.3.0"
|
||||
"swr": "^2.1.2",
|
||||
"tinymce": "^6.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
@ -35,13 +43,13 @@
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/react": "^17.0.40",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "^8.2.3",
|
||||
"eslint": "8.8.0",
|
||||
"eslint-config-next": "^13.0.5",
|
||||
"jest": "^27.5.1",
|
||||
"postcss": "^8.4.6",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
8
postinstall.js
Normal file
8
postinstall.js
Normal file
@ -0,0 +1,8 @@
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
const topDir = __dirname;
|
||||
fse.emptyDirSync(path.join(topDir, 'public', 'tinymce'));
|
||||
fse.copySync(path.join(topDir, 'node_modules', 'tinymce'), path.join(topDir, 'public', 'tinymce'),
|
||||
{
|
||||
overwrite: true
|
||||
});
|
||||
BIN
public/3133335_37294.jpg
Normal file
BIN
public/3133335_37294.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
BIN
public/5402993_2803242.jpg
Normal file
BIN
public/5402993_2803242.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
BIN
public/7119227_3323585.jpg
Normal file
BIN
public/7119227_3323585.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
@ -2,7 +2,7 @@ import React from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from "components/link/link"
|
||||
import { url } from 'services/general/general'
|
||||
import translate from 'services/translation/translation'
|
||||
import useTranslate from 'services/translation/translation'
|
||||
import { Icon } from "components/icons"
|
||||
|
||||
interface Breadcrumb {
|
||||
@ -26,6 +26,7 @@ interface Breadcrumb {
|
||||
const Breadcrumb: React.FunctionComponent<Breadcrumb> = ({ data, className, textClass, urlParam, withIcon }) => {
|
||||
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const activePath = router.pathname;
|
||||
const magazineParent = activePath.includes('magazine') && data.parent ? data.parent[0].related_magazine_categories_id.name : data;
|
||||
const marketParent = activePath.includes('market') && data.parent ? data.parent[0].related_market_categories_id.name : data;
|
||||
|
||||
@ -3,7 +3,7 @@ import Modal from "components/modal/modal"
|
||||
import { imageResizer, serverAddress } from 'services/general/general'
|
||||
import Thumbs from "./thumbs";
|
||||
import { Icon } from "components/icons";
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
|
||||
interface Gallery {
|
||||
className?: string,
|
||||
@ -49,6 +49,7 @@ const Gallery: React.FunctionComponent<Gallery> = ({ className, onClose, items,
|
||||
const [headerVisible, setHeaderVisible] = useState(true);
|
||||
const [isZoomed, setIsZoomed] = useState(false);
|
||||
const [zoomSliderVisible, setZoomSliderVisible] = useState(false);
|
||||
const translate = useTranslate();
|
||||
const zoom = useRef({
|
||||
minZoom: 1,
|
||||
maxZoom: 5,
|
||||
|
||||
@ -3,7 +3,7 @@ import Modal from "components/modal/modal"
|
||||
import { imageResizer, serverAddress } from 'services/general/general'
|
||||
import Thumbs from "./thumbs";
|
||||
import { Icon } from "components/icons";
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
|
||||
interface Gallery {
|
||||
className?: string,
|
||||
@ -49,6 +49,7 @@ const Gallery: React.FunctionComponent<Gallery> = ({ className, onClose, items,
|
||||
const [headerVisible, setHeaderVisible] = useState(true);
|
||||
const [isZoomed, setIsZoomed] = useState(false);
|
||||
const [zoomSliderVisible, setZoomSliderVisible] = useState(false);
|
||||
const translate = useTranslate();
|
||||
const zoom = useRef({
|
||||
minZoom: 1,
|
||||
maxZoom: 5,
|
||||
|
||||
@ -450,6 +450,9 @@ const icons: any = {
|
||||
trash_can_xmark_solid: {
|
||||
viewBox: "0 0 448 512", children: <path d="M163.8 0H284.2c12.1 0 23.2 6.8 28.6 17.7L320 32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 96 0 81.7 0 64S14.3 32 32 32h96l7.2-14.3C140.6 6.8 151.7 0 163.8 0zM32 128H416V448c0 35.3-28.7 64-64 64H96c-35.3 0-64-28.7-64-64V128zM143 239c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z" />
|
||||
},
|
||||
trash_can_check_solid: {
|
||||
viewBox: "0 0 448 512", children: <path xmlns="http://www.w3.org/2000/svg" d="M163.8 0H284.2c12.1 0 23.2 6.8 28.6 17.7L320 32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 96 0 81.7 0 64S14.3 32 32 32h96l7.2-14.3C140.6 6.8 151.7 0 163.8 0zM32 128H416L394.8 467c-1.6 25.3-22.6 45-47.9 45H101.1c-25.3 0-46.3-19.7-47.9-45L32 128zM339.8 251.8c10.9-10.9 10.9-28.7 0-39.6s-28.7-10.9-39.6 0L192 320.4l-44.2-44.2c-10.9-10.9-28.7-10.9-39.6 0s-10.9 28.7 0 39.6l64 64c10.9 10.9 28.7 10.9 39.6 0l128-128z" />
|
||||
},
|
||||
rss_solid: {
|
||||
viewBox: "0 0 448 512", children: <path d="M0 64C0 46.3 14.3 32 32 32c229.8 0 416 186.2 416 416c0 17.7-14.3 32-32 32s-32-14.3-32-32C384 253.6 226.4 96 32 96C14.3 96 0 81.7 0 64zM128 416c0 35.3-28.7 64-64 64s-64-28.7-64-64s28.7-64 64-64s64 28.7 64 64zM32 160c159.1 0 288 128.9 288 288c0 17.7-14.3 32-32 32s-32-14.3-32-32c0-123.7-100.3-224-224-224c-17.7 0-32-14.3-32-32s14.3-32 32-32z" />
|
||||
},
|
||||
@ -612,4 +615,28 @@ const icons: any = {
|
||||
arrows_rotate_solid: {
|
||||
viewBox: "0 0 512 512", children: <path d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160H336c-17.7 0-32 14.3-32 32s14.3 32 32 32H463.5c0 0 0 0 0 0h.4c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32s-32 14.3-32 32v51.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1V448c0 17.7 14.3 32 32 32s32-14.3 32-32V396.9l17.6 17.5 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.7c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352H176c17.7 0 32-14.3 32-32s-14.3-32-32-32H48.4c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z" />
|
||||
},
|
||||
triangle_exclamation_solid: {
|
||||
viewBox: "0 0 512 512", children: <path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224c0-17.7-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32s32-14.3 32-32z" />
|
||||
},
|
||||
skull_solid: {
|
||||
viewBox: "0 0 512 512", children: <path d="M416 398.9c58.5-41.1 96-104.1 96-174.9C512 100.3 397.4 0 256 0S0 100.3 0 224c0 70.7 37.5 133.8 96 174.9c0 .4 0 .7 0 1.1v64c0 26.5 21.5 48 48 48h48V464c0-8.8 7.2-16 16-16s16 7.2 16 16v48h64V464c0-8.8 7.2-16 16-16s16 7.2 16 16v48h48c26.5 0 48-21.5 48-48V400c0-.4 0-.7 0-1.1zM224 256c0 35.3-28.7 64-64 64s-64-28.7-64-64s28.7-64 64-64s64 28.7 64 64zm128 64c-35.3 0-64-28.7-64-64s28.7-64 64-64s64 28.7 64 64s-28.7 64-64 64z" />
|
||||
},
|
||||
skull_crossbones_solid: {
|
||||
viewBox: "0 0 512 512", children: <path d="M400 128c0 44.4-25.4 83.5-64 106.4V256c0 17.7-14.3 32-32 32H208c-17.7 0-32-14.3-32-32V234.4c-38.6-23-64-62.1-64-106.4C112 57.3 176.5 0 256 0s144 57.3 144 128zM200 176c17.7 0 32-14.3 32-32s-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32zm144-32c0-17.7-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32s32-14.3 32-32zM35.4 273.7c7.9-15.8 27.1-22.2 42.9-14.3L256 348.2l177.7-88.8c15.8-7.9 35-1.5 42.9 14.3s1.5 35-14.3 42.9L327.6 384l134.8 67.4c15.8 7.9 22.2 27.1 14.3 42.9s-27.1 22.2-42.9 14.3L256 419.8 78.3 508.6c-15.8 7.9-35 1.5-42.9-14.3s-1.5-35 14.3-42.9L184.4 384 49.7 316.6c-15.8-7.9-22.2-27.1-14.3-42.9z" />
|
||||
},
|
||||
siren_on_solid: {
|
||||
viewBox: "0 0 640 512", children: <path d="M69.3 36l48 32c11 7.4 14 22.3 6.7 33.3s-22.3 14-33.3 6.7l-48-32c-11-7.4-14-22.3-6.7-33.3s22.3-14 33.3-6.7zM597.3 76l-48 32c-11 7.4-25.9 4.4-33.3-6.7s-4.4-25.9 6.7-33.3l48-32c11-7.4 25.9-4.4 33.3 6.7s4.4 25.9-6.7 33.3zM24 192H88c13.3 0 24 10.7 24 24s-10.7 24-24 24H24c-13.3 0-24-10.7-24-24s10.7-24 24-24zm528 0h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H552c-13.3 0-24-10.7-24-24s10.7-24 24-24zM144 352l25-199.9c4-32 31.2-56.1 63.5-56.1h175c32.3 0 59.5 24 63.5 56.1L496 352H234.1l21.8-174c1.1-8.8-5.1-16.8-13.9-17.9s-16.8 5.1-17.9 13.9L201.9 352H144zM96 416c0-17.7 14.3-32 32-32H512c17.7 0 32 14.3 32 32v32c0 17.7-14.3 32-32 32H128c-17.7 0-32-14.3-32-32V416z" />
|
||||
},
|
||||
radiation_solid: {
|
||||
viewBox: "0 0 512 512", children: <path d="M216 186.7c-23.9 13.8-40 39.7-40 69.3L32 256C14.3 256-.2 241.6 2 224.1C10.7 154 47.8 92.7 101.3 52c14.1-10.7 33.8-5.3 42.7 10l72 124.7zM256 336c14.6 0 28.2-3.9 40-10.7l72 124.8c8.8 15.3 3.7 35.1-12.6 41.9c-30.6 12.9-64.2 20-99.4 20s-68.9-7.1-99.4-20c-16.3-6.9-21.4-26.6-12.6-41.9l72-124.8c11.8 6.8 25.4 10.7 40 10.7zm224-80l-144 0c0-29.6-16.1-55.5-40-69.3L368 62c8.8-15.3 28.6-20.7 42.7-10c53.6 40.7 90.6 102 99.4 172.1c2.2 17.5-12.4 31.9-30 31.9zM256 304c-26.5 0-48-21.5-48-48s21.5-48 48-48s48 21.5 48 48s-21.5 48-48 48z" />
|
||||
},
|
||||
radiation_circle_solid: {
|
||||
viewBox: "0 0 512 512", children: <path d="M256 448C150 448 64 362 64 256S150 64 256 64s192 86 192 192s-86 192-192 192zm0 64c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM200 256c0-20.7 11.3-38.8 28-48.5l-36-62.3c-8.8-15.3-28.7-20.8-42-9c-25.6 22.6-43.9 53.3-50.9 88.1C95.7 241.5 110.3 256 128 256l72 0zm28 48.5l-36 62.4c-8.8 15.3-3.6 35.2 13.1 40.8c16 5.4 33.1 8.3 50.9 8.3s34.9-2.9 50.9-8.3c16.7-5.6 21.9-25.5 13.1-40.8l-36-62.4c-8.2 4.8-17.8 7.5-28 7.5s-19.8-2.7-28-7.5zM312 256l72 0c17.7 0 32.3-14.5 28.8-31.8c-7-34.8-25.3-65.5-50.9-88.1c-13.2-11.7-33.1-6.3-42 9l-36 62.3c16.7 9.7 28 27.8 28 48.5zm-56 32c17.7 0 32-14.3 32-32s-14.3-32-32-32s-32 14.3-32 32s14.3 32 32 32z" />
|
||||
},
|
||||
light_emergency_on_solid: {
|
||||
viewBox: "0 0 640 512", children: <path d="M69.3 36c-11-7.4-25.9-4.4-33.3 6.7s-4.4 25.9 6.7 33.3l48 32c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3l-48-32zM597.3 76c11-7.4 14-22.3 6.7-33.3s-22.3-14-33.3-6.7l-48 32c-11 7.4-14 22.3-6.7 33.3s22.3 14 33.3 6.7l48-32zM24 192c-13.3 0-24 10.7-24 24s10.7 24 24 24H88c13.3 0 24-10.7 24-24s-10.7-24-24-24H24zm528 0c-13.3 0-24 10.7-24 24s10.7 24 24 24h64c13.3 0 24-10.7 24-24s-10.7-24-24-24H552zM320 32c-88.4 0-160 71.6-160 160V352H480V192c0-88.4-71.6-160-160-160zM240 192c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80zM96 416v32c0 17.7 14.3 32 32 32H512c17.7 0 32-14.3 32-32V416c0-17.7-14.3-32-32-32H128c-17.7 0-32 14.3-32 32z" />
|
||||
},
|
||||
cloud_plus_solid: {
|
||||
viewBox: "0 0 640 512", children: <path d="M144 480C64.5 480 0 415.5 0 336c0-62.8 40.2-116.2 96.2-135.9c-.1-2.7-.2-5.4-.2-8.1c0-88.4 71.6-160 160-160c59.3 0 111 32.2 138.7 80.2C409.9 102 428.3 96 448 96c53 0 96 43 96 96c0 12.2-2.3 23.8-6.4 34.6C596 238.4 640 290.1 640 352c0 70.7-57.3 128-128 128H144zM296 376c0 13.3 10.7 24 24 24s24-10.7 24-24V312h64c13.3 0 24-10.7 24-24s-10.7-24-24-24H344V200c0-13.3-10.7-24-24-24s-24 10.7-24 24v64H232c-13.3 0-24 10.7-24 24s10.7 24 24 24h64v64z" />
|
||||
},
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Icon } from "components/icons";
|
||||
import React, { useState, useEffect, useLayoutEffect, useRef } from "react"
|
||||
|
||||
interface Input {
|
||||
type: "text" | "search" | "password" | "email" | "number" | "hidden",
|
||||
type: "text" | "search" | "password" | "email" | "number" | "tel" | "hidden",
|
||||
readonly?: boolean,
|
||||
required?: boolean,
|
||||
clear?: boolean,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from "react"
|
||||
import Head from 'next/head'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import { basePath } from "services/general/general"
|
||||
|
||||
@ -25,6 +25,7 @@ interface Meta {
|
||||
const Meta: React.FunctionComponent<Meta> = ({ data }) => {
|
||||
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const websiteName = translate("website-name");
|
||||
const pageUrl = `${basePath()}${router.asPath}`;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import parse from 'html-react-parser';
|
||||
import Image from 'components/image/image'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import Gallery from "components/gallery/gallery";
|
||||
|
||||
interface Parser {
|
||||
@ -27,6 +27,7 @@ const Parser: React.FunctionComponent<Parser> = ({ text, limit, className }) =>
|
||||
const [galleryIndex, setGalleryIndex] = useState(0);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
let parsedHtml: [] = [];
|
||||
const imagesArray: ImagesArray[] = [];
|
||||
const showMoreText = translate("content-show-more");
|
||||
|
||||
@ -2,7 +2,7 @@ import React from "react"
|
||||
import Head from 'next/head'
|
||||
import { serverAddress, basePath } from 'services/general/general'
|
||||
import { useRouter } from 'next/router'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
|
||||
interface ArticleSchema {
|
||||
author: string,
|
||||
@ -52,6 +52,7 @@ interface MarketSchema {
|
||||
export const ArticleSchema: React.FunctionComponent<ArticleSchema> = ({ author, headline, abstract, articleBody, category, image, datePublished, dateModified }) => {
|
||||
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
|
||||
// YYYY MM DD
|
||||
// VANCOUVER
|
||||
|
||||
@ -5,8 +5,8 @@ import Modal from "components/modal/modal";
|
||||
import RadioButton from "components/radio-button/radio-button";
|
||||
import { stringify } from "querystring";
|
||||
import React, { useState, useRef, useEffect, useLayoutEffect, useMemo } from "react"
|
||||
import { jsonClone } from "services/general/general";
|
||||
import translate from "services/translation/translation";
|
||||
import { deepClone, jsonClone } from "services/general/general";
|
||||
import useTranslate from "services/translation/translation";
|
||||
|
||||
interface DataList {
|
||||
options: string[],
|
||||
@ -22,6 +22,7 @@ interface DataList {
|
||||
label: string,
|
||||
multiple?: boolean,
|
||||
labelHidden?: boolean,
|
||||
dashedValue?: boolean,
|
||||
value: string[],
|
||||
allValue?: string,
|
||||
isOpen?: boolean,
|
||||
@ -35,7 +36,7 @@ interface Items {
|
||||
}
|
||||
|
||||
|
||||
const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options, listClass, listItemClass, labelClass, wrapperClass, inputClass, buttonClass, multiLabelClass, listContainerClass, id, label, multiple, labelHidden = false, value, allValue, onChange, onClose }) => {
|
||||
const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options, listClass, listItemClass, labelClass, wrapperClass, inputClass, buttonClass, multiLabelClass, listContainerClass, dashedValue = true, id, label, multiple, labelHidden = false, value, allValue, onChange, onClose }) => {
|
||||
|
||||
// states
|
||||
const [currentItem, setCurrentItem] = useState<string[]>([]);
|
||||
@ -45,9 +46,12 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
const [open, setOpen] = useState(isOpen);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const search:any = useRef(null);
|
||||
const singleOptions: any = useRef([]);
|
||||
const multipleArray = useMemo(() => options.map(x => ({ name: x, checked: false })), [options]);
|
||||
const applyText = translate("apply");
|
||||
const searchPlaceHolderText = translate("datalist-search-placeholder");
|
||||
|
||||
// methods
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -66,14 +70,14 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
|
||||
const getOptions = () => {
|
||||
if (multipleOptions.length === 0) {
|
||||
let localOptions = jsonClone(multipleArray);
|
||||
let localOptions = deepClone(multipleArray);
|
||||
return localOptions;
|
||||
}
|
||||
else {
|
||||
let localOptions: Items[] = [];
|
||||
let miror: Items[] = jsonClone(multipleArray);
|
||||
let miror: Items[] = deepClone(multipleArray);
|
||||
miror.map(x => {
|
||||
jsonClone(multipleOptions).map((y:any) => {
|
||||
deepClone(multipleOptions).map((y:any) => {
|
||||
if (x.name === y.name) {
|
||||
x.checked = y.checked;
|
||||
}
|
||||
@ -85,10 +89,10 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
}
|
||||
|
||||
const handleMultipleSelection = (name:string, checked:boolean) => {
|
||||
const newArray = getOptions();
|
||||
newArray.filter((x:any) => x.name === name)[0].checked = checked;
|
||||
setMultipleOptions([...newArray]);
|
||||
sendSelected([...newArray]);
|
||||
const localOptions: Items[] = getOptions();
|
||||
localOptions.filter(x => x.name === name)[0].checked = checked;
|
||||
setMultipleOptions([...localOptions]);
|
||||
sendSelected([...localOptions]);
|
||||
}
|
||||
|
||||
const removeSelection = (event: React.MouseEvent, name:string, checked:boolean) => {
|
||||
@ -128,12 +132,24 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
|
||||
// Updates the list on programmatic change of value (including reset or any other change)
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
if (multiple && JSON.stringify(finalChoices) !== JSON.stringify(value)) {
|
||||
if (value.length === 0) {
|
||||
setMultipleOptions([]);
|
||||
setFinalChoices([]);
|
||||
} else {
|
||||
setMultipleOptions(getOptions());
|
||||
const cuurentValue: Items[] = [];
|
||||
getOptions().map((x:any) => {
|
||||
value.map((y: any) => {
|
||||
if (x.name === y) {
|
||||
x.checked = true;
|
||||
}
|
||||
})
|
||||
cuurentValue.push(x);
|
||||
});
|
||||
setMultipleOptions(cuurentValue);
|
||||
setFinalChoices(cuurentValue.filter(x => x.checked === true).map(x => x.name));
|
||||
}
|
||||
}
|
||||
if (!multiple) {
|
||||
@ -157,9 +173,9 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
{!labelHidden && <label htmlFor={id} className={`${labelClass}`}>{label}</label>}
|
||||
<span className={`select-none relative lg:cursor-pointer min-w-[150px] text-sm ${open ? "isOpen" : ""} ${buttonClass}`} onClick={handleOpen}>
|
||||
{multiple ?
|
||||
finalChoices.length === 0 ? allValue : getOptions().filter((x: any) => x.checked === true).map((x: any) => x.name).map((x: any) => <span key={x} className={`${multiLabelClass}`} onClick={(e) => removeSelection(e, x, false)}>{x}</span>)
|
||||
finalChoices.length === 0 ? allValue : multipleOptions.filter((x: any) => x.checked === true).map((x: any) => x.name).map((x: any) => <span key={x} className={`${multiLabelClass}`} onClick={(e) => removeSelection(e, x, false)}>{x}</span>)
|
||||
:
|
||||
value.length === 0 ? allValue : <span className="inline-block text-center text-[13px] lg:text-[15px] text-market-title">{value[0].includes("-") ? value[0].slice(0, value[0].indexOf(" - ")) : value[0]}</span>
|
||||
value.length === 0 ? allValue : <span className="inline-block text-center text-[13px] lg:text-[15px] text-market-title">{value[0].includes("-") && dashedValue ? value[0].slice(0, value[0].indexOf(" - ")) : value[0]}</span>
|
||||
}
|
||||
<Icon name={`${ multiple ? "chevron_down_solid" : currentItem.length === 0 ? "chevron_down_solid" : "xmark_solid"}`} className={`block ${value.length === 0 ? "fill-current" : "fill-market-title"} w-[13px] h-[13px] lg:w-4 lg:h-4 absolute left-1 lg:left-3 bottom-1/2 translate-y-1/2 transition-all ${open ? "rotate-180" : "rotate-0"}`} />
|
||||
</span>
|
||||
@ -178,12 +194,12 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
{multipleArray.length > 10 &&
|
||||
<div className={`z-10 ${inputClass} relative`}>
|
||||
<Icon name="MagnifyingGlass" className={``} />
|
||||
<input ref={search} type="search" aria-label={label} name="datalist" id={id} placeholder={`${translate("datalist-search-placeholder")} ${label}`} className={``} onChange={handleChange} autoComplete="off" tabIndex={0} />
|
||||
<input ref={search} type="search" aria-label={label} name="datalist" id={id} placeholder={`${searchPlaceHolderText} ${label}`} className={``} onChange={handleChange} autoComplete="off" tabIndex={0} />
|
||||
</div>
|
||||
}
|
||||
<ul className={`hide-scrollbar lg:cursor-pointer ${listClass}`}>
|
||||
{multiple ?
|
||||
(finalChoices.length === 0 ? jsonClone(multipleArray) : getOptions()).filter((x:any) => x.name.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())).map((x:any) => (
|
||||
(finalChoices.length === 0 ? deepClone(multipleArray) : multipleOptions).filter((x:any) => x.name.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())).map((x:any) => (
|
||||
<li key={x.name} className={`select-none ${listItemClass} [&>label]:mb-0`}>
|
||||
<CheckBox
|
||||
forId={x.name}
|
||||
@ -217,7 +233,7 @@ const DataList: React.FunctionComponent<DataList> = ({ isOpen = false, options,
|
||||
}
|
||||
</ul>
|
||||
<div className="grid grid-cols-1 gap-2 w-full sticky bottom-0 bg-white py-2 px-gi shadow-top">
|
||||
<Button type="button" text={`${translate("apply")}`} className="block w-full bg-market-title-light text-white text-base text-center py-2 px-3" onClick={() => setOpen(false)} />
|
||||
<Button type="button" text={`${applyText}`} className="block w-full bg-market-title-light text-white text-base text-center py-2 px-3" onClick={() => setOpen(false)} />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
14
src/common/components/skeleton/skeleton.tsx
Normal file
14
src/common/components/skeleton/skeleton.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
|
||||
interface Skeleton{
|
||||
className?: string,
|
||||
onClick?: () => void,
|
||||
}
|
||||
|
||||
const Skeleton: React.FunctionComponent<Skeleton> = ({ className, onClick }) => {
|
||||
return (
|
||||
<div className={`block animate-pulse rounded-md bg-market-input ${className}`} onClick={onClick}>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Skeleton
|
||||
@ -1,8 +1,9 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { gql } from '@apollo/client';
|
||||
import { dataFetch, fetchGeneralData, privateDataFetch } from 'common/data/apollo-client';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { gql, useMutation } from '@apollo/client';
|
||||
import { dataFetch, fetchGeneralData, generalFetch, privateDataFetch } from 'common/data/apollo-client';
|
||||
import { getUnitSystem } from 'common/redux/slices/global';
|
||||
import { useAppSelector, useAppDispatch } from '../../redux/hooks';
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// Url formatter
|
||||
export const url = (title) => {
|
||||
@ -10,6 +11,16 @@ export const url = (title) => {
|
||||
return cleanUrl;
|
||||
}
|
||||
|
||||
// email validation
|
||||
export const emailValidationRegex = new RegExp("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$");
|
||||
|
||||
// postal code validation
|
||||
export const canadianPostalCodeValidationRegex = new RegExp("^[ABCEGHJ-NPRSTVXY][0-9][ABCEGHJ-NPRSTV-Z][ ][0-9][ABCEGHJ-NPRSTV-Z][0-9]$");
|
||||
|
||||
// phone number validation
|
||||
export const canadianPhoneNumberValidationRegex = new RegExp("^(\\d{3}[ -]?\\d{3}[ -]?\\d{4})$");
|
||||
// "^\\d{3}\\s\\d{3}\\s\\d{4}$"
|
||||
|
||||
// Website general base path generator for both local and production
|
||||
// 167.99.189.252 live server's ip address
|
||||
export const basePath = () => {
|
||||
@ -35,6 +46,49 @@ export const stripHtml = (text) => {
|
||||
return strippedText;
|
||||
}
|
||||
|
||||
// Strips html tags from text
|
||||
export const hasValue = (value) => {
|
||||
const checkedValue = (value !== "" && value !== null && value !== undefined);
|
||||
return checkedValue;
|
||||
}
|
||||
|
||||
// preventRouteCahnge
|
||||
export const useBeforeUnload = (message, callback) => {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// const showCustomPrompt = (message) => {
|
||||
// return new Promise((resolve) => {
|
||||
// const confirmed = window.confirm(message);
|
||||
// resolve(confirmed);
|
||||
// });
|
||||
// };
|
||||
|
||||
const handleGeneralChange = (event) => {
|
||||
event.preventDefault();
|
||||
event.returnValue = message;
|
||||
};
|
||||
const handleRouteChange = (url) => {
|
||||
if (router.asPath !== url) {
|
||||
const accepted = callback();
|
||||
if (!accepted) {
|
||||
router.events.emit("routeChangeError")
|
||||
throw "Abort route change. Please ignore this error."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
router.events.on('routeChangeStart', handleRouteChange);
|
||||
window.addEventListener('beforeunload', handleGeneralChange);
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', handleRouteChange);
|
||||
window.removeEventListener('beforeunload', handleGeneralChange);
|
||||
};
|
||||
}, [router, message]);
|
||||
};
|
||||
|
||||
// Objects deep freeze
|
||||
export const deepFreeze = (object) => {
|
||||
// Freeze properties before freezing self
|
||||
@ -127,6 +181,37 @@ export const numberFormatter = (number, separator) => {
|
||||
return formattedNumber.reverse().join("");
|
||||
}
|
||||
|
||||
// returns file folders id
|
||||
export const getFolderID = (name) => {
|
||||
let folderId = "";
|
||||
|
||||
switch (name) {
|
||||
case "Article":
|
||||
folderId = "c98e6cea-c067-46aa-962a-0eb805918b9e";
|
||||
break;
|
||||
case "Billboard":
|
||||
folderId = "3627f878-3a61-44c4-bf92-149c3a699715";
|
||||
break;
|
||||
case "Market":
|
||||
folderId = "d61e5d52-3e5b-4b4c-a103-eb8af2068bb1";
|
||||
break;
|
||||
case "Brands":
|
||||
folderId = "8808e07b-06df-4a1c-b8eb-cf2e7eeab593";
|
||||
break;
|
||||
case "General":
|
||||
folderId = "d585cc02-f5d7-4ca8-b4c0-289a7f8189f2";
|
||||
break;
|
||||
case "Inline Images":
|
||||
folderId = "0b6f1dd3-3852-45cf-ba56-5377e22c9fec";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return folderId;
|
||||
}
|
||||
|
||||
// Converts imperial and metric measurement systems
|
||||
export const useMeasure = (unit, value) => {
|
||||
|
||||
@ -234,6 +319,16 @@ export const generalDataFetch = async (query) => {
|
||||
});
|
||||
return data;
|
||||
}
|
||||
export const systemDataFetch = async (query) => {
|
||||
const data = await generalFetch.query({
|
||||
query: gql`
|
||||
query GetData {
|
||||
${query}
|
||||
}
|
||||
`
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export const usePrivateDataFetch = async (token, query) => {
|
||||
const data = await privateDataFetch(token).query({
|
||||
@ -246,6 +341,42 @@ export const usePrivateDataFetch = async (token, query) => {
|
||||
return data;
|
||||
}
|
||||
|
||||
export const fileSubmitter = async (token, body, cb) => {
|
||||
try {
|
||||
const response = await fetch(`${cmsAddress()}/files`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Unable to upload file to endpoint.");
|
||||
}
|
||||
const parsedRes = await response.json();
|
||||
cb(parsedRes);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const fileUpload = async (token, pics, folder, cb) => {
|
||||
const folderId = getFolderID(folder);
|
||||
const submitData = new FormData();
|
||||
submitData.append('folder', folderId);
|
||||
pics.map(async (x, i) => {
|
||||
let imageTitle = x.name;
|
||||
let imageSrc = await fetch(x.src).then(r => r.blob());
|
||||
submitData.append('file', imageSrc, imageTitle);
|
||||
submitData.append('folder', folderId);
|
||||
if (submitData.getAll('file').length === pics.length) {
|
||||
fileSubmitter(token, submitData, cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const imageResizer = (imageWidth, imageHeight, containerWidth, containerHeight) => {
|
||||
let imageAspectRatio = imageWidth / imageHeight;
|
||||
let resizedWidth = 0;
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
import { useAppSelector } from 'common/redux/hooks';
|
||||
import { getDictionary } from 'common/redux/slices/global';
|
||||
import { store } from 'common/redux/store/store'
|
||||
import { Dictionary } from 'common/types/general';
|
||||
|
||||
const useTranslate = (token: string) => {
|
||||
const useTranslate = () => {
|
||||
const locale = 'fa-IR';
|
||||
const data: any = useAppSelector(getDictionary);
|
||||
|
||||
if (data.length > 0) {
|
||||
let translation = data
|
||||
.filter((x:any) => x.token === token)
|
||||
.map((x:any) => x.translations)[0]
|
||||
.filter((x:any) => x.languages_code.code === locale)[0].translation
|
||||
return translation;
|
||||
const getTranslation = (token: string) => {
|
||||
if (data.length > 0) {
|
||||
let translation:string = data
|
||||
.filter((x: any) => x.token === token)
|
||||
.map((x: any) => x.translations)[0]
|
||||
.filter((x: any) => x.languages_code.code === locale)[0].translation
|
||||
return translation;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
return getTranslation;
|
||||
}
|
||||
export default useTranslate
|
||||
@ -237,4 +237,13 @@
|
||||
.input-unit {
|
||||
@apply flex items-center w-auto bg-white py-1 px-2 h-full mr-1 rounded-l font-semibold text-sm text-secondary-light uppercase;
|
||||
}
|
||||
|
||||
|
||||
/* input unit : km, mile, etc... */
|
||||
.profile-form {
|
||||
@apply px-3 py-[0.375rem] text-sm leading-6 bg-white ring-1 ring-gray-300 placeholder:text-gray-400 rounded-lg outline-none focus:ring-2 focus:ring-market-title-light;
|
||||
}
|
||||
.profile-form-error {
|
||||
@apply !ring-light-red/40 ring-[3px] ring-offset-1 ring-offset-light-red;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import { UserData } from "common/types/user";
|
||||
@ -30,6 +30,7 @@ const Login: React.FunctionComponent<Login> = ({ isActive }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const loginForm:any = useRef(null);
|
||||
const receiveCsrfToken = async () => {
|
||||
@ -42,8 +43,6 @@ const Login: React.FunctionComponent<Login> = ({ isActive }) => {
|
||||
|
||||
// methods
|
||||
const handleSignIn = async () => {
|
||||
// const active = loginForm.current && loginForm['current'].closest("div").getAttribute('data-active');
|
||||
// console.log(active);
|
||||
const res = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email: loginForm.current ? loginForm['current']['email']['value'] : null,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import { UserData } from "common/types/user";
|
||||
@ -34,6 +34,7 @@ const SignUp: React.FunctionComponent<SignUp> = ({ isActive }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const formData = useRef(null);
|
||||
const receiveCsrfToken = async () => {
|
||||
|
||||
@ -2,7 +2,7 @@ import React from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import Image from "components/image/image"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
|
||||
interface LatestBillboards {
|
||||
@ -28,6 +28,8 @@ interface LatestBillboards {
|
||||
|
||||
const LatestBillboards: React.FunctionComponent<LatestBillboards> = ({ billboards }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
|
||||
const translation = translate('billboard-navigation-text');
|
||||
|
||||
return (
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import { UserData } from "common/types/user";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
|
||||
interface Ads {
|
||||
|
||||
}
|
||||
|
||||
const Ads: React.FunctionComponent<Ads> = ({ }) => {
|
||||
|
||||
// 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">
|
||||
Ads
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default Ads
|
||||
151
src/common/templates/dashboard/ads/data-types.tsx
Normal file
151
src/common/templates/dashboard/ads/data-types.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
export interface SystemFields {
|
||||
text: string,
|
||||
value: string
|
||||
}
|
||||
export interface Gallery {
|
||||
name: string,
|
||||
type: string,
|
||||
size: number,
|
||||
src: any
|
||||
}
|
||||
export interface FormErrors {
|
||||
stepOne: boolean,
|
||||
stepTwo: boolean,
|
||||
stepThree: boolean,
|
||||
}
|
||||
export interface GeneralData {
|
||||
title: string,
|
||||
mainCat: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
category: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
body: string | undefined,
|
||||
photos: Gallery[],
|
||||
advertiser: string,
|
||||
email: string,
|
||||
phoneNumber: string,
|
||||
city: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
address: string,
|
||||
postalCode: string,
|
||||
tags: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
}
|
||||
export interface JobData {
|
||||
jobTitle: string,
|
||||
employmentType: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
salaryType: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
salaryFrom: string,
|
||||
salaryTo: string,
|
||||
salaryOther: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
workStyle: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
insurance: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
experience: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
education: string,
|
||||
companyName: string,
|
||||
numberOfEmployees: string[],
|
||||
languages: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
linkToAd: string,
|
||||
}
|
||||
export interface SharedData {
|
||||
price: number | undefined,
|
||||
product_state: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
model: string,
|
||||
brand: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
color: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
interior_color: {
|
||||
names: string[],
|
||||
ids: string[],
|
||||
},
|
||||
}
|
||||
export interface VehicleData {
|
||||
vehicle_type: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
car_body_type: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
production_year: number | undefined,
|
||||
distance_traveled: number | undefined,
|
||||
transmission_type: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
drivetrain: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
fuel_type: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
engine_capacity: number | undefined,
|
||||
number_of_cylinders: number | undefined,
|
||||
horsepower: number | undefined,
|
||||
top_speed: number | undefined,
|
||||
car_range: number | undefined,
|
||||
fuel_tank_capacity: number | undefined,
|
||||
city_fuel_usage: number | undefined,
|
||||
highway_fuel_usage: number | undefined,
|
||||
seats_count: number | undefined,
|
||||
doors_count: number | undefined,
|
||||
trunk_space: number | undefined,
|
||||
length: number | undefined,
|
||||
width: number | undefined,
|
||||
height: number | undefined,
|
||||
weight: number | undefined,
|
||||
vehicle_safety: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
vehicle_options: {
|
||||
translation: string[],
|
||||
original: string[],
|
||||
},
|
||||
}
|
||||
export interface FormDataType {
|
||||
generalData: GeneralData,
|
||||
sharedData: SharedData,
|
||||
jobData: JobData,
|
||||
vehicleData: VehicleData,
|
||||
}
|
||||
50
src/common/templates/dashboard/ads/field-repeater.tsx
Normal file
50
src/common/templates/dashboard/ads/field-repeater.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { isObject } from "services/general/general";
|
||||
|
||||
export const dataRepeater = (data: any, type: string) => {
|
||||
const fieldsData = isObject(data) ? [data] : [...data];
|
||||
console.log(fieldsData);
|
||||
const items: any = [];
|
||||
fieldsData.map((x: any) => {
|
||||
if (type === "gallery") {
|
||||
items.unshift(galleryField(x.id, x.storage, x.filename_download));
|
||||
}
|
||||
if (type === "tags") {
|
||||
items.unshift(tagsField(x));
|
||||
}
|
||||
if (type === "languages") {
|
||||
items.unshift(languagesField(x));
|
||||
}
|
||||
});
|
||||
return JSON.stringify(items)
|
||||
.replaceAll('"directus_files_id"', 'directus_files_id')
|
||||
.replaceAll('"market_tags_id"', 'market_tags_id')
|
||||
.replaceAll('"id"', 'id')
|
||||
.replaceAll('"storage"', 'storage')
|
||||
.replaceAll('"filename_download"', 'filename_download')
|
||||
.replaceAll('"languages_code"', 'languages_code')
|
||||
.replaceAll('"code"', 'code')
|
||||
}
|
||||
|
||||
export const galleryField = (id: string, storage: string, name: string) => (
|
||||
{
|
||||
directus_files_id: {
|
||||
id: id,
|
||||
storage: storage,
|
||||
filename_download: name
|
||||
}
|
||||
}
|
||||
)
|
||||
export const tagsField = (id: string) => (
|
||||
{
|
||||
market_tags_id: {
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
)
|
||||
export const languagesField = (code: string) => (
|
||||
{
|
||||
languages_code: {
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
)
|
||||
24
src/common/templates/dashboard/ads/fields-query.tsx
Normal file
24
src/common/templates/dashboard/ads/fields-query.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
export const fieldsQuery = `
|
||||
Cities (filter: { status: { _eq: "published" } }, limit: -1) {
|
||||
id
|
||||
name
|
||||
English_name
|
||||
state {
|
||||
name
|
||||
}
|
||||
}
|
||||
market_categories (filter: { status: { _eq: "published" } }, limit: -1) {
|
||||
id
|
||||
name
|
||||
has_children
|
||||
parent {
|
||||
related_market_categories_id {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
market_tags (filter: { status: { _eq: "published" } }, limit: -1) {
|
||||
id
|
||||
name
|
||||
}
|
||||
`
|
||||
51
src/common/templates/dashboard/ads/form-item.tsx
Normal file
51
src/common/templates/dashboard/ads/form-item.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import { UserData } from "common/types/user";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import Input from "components/input/text";
|
||||
import Label from "components/label/label";
|
||||
|
||||
interface FormItem {
|
||||
className?: string,
|
||||
forId: string,
|
||||
title: string,
|
||||
required?: boolean,
|
||||
errorText?: string,
|
||||
errorVisible?: boolean,
|
||||
}
|
||||
interface ErrorItem {
|
||||
text: string | undefined,
|
||||
}
|
||||
|
||||
// Error component
|
||||
const ErrorItem: React.FunctionComponent<ErrorItem> = ({ text }) => {
|
||||
return <span className="flex items-center text-xs md:text-[13px] text-primary mt-2 md:mt-3">
|
||||
<Icon name="triangle_exclamation_solid" className="inline-block w-3 h-3 lg:w-4 lg:h-4 ml-2 -mt-1 fill-light-red" />
|
||||
{text}
|
||||
</span>;
|
||||
}
|
||||
|
||||
const FormItem: React.FunctionComponent<FormItem> = ({ forId, title, required, children, errorText, errorVisible = false, className }) => {
|
||||
|
||||
// states
|
||||
const [showError, setShowError] = useState(errorVisible);
|
||||
|
||||
// useEffects
|
||||
useEffect(() => {
|
||||
errorVisible !== showError && setShowError(errorVisible);
|
||||
}, [errorVisible])
|
||||
|
||||
// return
|
||||
return (
|
||||
<Label forId={forId} className={`block before:text-market-title-light before:ml-1 ${className}`} required={required}>
|
||||
<span className={`inline-block mb-3 font-semibold text-title-color text-sm lg:text-[15px]`}>{title}</span>
|
||||
{children}
|
||||
{showError && <ErrorItem text={errorText} />}
|
||||
</Label>
|
||||
)
|
||||
}
|
||||
export default FormItem
|
||||
309
src/common/templates/dashboard/ads/form-parent.tsx
Normal file
309
src/common/templates/dashboard/ads/form-parent.tsx
Normal file
@ -0,0 +1,309 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import GeneralDetailsForm from "./general-details";
|
||||
import Button from "components/button/button";
|
||||
import StepsMap from "./steps-map";
|
||||
import MainDetails from "./main-details";
|
||||
import ReviewDetails from "./review-details";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormDataType, FormErrors, GeneralData } from "./data-types";
|
||||
import { fileUpload, generalDataFetch, getFolderID, isObject, useBeforeUnload } from "services/general/general";
|
||||
import { userData, userIsDataReady } from "common/redux/slices/user";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { privateDataFetch } from "common/data/apollo-client";
|
||||
import useSWRImmutable from "swr";
|
||||
import Skeleton from "components/skeleton/skeleton";
|
||||
import { fieldsQuery } from "./fields-query";
|
||||
import { dataRepeater } from "./field-repeater";
|
||||
|
||||
interface FormParent {
|
||||
|
||||
}
|
||||
|
||||
const FormParent: React.FunctionComponent<FormParent> = ({ }) => {
|
||||
|
||||
// states
|
||||
const [formData, setFormData] = useState<FormDataType>({
|
||||
// general
|
||||
generalData: {
|
||||
title: "",
|
||||
mainCat: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
category: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
body: undefined,
|
||||
photos: [],
|
||||
advertiser: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
city: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
address: "",
|
||||
postalCode: "",
|
||||
tags: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
},
|
||||
// shared
|
||||
sharedData: {
|
||||
price: undefined,
|
||||
product_state: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
model: "",
|
||||
brand: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
color: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
interior_color: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
},
|
||||
// jobs
|
||||
jobData: {
|
||||
jobTitle: "",
|
||||
employmentType: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
salaryOther: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
salaryFrom: "",
|
||||
salaryTo: "",
|
||||
salaryType: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
workStyle: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
insurance: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
experience: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
education: "",
|
||||
companyName: "",
|
||||
numberOfEmployees: [],
|
||||
languages: {
|
||||
names: [],
|
||||
ids: [],
|
||||
},
|
||||
linkToAd: "",
|
||||
},
|
||||
// vehicles
|
||||
vehicleData: {
|
||||
vehicle_type: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
car_body_type: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
production_year: undefined,
|
||||
distance_traveled: undefined,
|
||||
transmission_type: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
fuel_type: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
engine_capacity: undefined,
|
||||
number_of_cylinders: undefined,
|
||||
drivetrain: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
horsepower: undefined,
|
||||
top_speed: undefined,
|
||||
car_range: undefined,
|
||||
fuel_tank_capacity: undefined,
|
||||
city_fuel_usage: undefined,
|
||||
highway_fuel_usage: undefined,
|
||||
seats_count: undefined,
|
||||
doors_count: undefined,
|
||||
trunk_space: undefined,
|
||||
length: undefined,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
weight: undefined,
|
||||
vehicle_safety: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
vehicle_options: {
|
||||
translation: [],
|
||||
original: [],
|
||||
},
|
||||
}
|
||||
});
|
||||
const [currentStep, setCurrentStep] = useState(2);
|
||||
const [uploadedPhotos, setUploadedPhotos] = useState<any>([
|
||||
{
|
||||
directus_files_id: {
|
||||
id: "",
|
||||
storage: "",
|
||||
filename_download: ""
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const router = useRouter();
|
||||
const user = useAppSelector(userData);
|
||||
const translate = useTranslate();
|
||||
const accessToken = user.auth.accessToken;
|
||||
const newAdTitle = translate("profile-ad-form-new-ad");
|
||||
const generalStep = translate("profile-ad-form-steps-general");
|
||||
const detailsStep = translate("profile-ad-form-steps-details");
|
||||
const reviewStep = translate("profile-ad-form-steps-review");
|
||||
const steps = [generalStep, detailsStep, reviewStep];
|
||||
const formErrors = useRef<FormErrors>({
|
||||
stepOne: true,
|
||||
stepTwo: true,
|
||||
stepThree: true,
|
||||
});
|
||||
const { data: fieldsData, error: fieldsError } = useSWRImmutable([fieldsQuery], generalDataFetch);
|
||||
|
||||
const addNewItem = gql`
|
||||
mutation {
|
||||
create_Ads_item (
|
||||
data: {
|
||||
# general fields
|
||||
status: "published"
|
||||
title: "${formData.generalData.title}",
|
||||
body: "${formData.generalData.body}",
|
||||
category: {
|
||||
market_categories_id: {
|
||||
id: ${formData.generalData.category.ids[0]}
|
||||
}
|
||||
},
|
||||
gallery: ${ uploadedPhotos },
|
||||
advertiser_name: "${formData.generalData.advertiser}",
|
||||
email: "${formData.generalData.email}",
|
||||
phone_number: [
|
||||
{phone_number: "${formData.generalData.phoneNumber}"}
|
||||
],
|
||||
city: {
|
||||
id: ${formData.generalData.city.ids[0]}
|
||||
},
|
||||
address: "${formData.generalData.address}",
|
||||
postal_code: "${formData.generalData.postalCode}",
|
||||
tags: ${dataRepeater(formData.generalData.tags.ids, "tags")},
|
||||
# job related fields
|
||||
job_title: "${formData.jobData.jobTitle}",
|
||||
employment_type: "${formData.jobData.employmentType.original}",
|
||||
salary_other: "${formData.jobData.salaryOther.original}",
|
||||
salary_from: ${Number(formData.jobData.salaryFrom)},
|
||||
salary_to: ${Number(formData.jobData.salaryTo)},
|
||||
salary_type: "${formData.jobData.salaryType.original}",
|
||||
work_style: ["${formData.jobData.workStyle.original}"],
|
||||
insurance: "${formData.jobData.insurance.original}",
|
||||
experience: "${formData.jobData.experience.original}",
|
||||
number_of_employees: "${formData.jobData.numberOfEmployees}",
|
||||
education: "${formData.jobData.education}",
|
||||
company_name: "${formData.jobData.companyName}",
|
||||
link_to_ad: "${formData.jobData.linkToAd}",
|
||||
languages: ${dataRepeater(formData.jobData.languages.ids, "languages")}
|
||||
}
|
||||
)
|
||||
{
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const [submitForm, { data: mutationData, error: mutationErrors, loading: mutating }] = useMutation(addNewItem, { client: privateDataFetch(accessToken) });
|
||||
|
||||
|
||||
// methods
|
||||
const handleInputChange = (step: number, data: any) => {
|
||||
if(step === 1) {
|
||||
setFormData({ ...formData, generalData: { ...data } });
|
||||
formErrors.current.stepOne = false;
|
||||
}
|
||||
if (step === 2) {
|
||||
setFormData({ ...formData, jobData: { ...data } });
|
||||
formErrors.current.stepTwo = false;
|
||||
}
|
||||
if (step === 3) {
|
||||
// setFormData({ ...formData, jobData: { ...data } });
|
||||
// formErrors.current.stepTwo = false;
|
||||
}
|
||||
};
|
||||
useBeforeUnload(translate("route-change-error"), () => {
|
||||
return confirm(translate("route-change-error"))
|
||||
});
|
||||
const handleNextStep = () => {
|
||||
setCurrentStep(currentStep + 1);
|
||||
};
|
||||
const handlePrevStep = () => {
|
||||
setCurrentStep(currentStep - 1);
|
||||
};
|
||||
const uploadFinished = (res:any) => {
|
||||
setUploadedPhotos(dataRepeater(res.data, "gallery"));
|
||||
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
fileUpload(accessToken, formData.generalData.photos, "Market", uploadFinished);
|
||||
};
|
||||
|
||||
|
||||
// useEffects
|
||||
useEffect(() => {
|
||||
if (!isObject(uploadedPhotos[0])) {
|
||||
submitForm();
|
||||
}
|
||||
}, [uploadedPhotos]);
|
||||
|
||||
|
||||
if (fieldsError) console.log(fieldsError);
|
||||
// return
|
||||
return (
|
||||
<section className="block w-full">
|
||||
{!fieldsData ?
|
||||
<Skeleton className="block w-full lg:w-8/12 h-full absolute top-gi left-1/2 -translate-x-1/2 mx-auto" />
|
||||
:
|
||||
<div className="block w-full">
|
||||
{/* <h1 className="block">{newAdTitle}</h1> */}
|
||||
<StepsMap steps={steps} currentStep={currentStep} onStepClick={setCurrentStep} formErrors={formErrors.current} />
|
||||
{currentStep === 1 && <GeneralDetailsForm fieldsData={fieldsData} finalData={formData.generalData} handleInputChange={handleInputChange} onNextStep={handleNextStep} />}
|
||||
{currentStep === 2 && <MainDetails handleInputChange={handleInputChange} onNextStep={handleNextStep} onPrevStep={handlePrevStep} cat={formData.generalData.mainCat.names[0]} formData={formData} />}
|
||||
{currentStep === 3 && <ReviewDetails onPrevStep={handlePrevStep} formData={formData} onSubmit={handleSubmit} />}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default FormParent
|
||||
131
src/common/templates/dashboard/ads/gallery/gallery-backup.tsx
Normal file
131
src/common/templates/dashboard/ads/gallery/gallery-backup.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import { deepClone } from "services/general/general";
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||
|
||||
interface FormGallery {
|
||||
maxPhotos: number;
|
||||
}
|
||||
interface Gallery {
|
||||
name: string,
|
||||
type: string,
|
||||
size: number,
|
||||
src: any,
|
||||
}
|
||||
interface Thumb {
|
||||
item: {
|
||||
name: string,
|
||||
type: string,
|
||||
size: number,
|
||||
src: any,
|
||||
},
|
||||
index: number
|
||||
}
|
||||
|
||||
// thumbnail component
|
||||
const Thumb: React.FunctionComponent<Thumb> = ({ item, index }) => {
|
||||
return <Draggable key={item.name + item.size + index} draggableId={item.name + item.size} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="flex items-center flex-col text-xs md:text-[13px] text-primary mt-2 md:mt-3 select-none"
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
{/* <Icon name="triangle_exclamation_solid" className="inline-block w-3 h-3 lg:w-4 lg:h-4 ml-2 -mt-1 fill-light-red" /> */}
|
||||
<img src={item.src} className="block mx-auto w-full object-cover rounded-lg aspect-3/2" />
|
||||
<span className="block text-center text-xs sm:text-sm font-semibold [direction:ltr] mt-2">{item.name.slice(0, 20)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
}
|
||||
|
||||
const FormGallery: React.FunctionComponent<FormGallery> = ({ maxPhotos = 10 }) => {
|
||||
|
||||
// states
|
||||
const [gallery, setGallery] = useState<Gallery[]>([]);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const selectPhotos = translate("select-photos");
|
||||
const photosFormats = translate("photos-formats");
|
||||
const photosSizeLimit = translate("photos-size-limit");
|
||||
let localGallery = gallery;
|
||||
|
||||
// methods
|
||||
const handlePhotoSelection = (items: any) => {
|
||||
const files = items.target.files;
|
||||
if (gallery.length < maxPhotos && files.length + gallery.length <= maxPhotos) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
e.target && localGallery.push({ name: files[i].name, type: files[i].type, size: files[i].size, src: e.target.result });
|
||||
setGallery([...deepClone(localGallery)]);
|
||||
}
|
||||
reader.readAsDataURL(files[i]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const reorderThumbs = (list: Gallery[], startIndex: number, endIndex: number) => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
return result;
|
||||
};
|
||||
|
||||
const onDragEndHandler = (result: any) => {
|
||||
// dropped outside the list
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
const items = reorderThumbs(
|
||||
gallery,
|
||||
result.source.index,
|
||||
result.destination.index
|
||||
);
|
||||
setGallery([...deepClone(items)]);
|
||||
}
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [gallery])
|
||||
|
||||
// return
|
||||
return (
|
||||
<>
|
||||
<div className={`block w-full border border-dashed ${gallery.length > 0 ? "border-gray-300" : "border-gray-300"} rounded-lg p-gi lg:cursor-pointer`}>
|
||||
{gallery.length > 0 ?
|
||||
<DragDropContext onDragEnd={onDragEndHandler}>
|
||||
<Droppable droppableId="gallery-droppable">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 w-full"
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{gallery.map((x, i) => (
|
||||
<Thumb key={x.name + x.size + i} item={x} index={i} />
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
:
|
||||
<div className={``}>
|
||||
<img src="/5402993_2803242.jpg" className="block mx-auto w-5/12" />
|
||||
<span className="block text-center text-market-title-light font-semibold text-lg mb-5">{selectPhotos}</span>
|
||||
<span className="block text-center text-gray-400 text-sm mb-2">{photosFormats}</span>
|
||||
<span className="block text-center text-gray-400 text-sm mb-1">{photosSizeLimit}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<input type="file" id="gallery" name="gallery" multiple className="invisible" accept="image/*" onChange={handlePhotoSelection} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default FormGallery
|
||||
148
src/common/templates/dashboard/ads/gallery/gallery.tsx
Normal file
148
src/common/templates/dashboard/ads/gallery/gallery.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import { deepClone } from "services/general/general";
|
||||
import {
|
||||
DndContext,
|
||||
closestCorners,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
DragOverlay,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
rectSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import Photo from "./photo";
|
||||
import { Gallery } from "../data-types";
|
||||
|
||||
interface FormGallery {
|
||||
maxPhotos: number;
|
||||
photos: Gallery[];
|
||||
onGalleryUpdate: (photos: Gallery[]) => void
|
||||
}
|
||||
|
||||
const FormGallery: React.FunctionComponent<FormGallery> = ({ maxPhotos = 10, onGalleryUpdate, photos }) => {
|
||||
|
||||
// states
|
||||
const [gallery, setGallery] = useState<Gallery[]>(photos);
|
||||
const [activeId, setActiveId] = useState(null);
|
||||
const [isDraggable, setIsDraggable] = useState(true);
|
||||
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const selectPhotos = translate("select-photos");
|
||||
const photosFormats = translate("photos-formats");
|
||||
const photosSizeLimit = translate("photos-size-limit");
|
||||
const numberOfSelectedPics = translate("photos-number-of-selected-pics");
|
||||
const addPhoto = translate("add-photo");
|
||||
let localGallery = gallery;
|
||||
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
|
||||
|
||||
// methods
|
||||
const handlePhotoSelection = (items: any) => {
|
||||
const files = items.target.files;
|
||||
if (gallery.length < maxPhotos && files.length + gallery.length <= maxPhotos) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
e.target && localGallery.push({ name: files[i].name, type: files[i].type, size: files[i].size, src: e.target.result });
|
||||
setGallery([...deepClone(localGallery)]);
|
||||
onGalleryUpdate([...deepClone(localGallery)]);
|
||||
}
|
||||
reader.readAsDataURL(files[i]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragStart = (event:any) => {
|
||||
setActiveId(event.active.id);
|
||||
}
|
||||
const handleDragEnd = (event: any) => {
|
||||
const { active, over } = event;
|
||||
if (over === null ? null : active.id !== over.id) {
|
||||
setGallery((items) => {
|
||||
const oldIndex = items.indexOf(active.id);
|
||||
const newIndex = items.indexOf(over.id);
|
||||
onGalleryUpdate([...deepClone(arrayMove(items, oldIndex, newIndex))]);
|
||||
return arrayMove(items, oldIndex, newIndex);
|
||||
});
|
||||
}
|
||||
|
||||
setActiveId(null);
|
||||
}
|
||||
const handleDragCancel = () => {
|
||||
setActiveId(null);
|
||||
}
|
||||
const activeRemove = () => {
|
||||
setIsDraggable(!isDraggable);
|
||||
}
|
||||
|
||||
const handleRemove = (name: string) => {
|
||||
const itemIndex = gallery.indexOf(gallery.filter(x => x.name === name)[0]);
|
||||
localGallery.splice(itemIndex, 1);
|
||||
setGallery([...deepClone(localGallery)]);
|
||||
onGalleryUpdate([...deepClone(localGallery)]);
|
||||
gallery.length === 0 && setIsDraggable(true);
|
||||
}
|
||||
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [gallery])
|
||||
|
||||
// return
|
||||
return (
|
||||
<div className={`block w-full border border-dashed ${gallery.length > 0 ? "border-gray-300" : "border-gray-300"} rounded-lg`}>
|
||||
{gallery.length > 0 ?
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
>
|
||||
<SortableContext items={gallery as any} strategy={rectSortingStrategy}>
|
||||
<div className="flex items-center justify-between w-full mb-6 bg-market-input pb-3 pt-3 px-gi select-none">
|
||||
<span className="inline-block text-sm lg:text-[15px] font-semibold text-market-title-light">{`${numberOfSelectedPics} : ${gallery.length}/${maxPhotos}`}</span>
|
||||
<Icon name={`${!isDraggable ? "trash_can_check_solid" : "trash_can_xmark_solid"}`} className={`inline-block z-10 w-5 h-5 lg:w-6 lg:h-6 ${isDraggable ? "fill-market-title-light" : "fill-light-red"} lg:cursor-pointer`} onClick={activeRemove} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 gap-y-4 lg:gap-y-6 gap-x-4 w-full px-gi pb-gi lg:pb-6">
|
||||
{gallery.map((x, i) => (
|
||||
<Photo onRemove={handleRemove} id={x} key={x.name + x.size + i} item={x} isDraggable={isDraggable} />
|
||||
))}
|
||||
{gallery.length < 10 && isDraggable &&
|
||||
<label htmlFor="gallery" className={`flex flex-col items-center col-span-1 aspect-3/2 justify-center lg:cursor-pointer select-none text-sm lg:text-base border-2 border-dashed border-market-title-light rounded-lg py-2`}>
|
||||
<Icon name="cloud_plus_solid" className={`inline-block z-10 w-8 h-8 lg:w-9 lg:h-9 fill-market-title-light mb-1 lg:cursor-pointer`} />
|
||||
{addPhoto}
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</SortableContext>
|
||||
<DragOverlay adjustScale={true}>
|
||||
{activeId ?
|
||||
<Photo onRemove={handleRemove} id={activeId} item={gallery[gallery.indexOf(activeId)]} isDraggable={isDraggable} />
|
||||
:
|
||||
null
|
||||
}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
:
|
||||
<label htmlFor="gallery" className={`flex flex-col items-center lg:cursor-pointer select-none px-2 pb-gi lg:p-gi lg:pt-0 lg:pb-6`}>
|
||||
<img src="/5402993_2803242.jpg" className="block mx-auto w-5/12" />
|
||||
<span className="block text-center text-market-title-light font-semibold text-lg mb-5">{selectPhotos}</span>
|
||||
<span className="block text-center text-gray-400 text-xs lg:text-sm mb-2">{photosFormats}</span>
|
||||
<span className="block text-center text-gray-400 text-xs lg:text-sm mb-1">{photosSizeLimit}</span>
|
||||
</label>
|
||||
}
|
||||
<input type="file" id="gallery" name="gallery" multiple className="hidden" accept="image/*" onChange={handlePhotoSelection} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default FormGallery
|
||||
70
src/common/templates/dashboard/ads/gallery/photo.tsx
Normal file
70
src/common/templates/dashboard/ads/gallery/photo.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Icon } from "components/icons";
|
||||
|
||||
interface Photo {
|
||||
item: {
|
||||
name: string,
|
||||
type: string,
|
||||
size: number,
|
||||
src: any,
|
||||
},
|
||||
id: number | any,
|
||||
isDraggable: boolean,
|
||||
onRemove: (name: string) => void
|
||||
}
|
||||
|
||||
const Photo: React.FunctionComponent<Photo> = ({ item, id, onRemove, isDraggable = true }) => {
|
||||
|
||||
// states
|
||||
const [draggable, setDraggable] = useState(isDraggable);
|
||||
|
||||
// variables
|
||||
const {
|
||||
attributes,
|
||||
isDragging,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition
|
||||
} = useSortable({
|
||||
id: id,
|
||||
disabled: !draggable
|
||||
});
|
||||
const inlineStyles = {
|
||||
opacity: isDragging ? '0.2' : '1',
|
||||
transformOrigin: '0 0',
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition
|
||||
};
|
||||
|
||||
// methods
|
||||
const handleRemove = () => {
|
||||
onRemove(item.name);
|
||||
}
|
||||
|
||||
isDraggable !== draggable && setDraggable(isDraggable);
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [])
|
||||
|
||||
// return
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="flex items-center flex-col text-xs md:text-[13px] text-primary select-none cursor-none lg:cursor-auto"
|
||||
style={inlineStyles}
|
||||
>
|
||||
<div className={`relative ${!draggable ? "[&>img]:hover:opacity-50 [&>svg]:hover:visible" : ""} cursor-none lg:cursor-pointer`} onClick={handleRemove}>
|
||||
<Icon name="xmark_solid" className={`${!draggable ? "inline-block lg:invisible" : "hidden"} absolute left-1/2 top-1/2 -translate-y-1/2 -translate-x-1/2 z-10 w-8 h-8 lg:w-8 lg:h-8 bg-market-title-light fill-white rounded-full p-2 shadow`} />
|
||||
<img src={item.src} className={`block mx-auto w-full object-cover rounded-lg aspect-3/2 ${draggable ? "" : "ring-4 scale-95 ring-market-border max-lg:opacity-50"}`} />
|
||||
</div>
|
||||
<span className="block text-center text-xs sm:text-sm font-semibold [direction:ltr] mt-2">{item.name.slice(0, 20)}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Photo
|
||||
326
src/common/templates/dashboard/ads/general-details.tsx
Normal file
326
src/common/templates/dashboard/ads/general-details.tsx
Normal file
@ -0,0 +1,326 @@
|
||||
import React, { useRef, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { Icon } from "components/icons";
|
||||
import Input from "components/input/text";
|
||||
import Label from "components/label/label";
|
||||
import FormItem from "./form-item";
|
||||
import {
|
||||
canadianPhoneNumberValidationRegex,
|
||||
canadianPostalCodeValidationRegex,
|
||||
deepClone,
|
||||
emailValidationRegex,
|
||||
generalDataFetch,
|
||||
hasValue
|
||||
} from "services/general/general";
|
||||
import Button from "components/button/button";
|
||||
import Skeleton from "components/skeleton/skeleton";
|
||||
import { Editor } from '@tinymce/tinymce-react';
|
||||
import FormGallery from "./gallery/gallery";
|
||||
import StepButtons from "./step-buttons";
|
||||
import { Gallery, GeneralData } from "./data-types";
|
||||
|
||||
interface GeneralDetailsForm {
|
||||
fieldsData: any,
|
||||
finalData: GeneralData,
|
||||
handleInputChange: (step: number, data: any) => void,
|
||||
onNextStep: () => void,
|
||||
}
|
||||
|
||||
const GeneralDetailsForm: React.FunctionComponent<GeneralDetailsForm> = ({ fieldsData, finalData, handleInputChange, onNextStep }) => {
|
||||
|
||||
// states
|
||||
const [selectionOpen, setSelectionOpen] = useState(false);
|
||||
const [mainCat, setMainCat] = useState<string[]>(finalData.mainCat.names);
|
||||
const [subCat, setSubCat] = useState<string[]>(finalData.category.names);
|
||||
const [city, setCity] = useState<string[]>(finalData.city.names);
|
||||
const [tags, setTags] = useState<string[]>(finalData.tags.names);
|
||||
|
||||
const formData = useRef<GeneralData>({
|
||||
title: finalData.title,
|
||||
mainCat: finalData.mainCat,
|
||||
category: finalData.category,
|
||||
body: finalData.body,
|
||||
photos: finalData.photos,
|
||||
advertiser: finalData.advertiser,
|
||||
email: finalData.email,
|
||||
phoneNumber: finalData.phoneNumber,
|
||||
city: finalData.city,
|
||||
address: finalData.address,
|
||||
postalCode: finalData.postalCode,
|
||||
tags: finalData.tags
|
||||
});
|
||||
const [fieldErrors, setFieldErrors] = useState({
|
||||
title: false,
|
||||
category: false,
|
||||
body: false,
|
||||
advertisor: false,
|
||||
email: false,
|
||||
city: false,
|
||||
phoneNumber: false,
|
||||
postalCode: false,
|
||||
});
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const topCities = ["Vancouver", "North Vancouver", "West Vancouver", "Toronto", "Montreal", "Calgary", "Ottawa", "Victoria",
|
||||
"Edmonton", "Quebec", "Mississauga", "Winnipeg", "Brampton", "Hamilton", "Surrey", "Halifax", "London", "Laval", "Markham",
|
||||
"Vaughan", "Gatineau", "Saskatoon"
|
||||
];
|
||||
const cities = fieldsData ?
|
||||
[...fieldsData.data.Cities].sort((a, b) => {
|
||||
if (topCities.includes(a.English_name) && topCities.includes(b.English_name)) {
|
||||
if (topCities.indexOf(a.English_name) < topCities.indexOf(b.English_name)) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
if (topCities.includes(a.English_name) && !topCities.includes(b.English_name)) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}): [];
|
||||
const editorRef:any = useRef(null);
|
||||
let noErrors = false;
|
||||
let isFirstLoad = useRef(true);
|
||||
|
||||
const formTitles = {
|
||||
title: translate("profile-ad-form-title"),
|
||||
titlePlaceholder: translate("profile-ad-form-title-placeholder"),
|
||||
mainCategory: translate("profile-ad-form-category"),
|
||||
subCategory: translate("profile-ad-form-sub-category"),
|
||||
body: translate("profile-ad-form-body"),
|
||||
gallery: translate("photos-gallery"),
|
||||
bodyPlaceholder: translate("profile-ad-form-body-placeholder"),
|
||||
advertiserName: translate("profile-ad-form-advertiser-name"),
|
||||
advertiserNamePlaceholder: translate("profile-ad-form-advertiser-name-placeholder"),
|
||||
address: translate("address"),
|
||||
email: translate("email-address"),
|
||||
postalCode: translate("postal-code"),
|
||||
allOption: translate("select"),
|
||||
otherCities: translate("other-cities"),
|
||||
city: translate("city"),
|
||||
phoneNumber: translate("phone-number"),
|
||||
tags: translate("tags"),
|
||||
}
|
||||
const formErrors = {
|
||||
necessaryFields: translate("all-fields-necessary-error"),
|
||||
title: translate("all-fields-necessary-error"),
|
||||
email: translate("email-format-error"),
|
||||
postalCode: translate("postal-code-format-error"),
|
||||
phoneNumber: translate("phone-number-format-error"),
|
||||
}
|
||||
|
||||
// methods
|
||||
const validate = () => {
|
||||
const localErrors = deepClone(fieldErrors);
|
||||
|
||||
hasValue(formData.current.title) ? localErrors.title = false : localErrors.title = true;
|
||||
(formData.current.category.names.length === 0) ? localErrors.category = true : localErrors.category = false;
|
||||
(formData.current.body === "" || formData.current.body === undefined) ? localErrors.body = true : localErrors.body = false;
|
||||
hasValue(formData.current.advertiser) ? localErrors.advertisor = false : localErrors.advertisor = true;
|
||||
(formData.current.email === "" || formData.current.email === null || !emailValidationRegex.test(formData.current.email)) ? localErrors.email = true : localErrors.email = false;
|
||||
(formData.current.city.names.length === 0) ? localErrors.city = true : localErrors.city = false;
|
||||
hasValue(formData.current.phoneNumber) ? !canadianPhoneNumberValidationRegex.test(formData.current.phoneNumber) ? localErrors.phoneNumber = true : localErrors.phoneNumber = false : "";
|
||||
hasValue(formData.current.postalCode) ? !canadianPostalCodeValidationRegex.test(formData.current.postalCode) ? localErrors.postalCode = true : localErrors.postalCode = false : "";
|
||||
|
||||
if (!isFirstLoad.current) {
|
||||
noErrors = !Object.values(localErrors).some(x => x === true);
|
||||
setFieldErrors({ ...fieldErrors, ...localErrors});
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormUpdate = (name: string, value: string | string[] | undefined | Gallery[]) => {
|
||||
formData.current = { ...formData.current, [name]: value };
|
||||
validate();
|
||||
}
|
||||
const handleNextStep = () => {
|
||||
isFirstLoad.current = false;
|
||||
validate();
|
||||
noErrors && handleInputChange(1, formData.current);
|
||||
noErrors && onNextStep();
|
||||
}
|
||||
const handleMainCatSelection = (items: string[]) => {
|
||||
setSubCat([]);
|
||||
const itemsIds = fieldsData.data.market_categories.filter((x: any) => items.some(y => y === x.name)).map((x: any) => x.id);
|
||||
const itemsNames = fieldsData.data.market_categories.filter((x: any) => items.some(y => y === x.name)).map((x: any) => x.name);
|
||||
const data: any = { names: itemsNames, ids: itemsIds };
|
||||
setMainCat(items);
|
||||
handleFormUpdate("mainCat", data);
|
||||
}
|
||||
const handleSubCatSelection = (items: string[]) => {
|
||||
const itemsIds = fieldsData.data.market_categories.filter((x: any) => items.some(y => y === x.name)).map((x: any) => x.id);
|
||||
const itemsNames = fieldsData.data.market_categories.filter((x: any) => items.some(y => y === x.name)).map((x: any) => x.name);
|
||||
const data: any = { names: itemsNames, ids: itemsIds };
|
||||
setSubCat(items);
|
||||
handleFormUpdate("category", data);
|
||||
validate();
|
||||
}
|
||||
const handleCitySelection = (items: string[]) => {
|
||||
const itemsIds = fieldsData.data.Cities.filter((x: any) => items.some(y => y === `${x.name} - ${x.English_name}`)).map((x:any) => x.id);
|
||||
const itemsNames = fieldsData.data.Cities.filter((x: any) => items.some(y => y === `${x.name} - ${x.English_name}`)).map((x: any) => `${x.name} - ${x.English_name}`);
|
||||
const data:any = { names: itemsNames, ids: itemsIds };
|
||||
setCity(items);
|
||||
handleFormUpdate("city", data);
|
||||
validate();
|
||||
}
|
||||
const handleTagsSelection = (items: string[]) => {
|
||||
const itemsIds = fieldsData.data.market_tags.filter((x: any) => items.some(y => y === x.name)).map((x: any) => x.id);
|
||||
const itemsNames = fieldsData.data.market_tags.filter((x: any) => items.some(y => y === x.name)).map((x: any) => x.name);
|
||||
const data: any = { names: itemsNames, ids: itemsIds };
|
||||
setTags(items);
|
||||
handleFormUpdate("tags", data);
|
||||
}
|
||||
|
||||
// useEffects
|
||||
|
||||
// }, [])
|
||||
// console.log(formData.current, tags);
|
||||
|
||||
// return
|
||||
return (
|
||||
<div>
|
||||
<form className="w-full lg:w-full grid grid-cols-6 gap-x-7 gap-y-5 lg:gap-y-6 bg-white shadow p-6 rounded-md">
|
||||
{/* title */}
|
||||
<FormItem forId="ad-title" title={formTitles.title} className="col-span-6 md:col-span-4" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.title}>
|
||||
<Input id="ad-title" name="title" type="text" required className={`profile-form placeholder:text-sm w-full ${fieldErrors.title ? "profile-form-error" : ""}`} placeholder={formTitles.titlePlaceholder} value={formData.current.title} onInput={data => handleFormUpdate("title", data)} />
|
||||
</FormItem>
|
||||
{/* main category */}
|
||||
<FormItem forId="ad-main-category" title={formTitles.mainCategory} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.category}>
|
||||
<DataList
|
||||
id={"ad-main-category"}
|
||||
label={formTitles.mainCategory}
|
||||
options={fieldsData.data.market_categories.filter((x:any) => x.has_children).map((x: any) => x.name)}
|
||||
onChange={handleMainCatSelection}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={mainCat}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.category ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* sub category */}
|
||||
<FormItem forId="ad-sub-category" title={formTitles.subCategory} className={`${mainCat.length > 0 ? "block" : "hidden"} col-span-6 md:col-span-3`} required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.category}>
|
||||
<DataList
|
||||
id={"ad-sub-category"}
|
||||
label={formTitles.subCategory}
|
||||
options={fieldsData.data.market_categories.filter((x: any) => x.parent[0] && x.parent[0].related_market_categories_id.name === mainCat[0]).map((x: any) => x.name)}
|
||||
onChange={handleSubCatSelection}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={subCat}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.category ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* body */}
|
||||
<FormItem forId="body" title={formTitles.body} className="col-span-6 md:col-span-6 min-h-[436px]" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.body}>
|
||||
<Editor
|
||||
tinymceScriptSrc={'/tinymce/tinymce.min.js'}
|
||||
onInit={(evt, editor) => editorRef.current = editor}
|
||||
initialValue={formData.current.body}
|
||||
init={{
|
||||
height: 400,
|
||||
menubar: false,
|
||||
plugins: [
|
||||
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap',
|
||||
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', 'directionality',
|
||||
'insertdatetime', 'media', 'table', 'preview', 'help', 'wordcount',
|
||||
],
|
||||
toolbar: 'undo redo | blocks | ' +
|
||||
'bold italic ltr rtl removeformat | alignleft aligncenter ' +
|
||||
'alignright alignjustify | bullist numlist outdent indent |',
|
||||
content_style: 'body { font-family:IRANSans,Helvetica,Arial,sans-serif; font-size:16px; direction:rtl; }',
|
||||
}}
|
||||
onChange={() => handleFormUpdate("body", editorRef.current?.getContent())}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* gallery */}
|
||||
<FormItem forId="" title={formTitles.gallery} className="col-span-6 md:col-span-6">
|
||||
<FormGallery maxPhotos={10} photos={formData.current.photos} onGalleryUpdate={data => handleFormUpdate("photos", data)} />
|
||||
</FormItem>
|
||||
{/* advertiser name */}
|
||||
<FormItem forId="advertiser-name" title={formTitles.advertiserName} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.advertisor}>
|
||||
<Input id="advertiser-name" name="advertiser" type="text" required className={`profile-form placeholder:text-sm w-full ${fieldErrors.advertisor ? "profile-form-error" : ""}`} placeholder={formTitles.advertiserNamePlaceholder} value={formData.current.advertiser} onInput={data => handleFormUpdate("advertiser", data)} />
|
||||
</FormItem>
|
||||
{/* email */}
|
||||
<FormItem forId="ad-email" title={formTitles.email} className="col-span-6 md:col-span-3" required errorText={formErrors.email} errorVisible={fieldErrors.email}>
|
||||
<Input id="ad-email" name="email" type="email" required className={`profile-form ${fieldErrors.email ? "profile-form-error" : ""} placeholder:text-sm w-full [direction:ltr]`} placeholder="you@example.com" value={formData.current.email} onInput={data => handleFormUpdate("email", data)} />
|
||||
</FormItem>
|
||||
{/* phone number */}
|
||||
<FormItem forId="ad-phone" title={formTitles.phoneNumber} className="col-span-6 md:col-span-3" errorText={formErrors.phoneNumber} errorVisible={fieldErrors.phoneNumber}>
|
||||
<Input id="ad-phone" name="phone-number" type="tel" required className={`profile-form ${fieldErrors.phoneNumber ? "profile-form-error" : ""} placeholder:text-sm w-full [direction:ltr]`} placeholder="604 555 5555" unit={"1+"} value={formData.current.phoneNumber} unitClass={`profile-form mr-2`} onInput={data => handleFormUpdate("phoneNumber", data)} />
|
||||
</FormItem>
|
||||
{/* city */}
|
||||
<FormItem forId="ad-city" title={formTitles.city} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.city}>
|
||||
<DataList
|
||||
id={"ad-city"}
|
||||
label={formTitles.city}
|
||||
options={[...cities.map((x: any) => `${x.name} - ${x.English_name}`), formTitles.otherCities]}
|
||||
onChange={handleCitySelection}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={city}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.city ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* address */}
|
||||
<FormItem forId="ad-address" title={formTitles.address} className="col-span-6 md:col-span-3">
|
||||
<Input id="ad-address" name="address" type="text" className="profile-form placeholder:text-sm w-full [direction:ltr]" placeholder="1499 W Pender St, Vancouver, BC" value={formData.current.address} onInput={data => handleFormUpdate("address", data)} />
|
||||
</FormItem>
|
||||
{/* postal code */}
|
||||
<FormItem forId="ad-postal-code" title={formTitles.postalCode} className="col-span-6 md:col-span-3" errorText={formErrors.postalCode} errorVisible={fieldErrors.postalCode}>
|
||||
<Input id="ad-postal-code" name="postal-code" type="text" className={`profile-form placeholder:text-sm w-full [direction:ltr] ${fieldErrors.postalCode ? "profile-form-error" : ""}`} placeholder="K1A 0T6" value={formData.current.postalCode} onInput={data => handleFormUpdate("postalCode", data)} />
|
||||
</FormItem>
|
||||
{/* tags */}
|
||||
<FormItem forId="ad-tags" title={formTitles.tags} className="col-span-6 md:col-span-6">
|
||||
<DataList
|
||||
id={"ad-tags"}
|
||||
label={formTitles.tags}
|
||||
options={fieldsData.data.market_tags.map((x: any) => x.name)}
|
||||
onChange={handleTagsSelection}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={tags}
|
||||
multiple
|
||||
wrapperClass="market-datalist-wrapper profile-form p-0"
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
</form>
|
||||
<StepButtons onNext={handleNextStep} stepsCount={3} currentStep={1} />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default GeneralDetailsForm
|
||||
491
src/common/templates/dashboard/ads/jobs-form.tsx
Normal file
491
src/common/templates/dashboard/ads/jobs-form.tsx
Normal file
@ -0,0 +1,491 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import Button from "components/button/button";
|
||||
import StepButtons from "./step-buttons";
|
||||
import { JobData, SystemFields } from "./data-types";
|
||||
import useSWRImmutable from "swr";
|
||||
import { deepClone, generalDataFetch, hasValue, systemDataFetch } from "services/general/general";
|
||||
import Skeleton from "components/skeleton/skeleton";
|
||||
import FormItem from "./form-item";
|
||||
import Input from "components/input/text";
|
||||
import DataList from "components/select/data-list";
|
||||
|
||||
interface JobsForm {
|
||||
className?: string,
|
||||
finalData: JobData,
|
||||
handleInputChange: (step: number, data: any) => void,
|
||||
onPrevStep: () => void,
|
||||
onNextStep: () => void,
|
||||
}
|
||||
|
||||
const JobsForm: React.FunctionComponent<JobsForm> = ({ finalData, handleInputChange, onPrevStep, onNextStep, className }) => {
|
||||
|
||||
// states
|
||||
const [selectionOpen, setSelectionOpen] = useState(false);
|
||||
const [employmentType, setEmploymentType] = useState<string[]>(finalData.employmentType.translation);
|
||||
const [salaryType, setSalaryType] = useState<string[]>(finalData.salaryType.translation);
|
||||
const [salaryOther, setSalaryOther] = useState<string[]>(finalData.salaryOther.translation);
|
||||
const [workStyle, setWorkStyle] = useState<string[]>(finalData.workStyle.translation);
|
||||
const [insurance, setInsurance] = useState<string[]>(finalData.insurance.translation);
|
||||
const [experience, setExperience] = useState<string[]>(finalData.experience.translation);
|
||||
const [numberOfEmployees, setNumberOfEmployees] = useState<string[]>(finalData.numberOfEmployees);
|
||||
const [languages, setLanguages] = useState<string[]>(finalData.languages.names);
|
||||
const translate = useTranslate();
|
||||
|
||||
const formData = useRef<JobData>({
|
||||
jobTitle: finalData.jobTitle,
|
||||
employmentType: finalData.employmentType,
|
||||
salaryType: finalData.salaryType,
|
||||
salaryFrom: finalData.salaryFrom,
|
||||
salaryTo: finalData.salaryTo,
|
||||
salaryOther: finalData.salaryOther,
|
||||
workStyle: finalData.workStyle,
|
||||
insurance: finalData.insurance,
|
||||
experience: finalData.experience,
|
||||
education: finalData.education,
|
||||
companyName: finalData.companyName,
|
||||
numberOfEmployees: finalData.numberOfEmployees,
|
||||
languages: finalData.languages,
|
||||
linkToAd: finalData.linkToAd,
|
||||
});
|
||||
const [fieldErrors, setFieldErrors] = useState({
|
||||
jobTitle: false,
|
||||
employmentType: false,
|
||||
salaryType: false,
|
||||
salaryFrom: false,
|
||||
salaryOther: false,
|
||||
workStyle: false,
|
||||
experience: false,
|
||||
companyName: false,
|
||||
languages: false,
|
||||
linkToAd: false,
|
||||
});
|
||||
|
||||
// variables
|
||||
const languagesQuery = `
|
||||
languages {
|
||||
code
|
||||
name
|
||||
persian_name
|
||||
}
|
||||
`
|
||||
const fieldsQuery = `
|
||||
employment_type: fields_by_name(collection: "Ads", field: "employment_type") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
work_style: fields_by_name(collection: "Ads", field: "work_style") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
experience: fields_by_name(collection: "Ads", field: "experience") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
insurance: fields_by_name(collection: "Ads", field: "insurance") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
salary_type: fields_by_name(collection: "Ads", field: "salary_type") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
salary_other: fields_by_name(collection: "Ads", field: "salary_other") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
number_of_employees: fields_by_name(collection: "Ads", field: "number_of_employees") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
`
|
||||
const { data: fieldsData, error: fieldsError } = useSWRImmutable([fieldsQuery], systemDataFetch);
|
||||
const { data: languagesData, error: languagesDataError } = useSWRImmutable([languagesQuery], generalDataFetch);
|
||||
|
||||
let noErrors = false;
|
||||
let isFirstLoad = useRef(true);
|
||||
|
||||
const formTitles = {
|
||||
allOption: translate("select"),
|
||||
jobTitle: translate("profile-ad-form-job-title"),
|
||||
jobTitlePlaceholder: translate("profile-ad-form-job-title-placeholder"),
|
||||
employmentType: translate("ads-ad-page-jobs-employment-type"),
|
||||
salaryType: translate("ads-ad-page-jobs-salary-type"),
|
||||
salaryText: translate("ads-ad-page-jobs-salary"),
|
||||
salaryAmount: translate("salary-amount"),
|
||||
salaryFrom: translate("price-from"),
|
||||
salaryTo: translate("price-to"),
|
||||
salaryOther: translate("profile-ad-form-job-salary-other"),
|
||||
paymentPeriod: translate("ads-ad-page-jobs-payment-period"),
|
||||
workStyle: translate("ads-ad-page-jobs-work-style"),
|
||||
insurance: translate("ads-ad-page-jobs-insurance"),
|
||||
experience: translate("ads-ad-page-jobs-experience"),
|
||||
education: translate("ads-ad-page-jobs-education"),
|
||||
educationPlaceholder: translate("profile-ad-form-job-education-placeholder"),
|
||||
companyName: translate("ads-ad-page-jobs-company-name"),
|
||||
companyNamePlaceholder: "A&H Stone Ltd",
|
||||
numberOfEmployees: translate("ads-ad-page-jobs-number-of-employees"),
|
||||
languages: translate("ads-ad-page-jobs-languages"),
|
||||
linkToAd: translate("profile-ad-form-job-link-to-ad"),
|
||||
linkToAdPlaceholder: "https://www.jobbank.gc.ca/jobsearch/jobposting/37954255",
|
||||
}
|
||||
const formErrors = {
|
||||
necessaryFields: translate("all-fields-necessary-error"),
|
||||
}
|
||||
|
||||
// methods
|
||||
const validate = () => {
|
||||
const localErrors = deepClone(fieldErrors);
|
||||
|
||||
hasValue(formData.current.jobTitle) ? localErrors.jobTitle = false : localErrors.jobTitle = true;
|
||||
employmentType.length > 0 ? localErrors.employmentType = false : localErrors.employmentType = true;
|
||||
salaryOther.length > 0 ? localErrors.salaryOther = false : localErrors.salaryOther = true;
|
||||
if (salaryOther[0] === 'مقدار حقوق به دلار کانادا') {
|
||||
hasValue(formData.current.salaryFrom) ? localErrors.salaryFrom = false : localErrors.salaryFrom = true;
|
||||
salaryType.length > 0 ? localErrors.salaryType = false : localErrors.salaryType = true;
|
||||
}
|
||||
workStyle.length > 0 ? localErrors.workStyle = false : localErrors.workStyle = true;
|
||||
experience.length > 0 ? localErrors.experience = false : localErrors.experience = true;
|
||||
formData.current.languages.names.length > 0 ? localErrors.languages = false : localErrors.languages = true;
|
||||
hasValue(formData.current.companyName) ? localErrors.companyName = false : localErrors.companyName = true;
|
||||
hasValue(formData.current.linkToAd) ? localErrors.linkToAd = false : localErrors.linkToAd = true;
|
||||
|
||||
if (!isFirstLoad.current) {
|
||||
noErrors = !Object.values(localErrors).some(x => x === true);
|
||||
setFieldErrors({ ...fieldErrors, ...localErrors });
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormUpdate = (name: string, value: string | string[] | undefined) => {
|
||||
formData.current = { ...formData.current, [name]: value };
|
||||
validate();
|
||||
}
|
||||
const handleNextStep = () => {
|
||||
isFirstLoad.current = false;
|
||||
validate();
|
||||
noErrors && handleInputChange(2, formData.current);
|
||||
noErrors && onNextStep();
|
||||
}
|
||||
const handlePrevStep = () => {
|
||||
isFirstLoad.current = false;
|
||||
validate();
|
||||
noErrors && handleInputChange(2, formData.current);
|
||||
noErrors && onPrevStep();
|
||||
}
|
||||
const handleDetailsSelection = (name: string, items: string[]) => {
|
||||
let itemsTr, itemsOr, itemsIds, itemsNames, data:any;
|
||||
switch (name) {
|
||||
case "employmentType":
|
||||
setEmploymentType(items);
|
||||
itemsTr = fieldsData?.data.employment_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => translate(x.value));
|
||||
itemsOr = fieldsData?.data.employment_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "salaryType":
|
||||
setSalaryType(items);
|
||||
itemsTr = fieldsData?.data.salary_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => translate(x.value));
|
||||
itemsOr = fieldsData?.data.salary_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "salaryOther":
|
||||
setSalaryOther(items);
|
||||
itemsTr = fieldsData?.data.salary_other.meta.options.choices.filter((x: any) => items.some(y => y === translate(`salary-${x.value}`))).map((x: any) => translate(`salary-${x.value}`));
|
||||
itemsOr = fieldsData?.data.salary_other.meta.options.choices.filter((x: any) => items.some(y => y === translate(`salary-${x.value}`))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "workStyle":
|
||||
setWorkStyle(items);
|
||||
itemsTr = fieldsData?.data.work_style.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => translate(x.value));
|
||||
itemsOr = fieldsData?.data.work_style.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "insurance":
|
||||
setInsurance(items);
|
||||
itemsTr = fieldsData?.data.insurance.meta.options.choices.filter((x: any) => items.some(y => y === translate(`logic-${x.value}`))).map((x: any) => translate(`logic-${x.value}`));
|
||||
itemsOr = fieldsData?.data.insurance.meta.options.choices.filter((x: any) => items.some(y => y === translate(`logic-${x.value}`))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "experience":
|
||||
setExperience(items);
|
||||
itemsTr = fieldsData?.data.experience.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => translate(x.value));
|
||||
itemsOr = fieldsData?.data.experience.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "numberOfEmployees":
|
||||
setNumberOfEmployees(items);
|
||||
handleFormUpdate(name, items);
|
||||
break;
|
||||
case "languages":
|
||||
itemsIds = languagesData?.data.languages.filter((x: any) => items.some(y => y === x.persian_name)).map((x: any) => x.code);
|
||||
itemsNames = languagesData?.data.languages.filter((x: any) => items.some(y => y === x.persian_name)).map((x: any) => x.persian_name);
|
||||
data = { names: itemsNames, ids: itemsIds };
|
||||
setLanguages(items);
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// useEffects
|
||||
|
||||
// }, [])
|
||||
|
||||
if (fieldsError || languagesDataError) console.log(fieldsError);
|
||||
|
||||
// return
|
||||
return (
|
||||
<div>
|
||||
{!fieldsData || !languagesData ?
|
||||
<Skeleton className="block w-full lg:w-8/12 h-full absolute top-gi left-1/2 -translate-x-1/2 mx-auto" />
|
||||
:
|
||||
<>
|
||||
<form className="w-full lg:w-full grid grid-cols-6 gap-x-7 gap-y-5 lg:gap-y-6 bg-white shadow p-6 rounded-md">
|
||||
{/* job title */}
|
||||
<FormItem forId="ad-job-title" title={formTitles.jobTitle} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.jobTitle}>
|
||||
<Input id="ad-job-title" name="title" type="text" required className={`profile-form placeholder:text-sm w-full ${fieldErrors.jobTitle ? "profile-form-error" : ""}`} placeholder={formTitles.jobTitlePlaceholder} value={formData.current.jobTitle} onInput={data => handleFormUpdate("jobTitle", data)} />
|
||||
</FormItem>
|
||||
{/* employment type */}
|
||||
<FormItem forId="ad-employment-type" title={formTitles.employmentType} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.employmentType}>
|
||||
<DataList
|
||||
id={"ad-employment-type"}
|
||||
label={formTitles.employmentType}
|
||||
options={fieldsData.data.employment_type.meta.options.choices.map((x: SystemFields) => translate(x.value))}
|
||||
onChange={(items) => handleDetailsSelection("employmentType", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={employmentType}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.employmentType ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* salary options */}
|
||||
<FormItem forId="ad-salary-other" title={formTitles.salaryText} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.salaryOther}>
|
||||
<DataList
|
||||
id={"ad-salary-other"}
|
||||
label={formTitles.salaryOther}
|
||||
options={fieldsData.data.salary_other.meta.options.choices.map((x: SystemFields) => translate(`salary-${x.value}`))}
|
||||
onChange={(items) => handleDetailsSelection("salaryOther", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={salaryOther}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.salaryOther ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* salary amount */}
|
||||
{salaryOther[0] === 'مقدار حقوق به دلار کانادا' &&
|
||||
<FormItem forId="ad-job-salary" title={formTitles.salaryAmount} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.jobTitle}>
|
||||
<div id="ad-job-salary" className="grid grid-cols-2 gap-6">
|
||||
<label htmlFor="ad-job-salary-from" className="">
|
||||
<Input
|
||||
id="ad-job-salary-from"
|
||||
name="title"
|
||||
type="number"
|
||||
required
|
||||
wrapperClass="[direction:ltr]"
|
||||
className={`profile-form placeholder:text-sm w-full text-center ${fieldErrors.salaryFrom ? "profile-form-error" : ""}`}
|
||||
value={formData.current.salaryFrom}
|
||||
onInput={data => handleFormUpdate("salaryFrom", data)}
|
||||
placeholder="24"
|
||||
unit={formTitles.salaryFrom}
|
||||
unitClass="profile-form ml-2"
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="ad-job-salary-to" className="">
|
||||
<Input
|
||||
id="ad-job-salary-to"
|
||||
name="title"
|
||||
type="number"
|
||||
required
|
||||
wrapperClass="[direction:ltr]"
|
||||
className={`profile-form placeholder:text-sm w-full text-center ${fieldErrors.salaryFrom ? "profile-form-error" : ""}`}
|
||||
value={formData.current.salaryTo}
|
||||
onInput={data => handleFormUpdate("salaryTo", data)}
|
||||
placeholder="36"
|
||||
unit={formTitles.salaryTo}
|
||||
unitClass="profile-form ml-2"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</FormItem>
|
||||
}
|
||||
{/* salary type */}
|
||||
{salaryOther[0] === 'مقدار حقوق به دلار کانادا' &&
|
||||
<FormItem forId="ad-salary-type" title={formTitles.salaryType} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.salaryType}>
|
||||
<DataList
|
||||
id={"ad-salary-type"}
|
||||
label={formTitles.salaryType}
|
||||
options={fieldsData.data.salary_type.meta.options.choices.map((x: SystemFields) => translate(x.value))}
|
||||
onChange={(items) => handleDetailsSelection("salaryType", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={salaryType}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.salaryType ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
}
|
||||
{/* work style */}
|
||||
<FormItem forId="ad-job-work-style" title={formTitles.workStyle} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.workStyle}>
|
||||
<DataList
|
||||
id={"ad-job-work-style"}
|
||||
label={formTitles.workStyle}
|
||||
options={fieldsData.data.work_style.meta.options.choices.map((x: SystemFields) => translate(`${x.value}`))}
|
||||
onChange={(items) => handleDetailsSelection("workStyle", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={workStyle}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.workStyle ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* insurance */}
|
||||
<FormItem forId="ad-job-insurance" title={formTitles.insurance} className="col-span-6 md:col-span-3">
|
||||
<DataList
|
||||
id={"ad-job-insurance"}
|
||||
label={formTitles.insurance}
|
||||
options={fieldsData.data.insurance.meta.options.choices.map((x: SystemFields) => translate(`logic-${x.value}`))}
|
||||
onChange={(items) => handleDetailsSelection("insurance", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={insurance}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* experience */}
|
||||
<FormItem forId="ad-job-experience" title={formTitles.experience} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.experience}>
|
||||
<DataList
|
||||
id={"ad-job-experience"}
|
||||
label={formTitles.experience}
|
||||
options={fieldsData.data.experience.meta.options.choices.map((x: SystemFields) => translate(`${x.value}`))}
|
||||
onChange={(items) => handleDetailsSelection("experience", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={experience}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.experience ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* education */}
|
||||
<FormItem forId="ad-job-education" title={formTitles.education} className="col-span-6 md:col-span-3">
|
||||
<Input id="ad-job-education" name="education" type="text" required className={`profile-form placeholder:text-sm w-full`} placeholder={formTitles.educationPlaceholder} value={formData.current.education} onInput={data => handleFormUpdate("education", data)} />
|
||||
</FormItem>
|
||||
{/* company name */}
|
||||
<FormItem forId="ad-job-comapny-name" title={formTitles.companyName} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.companyName}>
|
||||
<Input id="ad-job-comapny-name" name="company-name" type="text" required className={`profile-form placeholder:text-sm w-full ${fieldErrors.companyName ? "profile-form-error" : ""}`} placeholder={formTitles.companyNamePlaceholder} value={formData.current.companyName} onInput={data => handleFormUpdate("companyName", data)} />
|
||||
</FormItem>
|
||||
{/* numberOfEmployees */}
|
||||
<FormItem forId="ad-job-number-of-employees" title={formTitles.numberOfEmployees} className="col-span-6 md:col-span-3">
|
||||
<DataList
|
||||
id={"ad-job-number-of-employees"}
|
||||
label={formTitles.numberOfEmployees}
|
||||
options={fieldsData.data.number_of_employees.meta.options.choices.map((x: SystemFields) => x.value)}
|
||||
onChange={(items) => handleDetailsSelection("numberOfEmployees", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={numberOfEmployees}
|
||||
dashedValue={false}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* languages */}
|
||||
<FormItem forId="ad-job-languages" title={formTitles.languages} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.languages}>
|
||||
<DataList
|
||||
id={"ad-job-languages"}
|
||||
label={formTitles.languages}
|
||||
options={languagesData.data.languages.map((x: any) => x.persian_name)}
|
||||
onChange={(items) => handleDetailsSelection("languages", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
multiple
|
||||
allValue={formTitles.allOption}
|
||||
value={languages}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0 ${fieldErrors.languages ? "profile-form-error" : ""}`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* link to ad page */}
|
||||
<FormItem forId="ad-job-link-to-ad" title={formTitles.linkToAd} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.linkToAd}>
|
||||
<Input id="ad-job-link-to-ad" name="title" type="text" required className={`profile-form placeholder:text-sm w-full ${fieldErrors.linkToAd ? "profile-form-error" : ""} [direction:ltr]`} placeholder={formTitles.linkToAdPlaceholder} value={formData.current.linkToAd} onInput={data => handleFormUpdate("linkToAd", data)} />
|
||||
</FormItem>
|
||||
</form>
|
||||
<StepButtons onPrev={handlePrevStep} onNext={handleNextStep} stepsCount={3} currentStep={2} />
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default JobsForm
|
||||
51
src/common/templates/dashboard/ads/main-details.tsx
Normal file
51
src/common/templates/dashboard/ads/main-details.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import translate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import Button from "components/button/button";
|
||||
import StepButtons from "./step-buttons";
|
||||
import JobsForm from "./jobs-form";
|
||||
import { FormDataType } from "./data-types";
|
||||
import VehiclesForm from "./vehicles-form";
|
||||
|
||||
interface MainDetails {
|
||||
className?: string,
|
||||
formData: FormDataType,
|
||||
cat: string,
|
||||
onNextStep: () => void,
|
||||
onPrevStep: () => void,
|
||||
handleInputChange: (step: number, data: any) => void,
|
||||
}
|
||||
|
||||
const MainDetails: React.FunctionComponent<MainDetails> = ({ className, formData, cat, handleInputChange, onNextStep, onPrevStep }) => {
|
||||
|
||||
// states
|
||||
// const [showError, setShowError] = useState(errorVisible);
|
||||
|
||||
// variables
|
||||
|
||||
|
||||
// methods
|
||||
const handleNextStep = () => {
|
||||
onNextStep();
|
||||
};
|
||||
const handlePrevStep = () => {
|
||||
onPrevStep();
|
||||
};
|
||||
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [])
|
||||
|
||||
// return
|
||||
return (
|
||||
<div>
|
||||
{/* {cat === "استخدام" && <JobsForm finalData={formData.jobData} handleInputChange={handleInputChange} onNextStep={handleNextStep} onPrevStep={handlePrevStep} />} */}
|
||||
{<VehiclesForm finalData={{ vehicle: formData.vehicleData, shared: formData.sharedData }} handleInputChange={handleInputChange} onNextStep={handleNextStep} onPrevStep={handlePrevStep} />}
|
||||
{/* <JobsForm finalData={formData.jobData} handleInputChange={handleInputChange} onNextStep={handleNextStep} /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default MainDetails
|
||||
116
src/common/templates/dashboard/ads/review-details.tsx
Normal file
116
src/common/templates/dashboard/ads/review-details.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import Button from "components/button/button";
|
||||
import StepButtons from "./step-buttons";
|
||||
import { FormDataType } from "./data-types";
|
||||
import Tabs from "components/tabs/tabs";
|
||||
import Tab from "components/tabs/tab";
|
||||
|
||||
interface ReviewDetails {
|
||||
className?: string,
|
||||
formData: FormDataType,
|
||||
onPrevStep: () => void,
|
||||
onSubmit: () => void,
|
||||
}
|
||||
interface Preview {
|
||||
image: any,
|
||||
title: string,
|
||||
cat: string,
|
||||
employmentType: string[],
|
||||
salaryOther: string[],
|
||||
salaryType: string[],
|
||||
salaryTo: string,
|
||||
salaryFrom: string,
|
||||
city: string[]
|
||||
}
|
||||
|
||||
const Preview: React.FunctionComponent<Preview> = ({ image, title, cat, employmentType, salaryOther, salaryFrom, salaryTo, salaryType, city }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
// const otherSalaryText = translate(`salary-${salaryOther[0]}`);
|
||||
|
||||
const PlaceHolder = () => {
|
||||
return <div className="hidden lg:block w-full rounded shadow">
|
||||
<div className="block w-full aspect-3/2 animate-pulse bg-gray-200 rounded-t-md"></div>
|
||||
<div className="block w-full bg-white pt-3 p-gi rounded-b-md">
|
||||
<span className="block w-4/5 h-5 mb-5 rounded animate-pulse bg-gray-200"></span>
|
||||
<span className="block w-1/5 h-5 mb-2 rounded animate-pulse bg-gray-200"></span>
|
||||
<span className="block w-2/5 h-5 mb-2 rounded animate-pulse bg-gray-200"></span>
|
||||
<span className="block w-3/5 h-5 mb-2 rounded animate-pulse bg-gray-200"></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
return <div className="grid grid-cols-1 lg:grid-cols-3 gap-5 w-4/5 mx-auto p-gi col-span-2">
|
||||
<PlaceHolder />
|
||||
<div className="block w-full rounded shadow">
|
||||
<img src={image} alt={title} className="block w-full aspect-3/2 rounded-t-md object-cover" />
|
||||
<div className="block w-full bg-white pt-3 p-gi rounded-b-md">
|
||||
<span className="block text-sm max-md:text-ellipsis max-md:overflow-hidden max-md:whitespace-nowrap font-normal mb-4 text-footer lg:text-[15px] !leading-7">{title}</span>
|
||||
<div className="flex items-center">
|
||||
<span className="flex items-center mr-2 md:mr-1 first:mr-0 text-xs text-market-title bg-white ring-1 ring-gray-200 rounded px-2 py-[5px] md:py-[2px]">{cat}</span>
|
||||
<span className="flex items-center mr-2 md:mr-1 first:mr-0 text-xs text-market-title bg-white ring-1 ring-gray-200 rounded px-2 py-[5px] md:py-[2px]">{employmentType[0]}</span>
|
||||
</div>
|
||||
<span className="inline-block text-sm lg:text-[15px] lg:font-semibold mt-4 md:mt-5 py-[4px] lg:pt-[4px] lg:pb-[2px] px-2 rounded bg-market-input text-market-title">
|
||||
{salaryOther[0] === 'مقدار حقوق به دلار کانادا' ?
|
||||
`${salaryFrom}${salaryTo !== null ? " - " + salaryTo : ""} ${translate("dollar")} ${salaryType[0]}`
|
||||
:
|
||||
salaryOther[0]
|
||||
}
|
||||
</span>
|
||||
<span className="flex items-center text-xs md:text-[0.825rem] text-secondary-light mt-3 md:mt-5">
|
||||
<Icon name="LocationPin" className="inline-block w-3 h-3 lg:w-3 lg:h-3 ml-1 fill-market-title-light" />
|
||||
{city[0]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<PlaceHolder />
|
||||
</div>
|
||||
}
|
||||
|
||||
const ReviewDetails: React.FunctionComponent<ReviewDetails> = ({ className, formData, onPrevStep, onSubmit }) => {
|
||||
|
||||
// states
|
||||
const [currentTab, setCurrentTab] = useState(1);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
|
||||
// methods
|
||||
const handlePrevStep = () => {
|
||||
onPrevStep();
|
||||
};
|
||||
const handleSubmit = () => {
|
||||
console.log(formData);
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [])
|
||||
|
||||
// return
|
||||
return (
|
||||
<div>
|
||||
<div className="block w-full">
|
||||
<h1 className="block text-center text-xl lg:text-2xl lg:font-normal pt-6 pb-4 lg:pt-10 lg:pb-6">{translate("ad-preview")}</h1>
|
||||
<Preview
|
||||
image={formData.generalData.photos[0]?.src}
|
||||
title={formData.generalData.title}
|
||||
cat={formData.generalData.category.names[0]}
|
||||
employmentType={formData.jobData.employmentType.translation}
|
||||
salaryOther={formData.jobData.salaryOther.translation}
|
||||
salaryType={formData.jobData.salaryType.translation}
|
||||
salaryFrom={formData.jobData.salaryFrom}
|
||||
salaryTo={formData.jobData.salaryTo}
|
||||
city={formData.generalData.city.names}
|
||||
/>
|
||||
</div>
|
||||
<StepButtons onPrev={handlePrevStep} onSubmit={handleSubmit} stepsCount={3} currentStep={3} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ReviewDetails
|
||||
75
src/common/templates/dashboard/ads/step-buttons.tsx
Normal file
75
src/common/templates/dashboard/ads/step-buttons.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import Button from "components/button/button";
|
||||
|
||||
interface StepButtons {
|
||||
className?: string,
|
||||
currentStep: number,
|
||||
stepsCount: number,
|
||||
onPrev?: () => void,
|
||||
onNext?: () => void,
|
||||
onSubmit?: () => void,
|
||||
}
|
||||
|
||||
const StepButtons: React.FunctionComponent<StepButtons> = ({ className, onPrev, onNext, onSubmit, currentStep = 1, stepsCount }) => {
|
||||
|
||||
// states
|
||||
// const [showError, setShowError] = useState(errorVisible);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const NextStepText = translate('next-step');
|
||||
const prevStepText = translate('prev-step');
|
||||
const submitAdText = translate('submit-ad');
|
||||
|
||||
// methods
|
||||
const handleNextStep = () => {
|
||||
onNext && onNext();
|
||||
};
|
||||
|
||||
const handlePrevStep = () => {
|
||||
onPrev && onPrev();
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit && onSubmit();
|
||||
};
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [])
|
||||
|
||||
// return
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-8 w-full col-span-6 bg-white sticky bottom-0 shadow-top px-gi py-gi">
|
||||
{currentStep > 1 &&
|
||||
<Button
|
||||
text={prevStepText}
|
||||
type="button"
|
||||
onClick={handlePrevStep}
|
||||
className="block w-full rounded-md bg-market-title-light hover:bg-light-red focus:bg-light-red outline-none text-sm md:text-base text-white text-center px-3 py-2"
|
||||
/>
|
||||
}
|
||||
{currentStep < stepsCount &&
|
||||
<Button
|
||||
text={NextStepText}
|
||||
type="button"
|
||||
onClick={handleNextStep}
|
||||
className="block w-full rounded-md bg-market-title-light hover:bg-light-red focus:bg-light-red outline-none text-sm md:text-base text-white text-center px-3 py-2"
|
||||
/>
|
||||
}
|
||||
{currentStep === 3 &&
|
||||
<Button
|
||||
text={submitAdText}
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
className="block w-full rounded-md bg-market-title-light hover:bg-light-red focus:bg-light-red outline-none text-sm md:text-base text-white text-center px-3 py-2"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default StepButtons
|
||||
63
src/common/templates/dashboard/ads/steps-map.tsx
Normal file
63
src/common/templates/dashboard/ads/steps-map.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import { Icon } from "components/icons";
|
||||
import { FormErrors } from "./data-types";
|
||||
|
||||
interface StepsMap {
|
||||
className?: string,
|
||||
currentStep: number,
|
||||
steps: string[],
|
||||
formErrors: FormErrors,
|
||||
onStepClick: (index: number) => void
|
||||
}
|
||||
|
||||
const StepsMap: React.FunctionComponent<StepsMap> = ({ className, currentStep, steps, formErrors, onStepClick }) => {
|
||||
|
||||
// states
|
||||
// const [showError, setShowError] = useState(errorVisible);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
|
||||
// methods
|
||||
const handleStepClick = (index: number) => {
|
||||
if (index === 2) {
|
||||
!formErrors.stepOne && onStepClick(index);
|
||||
}
|
||||
else if (index === 3) {
|
||||
!formErrors.stepTwo && onStepClick(index);
|
||||
} else {
|
||||
onStepClick(index);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// useEffects
|
||||
// useEffect(() => {
|
||||
|
||||
// }, [])
|
||||
|
||||
// return
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full z-20 sticky top-0 shadow-bot bg-white border-t border-market-input p-gi">
|
||||
<ul className={`flex w-full lg:w-11/12 items-center justify-between relative ${className}`}>
|
||||
{steps.map((x, index) => (
|
||||
<li key={x + index} onClick={() => handleStepClick(index + 1)} className={`flex flex-col items-center justify-center select-none lg:cursor-pointer`}>
|
||||
<span className={`${currentStep >= index + 1 ? "text-white bg-market-title-light" : "text-market-border bg-market-input"} z-10 text-sm lg:text-lg flex items-center justify-center rounded-full shadow-inner w-7 h-7 lg:w-8 lg:h-8 text-center`}>
|
||||
{currentStep > index + 1 ? "✓" : index + 1}
|
||||
</span>
|
||||
<span onClick={() => handleStepClick(index + 1)} className={`${currentStep >= index + 1 ? "text-market-title-light" : "text-market-border"} w-20 lg:w-24 mt-2 text-xs lg:text-sm font-semibold flex flex-col items-center justify-center select-none lg:cursor-pointer`}>
|
||||
{x}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center w-[calc(100%-80px)] lg:w-[calc((100%*11/12)-96px)] px-gi absolute top-8">
|
||||
{steps.map((x, index) => (
|
||||
index + 1 < steps.length && <span key={x + index} className={`block w-1/2 z-0 h-[2px] ${currentStep > index + 1 ? "bg-market-title-light" : "bg-market-border"}`}></span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default StepsMap
|
||||
304
src/common/templates/dashboard/ads/vehicles-form.tsx
Normal file
304
src/common/templates/dashboard/ads/vehicles-form.tsx
Normal file
@ -0,0 +1,304 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Image from "components/image/image";
|
||||
import { Icon } from "components/icons";
|
||||
import Button from "components/button/button";
|
||||
import StepButtons from "./step-buttons";
|
||||
import { JobData, SharedData, SystemFields, VehicleData } from "./data-types";
|
||||
import useSWRImmutable from "swr";
|
||||
import { deepClone, generalDataFetch, hasValue, systemDataFetch } from "services/general/general";
|
||||
import Skeleton from "components/skeleton/skeleton";
|
||||
import FormItem from "./form-item";
|
||||
import Input from "components/input/text";
|
||||
import DataList from "components/select/data-list";
|
||||
|
||||
interface VehiclesForm {
|
||||
className?: string,
|
||||
finalData: {
|
||||
vehicle: VehicleData,
|
||||
shared: SharedData,
|
||||
},
|
||||
handleInputChange: (step: number, data: any) => void,
|
||||
onPrevStep: () => void,
|
||||
onNextStep: () => void,
|
||||
}
|
||||
|
||||
const VehiclesForm: React.FunctionComponent<VehiclesForm> = ({ finalData, handleInputChange, onPrevStep, onNextStep, className }) => {
|
||||
|
||||
// states
|
||||
const [selectionOpen, setSelectionOpen] = useState(false);
|
||||
const [vehicleType, setVehicleType] = useState<string[]>(finalData.vehicle.vehicle_type.translation);
|
||||
const [carBodyType, setCarBodyType] = useState<string[]>(finalData.vehicle.car_body_type.translation);
|
||||
const [transmissionType, setTransmissionType] = useState<string[]>(finalData.vehicle.transmission_type.translation);
|
||||
const [drivetrain, setDrivetrain] = useState<string[]>(finalData.vehicle.drivetrain.translation);
|
||||
const [fuelType, setFuelType] = useState<string[]>(finalData.vehicle.fuel_type.translation);
|
||||
const [vehicleSafety, setVehicleSafety] = useState<string[]>(finalData.vehicle.vehicle_safety.translation);
|
||||
const [vehicleOptions, setVehicleOptions] = useState<string[]>(finalData.vehicle.vehicle_options.translation);
|
||||
const [productState, setProductState] = useState<string[]>(finalData.shared.product_state.translation);
|
||||
const [brand, setBrand] = useState<string[]>(finalData.shared.brand.names);
|
||||
const [color, setColor] = useState<string[]>(finalData.shared.color.names);
|
||||
const [interiorColor, setInteriorColor] = useState<string[]>(finalData.shared.interior_color.names);
|
||||
const translate = useTranslate();
|
||||
|
||||
const formData = useRef({
|
||||
vehicle: {
|
||||
vehicle_type: finalData.vehicle.vehicle_type,
|
||||
car_body_type: finalData.vehicle.car_body_type,
|
||||
production_year: finalData.vehicle.production_year,
|
||||
distance_traveled: finalData.vehicle.distance_traveled,
|
||||
transmission_type: finalData.vehicle.transmission_type,
|
||||
fuel_type: finalData.vehicle.fuel_type,
|
||||
engine_capacity: finalData.vehicle.engine_capacity,
|
||||
number_of_cylinders: finalData.vehicle.number_of_cylinders,
|
||||
drivetrain: finalData.vehicle.drivetrain,
|
||||
horsepower: finalData.vehicle.horsepower,
|
||||
top_speed: finalData.vehicle.top_speed,
|
||||
car_range: finalData.vehicle.car_range,
|
||||
fuel_tank_capacity: finalData.vehicle.fuel_tank_capacity,
|
||||
city_fuel_usage: finalData.vehicle.city_fuel_usage,
|
||||
highway_fuel_usage: finalData.vehicle.highway_fuel_usage,
|
||||
seats_count: finalData.vehicle.seats_count,
|
||||
doors_count: finalData.vehicle.doors_count,
|
||||
trunk_space: finalData.vehicle.trunk_space,
|
||||
length: finalData.vehicle.length,
|
||||
width: finalData.vehicle.width,
|
||||
height: finalData.vehicle.height,
|
||||
weight: finalData.vehicle.weight,
|
||||
vehicle_safety: finalData.vehicle.vehicle_safety,
|
||||
vehicle_options: finalData.vehicle.vehicle_options,
|
||||
},
|
||||
shared: {
|
||||
price: finalData.shared.price,
|
||||
product_state: finalData.shared.product_state,
|
||||
model: finalData.shared.model,
|
||||
brand: finalData.shared.brand,
|
||||
color: finalData.shared.color,
|
||||
interior_color: finalData.shared.interior_color,
|
||||
}
|
||||
});
|
||||
const [fieldErrors, setFieldErrors] = useState({
|
||||
jobTitle: false,
|
||||
employmentType: false,
|
||||
salaryType: false,
|
||||
salaryFrom: false,
|
||||
salaryOther: false,
|
||||
workStyle: false,
|
||||
experience: false,
|
||||
companyName: false,
|
||||
languages: false,
|
||||
linkToAd: false,
|
||||
});
|
||||
|
||||
// variables
|
||||
const sharedQuery = `
|
||||
Colors {
|
||||
id
|
||||
persian_name
|
||||
}
|
||||
Brands {
|
||||
id
|
||||
persian_name
|
||||
english_name
|
||||
}
|
||||
`
|
||||
const fieldsQuery = `
|
||||
vehicle_type: fields_by_name(collection: "Ads", field: "vehicle_type") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
car_body_type: fields_by_name(collection: "Ads", field: "car_body_type") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
transmission_type: fields_by_name(collection: "Ads", field: "transmission_type") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
drivetrain: fields_by_name(collection: "Ads", field: "drivetrain") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
fuel_type: fields_by_name(collection: "Ads", field: "fuel_type") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
vehicle_safety: fields_by_name(collection: "Ads", field: "vehicle_safety") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
vehicle_options: fields_by_name(collection: "Ads", field: "vehicle_options") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
product_state: fields_by_name(collection: "Ads", field: "product_state") {
|
||||
meta {
|
||||
options
|
||||
}
|
||||
}
|
||||
`
|
||||
const { data: fieldsData, error: fieldsError } = useSWRImmutable([fieldsQuery], systemDataFetch);
|
||||
const { data: sharedData, error: sharedDataError } = useSWRImmutable([sharedQuery], generalDataFetch);
|
||||
|
||||
let noErrors = false;
|
||||
let isFirstLoad = useRef(true);
|
||||
|
||||
const formTitles = {
|
||||
allOption: translate("select"),
|
||||
vehicleType: translate("select"),
|
||||
carBodyType: translate("select"),
|
||||
}
|
||||
const formErrors = {
|
||||
necessaryFields: translate("all-fields-necessary-error"),
|
||||
}
|
||||
|
||||
// methods
|
||||
const validate = () => {
|
||||
const localErrors = deepClone(fieldErrors);
|
||||
|
||||
// hasValue(formData.current.jobTitle) ? localErrors.jobTitle = false : localErrors.jobTitle = true;
|
||||
// employmentType.length > 0 ? localErrors.employmentType = false : localErrors.employmentType = true;
|
||||
// salaryOther.length > 0 ? localErrors.salaryOther = false : localErrors.salaryOther = true;
|
||||
// if (salaryOther[0] === 'مقدار حقوق به دلار کانادا') {
|
||||
// hasValue(formData.current.salaryFrom) ? localErrors.salaryFrom = false : localErrors.salaryFrom = true;
|
||||
// salaryType.length > 0 ? localErrors.salaryType = false : localErrors.salaryType = true;
|
||||
// }
|
||||
// workStyle.length > 0 ? localErrors.workStyle = false : localErrors.workStyle = true;
|
||||
// experience.length > 0 ? localErrors.experience = false : localErrors.experience = true;
|
||||
// formData.current.languages.names.length > 0 ? localErrors.languages = false : localErrors.languages = true;
|
||||
// hasValue(formData.current.companyName) ? localErrors.companyName = false : localErrors.companyName = true;
|
||||
// hasValue(formData.current.linkToAd) ? localErrors.linkToAd = false : localErrors.linkToAd = true;
|
||||
|
||||
if (!isFirstLoad.current) {
|
||||
noErrors = !Object.values(localErrors).some(x => x === true);
|
||||
setFieldErrors({ ...fieldErrors, ...localErrors });
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormUpdate = (name: string, value: string | string[] | undefined) => {
|
||||
formData.current = { ...formData.current, [name]: value };
|
||||
validate();
|
||||
}
|
||||
const handleNextStep = () => {
|
||||
isFirstLoad.current = false;
|
||||
validate();
|
||||
noErrors && handleInputChange(2, formData.current);
|
||||
noErrors && onNextStep();
|
||||
}
|
||||
const handlePrevStep = () => {
|
||||
isFirstLoad.current = false;
|
||||
validate();
|
||||
noErrors && handleInputChange(2, formData.current);
|
||||
noErrors && onPrevStep();
|
||||
}
|
||||
const handleDetailsSelection = (name: string, items: string[]) => {
|
||||
let itemsTr, itemsOr, itemsIds, itemsNames, data:any;
|
||||
switch (name) {
|
||||
case "vehicle_type":
|
||||
setVehicleType(items);
|
||||
itemsTr = fieldsData?.data.vehicle_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => translate(x.value));
|
||||
itemsOr = fieldsData?.data.vehicle_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
case "car_body_type":
|
||||
setCarBodyType(items);
|
||||
itemsTr = fieldsData?.data.car_body_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => translate(x.value));
|
||||
itemsOr = fieldsData?.data.car_body_type.meta.options.choices.filter((x: any) => items.some(y => y === translate(x.value))).map((x: any) => x.value);
|
||||
data = { translation: itemsTr, original: itemsOr };
|
||||
handleFormUpdate(name, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// useEffects
|
||||
|
||||
// }, [])
|
||||
|
||||
if (fieldsError || sharedDataError) console.log(fieldsError);
|
||||
console.log(fieldsData, sharedData);
|
||||
|
||||
// return
|
||||
return (
|
||||
<div>
|
||||
{(!fieldsData || !sharedData) ?
|
||||
<Skeleton className="block w-full lg:w-8/12 h-full absolute top-gi left-1/2 -translate-x-1/2 mx-auto" />
|
||||
:
|
||||
<>
|
||||
<form className="w-full lg:w-full grid grid-cols-6 gap-x-7 gap-y-5 lg:gap-y-6 bg-white shadow p-6 rounded-md">
|
||||
{/* vehicleType */}
|
||||
<FormItem forId="ad-vehicle-vehicle-type" title={formTitles.vehicleType} className="col-span-6 md:col-span-3">
|
||||
<DataList
|
||||
id={"ad-vehicle-vehicle-type"}
|
||||
label={formTitles.vehicleType}
|
||||
options={fieldsData.data.vehicle_type.meta.options.choices.map((x: SystemFields) => translate(x.value))}
|
||||
onChange={(items) => handleDetailsSelection("vehicle_type", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={vehicleType}
|
||||
dashedValue={false}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* carBodyType */}
|
||||
<FormItem forId="ad-vehicle-car-body-type" title={formTitles.carBodyType} className="col-span-6 md:col-span-3">
|
||||
<DataList
|
||||
id={"ad-vehicle-car-body-type"}
|
||||
label={formTitles.carBodyType}
|
||||
options={fieldsData.data.car_body_type.meta.options.choices.map((x: SystemFields) => translate(x.value))}
|
||||
onChange={(items) => handleDetailsSelection("car_body_type", items)}
|
||||
isOpen={selectionOpen}
|
||||
onClose={() => setSelectionOpen(false)}
|
||||
labelHidden
|
||||
allValue={formTitles.allOption}
|
||||
value={carBodyType}
|
||||
dashedValue={false}
|
||||
wrapperClass={`market-datalist-wrapper profile-form p-0`}
|
||||
buttonClass="qf-datalist-list-button leading-6 px-3 py-[0.375rem]"
|
||||
inputClass="market-datalist-input"
|
||||
listContainerClass="market-datalist-list-container"
|
||||
listClass="market-datalist-list"
|
||||
listItemClass="market-datalist-list-item"
|
||||
multiLabelClass="qf-datalist-list-multi-labels"
|
||||
/>
|
||||
</FormItem>
|
||||
{/* salary amount */}
|
||||
{/* <FormItem forId="ad-job-salary" title={formTitles.salaryAmount} className="col-span-6 md:col-span-3" required errorText={formErrors.necessaryFields} errorVisible={fieldErrors.jobTitle}>
|
||||
<Input
|
||||
id="ad-job-salary-from"
|
||||
name="title"
|
||||
type="number"
|
||||
required
|
||||
wrapperClass="[direction:ltr]"
|
||||
className={`profile-form placeholder:text-sm w-full text-center ${fieldErrors.salaryFrom ? "profile-form-error" : ""}`}
|
||||
value={formData.current.salaryFrom}
|
||||
onInput={data => handleFormUpdate("salaryFrom", data)}
|
||||
placeholder="24"
|
||||
unit={formTitles.salaryFrom}
|
||||
unitClass="profile-form ml-2"
|
||||
/>
|
||||
</FormItem> */}
|
||||
</form>
|
||||
<StepButtons onPrev={handlePrevStep} onNext={handleNextStep} stepsCount={3} currentStep={2} />
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default VehiclesForm
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useLayoutEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import Link from "components/link/link";
|
||||
@ -34,6 +34,7 @@ const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children }) =>
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const translationsQuery = `
|
||||
dictionary (filter: { status: { _eq: "published" } }, limit: 600) {
|
||||
@ -79,6 +80,7 @@ const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children }) =>
|
||||
|
||||
useLayoutEffect(() => {
|
||||
userGeneralData && dispatch(setUserData({ generalDetails: userGeneralData.data.users_me }));
|
||||
userGeneralData && dispatch(setUserData({ auth: { accessToken: accessToken, refreshToken: "" } }));
|
||||
}, [userGeneralData]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -101,9 +103,9 @@ const GeneralLayout: React.FunctionComponent<GeneralLayout> = ({ children }) =>
|
||||
{userGeneralData && trData.length > 0 ?
|
||||
<div 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-[#fafafb]">
|
||||
<div className="block w-full h-screen lg:w-[calc(100%_-_300px)] bg-[#fafbfb]">
|
||||
<Header open={navOpen} onClose={toggleNav} />
|
||||
<main className="block">
|
||||
<main className="block overflow-y-scroll h-[calc(100vh_-_74px)] lg:h-[calc(100vh_-_100px)] hide-scrollbar">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -1,15 +1,9 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import Link from "components/link/link";
|
||||
import { useSession, signIn, signOut } from "next-auth/react"
|
||||
import useTranslate from "services/translation/translation";
|
||||
import { Icon } from "components/icons";
|
||||
import { useRouter } from "next/router";
|
||||
import { UserData } from "common/types/user";
|
||||
import Image from "components/image/image";
|
||||
import ClickOutside from "components/click-outside/click-outside";
|
||||
import { userData } from "common/redux/slices/user";
|
||||
|
||||
interface Header {
|
||||
@ -20,6 +14,21 @@ interface Header {
|
||||
const Header: React.FunctionComponent<Header> = ({ open, onClose }) => {
|
||||
|
||||
const user = useAppSelector(userData);
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
|
||||
const pageTitle = () => {
|
||||
let title = "";
|
||||
router.asPath === "/dashboard" ? title = translate("user-profile-menu-dashboard") : "";
|
||||
router.asPath.includes("dashboard/ads") ? title = translate("user-profile-menu-ads") : "";
|
||||
router.asPath.includes("dashboard/account") ? title = translate("user-profile-menu-account") : "";
|
||||
router.asPath.includes("dashboard/billboards") ? title = translate("user-profile-menu-billboards") : "";
|
||||
router.asPath.includes("dashboard/events") ? title = translate("user-profile-menu-events") : "";
|
||||
router.asPath.includes("dashboard/inbox") ? title = translate("user-profile-menu-inbox") : "";
|
||||
router.asPath.includes("dashboard/comments") ? title = translate("user-profile-menu-comments") : "";
|
||||
router.asPath.includes("dashboard/settings") ? title = translate("user-profile-menu-settings") : "";
|
||||
return title;
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="flex items-center w-full bg-white relative py-3 shadow-sm px-gi lg:py-8 lg:px-8 z-10">
|
||||
@ -28,7 +37,7 @@ const Header: React.FunctionComponent<Header> = ({ open, onClose }) => {
|
||||
<span className={`block h-[3px] rounded bg-secondary transition-all ease-in-out duration-[150ms] ${open ? 'w-0' : 'w-8'}`}></span>
|
||||
<span className={`block w-8 h-[3px] rounded bg-secondary transition-all ease-in-out duration-[300ms] origin-right ${open ? 'transform rotate-[45deg]' : ''}`}></span>
|
||||
</div>
|
||||
<div className="flex items-center mr-6">
|
||||
<div className="hidden lg:flex items-center mr-6">
|
||||
<Image
|
||||
src={user.generalDetails.avatar.id}
|
||||
alt={user.generalDetails.first_name}
|
||||
@ -40,6 +49,7 @@ const Header: React.FunctionComponent<Header> = ({ open, onClose }) => {
|
||||
/>
|
||||
<span className="text-secondary-light font-semibold text-[15px] lg:text-base">{`${user.generalDetails.first_name} ${user.generalDetails.last_name}`}</span>
|
||||
</div>
|
||||
<span className="inline-block text-lg text-secondary-light pr-6 font-semibold lg:hidden">{pageTitle()}</span>
|
||||
<Icon name="bell_solid" className={`block absolute left-6 lg:left-12 w-10 h-10 fill-secondary-light hover:fill-market-title-light lg:cursor-pointer rounded-full hover:bg-market-input p-2`} />
|
||||
</header>
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getFuelType, setMarketFilters } from "common/redux/slices/market";
|
||||
import Link from "components/link/link";
|
||||
@ -10,6 +10,8 @@ import { useRouter } from "next/router";
|
||||
import { UserData } from "common/types/user";
|
||||
import Image from "components/image/image";
|
||||
import ClickOutside from "components/click-outside/click-outside";
|
||||
import Skeleton from "components/skeleton/skeleton";
|
||||
import { userData } from "common/redux/slices/user";
|
||||
|
||||
interface SideNavigation {
|
||||
open: boolean,
|
||||
@ -26,35 +28,66 @@ interface MenuItem {
|
||||
// side menu item
|
||||
const MenuItem: React.FunctionComponent<MenuItem> = ({ href, icon, activeIcon, text, onClick }) => {
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const isActive = () => {
|
||||
if (href === "" && router.asPath === "/dashboard") {
|
||||
return true;
|
||||
}
|
||||
if (href === "/" && router.asPath === "/") {
|
||||
return true;
|
||||
}
|
||||
if (!(href === "") && !(href === "/") && router.asPath.includes(href)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return <Link href={href} shallow={true} className={`flex items-center relative border-l-[5px] select-none ${router.asPath === href ? "border-market-title-light" : "border-transparent"} 2xl:pl-[var(--gi)] mb-1 lg:mb-3 px-4`} onClick={onClick}>
|
||||
<div className={`flex items-center w-full rounded-xl p-4 hover:bg-market-input ${router.asPath === href ? "bg-market-input" : ""} [&>span]:hover:text-market-title [&>svg]:hover:fill-market-title-light`}>
|
||||
<Icon name={router.asPath === href ? activeIcon : icon} className={`block w-5 h-5 ${router.asPath === href ? "fill-market-title-light" : "fill-secondary-light"}`} />
|
||||
<span className={`inline-block text-[15px] mr-3 ${router.asPath === href ? "text-market-title" : "text-secondary-light"}`}>{translate(text)}</span>
|
||||
return <Link href={href === "/" ? "/" : `/dashboard${href}`} shallow={true} className={`flex items-center relative border-l-[5px] select-none ${isActive() ? "border-market-title-light" : "border-transparent"} 2xl:pl-[var(--gi)] mb-1 lg:mb-3 px-4`} onClick={onClick}>
|
||||
<div className={`flex items-center w-full rounded-xl p-3 lg:p-4 hover:bg-market-input ${isActive() ? "bg-market-input" : ""} [&>span]:hover:text-market-title [&>svg]:hover:fill-market-title-light`}>
|
||||
<Icon name={isActive() ? activeIcon : icon} className={`block w-5 h-5 ${isActive() ? "fill-market-title-light" : "fill-secondary-light"}`} />
|
||||
<span className={`inline-block text-[15px] mr-3 ${isActive() ? "text-market-title" : "text-secondary-light"}`}>{translate(text)}</span>
|
||||
</div>
|
||||
</Link>
|
||||
}
|
||||
|
||||
const SideNavigation: React.FunctionComponent<SideNavigation> = ({ open, onClose }) => {
|
||||
|
||||
const [logoLoaded, setLogoLoaded] = useState(false);
|
||||
const translate = useTranslate();
|
||||
const user = useAppSelector(userData);
|
||||
|
||||
return (
|
||||
<div className={`block absolute w-full h-screen lg:relative lg:w-auto z-20 ${open ? "" : "pointer-events-none"} lg:pointer-events-auto`}>
|
||||
<div className={`block absolute w-full h-screen lg:relative lg:w-auto z-30 ${open ? "" : "pointer-events-none"} lg:pointer-events-auto`}>
|
||||
<div className={`block absolute w-screen h-screen z-20 ${open ? "bg-secondary/40" : "bg-secondary/0"} lg:hidden transition-all duration-[280ms] [transition-timing-function:cubic-bezier(0.4,0,0.2,1)]`} onClick={onClose}></div>
|
||||
<aside className={`block relative w-[300px] h-screen z-30 shadow-md lg:shadow-sm bg-white pt-2 lg:pt-6 transition-transform will-change-transform duration-[280ms] [transition-timing-function:cubic-bezier(0.0,1,0.9,1)] ${open ? "" : "translate-x-full"} lg:translate-x-0`}>
|
||||
<Link href="/" className={`relative 2xl:pl-[var(--gi)]`}>
|
||||
<img src="/dark-logo-full.png" alt={translate('slogan')} className={`block w-8/12 mx-auto pb-8`} />
|
||||
<aside className={`block relative w-[300px] h-screen z-30 shadow-md lg:shadow-sm bg-white pt-2 lg:pt-0 transition-transform will-change-transform duration-[280ms] [transition-timing-function:cubic-bezier(0.0,1,0.9,1)] ${open ? "" : "translate-x-full"} lg:translate-x-0`}>
|
||||
<Link href="/" className={`block relative 2xl:p-gi h-[60px] mb-8`}>
|
||||
<img src="/dark-logo-full.png" alt={translate('slogan')} className={`block relative w-8/12 mx-auto`} onLoad={() => setLogoLoaded(true)} />
|
||||
{!logoLoaded && <Skeleton className="block w-8/12 h-full absolute top-gi left-1/2 -translate-x-1/2 mx-auto" />}
|
||||
</Link>
|
||||
<nav className="block w-full pt-0 pb-4 lg:py-4">
|
||||
<MenuItem href="/dashboard" icon="grid_2_light" activeIcon="grid_2_solid" text="user-profile-menu-dashboard" />
|
||||
<MenuItem href="/dashboard/account" icon="user_vneck_hair_light" activeIcon="user_vneck_hair_solid" text="user-profile-menu-account" />
|
||||
<MenuItem href="/dashboard/ads" icon="advertise_light" activeIcon="advertise_solid" text="user-profile-menu-ads" />
|
||||
<MenuItem href="/dashboard/billboards" icon="signs_post_light" activeIcon="signs_post_solid" text="user-profile-menu-billboards" />
|
||||
<MenuItem href="/dashboard/events" icon="calendar_star_light" activeIcon="calendar_star_solid" text="user-profile-menu-events" />
|
||||
<MenuItem href="/dashboard/inbox" icon="inbox_light" activeIcon="inbox_full_solid" text="user-profile-menu-inbox" />
|
||||
<MenuItem href="/dashboard/comments" icon="messages_light" activeIcon="messages_solid" text="user-profile-menu-comments" />
|
||||
<Link href="/dashboard/account" className="flex items-center mx-6 pb-4 pt-2 mb-2 lg:hidden border-b border-gray-200">
|
||||
<Image
|
||||
src={user.generalDetails.avatar.id}
|
||||
alt={user.generalDetails.first_name}
|
||||
width={user.generalDetails.avatar.width}
|
||||
height={user.generalDetails.avatar.height}
|
||||
ar={[1 / 1, 1 / 1, 1 / 1, 1 / 1]}
|
||||
imageSizes={[50, 50, 50, 50]}
|
||||
figureClass={`block w-9 h-9 rounded-full shadow-sm object-cover bg-secondary ml-4 shrink-0`}
|
||||
/>
|
||||
<span className="text-secondary-light font-semibold text-[15px] lg:text-base">{`${user.generalDetails.first_name} ${user.generalDetails.last_name}`}</span>
|
||||
</Link>
|
||||
<nav className="block w-full pt-0 pb-3 lg:py-4">
|
||||
<MenuItem href="" icon="grid_2_light" activeIcon="grid_2_solid" text="user-profile-menu-dashboard" />
|
||||
<MenuItem href="/account" icon="user_vneck_hair_light" activeIcon="user_vneck_hair_solid" text="user-profile-menu-account" />
|
||||
<MenuItem href="/ads" icon="advertise_light" activeIcon="advertise_solid" text="user-profile-menu-ads" />
|
||||
<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="inbox_light" activeIcon="inbox_full_solid" text="user-profile-menu-inbox" />
|
||||
<MenuItem href="/comments" icon="messages_light" activeIcon="messages_solid" text="user-profile-menu-comments" />
|
||||
</nav>
|
||||
<span className="block mx-8 border-t border-gray-200"></span>
|
||||
<span className="block mx-6 border-t border-gray-200"></span>
|
||||
<div className="block w-full py-4">
|
||||
<MenuItem href="/dashboard/settings" icon="gear_light" activeIcon="gear_solid" text="user-profile-menu-settings" />
|
||||
<MenuItem href="/settings" icon="gear_light" activeIcon="gear_solid" text="user-profile-menu-settings" />
|
||||
<MenuItem href="/" icon="signout_light" activeIcon="signout_solid" text="user-logout-text" onClick={() => signOut()} />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@ -2,7 +2,7 @@ import { Icon } from "components/icons"
|
||||
import React, { useState, useEffect, useLayoutEffect } from "react"
|
||||
import { useRouter } from "next/router";
|
||||
import Widget from "../navigation/widget";
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import Loading from "components/loading/loading";
|
||||
import useSWR from "swr";
|
||||
import { generalDataFetch } from "services/general/general";
|
||||
@ -40,6 +40,7 @@ const Stat: React.FunctionComponent<Stat> = ({ icon, title, color, lightColor, v
|
||||
const Overview: React.FunctionComponent<Overview> = ({ }) => {
|
||||
|
||||
// const [isOpen, setIsOpen] = useState(true);
|
||||
const translate = useTranslate();
|
||||
const statstitleText = translate("profile-overview-title");
|
||||
const adsCountText = translate("profile-ads");
|
||||
const billboardsCountText = translate("profile-billboards");
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from "react"
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { useAppSelector } from "common/redux/hooks"
|
||||
import { Icon } from "components/icons"
|
||||
@ -25,7 +25,7 @@ interface LinksGroup {
|
||||
}
|
||||
|
||||
const MobileLinks = ({ }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const linksArray = [
|
||||
{ icon:"house_solid", name: "main-menu-home-title", url: "/" },
|
||||
{ icon:"cart_arrow_up_solid", name: "main-menu-market-title", url: "/market" },
|
||||
@ -51,6 +51,7 @@ const MobileLinks = ({ }) => {
|
||||
}
|
||||
|
||||
const SocialMedia: React.FunctionComponent<SocialMedia> = ({ className }) => {
|
||||
const translate = useTranslate();
|
||||
return (
|
||||
<div className={`flex flex-col items-center md:pb-5 ${className}`}>
|
||||
<span className="text-sm md:text-[17px] text-sup-light text-center md:text-right lg:text-[19px] mb-2 md:mb-4 w-full">
|
||||
@ -72,6 +73,7 @@ const SocialMedia: React.FunctionComponent<SocialMedia> = ({ className }) => {
|
||||
const Footer: React.FunctionComponent<Footer> = () => {
|
||||
|
||||
const data = useGetGlobalData();
|
||||
const translate = useTranslate();
|
||||
const showAllText = translate("show-all");
|
||||
|
||||
const urlMaker = (item: any) => {
|
||||
|
||||
@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
|
||||
import MainMenu from './main-menu'
|
||||
import UserMenu from './user-menu'
|
||||
import SearchBox from "../search/search-box"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useGetGlobalData } from "services/general/data"
|
||||
|
||||
const Header = () => {
|
||||
@ -17,6 +17,7 @@ const Header = () => {
|
||||
// variables
|
||||
const data = useGetGlobalData();
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const activePath = router.pathname;
|
||||
const searchPlaceHolder = translate("main-search-help-text");
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useMemo } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import UserMenu from "./user-menu"
|
||||
import { Icon } from "components/icons"
|
||||
@ -48,6 +48,8 @@ interface MenuItem {
|
||||
}
|
||||
|
||||
const SocialMedia = () => {
|
||||
const translate = useTranslate();
|
||||
|
||||
return <div className="block w-full bg-market-sup-light py-8 mt-8 px-gi lg:hidden">
|
||||
<span className="block w-full text-center mb-6 text-sm font-semibold text-market-title-light">
|
||||
<Icon name="hashtag_sharp" className={`inline-block w-5 h-5 fill-[#c13584] ml-2`} />
|
||||
@ -70,6 +72,7 @@ const MainMenu: React.FunctionComponent<MainMenu> = ({ data, isOpen, searchActiv
|
||||
const [subActive, setSubActive] = useState<{id:string, isActive: boolean}[]>([]);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const activePath = router.pathname;
|
||||
const searchMenuTitle = translate("main-search-place-holder");
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { useSession, signIn, signOut } from "next-auth/react"
|
||||
import Image from "components/image/image"
|
||||
@ -22,6 +22,7 @@ const UserMenu: React.FunctionComponent<UserMenu> = ({ className }) => {
|
||||
|
||||
// variables
|
||||
const { data: session, status }: any = useSession();
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const activePath = router.pathname;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -34,6 +34,7 @@ const Layout = ({children, fullScreen = false, bgColor = "#fff"}:any) => {
|
||||
|
||||
useEffect(() => {
|
||||
userGeneralData && dispatch(setUserData({ generalDetails: userGeneralData.data.users_me }));
|
||||
userGeneralData && dispatch(setUserData({ auth: { accessToken: accessToken, refreshToken: "" } }));
|
||||
}, [userGeneralData]);
|
||||
|
||||
return (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react"
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { generalDataFetch, stripHtml} from 'services/general/general';
|
||||
import { url } from 'services/general/general'
|
||||
import Input from "components/input/text"
|
||||
@ -44,6 +44,7 @@ const highlight = (currentText: string, html: any, type: string, index?: number)
|
||||
const ResultsList: React.FunctionComponent<ResultsList> = ({ text, results, type, mainTitle, title, body }) => {
|
||||
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const activePath = router.pathname;
|
||||
const resultsCountText = translate("search-number-of-results-found");
|
||||
|
||||
@ -112,6 +113,7 @@ const SearchBox: React.FunctionComponent<SearchBox> = ({ searchPlaceHolder, mark
|
||||
|
||||
let timer: any;
|
||||
const waitTime = 400;
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const activePath = router.pathname;
|
||||
const marketTitle = translate("main-menu-market-title");
|
||||
|
||||
@ -2,12 +2,8 @@ import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../redux/hooks';
|
||||
import { getAreFiltersReset, getCity, getState, setMarketFilters } from '../../redux/slices/market'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Select from "components/select/select"
|
||||
import FilterItem from "../market/cat-page/filters/filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { City } from "common/types/general";
|
||||
@ -33,6 +29,7 @@ const CitiesFilter: React.FunctionComponent<CitiesFilter> = ({ single = false, c
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const allOption = translate("all");
|
||||
const otherCities = translate("other-cities");
|
||||
const items = cities;
|
||||
|
||||
@ -2,12 +2,8 @@ import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../redux/hooks';
|
||||
import { getAreFiltersReset, getState, setMarketFilters } from '../../redux/slices/market'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Select from "components/select/select"
|
||||
import FilterItem from "../market/cat-page/filters/filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { State } from "common/types/general";
|
||||
@ -26,6 +22,7 @@ const StatesFilter: React.FunctionComponent<StatesFilter> = ({ single = false, s
|
||||
const [value, setValue] = useState<{ value: string[], active: boolean }>({ value: [], active: false });
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const allOption = translate("all");
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
@ -1,18 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import Image from "components/image/image"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import Input from "components/input/text"
|
||||
import { search } from "services/search/search"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from "next/router"
|
||||
import SearchBox from "../general/search/search-box"
|
||||
import CitiesFilter from "common/templates/global/cities"
|
||||
import StatesFilter from "common/templates/global/states"
|
||||
import { City, State } from "common/types/general"
|
||||
import useEmblaCarousel from 'embla-carousel-react'
|
||||
import Autoplay from 'embla-carousel-autoplay'
|
||||
import { Icon } from "components/icons"
|
||||
import Button from "components/button/button"
|
||||
|
||||
@ -34,6 +23,7 @@ const BillboardCats: React.FunctionComponent<BillboardCats> = ({ }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const tiles: Tiles[] = [
|
||||
{ title: "home-promo-shopping-title", text: "home-promo-shopping-text", icon: "diet.png", url: "فروشگاه-ها-و-مراکز-خرید", pic: "home-promo-groceries.webp" },
|
||||
{ title: "home-promo-fun-title", text: "home-promo-fun-text", icon: "park.png", url: "تفریحی-و-سرگرمی", pic: "home-promo-fun.jpg" },
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import Image from "components/image/image"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import Input from "components/input/text"
|
||||
import { search } from "services/search/search"
|
||||
import { useRouter } from "next/router"
|
||||
import SearchBox from "../general/search/search-box"
|
||||
import CitiesFilter from "common/templates/global/cities"
|
||||
import StatesFilter from "common/templates/global/states"
|
||||
import { City, State } from "common/types/general"
|
||||
import useEmblaCarousel from 'embla-carousel-react'
|
||||
import Autoplay from 'embla-carousel-autoplay'
|
||||
import { Icon } from "components/icons"
|
||||
import Button from "components/button/button"
|
||||
|
||||
@ -44,6 +35,7 @@ const HomeMagazine: React.FunctionComponent<HomeMagazine> = ({ items }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
// const cat1 = items.filter(x => x.article_category[0].magazine_categories_id.name === "جاذبه های گردشگری")[0];
|
||||
// const cat2 = items.filter(x => x.article_category[0].magazine_categories_id.name === "طبیعت کانادا")[0];
|
||||
// const cat3 = items.filter(x => x.article_category[0].magazine_categories_id.name === "فرهنگ و مذهب")[0];
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import Link from "components/link/link"
|
||||
import Image from "components/image/image"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { useRouter } from "next/router"
|
||||
import SearchBox from "../general/search/search-box"
|
||||
@ -22,6 +22,7 @@ interface categories {
|
||||
// market promo reasons slider
|
||||
const IdeasSlider = () => {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, axis: "y", draggable: false }, [Autoplay({ delay: 4200 })])
|
||||
const translate = useTranslate();
|
||||
|
||||
const ideas: string[] = [
|
||||
translate("market-page-promo-1"),
|
||||
@ -51,7 +52,7 @@ const IdeasSlider = () => {
|
||||
// market categories slider
|
||||
const CatsSlider = () => {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({ direction: "rtl", dragFree: true, containScroll: "trimSnaps" })
|
||||
|
||||
const translate = useTranslate();
|
||||
const categories: categories[] = [
|
||||
{ title: "market-page-real-estate", icon: "house_building_solid", pic: "House-3.png", url: "املاک" },
|
||||
{ title: "market-page-cars", icon: "car_mirrors_solid", pic: "market-cats-vehicles.png", url: "وسایل نقلیه" },
|
||||
@ -91,6 +92,7 @@ const HomeSearch: React.FunctionComponent<HomeSearch> = ({ }) => {
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const searchPlaceHolder = translate("home-page-search-in-ads");
|
||||
|
||||
|
||||
@ -1,20 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import Image from "components/image/image"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import Input from "components/input/text"
|
||||
import { search } from "services/search/search"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from "next/router"
|
||||
import SearchBox from "../general/search/search-box"
|
||||
import CitiesFilter from "common/templates/global/cities"
|
||||
import StatesFilter from "common/templates/global/states"
|
||||
import { City, State } from "common/types/general"
|
||||
import useEmblaCarousel from 'embla-carousel-react'
|
||||
import Autoplay from 'embla-carousel-autoplay'
|
||||
import { Icon } from "components/icons"
|
||||
import Button from "components/button/button"
|
||||
|
||||
interface WhyUs {
|
||||
|
||||
@ -32,6 +19,7 @@ const WhyUs: React.FunctionComponent<WhyUs> = ({ }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const reasons: Reasons[] = [
|
||||
{ title: "home-why-us-two-title", text: "home-why-us-two-text", icon: "lightning_bolt_solid" },
|
||||
{ title: "home-why-us-one-title", text: "home-why-us-one-text", icon: "bullseye_regular" },
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import React, { Children, useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import Accordion from "components/accordion/accordion"
|
||||
|
||||
interface AdItem {
|
||||
@ -19,6 +13,9 @@ interface AdItem {
|
||||
}
|
||||
|
||||
const AdItem: React.FunctionComponent<AdItem> = ({ details, className, children, iconClass }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
title={translate(details.title)}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import Button from "components/button/button"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
@ -16,6 +16,8 @@ interface Address {
|
||||
// Overview for realestate & vehicles //
|
||||
const Address: React.FunctionComponent<Address> = ({ data, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
|
||||
const Item = ({title, value}: any) => {
|
||||
return <li className="block py-0 lg:py-1">
|
||||
<label className="text-sm text-secondary-light font-semibold ml-2 xl:text-base">{translate(title)} :</label>
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import Title from "./title"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface BuildingFacility {
|
||||
@ -12,6 +10,7 @@ interface BuildingFacility {
|
||||
// Overview for realestate & vehicles //
|
||||
const BuildingFacility: React.FunctionComponent<BuildingFacility> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "elevator", tr: translate("ads-ad-page-elevator") },
|
||||
{ token: "parking", tr: translate("ads-ad-page-parking") },
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url, useMeasure } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface CarDimensions {
|
||||
@ -22,6 +17,7 @@ interface CarDimensions {
|
||||
// Overview for realestate & vehicles //
|
||||
const CarDimensions: React.FunctionComponent<CarDimensions> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
let length = useMeasure("millimeter", details.length);
|
||||
let width = useMeasure("millimeter", details.width);
|
||||
let height = useMeasure("millimeter", details.height);
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface CarOptions {
|
||||
@ -15,6 +9,7 @@ interface CarOptions {
|
||||
|
||||
const CarOptions: React.FunctionComponent<CarOptions> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "air-conditioning", tr: translate("air-conditioning") },
|
||||
{ token: "child-seat", tr: translate("child-seat") },
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface CarSafety {
|
||||
@ -13,6 +9,7 @@ interface CarSafety {
|
||||
|
||||
const CarSafety: React.FunctionComponent<CarSafety> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "lane-change-assist", tr: translate("lane-change-assist") },
|
||||
{ token: "cruise-control", tr: translate("cruise-control") },
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url, useMeasure } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface CarTechnical {
|
||||
@ -28,6 +24,8 @@ interface CarTechnical {
|
||||
|
||||
// Overview for realestate & vehicles //
|
||||
const CarTechnical: React.FunctionComponent<CarTechnical> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "all-wheel-drive", tr: translate("all-wheel-drive") },
|
||||
{ token: "four-wheel-drive", tr: translate("four-wheel-drive") },
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { Icon } from "components/icons"
|
||||
import Title from "./title"
|
||||
import Image from "components/image/image"
|
||||
@ -32,6 +32,7 @@ const ContactInfo: React.FunctionComponent<ContactInfo> = ({ data, className })
|
||||
const [copyUrlPopoverOpen, setCopyUrlPopoverOpen] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
|
||||
const copy = () => {
|
||||
copyPageUrl(setCopyUrltext, router);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { numberFormatter, url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Title from "./title"
|
||||
@ -22,6 +22,7 @@ interface Contract {
|
||||
|
||||
const Contract: React.FunctionComponent<Contract> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const monthlyText = translate("monthly");
|
||||
const unitsYearText = translate("units-year");
|
||||
const unitsMonthText = translate("units-month");
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { numberFormatter, url, useMeasure } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Title from "./title"
|
||||
@ -32,6 +30,7 @@ interface GeneralDetails {
|
||||
|
||||
const GeneralDetails: React.FunctionComponent<GeneralDetails> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
let measure = useMeasure("kilometer", details.distance_traveled);
|
||||
let traveledDistance = details.distance_traveled === null ? null : `${numberFormatter(measure.value, ",")} ${translate(measure.symbol)}`;
|
||||
const contactText = translate("please-contact");
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { numberFormatter, url } from 'services/general/general'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { Icon } from "components/icons"
|
||||
import Title from "./title"
|
||||
|
||||
@ -33,6 +30,7 @@ interface JobDetails {
|
||||
|
||||
const JobDetails: React.FunctionComponent<JobDetails> = ({ details }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const dollarText = translate("dollar");
|
||||
const withSalaryText = translate(details.salary_type ? details.salary_type : "empty-tr");
|
||||
const salaryText = `${dollarText} ${withSalaryText}`;
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { setMeasures } from 'common/redux/slices/global'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import Button from "components/button/button"
|
||||
|
||||
interface Menu {
|
||||
className?: string,
|
||||
@ -34,6 +29,7 @@ const MoreMenu: React.FunctionComponent<MoreMenu> = ({ open = false, }) => {
|
||||
const [isMetric, setIsMetric] = useState(true);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const translate = useTranslate();
|
||||
|
||||
const changeMeasurementSystem = () => {
|
||||
setIsMetric(!isMetric);
|
||||
@ -63,6 +59,8 @@ const Menu: React.FunctionComponent<Menu> = ({ className, onGallery }) => {
|
||||
const [isActive, setIsActive] = useState(0);
|
||||
const [isMoreOpen, setIsMoreOpen] = useState(false);
|
||||
|
||||
const translate = useTranslate();
|
||||
|
||||
const removeFocus = (e: any) => {
|
||||
let target = e.target.classList.contains('ad-menu');
|
||||
let moreMenu = e.target.classList.contains('more-menu') || e.target.classList.contains('ad-menu');
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
@ -15,6 +15,7 @@ interface NearbyFacility {
|
||||
|
||||
const NearbyFacility: React.FunctionComponent<NearbyFacility> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "beach", tr: translate("ads-ad-page-beach") },
|
||||
{ token: "shopping-center", tr: translate("ads-ad-page-shopping-center") },
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { url, useMeasure } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Title from "./title"
|
||||
@ -22,7 +20,7 @@ interface Overview {
|
||||
const Overview: React.FunctionComponent<Overview> = ({ details, className }) => {
|
||||
|
||||
let areaMeasure = useMeasure("sqmt", details.area);
|
||||
|
||||
const translate = useTranslate();
|
||||
const Item = ({ icon, label, value, unit, style }: any) => {
|
||||
return <div className="block">
|
||||
<Icon name={icon} className={`inline-block fill-market-title ml-2 ${style}`} />
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface PublicTransport {
|
||||
@ -16,6 +10,7 @@ interface PublicTransport {
|
||||
// Overview for realestate & vehicles //
|
||||
const PublicTransport: React.FunctionComponent<PublicTransport> = ({ details, className }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "bus", tr: translate("ads-ad-page-bus") },
|
||||
{ token: "metro", tr: translate("ads-ad-page-metro") },
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
|
||||
interface Title {
|
||||
title: string,
|
||||
@ -16,6 +12,9 @@ interface Title {
|
||||
|
||||
// Overview for realestate & vehicles //
|
||||
const Title: React.FunctionComponent<Title> = ({ title, icon, id, className, iconClass }) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<span id={id} className="block absolute -top-[80px]"></span>
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { setMeasures } from 'common/redux/slices/global'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import Title from "./title"
|
||||
|
||||
interface UnitConversion {
|
||||
@ -17,6 +14,7 @@ const UnitConversion: React.FunctionComponent<UnitConversion> = ({ details, clas
|
||||
const [isImperial, setIsImperial] = useState(true);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const translate = useTranslate();
|
||||
|
||||
const changeMeasurementSystem = () => {
|
||||
setIsImperial(!isImperial);
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import React, { useState } from "react"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { url } from 'services/general/general'
|
||||
import { Icon } from "components/icons"
|
||||
import Parser from "components/parser/parser"
|
||||
import Title from "./title"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import AdItem from "./ad-item"
|
||||
|
||||
interface UnitFacility {
|
||||
@ -30,6 +24,7 @@ interface UnitFacility {
|
||||
|
||||
const Category = ({items, title, custom} : any) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "fridge", tr: translate("ads-ad-page-fridge") },
|
||||
{ token: "freezer", tr: translate("ads-ad-page-freezer") },
|
||||
@ -79,6 +74,7 @@ const Category = ({items, title, custom} : any) => {
|
||||
|
||||
const bedroomList = (items:any) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const unitsNumberText = translate("units-number");
|
||||
const bedroomsText = translate("ads-ad-page-bedrooms");
|
||||
const kingBedsText = translate("ads-ad-page-king-beds");
|
||||
@ -99,6 +95,7 @@ const bedroomList = (items:any) => {
|
||||
|
||||
const bathroomList = (items:any) => {
|
||||
|
||||
const translate = useTranslate();
|
||||
const translations = [
|
||||
{ token: "washing-machine", tr: translate("ads-ad-page-washing-machine") },
|
||||
{ token: "dryer", tr: translate("ads-ad-page-dryer") },
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import Link from "components/link/link"
|
||||
import translate from "services/translation/translation"
|
||||
import { Icon } from "components/icons"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { City, State, Color, FormOptions, Languages, Brand } from "../../../types/general"
|
||||
import { setMeasures } from "common/redux/slices/global";
|
||||
import AdBrand from "./quick-filters/ad-brand";
|
||||
import VehicleType from "./quick-filters/vehicle-type";
|
||||
import AdCity from "./quick-filters/city";
|
||||
@ -45,6 +42,7 @@ const FiltersBar: React.FunctionComponent<FiltersBar> = ({ data, applyFilters, o
|
||||
const [filtersOpen, setFiltersOpen] = useState(isOpen);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const router = useRouter();
|
||||
const filtersText = translate("ads-filter-results");
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../redux/hooks';
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import { Icon } from "components/icons"
|
||||
import PriceFilter from "./filters/price-filter"
|
||||
@ -75,6 +75,7 @@ const FilterCat: React.FunctionComponent<FilterCat> = ({ children, title, name,
|
||||
|
||||
const [isActive, setIsActive] = useState(active);
|
||||
|
||||
const translate = useTranslate();
|
||||
const currentFilters = useAppSelector(getCurrentFilters); // all filters
|
||||
const af: string[] = []; // currently active filters
|
||||
for (const [key, value] of Object.entries(currentFilters)) {
|
||||
@ -111,6 +112,7 @@ const FiltersSide: React.FunctionComponent<FiltersSide> = ({ className, cat, cat
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const headerTitle = translate("filters");
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { Icon } from "components/icons";
|
||||
import { getCurrentFilters, setMarketFilters } from "common/redux/slices/market";
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
|
||||
interface Filter {
|
||||
name: string,
|
||||
@ -23,6 +23,7 @@ const ActiveFilters: React.FunctionComponent<ActiveFilters> = ({ }) => {
|
||||
// states
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const activeFiltersTitle = translate("current-active-filters-title");
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useUnitConverter } from 'services/general/general'
|
||||
import FilterItem from "./filter-item"
|
||||
@ -20,6 +20,7 @@ const AreaFilter: React.FunctionComponent<AreaFilter> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
const system = useAppSelector(getUnitSystem);
|
||||
const convertedMinArea = useUnitConverter("sqft", minArea, "metric");
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from "common/redux/slices/market";
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import CheckBox from "components/checkbox/checkbox";
|
||||
|
||||
@ -17,6 +17,7 @@ const AvailabilityDate: React.FunctionComponent<AvailabilityDate> = ({ className
|
||||
const [date, setDate] = useState(Date.now());
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import FilterItem from "./filter-item"
|
||||
import ToggleGroup from "components/button/toggleGroup";
|
||||
import ToggleButton from "components/button/toggleButton";
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import ToggleGroup from "components/button/toggleGroup";
|
||||
import ToggleButton from "components/button/toggleButton";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, getBuildingFacilities, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
@ -24,6 +24,7 @@ const BuildingFacilities: React.FunctionComponent<BuildingFacilities> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
const facilities = [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import { MarketCat } from "common/types/market";
|
||||
import { Icon } from "components/icons";
|
||||
@ -22,6 +22,7 @@ const Categories: React.FunctionComponent<Categories> = ({ wrapperClass, parentC
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const allCatsText = translate("all-categories");
|
||||
const catIcons = [
|
||||
{ title: "املاک", icon: "house_chimney_solid" },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getColor, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -17,6 +17,7 @@ const Colors: React.FunctionComponent<Colors> = ({ colors }) => {
|
||||
const [value, setValue] = useState<{ value: string[], active: boolean }>({ value: [], active: false });
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const items = colors.map(x => x.persian_name);
|
||||
const allOption = translate("all");
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
|
||||
@ -23,6 +22,7 @@ const ContractOptions: React.FunctionComponent<ContractOptions> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
const utilityOptions = [
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, getCooling, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
|
||||
@ -20,6 +19,7 @@ const Cooling: React.FunctionComponent<Cooling> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
const coolingOptions = [
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useUnitConverter } from 'services/general/general'
|
||||
import FilterItem from "./filter-item"
|
||||
import Input from "components/input/text"
|
||||
@ -19,6 +18,7 @@ const DistanceTraveled: React.FunctionComponent<DistanceTraveled> = () => {
|
||||
const [maxDistance, setMaxDistance] = useState<number | undefined>(undefined);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
const system = useAppSelector(getUnitSystem);
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import Input from "components/input/text"
|
||||
import Button from "components/button/button"
|
||||
@ -16,6 +15,7 @@ const FloorNumber: React.FunctionComponent<FloorNumber> = () => {
|
||||
const [minFloor, setMinFloor] = useState<number>(0);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import Input from "components/input/text"
|
||||
import Button from "components/button/button"
|
||||
@ -18,6 +17,7 @@ const FuelTankCapacity: React.FunctionComponent<FuelTankCapacity> = () => {
|
||||
const [minTank, setMinTank] = useState<number | undefined>(undefined);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
const system = useAppSelector(getUnitSystem);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
|
||||
@ -18,6 +18,7 @@ const Heating: React.FunctionComponent<Heating> = () => {
|
||||
const [heating, setHeating] = useState<string[]>([]);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -17,6 +17,7 @@ const JobsExperience: React.FunctionComponent<JobsExperience> = ({ data }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const items = data.map(x => translate(x));
|
||||
const allOption = translate("all");
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -16,6 +16,7 @@ const JobsInsurance: React.FunctionComponent<JobsInsurance> = ({ }) => {
|
||||
const [value, setValue] = useState<{ value: string[], active: boolean }>({ value: [], active: false });
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const router = useRouter();
|
||||
const items = [translate("logic-yes"), translate("logic-no")]
|
||||
const allOption = translate("all");
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -18,6 +18,7 @@ const JobsLanguages: React.FunctionComponent<JobsLanguages> = ({ data }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const items = data.map(x => x.persian_name);
|
||||
const allOption = translate("all");
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -17,6 +17,7 @@ const JobsWorkStyle: React.FunctionComponent<JobsWorkStyle> = ({ data }) => {
|
||||
|
||||
// variables
|
||||
const router = useRouter();
|
||||
const translate = useTranslate();
|
||||
const items = data.map(x => translate(x));
|
||||
const allOption = translate("all");
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, getNearbyFacilities, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
|
||||
@ -21,6 +21,7 @@ const NearbyFacilities: React.FunctionComponent<NearbyFacilities> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset)
|
||||
|
||||
const facilities = [
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import Input from "components/input/text"
|
||||
import Button from "components/button/button"
|
||||
@ -18,6 +17,7 @@ const PriceFilter: React.FunctionComponent<PriceFilter> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
// methods
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import Input from "components/input/text"
|
||||
import Button from "components/button/button"
|
||||
@ -16,6 +15,7 @@ const ProductionYear: React.FunctionComponent<ProductionYear> = () => {
|
||||
const [minYear, setMinYear] = useState<number | undefined>(undefined);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, getPublishDate, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -16,6 +16,7 @@ const PublishDate: React.FunctionComponent<PublishDate> = ({ }) => {
|
||||
const [value, setValue] = useState<{ value: string[], active: boolean }>({ value: [], active: false });
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const items = ["today", "yesterday", "past-week", "past-month", "past-three-months", "past-year"];
|
||||
const allOption = translate("all");
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import useTranslate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
@ -16,6 +16,7 @@ const RentBuy: React.FunctionComponent<RentBuy> = () => {
|
||||
const [buy, setBuy] = useState(true);
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const dispatch = useAppDispatch();
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import { getAreFiltersReset, getUnitFacilities, setMarketFilters } from '../../../../redux/slices/market'
|
||||
import translate from "services/translation/translation"
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslate from "services/translation/translation"
|
||||
import FilterItem from "./filter-item"
|
||||
import CheckBox from "components/checkbox/checkbox"
|
||||
|
||||
@ -24,6 +23,7 @@ const UnitFacilities: React.FunctionComponent<UnitFacilities> = () => {
|
||||
|
||||
// variables
|
||||
const dispatch = useAppDispatch();
|
||||
const translate = useTranslate();
|
||||
const areFiltersReset = useAppSelector(getAreFiltersReset);
|
||||
|
||||
const facilities = [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||
import translate from "services/translation/translation";
|
||||
import useTranslate from "services/translation/translation";
|
||||
import FilterItem from "./filter-item";
|
||||
import DataList from "components/select/data-list";
|
||||
import { getAreFiltersReset, setMarketFilters } from "common/redux/slices/market";
|
||||
@ -15,6 +15,7 @@ const VehicleBody: React.FunctionComponent<VehicleBody> = ({ types }) => {
|
||||
const [value, setValue] = useState<{ value: string[], active: boolean }>({ value: [], active: false });
|
||||
|
||||
// variables
|
||||
const translate = useTranslate();
|
||||
const items = types.map(x => translate(x));
|
||||
const allOption = translate("all");
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user