Added Redux + general changes

This commit is contained in:
Deltora72 2022-03-12 12:52:12 +01:00
parent ba30f13e06
commit e8d5b89999
34 changed files with 7140 additions and 42 deletions

22
jest.config.js Normal file
View File

@ -0,0 +1,22 @@
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^/components/(.*)$': '<rootDir>/common/components/$1',
'^/services/(.*)$': '<rootDir>/common/services/$1',
},
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)

View File

@ -2,7 +2,8 @@
"compilerOptions": {
"baseUrl": "src",
"paths": {
"components/*": ["./common/components/*"]
"components/*": ["./common/components/*"],
"services/*": ["./common/services/*"]
}
}
}

View File

@ -1,3 +1,12 @@
module.exports = {
reactStrictMode: true,
i18n: {
// These are all the locales you want to support in
// your application
locales: ['en-US', 'fa-IR'],
// This is the default locale you want to be used when visiting
// a non-locale prefixed path e.g. `/hello`
defaultLocale: 'fa-IR',
localeDetection: false,
},
}

6770
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,19 +3,27 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "next build && next export",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "jest --watch"
},
"dependencies": {
"@apollo/client": "^3.5.9",
"@reduxjs/toolkit": "^1.8.0",
"graphql": "^16.3.0",
"next": "12.0.10",
"react": "17.0.2",
"react-dom": "17.0.2"
"react-dom": "17.0.2",
"react-redux": "^7.2.6"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"autoprefixer": "^10.4.2",
"eslint": "8.8.0",
"eslint-config-next": "12.0.10",
"jest": "^27.5.1",
"postcss": "^8.4.6",
"tailwindcss": "^3.0.18"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,9 +2,17 @@ import React from "react"
import NextImage from 'next/image'
const Image = ({caption, ...props}) => {
const cfLoader = ({ src, width, quality }) => {
return `https://flierland.ca/cdn-cgi/imagedelivery/NB6v-3jb2osnFlFLXuQNUA/${props.src}?w=${props.width}&q=${props.quality || 75}`
}
return (
<figure className="relative flex justify-center">
<NextImage {...props} />
<NextImage
{...props}
loader={cfLoader}
/>
{caption && <figcaption>{caption}</figcaption>}
</figure>
)

View File

@ -0,0 +1,11 @@
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: 'http://localhost:8055/graphql',
cache: new InMemoryCache()
// headers: {
// authorization: 'Bearer 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',
// }
});
export default client;

View File

@ -0,0 +1,19 @@
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
value: {},
}
export const globalSlice = createSlice({
name: 'global',
initialState,
reducers: {
setGlobalData: (state, action) => {
state.value = action.payload
},
},
})
export const { setGlobalData } = globalSlice.actions
export default globalSlice.reducer

View File

@ -0,0 +1,19 @@
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
value: 'fa-IR',
}
export const localeSlice = createSlice({
name: 'locale',
initialState,
reducers: {
setLocale: (state, action) => {
state.value = action.payload
},
},
})
export const { setLocale } = localeSlice.actions
export default localeSlice.reducer

View File

@ -0,0 +1,8 @@
import { configureStore } from '@reduxjs/toolkit'
import globalReducer from '../slices/global'
export const store = configureStore({
reducer: {
global: globalReducer,
},
})

View File

@ -0,0 +1,17 @@
import { useSelector } from 'react-redux'
import { useRouter } from 'next/router'
const Translate = (token) => {
const router = useRouter();
const locale = router.locale;
const data = useSelector((state) => state.global.value);
const translation = data.dictionary
.filter(x => x.token === token)
.map(x => x.translations)[0]
.filter(x => x.languages_code.code === locale)
.map(x => x.translation)[0]
return translation;
}
export default Translate

View File

@ -0,0 +1,3 @@
it("Test works correctly!", () => {
expect(2+2).toBe(4);
});

View File

