(feat): header model and reducer, reserved words consolidation
This commit is contained in:
parent
901b7f02e3
commit
8fe4d93000
@ -42,12 +42,7 @@ const App = () => {
|
|||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const { content, loader } = useSelector((state: ApplicationState) => state)
|
const { content, header, loader } = useSelector((state: ApplicationState) => state)
|
||||||
|
|
||||||
|
|
||||||
const {
|
|
||||||
siteName = ""
|
|
||||||
} = content ? content : {}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(settingsActionCreators.requestContent())
|
dispatch(settingsActionCreators.requestContent())
|
||||||
@ -60,14 +55,19 @@ const App = () => {
|
|||||||
})
|
})
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
title = "",
|
||||||
|
link = {},
|
||||||
|
meta = {}
|
||||||
|
} = header ? header : {}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{siteName}</title>
|
<title>{title}</title>
|
||||||
<meta charSet="utf-8" />
|
<meta charSet="utf-8" />
|
||||||
|
{Object.keys(link).map((rel, index) => <link key={index} rel={rel} href={link[index]} />)}
|
||||||
<link rel="canonical" href="http://mysite.com/example" />
|
{Object.keys(meta).map((name, index) => <meta key={index} name={name} content={meta[index]} />)}
|
||||||
|
|
||||||
<meta name="description" content="react-redux" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|||||||
17
webapi/ClientApp/src/enumerations/index.ts
Normal file
17
webapi/ClientApp/src/enumerations/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
enum ReservedWords {
|
||||||
|
siteName = "{siteName}",
|
||||||
|
siteUrl = "{siteUrl}",
|
||||||
|
quantity = "{quantity}",
|
||||||
|
productTitle = "{productTitle}",
|
||||||
|
currency = "{currency}",
|
||||||
|
date = "{date}",
|
||||||
|
readTime = "{readTime}",
|
||||||
|
blogTitle = "{blogTitle}",
|
||||||
|
nickName = "{nickName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ReservedWords
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { AuthorModel, FormItemModel, ImageModel } from "./"
|
import { AuthorModel, FormItemModel, HeaderModel, ImageModel } from "./"
|
||||||
|
|
||||||
|
|
||||||
export interface RequestModel {
|
export interface RequestModel {
|
||||||
[key: string]: string | undefined
|
[key: string]: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponseModel { }
|
export interface ResponseModel {}
|
||||||
|
|
||||||
export interface AddressPageSectionModel extends PageSectionModel {
|
export interface AddressPageSectionModel extends PageSectionModel {
|
||||||
firstName: FormItemModel,
|
firstName: FormItemModel,
|
||||||
@ -19,7 +19,7 @@ export interface AddressPageSectionModel extends PageSectionModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PageModel {
|
export interface PageModel {
|
||||||
|
header: HeaderModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageSectionModel {
|
export interface PageSectionModel {
|
||||||
|
|||||||
@ -93,3 +93,17 @@ export interface TestimonialModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface HeaderLink {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Meta {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
export interface HeaderModel {
|
||||||
|
title: string,
|
||||||
|
link: HeaderLink,
|
||||||
|
meta: Meta
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { BlogItemModel, CategoryModel, CommentModel, LocalizationModel, MenuItemModel, PaginationModel, RouteModel, ShopItemModel } from "./"
|
import { BlogItemModel, CategoryModel, CommentModel, HeaderModel, LocalizationModel, MenuItemModel, PaginationModel, RouteModel, ShopItemModel } from "./"
|
||||||
import { ResponseModel } from "./abstractions"
|
import { ResponseModel } from "./abstractions"
|
||||||
import * as Pages from "./pages"
|
import * as Pages from "./pages"
|
||||||
|
|
||||||
@ -32,8 +32,9 @@ export interface GetShopCartResponseModel extends ResponseModel {
|
|||||||
// Static content response model
|
// Static content response model
|
||||||
export interface GetContentResponseModel extends ResponseModel {
|
export interface GetContentResponseModel extends ResponseModel {
|
||||||
siteName: string,
|
siteName: string,
|
||||||
|
siteUrl: string,
|
||||||
|
|
||||||
helmet: any,
|
header: HeaderModel,
|
||||||
|
|
||||||
localization: LocalizationModel,
|
localization: LocalizationModel,
|
||||||
|
|
||||||
|
|||||||
@ -7,13 +7,14 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||||||
import { ApplicationState } from '../../store'
|
import { ApplicationState } from '../../store'
|
||||||
import { actionCreators as loaderActionCreators } from '../../store/reducers/Loader'
|
import { actionCreators as loaderActionCreators } from '../../store/reducers/Loader'
|
||||||
import { actionCreators as blogFeaturedActionCreators } from '../../store/reducers/BlogFeatured'
|
import { actionCreators as blogFeaturedActionCreators } from '../../store/reducers/BlogFeatured'
|
||||||
|
import { actionCreators as headerActionCreators } from '../../store/reducers/Header'
|
||||||
|
|
||||||
// Reactstrap
|
// Reactstrap
|
||||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||||
|
|
||||||
// Models (interfaces)
|
// Models (interfaces)
|
||||||
import { CallToActionSectionModel, FeaturedBlogsSectionModel, FeaturesSectionModel, TestimonialsSectionModel, TitleSectionModel } from '../../models/pageSections'
|
import { CallToActionSectionModel, FeaturedBlogsSectionModel, FeaturesSectionModel, TestimonialsSectionModel, TitleSectionModel } from '../../models/pageSections'
|
||||||
import { BlogItemModel, FeatureModel, TestimonialModel } from '../../models'
|
import { BlogItemModel, FeatureModel, HeaderModel, TestimonialModel } from '../../models'
|
||||||
|
|
||||||
// Custom components
|
// Custom components
|
||||||
import { FeatherIcon } from '../../components/FeatherIcons'
|
import { FeatherIcon } from '../../components/FeatherIcons'
|
||||||
@ -155,9 +156,9 @@ const FeaturedBlogsSection: FC<FeaturedBlogs> = ({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
const CallToActionSection: FC<CallToActionSectionModel> = ({
|
const CallToActionSection: FC<CallToActionSectionModel> = ({
|
||||||
title = "",
|
title,
|
||||||
text = "",
|
text,
|
||||||
privacyDisclaimer = "",
|
privacyDisclaimer,
|
||||||
email = {
|
email = {
|
||||||
placeHolder: "",
|
placeHolder: "",
|
||||||
title: ""
|
title: ""
|
||||||
@ -202,12 +203,37 @@ const Home = () => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}, [content?.isLoading, blogFeatured?.isLoading])
|
}, [content?.isLoading, blogFeatured?.isLoading])
|
||||||
|
|
||||||
|
const {
|
||||||
|
header = {},
|
||||||
|
titleSection = {
|
||||||
|
title: "",
|
||||||
|
text: ""
|
||||||
|
},
|
||||||
|
featuresSection = {},
|
||||||
|
testimonialsSection = {},
|
||||||
|
featuredBlogsSection = {},
|
||||||
|
callToActionSection = {
|
||||||
|
title: "",
|
||||||
|
text: "",
|
||||||
|
privacyDisclaimer: "",
|
||||||
|
email: {
|
||||||
|
placeHolder: "",
|
||||||
|
title: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} = content?.homePage ? content.homePage : {}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(headerActionCreators.updateHeader(header as HeaderModel))
|
||||||
|
}, [header])
|
||||||
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<TitleSection {...page?.titleSection} />
|
<TitleSection {...titleSection} />
|
||||||
<FeaturesSection {...page?.featuresSection} />
|
<FeaturesSection {...featuresSection} />
|
||||||
<TestimonialsSection {...page?.testimonialsSection} />
|
<TestimonialsSection {...testimonialsSection} />
|
||||||
<FeaturedBlogsSection items={blogFeatured?.items} {...page?.featuredBlogsSection} />
|
<FeaturedBlogsSection items={blogFeatured?.items} {...featuredBlogsSection} />
|
||||||
<CallToActionSection {...page?.callToActionSection} />
|
<CallToActionSection {...callToActionSection} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import * as BlogFeatured from './reducers/BlogFeatured'
|
|||||||
import * as BlogItem from './reducers/BlogItem'
|
import * as BlogItem from './reducers/BlogItem'
|
||||||
|
|
||||||
import * as Counter from './reducers/Counter'
|
import * as Counter from './reducers/Counter'
|
||||||
|
import * as Header from './reducers/Header'
|
||||||
import * as Loader from './reducers/Loader'
|
import * as Loader from './reducers/Loader'
|
||||||
|
|
||||||
import * as Content from './reducers/Content'
|
import * as Content from './reducers/Content'
|
||||||
@ -27,6 +28,7 @@ export interface ApplicationState {
|
|||||||
content: Content.ContentState | undefined
|
content: Content.ContentState | undefined
|
||||||
|
|
||||||
counter: Counter.CounterState | undefined
|
counter: Counter.CounterState | undefined
|
||||||
|
header: Header.HeaderState | undefined
|
||||||
loader: Loader.LoaderState | undefined
|
loader: Loader.LoaderState | undefined
|
||||||
|
|
||||||
shopCatalog: ShopCatalog.ShopCatalogState | undefined
|
shopCatalog: ShopCatalog.ShopCatalogState | undefined
|
||||||
@ -51,6 +53,7 @@ export const reducers = {
|
|||||||
content: Content.reducer,
|
content: Content.reducer,
|
||||||
|
|
||||||
counter: Counter.reducer,
|
counter: Counter.reducer,
|
||||||
|
header: Header.reducer,
|
||||||
loader: Loader.reducer,
|
loader: Loader.reducer,
|
||||||
|
|
||||||
shopCatalog: ShopCatalog.reducer,
|
shopCatalog: ShopCatalog.reducer,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Action, Reducer } from 'redux'
|
import { Action, Reducer } from 'redux'
|
||||||
import { AppThunkAction } from '../'
|
import { AppThunkAction } from '../'
|
||||||
|
import { ReservedWords } from '../../enumerations'
|
||||||
|
|
||||||
import { GetContentRequestModel } from '../../models/requests'
|
import { GetContentRequestModel } from '../../models/requests'
|
||||||
import { GetContentResponseModel } from '../../models/responses'
|
import { GetContentResponseModel } from '../../models/responses'
|
||||||
@ -36,17 +37,15 @@ export const actionCreators = {
|
|||||||
|
|
||||||
const unloadedState: ContentState = {
|
const unloadedState: ContentState = {
|
||||||
siteName: "MAKS-IT",
|
siteName: "MAKS-IT",
|
||||||
|
siteUrl: "https://maks-it.com",
|
||||||
|
|
||||||
helmet: {
|
header: {
|
||||||
title: "{siteName}",
|
title: `${ReservedWords.siteName}`,
|
||||||
meta: {
|
meta: {
|
||||||
chartset: "utf-8",
|
chartset: "utf-8",
|
||||||
description: "react-redux",
|
"google-site-verification": ""
|
||||||
"google-site-verification": "",
|
|
||||||
robots: "noindex, nofollow"
|
|
||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
canonical: ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -94,11 +93,20 @@ const unloadedState: ContentState = {
|
|||||||
{ target: "/signin", title: "Sing in" },
|
{ target: "/signin", title: "Sing in" },
|
||||||
{ target: "/signup", title: "Sign up" },
|
{ target: "/signup", title: "Sign up" },
|
||||||
|
|
||||||
{ target: "/shop/cart", icon: "shopping-cart", title: "Cart ({quantity})" }
|
{ target: "/shop/cart", icon: "shopping-cart", title: `Cart (${ReservedWords.quantity})` }
|
||||||
],
|
],
|
||||||
sideMenu: [],
|
sideMenu: [],
|
||||||
|
|
||||||
homePage: {
|
homePage: {
|
||||||
|
header: {
|
||||||
|
title: `Home - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application home page",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: `${ReservedWords.siteUrl}`
|
||||||
|
}
|
||||||
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
title: "Hello, World! by Redux",
|
title: "Hello, World! by Redux",
|
||||||
text: `<p>Welcome to your new single-page application, built with:</p>
|
text: `<p>Welcome to your new single-page application, built with:</p>
|
||||||
@ -156,6 +164,15 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
shopCatalog: {
|
shopCatalog: {
|
||||||
|
header: {
|
||||||
|
title: `Shop catalog - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application shop catalog",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
title: "Shop in style",
|
title: "Shop in style",
|
||||||
text: "With this shop hompeage template"
|
text: "With this shop hompeage template"
|
||||||
@ -166,6 +183,15 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
shopItem: {
|
shopItem: {
|
||||||
|
header: {
|
||||||
|
title: `${ReservedWords.productTitle} - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application shop item",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
productSection: {
|
productSection: {
|
||||||
availableQuantity: "Available Qty.",
|
availableQuantity: "Available Qty.",
|
||||||
addToCart: "Add to cart"
|
addToCart: "Add to cart"
|
||||||
@ -177,13 +203,22 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
shopCart: {
|
shopCart: {
|
||||||
|
header: {
|
||||||
|
title: `Shop cart - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application shop cart",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
title: "Shopping Cart",
|
title: "Shopping Cart",
|
||||||
text: "items in your cart"
|
text: "items in your cart"
|
||||||
},
|
},
|
||||||
productsSection: {
|
productsSection: {
|
||||||
title: "Shopping Cart",
|
title: "Shopping Cart",
|
||||||
text: "{quantity} items in your cart",
|
text: `${ReservedWords.quantity} items in your cart`,
|
||||||
product: "Product",
|
product: "Product",
|
||||||
price: "Price",
|
price: "Price",
|
||||||
quantity: "Quantity",
|
quantity: "Quantity",
|
||||||
@ -199,6 +234,15 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
shopCheckout: {
|
shopCheckout: {
|
||||||
|
header: {
|
||||||
|
title: `Shop - checkout ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application checkout",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
title: "Checkout",
|
title: "Checkout",
|
||||||
text: "Below is an example form built entirely with Bootstrap’s form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it."
|
text: "Below is an example form built entirely with Bootstrap’s form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it."
|
||||||
@ -281,7 +325,7 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
summarySection: {
|
summarySection: {
|
||||||
title: "Your cart",
|
title: "Your cart",
|
||||||
total: "Total ({currency})",
|
total: `Total (${ReservedWords.currency})`,
|
||||||
promoCode: {
|
promoCode: {
|
||||||
placeHolder: "Promo code"
|
placeHolder: "Promo code"
|
||||||
},
|
},
|
||||||
@ -315,18 +359,36 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
blogCatalog: {
|
blogCatalog: {
|
||||||
|
header: {
|
||||||
|
title: `Blog catalog - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application blog catalog",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
title: "Welcome to Blog Home!",
|
title: "Welcome to Blog Home!",
|
||||||
text: "A Bootstrap 5 starter layout for your next blog homepage"
|
text: "A Bootstrap 5 starter layout for your next blog homepage"
|
||||||
},
|
},
|
||||||
featuredBlogSection: {
|
featuredBlogSection: {
|
||||||
readTime: "{date} Time to read: {readTime} min"
|
readTime: `${ReservedWords.date} Time to read: ${ReservedWords.readTime} min`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
blogItem: {
|
blogItem: {
|
||||||
|
header: {
|
||||||
|
title: `${ReservedWords.blogTitle} - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application blog item",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
postedOnBy: "Posted on {date} by {nickName}"
|
postedOnBy: `Posted on ${ReservedWords.date} by ${ReservedWords.nickName}`
|
||||||
},
|
},
|
||||||
commentsSection: {
|
commentsSection: {
|
||||||
leaveComment: "Join the discussion and leave a comment!"
|
leaveComment: "Join the discussion and leave a comment!"
|
||||||
@ -335,6 +397,16 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
signIn: {
|
signIn: {
|
||||||
|
header: {
|
||||||
|
title: `Sign in - ${ReservedWords.siteName}`,
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application sign in",
|
||||||
|
robots: "noindex, nofollow"
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
title: "Sign in",
|
title: "Sign in",
|
||||||
email: {
|
email: {
|
||||||
title: "Email address",
|
title: "Email address",
|
||||||
@ -355,6 +427,16 @@ const unloadedState: ContentState = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
signUp: {
|
signUp: {
|
||||||
|
header: {
|
||||||
|
title: "Sign up - {siteName}",
|
||||||
|
meta: {
|
||||||
|
description: "Single-page application sign up",
|
||||||
|
robots: "noindex, nofollow"
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
canonical: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
title: "Sign up",
|
title: "Sign up",
|
||||||
username: {
|
username: {
|
||||||
title: "Username",
|
title: "Username",
|
||||||
|
|||||||
88
webapi/ClientApp/src/store/reducers/Header.ts
Normal file
88
webapi/ClientApp/src/store/reducers/Header.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Action, Reducer } from 'redux'
|
||||||
|
import { AppThunkAction } from '..'
|
||||||
|
import { ReservedWords } from '../../enumerations'
|
||||||
|
|
||||||
|
import { HeaderLink, HeaderModel } from '../../models'
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
// STATE - This defines the type of data maintained in the Redux store.
|
||||||
|
|
||||||
|
export interface HeaderState extends HeaderModel {}
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
|
||||||
|
// They do not themselves have any side-effects they just describe something that is going to happen.
|
||||||
|
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
|
||||||
|
|
||||||
|
export interface ReceiveAction extends HeaderModel { type: 'RECEIVE_UPDATE_HEADER' }
|
||||||
|
|
||||||
|
|
||||||
|
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
|
||||||
|
// declared type strings (and not any other arbitrary string).
|
||||||
|
export type KnownAction = ReceiveAction
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
|
||||||
|
// They don't directly mutate state, but they can have external side-effects (such as loading data).
|
||||||
|
|
||||||
|
export const actionCreators = {
|
||||||
|
|
||||||
|
updateHeader: (props: HeaderModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||||
|
|
||||||
|
const siteName = getState().content?.siteName
|
||||||
|
const baseHeader = getState().content?.header
|
||||||
|
|
||||||
|
const title = (props?.title
|
||||||
|
? props.title
|
||||||
|
: baseHeader?.title
|
||||||
|
? baseHeader.title
|
||||||
|
: ReservedWords.siteName).replace(ReservedWords.siteName, siteName ? siteName : "")
|
||||||
|
|
||||||
|
// assign default link
|
||||||
|
const link = baseHeader?.link
|
||||||
|
? baseHeader.link
|
||||||
|
: {}
|
||||||
|
|
||||||
|
// assign default meta
|
||||||
|
const meta = baseHeader?.meta
|
||||||
|
? baseHeader.meta
|
||||||
|
: {}
|
||||||
|
|
||||||
|
// overrid link
|
||||||
|
if (props.link)
|
||||||
|
Object.keys(props.link).forEach(key => link[key] = props.link[key])
|
||||||
|
|
||||||
|
// override meta
|
||||||
|
if(props.meta)
|
||||||
|
Object.keys(props.meta).forEach(key => meta[key] = props.meta[key])
|
||||||
|
|
||||||
|
dispatch({ type: 'RECEIVE_UPDATE_HEADER', ...{ title, link, meta } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unloadedState: HeaderState = {
|
||||||
|
title: "",
|
||||||
|
link: {},
|
||||||
|
meta: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
|
||||||
|
|
||||||
|
export const reducer: Reducer<HeaderState> = (state: HeaderState | undefined, incomingAction: Action): HeaderState => {
|
||||||
|
if (state === undefined) {
|
||||||
|
return unloadedState
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = incomingAction as KnownAction
|
||||||
|
switch (action.type) {
|
||||||
|
case 'RECEIVE_UPDATE_HEADER':
|
||||||
|
return {
|
||||||
|
...action
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user