Huge update, mostly to profile + translations

# translations system updated
# profile ad form ready (almost)
# general bug fixes
This commit is contained in:
Deltora72 2023-04-23 15:09:05 +04:30
parent e39638ad46
commit 2b0aaa8a30
137 changed files with 4573 additions and 1564 deletions

3
.gitignore vendored
View File

@ -32,3 +32,6 @@ yarn-error.log*
# vercel
.vercel
# tinymce
/public/tinymce/

2696
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/5402993_2803242.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

BIN
public/7119227_3323585.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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" />
},
}

View File

@ -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,

View File

@ -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}`;

View File

@ -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");

View File

@ -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

View File

@ -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>

View 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

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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 () => {

View File

@ -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 (

View File

@ -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

View 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,
}

View 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,
}
}
)

View 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
}
`

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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>

View File

@ -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>
)

View File

@ -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>

View File

@ -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");

View File

@ -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) => {

View File

@ -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");

View File

@ -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");

View File

@ -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();

View File

@ -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 (

View File

@ -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");

View File

@ -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;

View File

@ -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);

View File

@ -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" },

View File

@ -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];

View File

@ -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");

View File

@ -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" },

View File

@ -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)}

View File

@ -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>

View File

@ -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") },

View File

@ -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);

View File

@ -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") },

View File

@ -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") },

View File

@ -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") },

View File

@ -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);

View File

@ -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");

View File

@ -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");

View File

@ -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}`;

View File

@ -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');

View File

@ -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") },

View File

@ -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}`} />

View File

@ -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") },

View File

@ -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>

View File

@ -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);

View File

@ -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") },

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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();

View File

@ -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";

View File

@ -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";

View File

@ -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 = [

View File

@ -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" },

View File

@ -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();

View File

@ -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 = [

View File

@ -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 = [

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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");

View File

@ -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();

View File

@ -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();

View File

@ -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 = [

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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 = [

View File

@ -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