This commit is contained in:
VividTruthKeeper 2023-03-05 21:55:42 +05:00
commit 742920c780
17 changed files with 320 additions and 267 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@ -6,8 +6,8 @@
<meta name="description" id="meta-description" content="" />
<meta name="keywords" id="meta-keywords" content="" />
<title>Türkmenistan Habarlar Portaly</title>
<script type="module" crossorigin src="/assets/index-42dcf52a.js"></script>
<link rel="stylesheet" href="/assets/index-f119abb1.css">
<script type="module" crossorigin src="/assets/index-eae81109.js"></script>
<link rel="stylesheet" href="/assets/index-be370464.css">
</head>
<body>
<div id="root"></div>

View File

@ -1,5 +1,8 @@
// Types
import { ICategoriesData } from "../types/data.types";
import {
ICategoryData,
ICategoryDataAction,
IFeaturedData,
IFeaturedDataAction,
INewsScroll,
@ -10,33 +13,46 @@ import {
ISearchResultAction,
IVideoData,
IVideoDataAction,
} from '../types/store.types';
} from "../types/store.types";
// NewsScroll
export const setNewsScroll = (data: INewsScroll['data']): INewsScrollAction => ({
type: 'SET_NEWS_SCROLL',
export const setNewsScroll = (
data: INewsScroll["data"]
): INewsScrollAction => ({
type: "SET_NEWS_SCROLL",
payload: data,
});
export const setSearchResult = (data: ISearchResult['data']): ISearchResultAction => ({
type: 'SET_SEARCH_DATA',
export const setSearchResult = (
data: ISearchResult["data"]
): ISearchResultAction => ({
type: "SET_SEARCH_DATA",
payload: data,
});
// Post
export const setPost = (data: IPostData['data']): IPostDataAction => ({
type: 'SET_POST',
export const setPost = (data: IPostData["data"]): IPostDataAction => ({
type: "SET_POST",
payload: data,
});
export const setVideo = (data: IVideoData['data']): IVideoDataAction => ({
type: 'SET_VIDEO',
export const setVideo = (data: IVideoData["data"]): IVideoDataAction => ({
type: "SET_VIDEO",
payload: data,
});
export const setFeatured = (data: IFeaturedData['data']): IFeaturedDataAction => ({
type: 'SET_FEATURED',
export const setFeatured = (
data: IFeaturedData["data"]
): IFeaturedDataAction => ({
type: "SET_FEATURED",
payload: data,
});
export const setCategories = (
data: ICategoryData["data"]
): ICategoryDataAction => ({
type: "SET_CATEGORIES",
payload: data,
});

View File

@ -1,59 +0,0 @@
// Modules
import { LazyLoadImage } from "react-lazy-load-image-component";
import { Link } from "react-router-dom";
import { useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import placeholder from "../../assets/images/placeholder.webp";
// Types
import { RootState } from "../../types/store.types";
const MainImg = () => {
// redux
const data = useSelector<RootState, RootState["newsScroll"]["data"]>(
(state) => state.newsScroll.data
);
return (
<>
{data.length > 0 ? (
data[0].id > -1 ? (
<Link to={`/news/${data[0].id}`} className="main-img">
<LazyLoadImage
placeholderSrc={placeholder}
src={
(data[0].featured_images.length > 0
? data[0].featured_images[0].path
: "") as string
}
alt={
(data[0].featured_images.length > 0
? data[0].featured_images[0].file_name
: "") as string
}
useIntersectionObserver
effect="opacity"
/>
<div className="main-img-overlay">
<h2>{data[0].title}</h2>
<div className="main-img-overlay-wrapper">
<p className="cats">
{data[0].categories.map((category) => {
return <span key={uuidv4()}>{category.name}</span>;
})}
</p>
<span className="date">{data[0].published_at}</span>
</div>
</div>
</Link>
) : (
""
)
) : (
""
)}
</>
);
};
export default MainImg;

View File

@ -1,59 +1,39 @@
// Modules
import { Link } from 'react-router-dom';
import { v4 as uuidv4 } from "uuid";
import { useSelector } from "react-redux";
// Icons
import phone from '../../assets/icons/phone.svg';
import mail from '../../assets/icons/mail.svg';
import location from '../../assets/icons/location.svg';
import { ReactComponent as Instagram } from '../../assets/icons/insta-black.svg';
import { ReactComponent as Facebook } from '../../assets/icons/fb-black.svg';
import { ReactComponent as TikTok } from '../../assets/icons/tiktok-black.svg';
import phone from "../../assets/icons/phone.svg";
import mail from "../../assets/icons/mail.svg";
import location from "../../assets/icons/location.svg";
import { ReactComponent as Instagram } from "../../assets/icons/insta-black.svg";
import { ReactComponent as Facebook } from "../../assets/icons/fb-black.svg";
import { ReactComponent as TikTok } from "../../assets/icons/tiktok-black.svg";
// Types
import { RootState } from "../../types/store.types";
// Components
import FooterListItem from "./FooterListItem";
const Footer = () => {
const categories = useSelector<RootState, RootState["categories"]["data"]>(
(state) => state.categories.data
);
return (
<footer className="footer">
<div className="container">
<div className="footer-wrapper">
<nav className="footer-nav">
<ul className="footer-nav-list">
<li className="footer-nav-list-item">
<Link to="/">В Туркменистане</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Политика</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Общество</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Статьи</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Культура</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Новости</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Экономика</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Наука и технологии</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Медиа</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">В мире</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Спорт</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Здоровье и медицина</Link>
</li>
<li className="footer-nav-list-item">
<Link to="/">Погода</Link>
</li>
{categories.data[0].id > -1
? categories.data.map((category) => (
<FooterListItem
id={category.id}
name={category.name}
key={uuidv4()}
/>
))
: ""}
</ul>
</nav>
<div className="footer-info">
@ -61,7 +41,9 @@ const Footer = () => {
<div className="footer-info-item-icon">
<img src={phone} alt="phone" />
</div>
<h3 className="footer-info-item-title">+(993) 12 68-07-92, 94-08-01 </h3>
<h3 className="footer-info-item-title">
+(993) 12 68-07-92, 94-08-01{" "}
</h3>
</div>
<div className="footer-info-item">
<div className="footer-info-item-icon">
@ -74,7 +56,8 @@ const Footer = () => {
<img src={location} alt="location" />
</div>
<h3 className="footer-info-item-title">
115184, Ашхабад, Битарап шаелы, 25 (Центр телерадиовещания Туркменистана)
115184, Ашхабад, Битарап шаелы, 25 (Центр телерадиовещания
Туркменистана)
</h3>
</div>
@ -100,10 +83,14 @@ const Footer = () => {
</div>
<div className="footer-info-item">
<h3 className="footer-info-item-title">Реклама на ТВ и радио: (993) 12 78-13-99</h3>
<h3 className="footer-info-item-title">
Реклама на ТВ и радио: (993) 12 78-13-99
</h3>
</div>
<div className="footer-info-item">
<h3 className="footer-info-item-title">Реклама на сайте: (993) 12 78-13-99</h3>
<h3 className="footer-info-item-title">
Реклама на сайте: (993) 12 78-13-99
</h3>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
// Modules
import { Link } from "react-router-dom";
interface IProps {
id: number;
name: string;
}
const FooterListItem = ({ id, name }: IProps) => {
return (
<li className="footer-nav-list-item">
<Link to={`/category/${id}`}>{name}</Link>
</li>
);
};
export default FooterListItem;

View File

@ -28,6 +28,10 @@ const Search = ({ isSmall, isInputFocused, setIsInputFocused }: IProps) => {
dispatch(setSearch(value));
};
const searchValue = useSelector<RootState, RootState["search"]["value"]>(
(state) => state.search.value
);
// redux
const dispatch = useDispatch();
const inputValue = useSelector<RootState, RootState["search"]["value"]>(
@ -44,7 +48,9 @@ const Search = ({ isSmall, isInputFocused, setIsInputFocused }: IProps) => {
className="search"
onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
navigate(`/search/${inputValue}`);
if (searchValue.length > 0) {
navigate(`/search/${inputValue}`);
}
}}
variants={searchMobileMotion}
initial={isSmall ? "borderRest" : {}}

View File

@ -5,7 +5,7 @@ import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
// Types
import { RootState } from "../../types/store.types";
import { ICategoryData, RootState } from "../../types/store.types";
import { ICategoriesData } from "../../types/data.types";
// Actions
@ -20,6 +20,7 @@ import { categoriesParams } from "../../api/params";
import Loader from "../global/Loader";
import SubNavLi from "./SubNavLi";
import SubNavLiMain from "./SubNavLiMain";
import { setCategories } from "../../actions/setData";
const SubNav = () => {
const activeLink = useSelector<RootState, RootState["activeLink"]["active"]>(
@ -29,19 +30,23 @@ const SubNav = () => {
(state) => state.language.title
);
const categories = useSelector<RootState, RootState["categories"]["data"]>(
(state) => state.categories.data
);
const dispatch = useDispatch();
const onClickLink = (active: number) => {
dispatch(setActiveLink(active));
};
const [data, setData] = useState<ICategoriesData>();
// Api
const api = new Api(url + "/categories", categoriesParams);
useEffect(() => {
api.get(data, setData);
api.get(categories, (data: ICategoryData["data"]) =>
dispatch(setCategories(data))
);
}, [language]);
const location = useLocation();
@ -58,15 +63,19 @@ const SubNav = () => {
return (
<nav className="subnav">
<div className="container">
<ul className={`subnav-inner ${!data ? "loading" : ""}`}>
{data ? (
<ul
className={`subnav-inner ${
!(categories.data[0].id > -1) ? "loading" : ""
}`}
>
{categories.data[0].id > -1 ? (
<>
<SubNavLiMain
data={data}
data={categories}
activeLink={activeLink}
onClickLink={onClickLink}
/>
{data.data.map((dataEl, index) =>
{categories.data.map((dataEl, index) =>
index <= 4 ? (
<SubNavLi
key={uuidv4()}

View File

@ -1,3 +0,0 @@
{
"name": "Turkmenistan news portal"
}

View File

@ -1,3 +0,0 @@
{
"name": "Туркменистан новостной портал"
}

View File

@ -1,3 +0,0 @@
{
"name": "Turkmenistan habarlar portaly"
}

View File

@ -1,14 +1,22 @@
// Modules
import { motion } from "framer-motion";
import { useSelector } from "react-redux";
// Components
import { useParams } from "react-router-dom";
import Aside from "../components/aside/Aside";
import NewsScroll from "../components/global/NewsScroll";
import MainImg from "../components/category/MainImg";
import ContentItem from "../components/main/ContentItem";
// Types
import { RootState } from "../types/store.types";
import Loader from "../components/global/Loader";
const Category = () => {
let { category } = useParams();
const data = useSelector<RootState, RootState["newsScroll"]["data"]>(
(state) => state.newsScroll.data
);
return (
<motion.main
className="category"
@ -19,7 +27,24 @@ const Category = () => {
<div className="container">
<div className="category-inner">
<div className="category-left">
<MainImg />
{data.length > 0 ? (
data[0].id > -1 ? (
<ContentItem
id={data[0].id}
img={
data[0].featured_images[0]
? data[0].featured_images[0].path
: ""
}
title={data[0].title}
type={"big"}
/>
) : (
<Loader />
)
) : (
<Loader />
)}
<NewsScroll
title={false}
category={parseInt(category as string)}

View File

@ -1,5 +1,7 @@
// Types
import {
ICategoryData,
ICategoryDataAction,
IFeaturedData,
IFeaturedDataAction,
INewsScroll,
@ -10,7 +12,7 @@ import {
ISearchResultAction,
IVideoData,
IVideoDataAction,
} from '../types/store.types';
} from "../types/store.types";
// NewsScroll
@ -18,54 +20,54 @@ export const newsScrollInitialState = {
data: [
{
id: -1,
title: '',
slug: '',
excerpt: '',
published_at: '',
title: "",
slug: "",
excerpt: "",
published_at: "",
featured_images: [
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
],
content_html: '',
content_html: "",
categories: [
{
id: -1,
name: '',
name: "",
},
],
video: null,
powerseo_title: '',
powerseo_description: '',
powerseo_keywords: '',
powerseo_title: "",
powerseo_description: "",
powerseo_keywords: "",
},
],
};
export const newsScrollReducer = (
state: INewsScroll = newsScrollInitialState,
action: INewsScrollAction,
action: INewsScrollAction
) => {
switch (action.type) {
case 'SET_NEWS_SCROLL': {
case "SET_NEWS_SCROLL": {
return { ...state, data: action.payload };
}
default: {
@ -80,51 +82,54 @@ export const postInitialState = {
data: {
data: {
id: -1,
title: '',
slug: '',
excerpt: '',
published_at: '',
title: "",
slug: "",
excerpt: "",
published_at: "",
video: null,
featured_images: [
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
],
content_html: '',
content_html: "",
categories: [
{
id: -1,
name: '',
name: "",
},
],
powerseo_title: '',
powerseo_description: '',
powerseo_keywords: '',
powerseo_title: "",
powerseo_description: "",
powerseo_keywords: "",
},
},
};
export const postReducer = (state: IPostData = postInitialState, action: IPostDataAction) => {
export const postReducer = (
state: IPostData = postInitialState,
action: IPostDataAction
) => {
switch (action.type) {
case 'SET_POST': {
case "SET_POST": {
return { ...state, data: action.payload };
}
default: {
@ -137,54 +142,54 @@ export const featuredInitialState = {
data: [
{
id: -1,
title: '',
slug: '',
excerpt: '',
published_at: '',
title: "",
slug: "",
excerpt: "",
published_at: "",
video: null,
featured_images: [
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
{
id: -1,
disk_name: '',
file_name: '',
path: '',
extension: '',
disk_name: "",
file_name: "",
path: "",
extension: "",
},
],
content_html: '',
content_html: "",
categories: [
{
id: -1,
name: '',
name: "",
},
],
powerseo_title: '',
powerseo_description: '',
powerseo_keywords: '',
powerseo_title: "",
powerseo_description: "",
powerseo_keywords: "",
},
],
};
export const featuredReducer = (
state: IFeaturedData = featuredInitialState,
action: IFeaturedDataAction,
action: IFeaturedDataAction
) => {
switch (action.type) {
case 'SET_FEATURED': {
case "SET_FEATURED": {
return { ...state, data: action.payload };
}
default: {
@ -195,10 +200,10 @@ export const featuredReducer = (
export const searchDataReducer = (
state: ISearchResult = newsScrollInitialState,
action: ISearchResultAction,
action: ISearchResultAction
) => {
switch (action.type) {
case 'SET_SEARCH_DATA': {
case "SET_SEARCH_DATA": {
return { data: action.payload };
}
default: {
@ -209,10 +214,10 @@ export const searchDataReducer = (
export const videoReducer = (
state: IVideoData = newsScrollInitialState,
action: IVideoDataAction,
action: IVideoDataAction
) => {
switch (action.type) {
case 'SET_VIDEO': {
case "SET_VIDEO": {
return { ...state, data: action.payload };
}
default: {
@ -220,3 +225,43 @@ export const videoReducer = (
}
}
};
export const categoriesInitialState: ICategoryData = {
data: {
data: [
{
id: -1,
name: "",
},
],
links: {
first: "",
last: "",
prev: null,
next: null,
},
meta: {
current_page: -1,
from: -1,
last_page: -1,
path: "",
per_page: -1,
to: -1,
total: -1,
},
},
};
export const categoriesReducer = (
state: ICategoryData = categoriesInitialState,
action: ICategoryDataAction
) => {
switch (action.type) {
case "SET_CATEGORIES": {
return { data: action.payload };
}
default: {
return state;
}
}
};

View File

@ -1,17 +1,18 @@
// Modules
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { combineReducers, configureStore } from "@reduxjs/toolkit";
// Reducers
import { activeLinkReducer } from '../reducers/activeLink.reducer';
import { languageReducer } from '../reducers/language.reducer';
import { activeLinkReducer } from "../reducers/activeLink.reducer";
import { languageReducer } from "../reducers/language.reducer";
import {
newsScrollReducer,
postReducer,
searchDataReducer,
featuredReducer,
videoReducer,
} from '../reducers/dataReducer';
import { searchReducer } from '../reducers/searchReducer';
categoriesReducer,
} from "../reducers/dataReducer";
import { searchReducer } from "../reducers/searchReducer";
export const allReducers = combineReducers({
activeLink: activeLinkReducer,
@ -22,6 +23,7 @@ export const allReducers = combineReducers({
searchData: searchDataReducer,
featured: featuredReducer,
video: videoReducer,
categories: categoriesReducer,
});
export const functionalityStore = configureStore({

View File

@ -92,6 +92,11 @@
display: flex;
flex-direction: column;
gap: 2.4rem;
.main-content-item {
border-radius: 0.5rem;
overflow: hidden;
height: 40rem;
}
}
@media (max-width: 1024px) {

View File

@ -1,26 +1,26 @@
// Reducers
import { allReducers } from '../store/functionality';
import { allReducers } from "../store/functionality";
// Types
import { IPostsData } from './data.types';
import { ICategoriesData, IPostsData } from "./data.types";
// NavLink
export interface ActiveLinkType {
active: number;
}
export interface ActiveLinkActionType {
type: 'SET_ACTIVE_LINK';
type: "SET_ACTIVE_LINK";
payload: number;
}
// Language
export interface ILanguage {
title: 'RU' | 'EN' | 'TM';
title: "RU" | "EN" | "TM";
}
export interface ILanguageAction {
type: 'SET_LANGUAGE';
payload: 'RU' | 'EN' | 'TM';
type: "SET_LANGUAGE";
payload: "RU" | "EN" | "TM";
}
// NewsScroll
@ -29,8 +29,8 @@ export interface INewsScroll {
data: IPostsData[];
}
export interface INewsScrollAction {
type: 'SET_NEWS_SCROLL';
payload: INewsScroll['data'];
type: "SET_NEWS_SCROLL";
payload: INewsScroll["data"];
}
// Post
@ -42,24 +42,24 @@ export interface IPostData {
}
export interface IPostDataAction {
type: 'SET_POST';
payload: IPostData['data'];
type: "SET_POST";
payload: IPostData["data"];
}
export interface IVideoData {
data: IPostsData[];
}
export interface IVideoDataAction {
type: 'SET_VIDEO';
payload: INewsScroll['data'];
type: "SET_VIDEO";
payload: INewsScroll["data"];
}
export interface IFeaturedData {
data: IPostsData[];
}
export interface IFeaturedDataAction {
type: 'SET_FEATURED';
payload: IFeaturedData['data'];
type: "SET_FEATURED";
payload: IFeaturedData["data"];
}
//
@ -68,16 +68,25 @@ export interface ISearchData {
value: string;
}
export interface ISearchDataAction {
type: 'SET_SEARCH';
payload: ISearchData['value'];
type: "SET_SEARCH";
payload: ISearchData["value"];
}
export interface ISearchResult {
data: IPostsData[];
}
export interface ISearchResultAction {
type: 'SET_SEARCH_DATA';
payload: ISearchResult['data'];
type: "SET_SEARCH_DATA";
payload: ISearchResult["data"];
}
export interface ICategoryData {
data: ICategoriesData;
}
export interface ICategoryDataAction {
type: "SET_CATEGORIES";
payload: ICategoryData["data"];
}
// ALL TYPES BEFORE THIS LINE ==================