@ -2,43 +2,40 @@ import React, { useState, useEffect } from "react"
import { useRouter } from 'next/router'
import Link from "components/link/link"
import Image from "components/image/image"
import { useSelector, useDispatch } from 'react-redux'
import MainMenu from './main-menu'
const Header = () => {
const [menuOpen, setMenuOpen] = useState(false);
const data = useSelector((state) => state.global.value);
const router = useRouter();
const toggleMenu = () => {
menuOpen ? setMenuOpen(false) : setMenuOpen(true);
};
const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
const handleRouteChange = () => {
setMenuOpen(false);
console.log('changed!');
console.log('Route has changed!');
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [])
})
const toggleMenu = () => {
setMenuOpen(!menuOpen);
};
return (
<header className="relative flex justify-between items-center py-1 px-1 bg-secondary shadow">
<Link href="/"><Image src="/logo.png" width={180} height={64}/></Link>
<div className="pr-2 cursor-pointer" onClick={toggleMenu}>
<span className="block w-8 h-[3px] rounded bg-white mb-2"></span>
<span className="block w-8 h-[3px] rounded bg-white mb-2"></span>
<span className="block w-8 h-[3px] rounded bg-white"></span>
</div>
<nav className={`${menuOpen ? "block" : "hidden"} w-full absolute top-full left-0 bg-white shadow-sm`}>
<div className="grid gap-1 p-1 grid-cols-1 px-3 py-3 border-b border-gray-200 border-solid border-t">
<Link className="link select-none" href="/">Home</Link>
<Link className="link select-none" href="/components">Components</Link>
<Link className="link select-none" href="/about">About</Link>
</div>
</nav>
<Link href="/"><Image src="08490987-279d-477f-5eab-58799e79cc00/public" alt="flierland logo" width={180} height={64}/></Link>
<MainMenu data={data} isOpen={menuOpen}/>
</header>
)
}

View File

@ -0,0 +1,133 @@
import React, { useState, useEffect } from "react"
import translate from "services/translations"
import Link from "components/link/link"
import { useRouter } from 'next/router'
const MainMenu = ({ data, isOpen }) => {
const router = useRouter();
const [subActive, setSubActive] = useState([]);
const changeLocal = (locale) => {
router.push('/', '/', { locale: locale })
}
const openSub = (id) => {
const menuStats = subActive;
const current = menuStats.findIndex(x => x.id === id);
if (current === -1) {
menuStats.push({ id: id, isActive: true });
setSubActive([...menuStats]);
} else {
menuStats[current].isActive = !menuStats[current].isActive;
setSubActive([...menuStats]);
}
}
useEffect(() => {
!isOpen && setSubActive([]);
}, [isOpen])
// checks if this submenu is active now
const matchId = (id) => {
const isMatch = subActive.find(x => x.id === id) && subActive.find(x => x.id === id).isActive;
return isMatch;
}
const MenuItem = ({ title, href, isSub, className }) => {
return <Link className={`block link select-none ${isSub ? 'text-sm py-2' : 'text-base py-3'} w-full ${className}`} href={href}>{title}</Link>
}
const MenuBuilder = ({ menu, title, href }) => {
return (
<li className={`block w-full px-3 ${matchId(title) ? 'border-r-2 border-solid border-r-primary' : ''} border-t border-b border-t-white border-b-border-gray-bottom first:border-t-0 last:border-b-0`}>
<div className="flex justify-between w-full flex-row items-center">
{href ?
// lvl one
<MenuItem className={`${matchId(title) ? 'text-primary' : ''}`} title={translate(title)} href={href} />
:
// lvl one
<span className={`block w-full link select-none leading-4 py-3 ${matchId(title) ? 'text-primary' : ''}`}>{translate(title)}</span>
}
{menu &&
<span
className={`
${matchId(title) ? '-rotate-90 text-primary' : ''}
w-8 text-center text-2xl font-bold cursor-pointer hover:text-primary
`}
onClick={() => openSub(title)}
>
</span>
}
</div>
{menu &&
<ul className={`${matchId(title) ? 'block' : 'hidden'} px-3 mb-2`} >
{menu !== null && menu.map(item => (
item.has_children ?
// lvl two
<li className="border-t border-b border-t-white border-b-border-gray-bottom first:border-t-0 last:border-b-0" key={item.id}>
<div className="flex justify-between w-full flex-row items-center">
<MenuItem className={`${matchId(item.id) ? 'text-primary' : ''}`} title={item.name} href={"/"} isSub={true}/>
<span
className={`
${matchId(item.id) ? '-rotate-90 text-primary' : ''}
w-8 text-center text-2xl font-bold cursor-pointer hover:text-primary
`}
onClick={() => openSub(item.id)}
>
</span>
</div>
<ul className={`${matchId(item.id) ? 'block' : 'hidden'} px-3 pb-2`}>
{menu.filter(x => x.parent && x.parent.length > 0 && x.parent[0].related_magazine_categories_id.name === item.name)
.map(x => (
// lvl three
<li className ="border-t border-b border-t-white border-b-border-gray-bottom first:border-t-0 last:border-b-0 hover:bg-white" key={x.id}>
<div className="flex justify-between w-full flex-row items-center">
<span
className={`w-8 text-center text-xl`}
>
</span>
<MenuItem title={x.name} href="/" isSub={true}/>
</div>
</li>
)
)}
</ul>
</li>
:
item.parent && item.parent.length < 1 ?
// lvl two
<li className="border-t border-b border-t-white border-b-border-gray-bottom first:border-t-0 last:border-b-0 hover:bg-white" key={item.id}>
<div className="flex justify-between w-full flex-row items-center">
<span
className={`w-8 text-center text-xl`}
>
</span>
<MenuItem title={item.name} href="/" isSub={true} />
</div>
</li> : ''
))}
</ul>
}
</li>
)
}
return (
<nav className={`${isOpen ? "block" : "hidden"} w-full absolute top-full left-0 bg-light-gray shadow`}>
{/* Main menu */}
<ul className="grid gap-0 p-1 grid-cols-1 px-0 py-3 border-b border-border-gray-bottom border-solid">
<MenuBuilder title="main-menu-home-title" href={"/"}/>
<MenuBuilder title="main-menu-billboard-title" menu={data.billboard_categories}/>
<MenuBuilder title="main-menu-magazine-title" menu={data.magazine_categories} />
<MenuBuilder title="main-menu-contact-title" href={"/contact"}/>
</ul>
{/* Locale selection */}
<div className="grid grid-cols-2 border-t border-solid border-white p-2">
<button className="text-center" onClick={() => changeLocal('fa-IR')}>fa-IR</button>
<button className="text-center" onClick={() => changeLocal('en-US')}>en-US</button>
</div>
</nav>
)
}
export default MainMenu

