Welcome to your new single-page application, built with:
@@ -156,6 +164,15 @@ const unloadedState: ContentState = { }, shopCatalog: { + header: { + title: `Shop catalog - ${ReservedWords.siteName}`, + meta: { + description: "Single-page application shop catalog", + }, + link: { + canonical: "" + } + }, titleSection: { title: "Shop in style", text: "With this shop hompeage template" @@ -166,6 +183,15 @@ const unloadedState: ContentState = { }, shopItem: { + header: { + title: `${ReservedWords.productTitle} - ${ReservedWords.siteName}`, + meta: { + description: "Single-page application shop item", + }, + link: { + canonical: "" + } + }, productSection: { availableQuantity: "Available Qty.", addToCart: "Add to cart" @@ -177,13 +203,22 @@ const unloadedState: ContentState = { }, shopCart: { + header: { + title: `Shop cart - ${ReservedWords.siteName}`, + meta: { + description: "Single-page application shop cart", + }, + link: { + canonical: "" + } + }, titleSection: { title: "Shopping Cart", text: "items in your cart" }, productsSection: { title: "Shopping Cart", - text: "{quantity} items in your cart", + text: `${ReservedWords.quantity} items in your cart`, product: "Product", price: "Price", quantity: "Quantity", @@ -199,6 +234,15 @@ const unloadedState: ContentState = { }, shopCheckout: { + header: { + title: `Shop - checkout ${ReservedWords.siteName}`, + meta: { + description: "Single-page application checkout", + }, + link: { + canonical: "" + } + }, titleSection: { 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." @@ -281,7 +325,7 @@ const unloadedState: ContentState = { }, summarySection: { title: "Your cart", - total: "Total ({currency})", + total: `Total (${ReservedWords.currency})`, promoCode: { placeHolder: "Promo code" }, @@ -315,18 +359,36 @@ const unloadedState: ContentState = { }, blogCatalog: { + header: { + title: `Blog catalog - ${ReservedWords.siteName}`, + meta: { + description: "Single-page application blog catalog", + }, + link: { + canonical: "" + } + }, titleSection: { title: "Welcome to Blog Home!", text: "A Bootstrap 5 starter layout for your next blog homepage" }, featuredBlogSection: { - readTime: "{date} Time to read: {readTime} min" + readTime: `${ReservedWords.date} Time to read: ${ReservedWords.readTime} min` }, }, blogItem: { + header: { + title: `${ReservedWords.blogTitle} - ${ReservedWords.siteName}`, + meta: { + description: "Single-page application blog item", + }, + link: { + canonical: "" + } + }, titleSection: { - postedOnBy: "Posted on {date} by {nickName}" + postedOnBy: `Posted on ${ReservedWords.date} by ${ReservedWords.nickName}` }, commentsSection: { leaveComment: "Join the discussion and leave a comment!" @@ -335,6 +397,16 @@ const unloadedState: ContentState = { }, signIn: { + header: { + title: `Sign in - ${ReservedWords.siteName}`, + meta: { + description: "Single-page application sign in", + robots: "noindex, nofollow" + }, + link: { + canonical: "" + } + }, title: "Sign in", email: { title: "Email address", @@ -355,6 +427,16 @@ const unloadedState: ContentState = { }, signUp: { + header: { + title: "Sign up - {siteName}", + meta: { + description: "Single-page application sign up", + robots: "noindex, nofollow" + }, + link: { + canonical: "" + } + }, title: "Sign up", username: { title: "Username", diff --git a/webapi/ClientApp/src/store/reducers/Header.ts b/webapi/ClientApp/src/store/reducers/Header.ts new file mode 100644 index 0000000..fdf05e5 --- /dev/null +++ b/webapi/ClientApp/src/store/reducers/Header.ts @@ -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