View File

@ -1,10 +1,56 @@
import React from "react"
import React, { useState, useEffect } from "react"
import Header from "../header/header"
import Footer from "../footer/footer"
import { useQuery, gql } from "@apollo/client";
import { useSelector, useDispatch } from 'react-redux'
import { setGlobalData } from '../../../redux/slices/global'
const Layout = ({children}) => {
const dispatch = useDispatch()
const getGlobalData = gql`
query GetTranslations {
dictionary {
token
translations {
languages_code {
code
}
translation
}
}
billboard_categories {
name
id
has_children
parent {
related_billboard_categories_id {
name
}
}
}
magazine_categories {
name
id
has_children
parent {
related_magazine_categories_id {
name
}
}
}
}
`;
const { loading, error, data } = useQuery(getGlobalData);
if (loading) return <p>Loading...</p>;
if (error) return <p>{error.message} :(</p>;
dispatch(setGlobalData(data))
return (
<div className="">
<div className="" style={{'direction': 'rtl'}}>
<Header />
<main>
{children}

View File

@ -1,12 +1,20 @@
import './styles/globals.css'
import Layout from '../common/templates/general/layout/layout'
import { store } from '../common/redux/store/store'
import { Provider } from 'react-redux'
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, gql } from "@apollo/client";
import client from "../common/data/apollo-client";
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
<Provider store={store}>
<ApolloProvider client={client}>
<Layout>
<Component {...pageProps} />
</Layout>
</ApolloProvider>
</Provider>
)
}
export default MyApp
export default MyApp

View File

@ -1,7 +1,7 @@
import Image from 'next/image'
import translate from "services/translations"
export default function Home() {
return (
<h1 className="">Deltora react components</h1>
<h1 className="">{translate("home-page-main-title")}</h1>
)
}
}

View File

@ -2,7 +2,47 @@
@tailwind components;
@tailwind utilities;
/* :root {
--primary: #e12828;
--secondary: #2A2E3C;
--sup-light: #E6E6EA;
--sup-dark: #fed766;
--light-bg: #f8f8f8;
--icon-color: #757575;
--title-color: #2b2b2b;
--text-color: #464040;
--main-font: IRANSans, sans-serif;
--fs: 16px;
--fw-normal: 400;
--fw-semi-bold: 500;
--gi-side: 92px;
--gi-double: 184px;
--li-side: calc((100vw - 1400px) / 2);
--li-double: calc(100vw - 1400px);
--scale: scale(95);
} */
@font-face {
font-family: IRANSans;
src: local('IRANSans'), url(/fonts/IRANSansWeb.woff2) format("woff2"),
url(/fonts/IRANSansWeb.woff) format("woff"),
url(/fonts/IRANSansWeb.ttf) format("truetype"),
url(/fonts/IRANSansWeb.eot),
url(/fonts/IRANSansWeb.eot?#iefix) format("embedded-opentype");
unicode-range: 0020-007F, 00A0-00BB, 0600-067F;
font-weight: 400;
font-style: normal;
font-display: swap;
}
@layer base {
html {
@apply font-main;
}
h1 {
@apply text-2xl font-bold;
}

View File

@ -6,9 +6,14 @@ module.exports = {
],
theme: {
extend: {
fontFamily: {
'main': ['IRANSans', 'sans-serif'],
},
colors: {
'primary': '#e12828',
'secondary': '#2A2E3C',
'light-gray': '#f5f7fa',
'border-gray-bottom': '#e0e7f0',
'sup-light': '#E6E6EA',
'sup-dark': '#fed766',
'light-bg': '#f8f8f8',