Compare commits
No commits in common. "2bfc919feb2ce51b8affc64e6298c1a346ebcbe1" and "5ad87b6d48602b6744376b71295df79d6c8b6006" have entirely different histories.
2bfc919feb
...
5ad87b6d48
@ -1,22 +0,0 @@
|
||||
image: docker:stable
|
||||
|
||||
services:
|
||||
- docker:dind
|
||||
|
||||
before_script:
|
||||
- docker info
|
||||
|
||||
build:
|
||||
only:
|
||||
- master
|
||||
before_script:
|
||||
- docker login ${CI_REGISTRY} -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD}
|
||||
script:
|
||||
# weatherforecast
|
||||
- docker build ./src/ -f ./src/WeatherForecast/Dockerfile -t ${CI_REGISTRY}/weatherforecast:latest
|
||||
- docker push ${CI_REGISTRY}/weatherforecast:latest
|
||||
after_script:
|
||||
- docker logout ${CI_REGISTRY}
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
23
Jenkinsfile
vendored
23
Jenkinsfile
vendored
@ -1,23 +0,0 @@
|
||||
|
||||
// Note: Visual studio is using the solution root folder as build context
|
||||
// docker build -f "<path-to-your-dockerfile>" -t some-name "<!!!path-to-your-solution-folder!!!>"
|
||||
|
||||
// Define variable
|
||||
def CI_REGISTRY = "https://hcrsrv0001.corp.maks-it.com"
|
||||
def PROJECT_NAME = "reactredux"
|
||||
def DOCKERHUB_CACHE= "dockerhub_cache/library"
|
||||
|
||||
node {
|
||||
checkout scm
|
||||
|
||||
docker.withRegistry("${CI_REGISTRY}", "harbor-credentials-id") {
|
||||
def clientApp = docker.build("${PROJECT_NAME}/clientapp:${env.BUILD_ID}", "./src/ -f ./src/ClientApp/Dockerfile")
|
||||
clientApp.push()
|
||||
|
||||
def reverseProxy = docker.build("${PROJECT_NAME}/reverseproxy:${env.BUILD_ID}", "./src/ -f ./src/ReverseProxy/Dockerfile")
|
||||
reverseProxy.push()
|
||||
|
||||
def weatherForecast = docker.build("${PROJECT_NAME}/weatherforecast:${env.BUILD_ID}", "./src/ -f ./src/WeatherForecast/Dockerfile")
|
||||
weatherForecast.push()
|
||||
}
|
||||
}
|
||||
@ -1,507 +1,496 @@
|
||||
{
|
||||
"_id": "404c8232-9048-4519-bfba-6e78dc7005ca",
|
||||
|
||||
"l10n": [
|
||||
"_id": "b3f39a82-6a1b-46a4-85cc-04c3b4315511",
|
||||
"siteId": "404c8232-9048-4519-bfba-6e78dc7005ca",
|
||||
"siteName": "MAKS-IT",
|
||||
"siteUrl": "https://maks-it.com",
|
||||
"header": {
|
||||
"title": "{siteName}",
|
||||
"meta": {
|
||||
"chartset": "utf-8",
|
||||
"google-site-verification": "",
|
||||
"description": "Single-page application home page"
|
||||
},
|
||||
"link": {
|
||||
"canonical": "{siteUrl}"
|
||||
}
|
||||
},
|
||||
"localization": {
|
||||
"timeZone": "+1",
|
||||
"locale": "en-US",
|
||||
"dateFormat": "MMMM YYYY, dddd",
|
||||
"timeFormat": "HH:mm",
|
||||
"currency": "EUR",
|
||||
"currencySymbol": "€"
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"locale": 0,
|
||||
"siteName": "Contoso",
|
||||
"siteUrl": "https://maks-it.com",
|
||||
"settings": {
|
||||
"timeZone": "+1",
|
||||
"dateFormat": "MMMM YYYY, dddd",
|
||||
"timeFormat": "HH:mm",
|
||||
"currency": "EUR",
|
||||
"currencySymbol": "€"
|
||||
},
|
||||
|
||||
|
||||
"header": {
|
||||
"title": "{siteName}",
|
||||
"meta": {
|
||||
"chartset": "utf-8",
|
||||
"google-site-verification": "",
|
||||
"description": "Single-page application home page"
|
||||
},
|
||||
"link": {
|
||||
"canonical": "{siteUrl}"
|
||||
}
|
||||
},
|
||||
|
||||
"routes": [
|
||||
"target": "/",
|
||||
"component": "Home"
|
||||
},
|
||||
{
|
||||
"target": "/home",
|
||||
"component": "Home"
|
||||
},
|
||||
{
|
||||
"target": "/shop",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": "/",
|
||||
"component": "Home"
|
||||
"target": "",
|
||||
"component": "ShopCatalog"
|
||||
},
|
||||
{
|
||||
"target": "/home",
|
||||
"component": "Home"
|
||||
"target": ":page",
|
||||
"component": "ShopCatalog"
|
||||
},
|
||||
{
|
||||
"target": "/shop",
|
||||
"target": ":page",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": ":slug",
|
||||
"component": "ShopItem"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "cart",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": "",
|
||||
"component": "ShopCatalog"
|
||||
"component": "Cart"
|
||||
},
|
||||
{
|
||||
"target": ":page",
|
||||
"component": "ShopCatalog"
|
||||
},
|
||||
{
|
||||
"target": ":page",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": ":slug",
|
||||
"component": "ShopItem"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "cart",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": "",
|
||||
"component": "Cart"
|
||||
},
|
||||
{
|
||||
"target": "checkout",
|
||||
"component": "Checkout"
|
||||
}
|
||||
]
|
||||
"target": "checkout",
|
||||
"component": "Checkout"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "/blog",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": "",
|
||||
"component": "BlogCatalog"
|
||||
},
|
||||
{
|
||||
"target": "/blog",
|
||||
"target": ":page",
|
||||
"component": "BlogCatalog"
|
||||
},
|
||||
{
|
||||
"target": ":page",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": "",
|
||||
"component": "BlogCatalog"
|
||||
},
|
||||
{
|
||||
"target": ":page",
|
||||
"component": "BlogCatalog"
|
||||
},
|
||||
{
|
||||
"target": ":page",
|
||||
"childRoutes": [
|
||||
{
|
||||
"target": ":slug",
|
||||
"component": "BlogItem"
|
||||
}
|
||||
]
|
||||
"target": ":slug",
|
||||
"component": "BlogItem"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"adminRoutes": [],
|
||||
"serviceRoutes": [
|
||||
]
|
||||
}
|
||||
],
|
||||
"adminRoutes": [],
|
||||
"serviceRoutes": [
|
||||
{
|
||||
"target": "/signin",
|
||||
"component": "Signin"
|
||||
},
|
||||
{
|
||||
"target": "/signup",
|
||||
"component": "Signup"
|
||||
}
|
||||
],
|
||||
"topMenu": [
|
||||
{
|
||||
"target": "/",
|
||||
"title": "Home"
|
||||
},
|
||||
{
|
||||
"target": "/shop",
|
||||
"title": "Shop"
|
||||
},
|
||||
{
|
||||
"target": "/blog",
|
||||
"title": "Blog"
|
||||
},
|
||||
{
|
||||
"target": "/signin",
|
||||
"title": "Sing in"
|
||||
},
|
||||
{
|
||||
"target": "/signup",
|
||||
"title": "Sign up"
|
||||
},
|
||||
{
|
||||
"target": "/shop/cart",
|
||||
"icon": "shopping-cart",
|
||||
"title": "Cart ({quantity})"
|
||||
}
|
||||
],
|
||||
"sideMenu": [],
|
||||
"homePage": {
|
||||
"header": {
|
||||
"title": "Home - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application home page"
|
||||
},
|
||||
"link": {
|
||||
"canonical": "{siteUrl}"
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"title": "Hello, World! by C# and Mongo",
|
||||
"text": "<p>Welcome to your new single-page application, built with:</p>\n <ul>\n <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n <li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>\n <li><a href='https://getbootstrap.com/'>Bootstrap</a>, <a href='https://reactstrap.github.io/?path=/story/home-installation--page'>Reactstrap</a> and <a href=\"\"https://feathericons.com/\"\">Feather icons</a> for layout and styling</li>\n </ul>",
|
||||
"primaryLink": {
|
||||
"target": "#!",
|
||||
"anchorText": "Get Started"
|
||||
},
|
||||
"secondaryLink": {
|
||||
"target": "#!",
|
||||
"anchorText": "Learn more"
|
||||
},
|
||||
"image": {
|
||||
"src": "https://dummyimage.com/600x400/343a40/6c757d",
|
||||
"alt": "..."
|
||||
}
|
||||
},
|
||||
"featuresSection": {
|
||||
"title": "To help you get started, we have also set up:",
|
||||
"items": [
|
||||
{
|
||||
"target": "/signin",
|
||||
"component": "Signin"
|
||||
"icon": "navigation",
|
||||
"title": "Client-side navigation",
|
||||
"text": "For example, click <em>Counter</em> then <em>Back</em> to return here."
|
||||
},
|
||||
{
|
||||
"target": "/signup",
|
||||
"component": "Signup"
|
||||
"icon": "server",
|
||||
"title": "Development server integration",
|
||||
"text": "In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file."
|
||||
},
|
||||
{
|
||||
"icon": "terminal",
|
||||
"title": "Efficient production builds",
|
||||
"text": "In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files."
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
"topMenu": [
|
||||
]
|
||||
},
|
||||
"testimonialsSection": {
|
||||
"items": [
|
||||
{
|
||||
"target": "/",
|
||||
"title": "Home"
|
||||
},
|
||||
{
|
||||
"target": "/shop",
|
||||
"title": "Shop"
|
||||
},
|
||||
{
|
||||
"target": "/blog",
|
||||
"title": "Blog"
|
||||
},
|
||||
{
|
||||
"target": "/signin",
|
||||
"title": "Sign in"
|
||||
},
|
||||
{
|
||||
"target": "/signup",
|
||||
"title": "Sign up"
|
||||
},
|
||||
{
|
||||
"target": "/shop/cart",
|
||||
"icon": "shopping-cart",
|
||||
"title": "Cart ({quantity})"
|
||||
|
||||
}
|
||||
],
|
||||
"sideMenu": [],
|
||||
|
||||
|
||||
"homePage": {
|
||||
"header": {
|
||||
"title": "Home - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application home page"
|
||||
},
|
||||
"link": {
|
||||
"canonical": "{siteUrl}"
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"title": "Hello, World! by C# and Mongo",
|
||||
"text": "<p>Welcome to your new single-page application, built with:</p>\n <ul>\n <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n <li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>\n <li><a href='https://getbootstrap.com/'>Bootstrap</a>, <a href='https://reactstrap.github.io/?path=/story/home-installation--page'>Reactstrap</a> and <a href=\"\"https://feathericons.com/\"\">Feather icons</a> for layout and styling</li>\n </ul>",
|
||||
"primaryLink": {
|
||||
"target": "#!",
|
||||
"anchorText": "Get Started"
|
||||
},
|
||||
"secondaryLink": {
|
||||
"target": "#!",
|
||||
"anchorText": "Learn more"
|
||||
},
|
||||
"image": {
|
||||
"src": "/Image/600x400/343a40/6c757d",
|
||||
"alt": "..."
|
||||
}
|
||||
},
|
||||
"featuresSection": {
|
||||
"title": "To help you get started, we have also set up:",
|
||||
"items": [
|
||||
{
|
||||
"icon": "navigation",
|
||||
"title": "Client-side navigation",
|
||||
"text": "For example, click <em>Counter</em> then <em>Back</em> to return here."
|
||||
"text": "The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>yarn</code> commands such as <code>yarn test</code> or <code>yarn install</code>.",
|
||||
"reviewer": {
|
||||
"_id": "c5295208-8950-441f-8217-bd7c4a907a0f",
|
||||
"image": {
|
||||
"src": "https://dummyimage.com/40x40/ced4da/6c757d",
|
||||
"alt": "..."
|
||||
},
|
||||
{
|
||||
"icon": "server",
|
||||
"title": "Development server integration",
|
||||
"text": "In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file."
|
||||
},
|
||||
{
|
||||
"icon": "terminal",
|
||||
"title": "Efficient production builds",
|
||||
"text": "In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files."
|
||||
}
|
||||
]
|
||||
},
|
||||
"testimonialsSection": {
|
||||
"items": [
|
||||
{
|
||||
"text": "The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>yarn</code> commands such as <code>yarn test</code> or <code>yarn install</code>.",
|
||||
"reviewer": {
|
||||
"image": {
|
||||
"src": "/Image/40x40/ced4da/6c757d",
|
||||
"alt": "..."
|
||||
},
|
||||
"fullName": "Admin",
|
||||
"position": "CEO, MAKS-IT"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"featuredBlogsSection": {
|
||||
"title": "Featured blogs"
|
||||
},
|
||||
"callToActionSection": {
|
||||
"title": "New products, delivered to you.",
|
||||
"text": "Sign up for our newsletter for the latest updates.",
|
||||
"privacyDisclaimer": "We care about privacy, and will never share your data.",
|
||||
"email": {
|
||||
"title": "Sign up",
|
||||
"placeHolder": "Email address..."
|
||||
"fullName": "Admin",
|
||||
"position": "CEO, MAKS-IT"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shopCatalog": {
|
||||
"header": {
|
||||
"title": "Shop catalog - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application shop catalog"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"title": "Shop in style",
|
||||
"text": "With this shop hompeage template"
|
||||
},
|
||||
"shopItemsSection": {
|
||||
"addToCart": "Add to cart"
|
||||
}
|
||||
},
|
||||
"shopItem": {
|
||||
"header": {
|
||||
"title": "{productTitle} - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application shop item"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"productSection": {
|
||||
"availableQuantity": "Available Qty.",
|
||||
"addToCart": "Add to cart"
|
||||
},
|
||||
"relatedProductsSection": {
|
||||
"title": "Related products",
|
||||
"addToCart": "Add to cart"
|
||||
}
|
||||
},
|
||||
"shopCart": {
|
||||
"header": {
|
||||
"title": "Shop cart - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application shop cart"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"title": "Shopping Cart",
|
||||
"text": "<i class=\"text-info font-weight-bold\">{quantity}</i> items in your cart"
|
||||
},
|
||||
"productsSection": {
|
||||
"product": "Product",
|
||||
"price": "Price",
|
||||
"quantity": "Quantity",
|
||||
"subtotal": "Subtotal:",
|
||||
"continueShopping": {
|
||||
"target": "/shop",
|
||||
"anchorText": "Continue shopping"
|
||||
},
|
||||
"checkout": {
|
||||
"target": "checkout",
|
||||
"anchorText": "Checkout"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shopCheckout": {
|
||||
"header": {
|
||||
"title": "Shop - checkout {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."
|
||||
},
|
||||
"billingAddressSection": {
|
||||
"title": "Billing address",
|
||||
"firstName": {
|
||||
"title": "First name",
|
||||
"placeHolder": "First name..."
|
||||
},
|
||||
"lastName": {
|
||||
"title": "Last name",
|
||||
"placeHolder": "Last name..."
|
||||
},
|
||||
"address": {
|
||||
"title": "Address",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"address2": {
|
||||
"title": "Address",
|
||||
"optional": "(Optional)",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"country": {
|
||||
"title": "Country",
|
||||
"placeHolder": "Country..."
|
||||
},
|
||||
"state": {
|
||||
"title": "State",
|
||||
"placeHolder": "State..."
|
||||
},
|
||||
"city": {
|
||||
"title": "City",
|
||||
"placeHolder": "City..."
|
||||
},
|
||||
"zip": {
|
||||
"title": "Zip",
|
||||
"placeHolder": "Zip..."
|
||||
}
|
||||
},
|
||||
"shippingAddressSection": {
|
||||
"title": "Shipping address",
|
||||
"firstName": {
|
||||
"title": "First name",
|
||||
"placeHolder": "First name..."
|
||||
},
|
||||
"lastName": {
|
||||
"title": "Last name",
|
||||
"placeHolder": "Last name..."
|
||||
},
|
||||
"address": {
|
||||
"title": "Address",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"address2": {
|
||||
"title": "Address",
|
||||
"optional": "(Optional)",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"country": {
|
||||
"title": "Country",
|
||||
"placeHolder": "Country..."
|
||||
},
|
||||
"state": {
|
||||
"title": "State",
|
||||
"placeHolder": "State..."
|
||||
},
|
||||
"city": {
|
||||
"title": "City",
|
||||
"placeHolder": "City..."
|
||||
},
|
||||
"zip": {
|
||||
"title": "Zip",
|
||||
"placeHolder": "Zip..."
|
||||
}
|
||||
},
|
||||
"settingsSection": {
|
||||
"shippingAddressSameAsBillingAddress": "Shipping address is the same as my billing address",
|
||||
"saveThisInformation": "Save this information for next time"
|
||||
},
|
||||
"summarySection": {
|
||||
"title": "Your cart",
|
||||
"total": "Total ({currency})",
|
||||
"promoCode": {
|
||||
"placeHolder": "Promo code"
|
||||
},
|
||||
"submit": {
|
||||
"title": "Redeem"
|
||||
}
|
||||
},
|
||||
"paymentSection": {
|
||||
"title": "Payment",
|
||||
"nameOnCard": {
|
||||
"title": "Name on card",
|
||||
"placeHolder": "John Doe"
|
||||
},
|
||||
"cardNumber": {
|
||||
"title": "Credit card number",
|
||||
"placeHolder": ""
|
||||
},
|
||||
"expiration": {
|
||||
"title": "Expiration",
|
||||
"placeHolder": "MM/YY"
|
||||
},
|
||||
"cvv": {
|
||||
"title": "CVV",
|
||||
"placeHolder": "123"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"title": "Continue to checkout"
|
||||
}
|
||||
},
|
||||
"blogCatalog": {
|
||||
"header": {
|
||||
"title": "Blog catalog - {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"
|
||||
}
|
||||
},
|
||||
"blogItem": {
|
||||
"header": {
|
||||
"title": "{blogTitle} - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application blog item"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"postedOnBy": "Posted on {date} by {nickName}"
|
||||
},
|
||||
"commentsSection": {
|
||||
"leaveComment": "Join the discussion and leave a comment!"
|
||||
}
|
||||
},
|
||||
"signIn": {
|
||||
"header": {
|
||||
"title": "Sign in - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application sign in",
|
||||
"robots": "noindex, nofollow"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"title": "Sign in",
|
||||
"email": {
|
||||
"title": "Email address",
|
||||
"placeHolder": "Email address..."
|
||||
},
|
||||
"password": {
|
||||
"title": "Password",
|
||||
"placeHolder": "Password..."
|
||||
},
|
||||
"dontHaveAnAccount": "Don't have an account yet? Please",
|
||||
"signUpLink": {
|
||||
"target": "/signup",
|
||||
"anchorText": "Sign up"
|
||||
},
|
||||
"submit": {
|
||||
"title": "Sign in"
|
||||
}
|
||||
},
|
||||
"signUp": {
|
||||
"header": {
|
||||
"title": "Sign up - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application sign up",
|
||||
"robots": "noindex, nofollow"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
"featuredBlogsSection": {
|
||||
"title": "Featured blogs"
|
||||
},
|
||||
"callToActionSection": {
|
||||
"title": "New products, delivered to you.",
|
||||
"text": "Sign up for our newsletter for the latest updates.",
|
||||
"privacyDisclaimer": "We care about privacy, and will never share your data.",
|
||||
"email": {
|
||||
"title": "Sign up",
|
||||
"username": {
|
||||
"title": "Username",
|
||||
"placeHolder": "Username..."
|
||||
},
|
||||
"email": {
|
||||
"title": "Email address",
|
||||
"placeHolder": "Email address..."
|
||||
},
|
||||
"reEmail": {
|
||||
"title": "Repeat email address",
|
||||
"placeHolder": "Repeat email address..."
|
||||
},
|
||||
"password": {
|
||||
"title": "Password",
|
||||
"placeHolder": "Password..."
|
||||
},
|
||||
"rePassword": {
|
||||
"title": "Repeat password",
|
||||
"placeHolder": "Repeat password..."
|
||||
},
|
||||
"acceptTermsAndConditions": "Accept terms and conditions",
|
||||
"submit": {
|
||||
"title": "Sing up"
|
||||
}
|
||||
"placeHolder": "Email address..."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"shopCatalog": {
|
||||
"header": {
|
||||
"title": "Shop catalog - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application shop catalog"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"title": "Shop in style",
|
||||
"text": "With this shop hompeage template"
|
||||
},
|
||||
"shopItemsSection": {
|
||||
"addToCart": "Add to cart"
|
||||
}
|
||||
},
|
||||
"shopItem": {
|
||||
"header": {
|
||||
"title": "{productTitle} - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application shop item"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"productSection": {
|
||||
"availableQuantity": "Available Qty.",
|
||||
"addToCart": "Add to cart"
|
||||
},
|
||||
"relatedProductsSection": {
|
||||
"title": "Related products",
|
||||
"addToCart": "Add to cart"
|
||||
}
|
||||
},
|
||||
"shopCart": {
|
||||
"header": {
|
||||
"title": "Shop cart - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application shop cart"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"title": "Shopping Cart",
|
||||
"text": "<i class=\"text-info font-weight-bold\">{quantity}</i> items in your cart"
|
||||
},
|
||||
"productsSection": {
|
||||
"product": "Product",
|
||||
"price": "Price",
|
||||
"quantity": "Quantity",
|
||||
"subtotal": "Subtotal:",
|
||||
"continueShopping": {
|
||||
"target": "/shop",
|
||||
"anchorText": "Continue shopping"
|
||||
},
|
||||
"checkout": {
|
||||
"target": "checkout",
|
||||
"anchorText": "Checkout"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shopCheckout": {
|
||||
"header": {
|
||||
"title": "Shop - checkout {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."
|
||||
},
|
||||
"billingAddressSection": {
|
||||
"title": "Billing address",
|
||||
"firstName": {
|
||||
"title": "First name",
|
||||
"placeHolder": "First name..."
|
||||
},
|
||||
"lastName": {
|
||||
"title": "Last name",
|
||||
"placeHolder": "Last name..."
|
||||
},
|
||||
"address": {
|
||||
"title": "Address",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"address2": {
|
||||
"title": "Address",
|
||||
"optional": "(Optional)",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"country": {
|
||||
"title": "Country",
|
||||
"placeHolder": "Country..."
|
||||
},
|
||||
"state": {
|
||||
"title": "State",
|
||||
"placeHolder": "State..."
|
||||
},
|
||||
"city": {
|
||||
"title": "City",
|
||||
"placeHolder": "City..."
|
||||
},
|
||||
"zip": {
|
||||
"title": "Zip",
|
||||
"placeHolder": "Zip..."
|
||||
}
|
||||
},
|
||||
"shippingAddressSection": {
|
||||
"title": "Shipping address",
|
||||
"firstName": {
|
||||
"title": "First name",
|
||||
"placeHolder": "First name..."
|
||||
},
|
||||
"lastName": {
|
||||
"title": "Last name",
|
||||
"placeHolder": "Last name..."
|
||||
},
|
||||
"address": {
|
||||
"title": "Address",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"address2": {
|
||||
"title": "Address",
|
||||
"optional": "(Optional)",
|
||||
"placeHolder": "1234 Main Str.."
|
||||
},
|
||||
"country": {
|
||||
"title": "Country",
|
||||
"placeHolder": "Country..."
|
||||
},
|
||||
"state": {
|
||||
"title": "State",
|
||||
"placeHolder": "State..."
|
||||
},
|
||||
"city": {
|
||||
"title": "City",
|
||||
"placeHolder": "City..."
|
||||
},
|
||||
"zip": {
|
||||
"title": "Zip",
|
||||
"placeHolder": "Zip..."
|
||||
}
|
||||
},
|
||||
"settingsSection": {
|
||||
"shippingAddressSameAsBillingAddress": "Shipping address is the same as my billing address",
|
||||
"saveThisInformation": "Save this information for next time"
|
||||
},
|
||||
"summarySection": {
|
||||
"title": "Your cart",
|
||||
"total": "Total ({currency})",
|
||||
"promoCode": {
|
||||
"placeHolder": "Promo code"
|
||||
},
|
||||
"submit": {
|
||||
"title": "Redeem"
|
||||
}
|
||||
},
|
||||
"paymentSection": {
|
||||
"title": "Payment",
|
||||
"nameOnCard": {
|
||||
"title": "Name on card",
|
||||
"placeHolder": "John Doe"
|
||||
},
|
||||
"cardNumber": {
|
||||
"title": "Credit card number",
|
||||
"placeHolder": ""
|
||||
},
|
||||
"expiration": {
|
||||
"title": "Expiration",
|
||||
"placeHolder": "MM/YY"
|
||||
},
|
||||
"cvv": {
|
||||
"title": "CVV",
|
||||
"placeHolder": "123"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"title": "Continue to checkout"
|
||||
}
|
||||
},
|
||||
"blogCatalog": {
|
||||
"header": {
|
||||
"title": "Blog catalog - {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"
|
||||
}
|
||||
},
|
||||
"blogItem": {
|
||||
"header": {
|
||||
"title": "{blogTitle} - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application blog item"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"titleSection": {
|
||||
"postedOnBy": "Posted on {date} by {nickName}"
|
||||
},
|
||||
"commentsSection": {
|
||||
"leaveComment": "Join the discussion and leave a comment!"
|
||||
}
|
||||
},
|
||||
"signIn": {
|
||||
"header": {
|
||||
"title": "Sign in - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application sign in",
|
||||
"robots": "noindex, nofollow"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"title": "Sign in",
|
||||
"email": {
|
||||
"title": "Email address",
|
||||
"placeHolder": "Email address..."
|
||||
},
|
||||
"password": {
|
||||
"title": "Password",
|
||||
"placeHolder": "Password..."
|
||||
},
|
||||
"dontHaveAnAccount": "Don't have an account yet? Please",
|
||||
"signUpLink": {
|
||||
"target": "/signup",
|
||||
"anchorText": "Sign up"
|
||||
},
|
||||
"submit": {
|
||||
"title": "Sign in"
|
||||
}
|
||||
},
|
||||
"signUp": {
|
||||
"header": {
|
||||
"title": "Sign up - {siteName}",
|
||||
"meta": {
|
||||
"description": "Single-page application sign up",
|
||||
"robots": "noindex, nofollow"
|
||||
},
|
||||
"link": {
|
||||
"canonical": ""
|
||||
}
|
||||
},
|
||||
"title": "Sign up",
|
||||
"username": {
|
||||
"title": "Username",
|
||||
"placeHolder": "Username..."
|
||||
},
|
||||
"email": {
|
||||
"title": "Email address",
|
||||
"placeHolder": "Email address..."
|
||||
},
|
||||
"reEmail": {
|
||||
"title": "Repeat email address",
|
||||
"placeHolder": "Repeat email address..."
|
||||
},
|
||||
"password": {
|
||||
"title": "Password",
|
||||
"placeHolder": "Password..."
|
||||
},
|
||||
"rePassword": {
|
||||
"title": "Repeat password",
|
||||
"placeHolder": "Repeat password..."
|
||||
},
|
||||
"acceptTermsAndConditions": "Accept terms and conditions",
|
||||
"submit": {
|
||||
"title": "Sing up"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,3 @@
|
||||
**/.dockerignore
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
!**/.env
|
||||
*
|
||||
!obj\Docker\publish\*
|
||||
!obj\Docker\empty\
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
BROWSER=none
|
||||
|
||||
REACT_APP_LOCAL_ONLY=N
|
||||
|
||||
REACT_APP_FRONTEND=https://localhost:7174
|
||||
REACT_APP_API=https://localhost:7174/api
|
||||
|
||||
REACT_APP_API=https://localhost:7151/api
|
||||
REACT_APP_SITEID=404c8232-9048-4519-bfba-6e78dc7005ca
|
||||
REACT_APP_LOCALE=en-US
|
||||
|
||||
REACT_APP_ACCOUNT=Account
|
||||
|
||||
|
||||
23
src/ClientApp/.gitignore
vendored
23
src/ClientApp/.gitignore
vendored
@ -1,23 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
22
src/ClientApp/.vscode/launch.json
vendored
Normal file
22
src/ClientApp/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "pwa-msedge",
|
||||
"request": "launch",
|
||||
"name": "Launch Edge against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -37,7 +37,6 @@
|
||||
<DependentUpon>Dockerfile</DependentUpon>
|
||||
</Content>
|
||||
<None Include="Dockerfile" />
|
||||
<Content Include=".env" />
|
||||
<Content Include="src\components\FeatherIcons\icons.json" />
|
||||
<Content Include="src\components\Loader\scss\animations\ball-beat.scss" />
|
||||
<Content Include="src\components\Loader\scss\animations\ball-clip-rotate-multiple.scss" />
|
||||
@ -99,6 +98,7 @@
|
||||
<Content Include="src\pages\Shop\Checkout\scss\style.module.scss" />
|
||||
<Content Include="src\pages\Signin\scss\style.scss" />
|
||||
<Content Include="src\pages\Signup\scss\style.scss" />
|
||||
<Content Include="tsconfig.json" />
|
||||
<Content Include="package.json" />
|
||||
<Content Include="README.md" />
|
||||
</ItemGroup>
|
||||
@ -121,7 +121,6 @@
|
||||
<Folder Include="src\enumerations\" />
|
||||
<Folder Include="src\functions\" />
|
||||
<Folder Include="src\functions\jwtDecode\" />
|
||||
<Folder Include="src\interfaces\" />
|
||||
<Folder Include="src\layouts\" />
|
||||
<Folder Include="src\layouts\admin\" />
|
||||
<Folder Include="src\layouts\admin\NavMenu\" />
|
||||
@ -133,6 +132,7 @@
|
||||
<Folder Include="src\layouts\public\NavMenu\" />
|
||||
<Folder Include="src\layouts\public\scss\" />
|
||||
<Folder Include="src\layouts\scss\" />
|
||||
<Folder Include="src\models\" />
|
||||
<Folder Include="src\pages\" />
|
||||
<Folder Include="src\pages\Blog\" />
|
||||
<Folder Include="src\pages\Blog\Catalog\" />
|
||||
@ -183,7 +183,6 @@
|
||||
<TypeScriptCompile Include="src\functions\jwtDecode\base64_url_decode.ts" />
|
||||
<TypeScriptCompile Include="src\functions\jwtDecode\index.ts" />
|
||||
<TypeScriptCompile Include="src\index.tsx" />
|
||||
<TypeScriptCompile Include="src\interfaces\index.ts" />
|
||||
<TypeScriptCompile Include="src\layouts\admin\index.tsx" />
|
||||
<TypeScriptCompile Include="src\layouts\admin\NavMenu\index.tsx" />
|
||||
<TypeScriptCompile Include="src\layouts\admin\SideMenu\index.tsx" />
|
||||
@ -192,6 +191,12 @@
|
||||
<TypeScriptCompile Include="src\layouts\public\Footer\index.tsx" />
|
||||
<TypeScriptCompile Include="src\layouts\public\index.tsx" />
|
||||
<TypeScriptCompile Include="src\layouts\public\NavMenu\index.tsx" />
|
||||
<TypeScriptCompile Include="src\models\abstractions.ts" />
|
||||
<TypeScriptCompile Include="src\models\index.ts" />
|
||||
<TypeScriptCompile Include="src\models\pages.ts" />
|
||||
<TypeScriptCompile Include="src\models\pageSections.ts" />
|
||||
<TypeScriptCompile Include="src\models\requests.ts" />
|
||||
<TypeScriptCompile Include="src\models\responses.ts" />
|
||||
<TypeScriptCompile Include="src\pages\AdminHome.tsx" />
|
||||
<TypeScriptCompile Include="src\pages\Blog\Catalog\index.tsx" />
|
||||
<TypeScriptCompile Include="src\pages\Blog\index.ts" />
|
||||
@ -221,6 +226,7 @@
|
||||
<TypeScriptCompile Include="src\store\reducers\Content.ts" />
|
||||
<TypeScriptCompile Include="src\store\reducers\Counter.ts" />
|
||||
<TypeScriptCompile Include="src\store\reducers\Header.ts" />
|
||||
<TypeScriptCompile Include="src\store\reducers\Loader.ts" />
|
||||
<TypeScriptCompile Include="src\store\reducers\ShopCart.ts" />
|
||||
<TypeScriptCompile Include="src\store\reducers\ShopCatalog.ts" />
|
||||
<TypeScriptCompile Include="src\store\reducers\ShopCategories.ts" />
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
FROM node:lts as base
|
||||
# ==== CONFIGURE =====
|
||||
# Use a Node 16 base image
|
||||
FROM node:16-alpine
|
||||
# Set the working directory to /app inside the container
|
||||
WORKDIR /app
|
||||
ENV CI=true
|
||||
ENV PORT=3000
|
||||
|
||||
# Copy app files
|
||||
COPY . .
|
||||
# ==== BUILD =====
|
||||
# Install dependencies (npm ci makes sure the exact versions in the lockfile gets installed)
|
||||
RUN npm ci
|
||||
# Build the app
|
||||
RUN npm run build
|
||||
# ==== RUN =======
|
||||
# Set the env to "production"
|
||||
ENV NODE_ENV production
|
||||
# Expose the port on which the app will be running (3000 is the default that `serve` uses)
|
||||
EXPOSE 3000
|
||||
|
||||
FROM base as build
|
||||
COPY ["ClientApp/", "/app"]
|
||||
RUN npm i --package-lock-only
|
||||
RUN npm ci
|
||||
CMD [ "npm", "start" ]
|
||||
|
||||
#FROM build as final
|
||||
#COPY ["ClientApp/build", "/app"]
|
||||
# Start the app
|
||||
CMD [ "npx", "serve", "build" ]
|
||||
@ -1,46 +1,3 @@
|
||||
# Getting Started with Create React App
|
||||
# ClientApp
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
18782
src/ClientApp/package-lock.json
generated
18782
src/ClientApp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,59 +1,60 @@
|
||||
{
|
||||
"name": "reactredux",
|
||||
"name": "react-redux-template",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.33",
|
||||
"@types/react": "^18.2.7",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"axios": "^1.4.0",
|
||||
"bootstrap": "^5.2.3",
|
||||
"classnames": "^2.3.2",
|
||||
"dayjs": "^1.11.7",
|
||||
"history": "^5.3.0",
|
||||
"bootstrap": "5.1.3",
|
||||
"classnames": "^2.3.1",
|
||||
"dayjs": "^1.11.2",
|
||||
"history": "5.3.0",
|
||||
"merge": "^2.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router": "^6.11.2",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"reactstrap": "^9.1.10",
|
||||
"redux": "^4.2.1",
|
||||
"redux-first-history": "^5.1.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"sass": "^1.62.1",
|
||||
"svgo": "^3.0.2",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"react-redux": "7.2.8",
|
||||
"react-router": "6.3.0",
|
||||
"react-router-dom": "6.3.0",
|
||||
"reactstrap": "9.0.2",
|
||||
"redux": "4.1.2",
|
||||
"redux-first-history": "^5.0.9",
|
||||
"redux-thunk": "2.4.1",
|
||||
"svgo": "2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/node": "17.0.24",
|
||||
"@types/react": "18.0.5",
|
||||
"@types/react-dom": "18.0.1",
|
||||
"@types/react-redux": "7.1.24",
|
||||
"@types/react-router": "5.1.18",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@typescript-eslint/parser": "^5.19.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"nan": "^2.15.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"sass": "^1.50.0",
|
||||
"typescript": "4.6.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"test": "cross-env CI=true react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "eslint ./src/**/*.ts ./src/**/*.tsx"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
"resolutions": {
|
||||
"url-parse": ">=1.5.0",
|
||||
"lodash": ">=4.17.21"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 5.3 KiB |
@ -1,20 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<base href="%PUBLIC_URL%/" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
@ -24,10 +20,12 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>react-redux-template</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
@ -1,24 +1,14 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "react-redux-template",
|
||||
"name": "react-redux-template",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@ -1,38 +1,28 @@
|
||||
// React
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { Route, Routes, useLocation } from 'react-router'
|
||||
|
||||
// Redux
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import type {} from 'redux-thunk/extend-redux'
|
||||
import { actionCreators as contentActionCreators } from './store/reducers/Content'
|
||||
|
||||
import { actionCreators as settingsActionCreators } from './store/reducers/Content'
|
||||
|
||||
// Components
|
||||
import { DynamicLayout } from './layouts'
|
||||
import { DynamicPage } from './pages'
|
||||
import { RouteModel } from './models'
|
||||
import { ApplicationState } from './store'
|
||||
import { Loader } from './components/Loader'
|
||||
import { Helmet } from './components/ReactHelmet'
|
||||
|
||||
import axios from 'axios'
|
||||
import ModalComponent from './components/Modal'
|
||||
|
||||
export interface IRoute {
|
||||
target: string
|
||||
component?: string
|
||||
childRoutes?: IRoute []
|
||||
}
|
||||
|
||||
interface IRouteProp {
|
||||
path: string,
|
||||
element?: JSX.Element
|
||||
}
|
||||
|
||||
const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) => {
|
||||
const NestedRoutes = (routes: RouteModel[], tag: string | undefined = undefined) => {
|
||||
if(!Array.isArray(routes)) return
|
||||
|
||||
return routes.map((route: IRoute, index: number) => {
|
||||
return routes.map((route: RouteModel, index: number) => {
|
||||
const routeProps: IRouteProp = {
|
||||
path: route.target
|
||||
}
|
||||
@ -42,51 +32,20 @@ const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) =>
|
||||
routeProps.element = tag ? <DynamicLayout tag={tag}>{page}</DynamicLayout> : page
|
||||
}
|
||||
|
||||
|
||||
|
||||
return <Route key={index} { ...routeProps }>{Array.isArray(route.childRoutes) ? NestedRoutes(route.childRoutes, tag) : ''}</Route>
|
||||
})
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const [loaderCounter, setLoaderCounter] = useState<number>(0)
|
||||
|
||||
// Add a request interceptor
|
||||
axios.interceptors.request.use((config) => {
|
||||
setLoaderCounter(loaderCounter + 1)
|
||||
|
||||
// Do something before request is sent
|
||||
return config
|
||||
}, err => {
|
||||
// console.log(err)
|
||||
setLoaderCounter(0)
|
||||
|
||||
// Do something with request error
|
||||
return Promise.reject(err)
|
||||
})
|
||||
|
||||
// Add a response interceptor
|
||||
axios.interceptors.response.use(function (response) {
|
||||
setLoaderCounter(loaderCounter > 0 ? loaderCounter - 1 : 0)
|
||||
|
||||
// Any status code that lie within the range of 2xx cause this function to trigger
|
||||
// Do something with response data
|
||||
return response
|
||||
}, err => {
|
||||
// console.log(err)
|
||||
setLoaderCounter(0)
|
||||
|
||||
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
||||
// Do something with response error
|
||||
return Promise.reject(err)
|
||||
})
|
||||
|
||||
|
||||
const { pathname } = useLocation()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, header } = useSelector((state: ApplicationState) => state)
|
||||
const { content, header, loader } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(contentActionCreators.requestContent())
|
||||
dispatch(settingsActionCreators.requestContent())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@ -97,7 +56,11 @@ const App = () => {
|
||||
}, [pathname])
|
||||
|
||||
|
||||
const { title, link, meta } = header
|
||||
const {
|
||||
title = "",
|
||||
link = {},
|
||||
meta = {}
|
||||
} = header ? header : {}
|
||||
|
||||
return <>
|
||||
<Helmet>
|
||||
@ -113,13 +76,7 @@ const App = () => {
|
||||
{content?.serviceRoutes ? NestedRoutes(content.serviceRoutes) : ''}
|
||||
</Routes>
|
||||
|
||||
{<ModalComponent {...{
|
||||
visible: false,
|
||||
title: 'Message title',
|
||||
text: 'Some message'
|
||||
}} />}
|
||||
|
||||
{<Loader {...{visible: loaderCounter > 0 }} />}
|
||||
{loader ? <Loader {...loader} /> : ''}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -1,48 +1,25 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Card, CardBody } from 'reactstrap'
|
||||
import { CommentModel } from '../../models'
|
||||
import { CommentsSectionModel } from '../../models/pageSections'
|
||||
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
|
||||
interface Comments {
|
||||
staticContent?: CommentsSectionModel,
|
||||
items?: CommentModel []
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
|
||||
export interface IComment {
|
||||
author: IAuthor,
|
||||
comment: string,
|
||||
responses?: IComment []
|
||||
}
|
||||
|
||||
export interface IComments {
|
||||
path?: string
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: IComment []
|
||||
}
|
||||
|
||||
export interface ICommentsSection {
|
||||
leaveComment: string
|
||||
}
|
||||
|
||||
export interface ICommentsComponent extends ICommentsSection, IComments { }
|
||||
|
||||
const CommentsComponent: FC<ICommentsComponent> = (props) => {
|
||||
|
||||
const { leaveComment, items = [] } = props
|
||||
const CommentsSection: FC<Comments> = ({
|
||||
staticContent,
|
||||
items = []
|
||||
}) => {
|
||||
|
||||
return <section className="mb-5">
|
||||
<Card className="card bg-light">
|
||||
<CardBody className="card-body">
|
||||
<form className="mb-4">
|
||||
<textarea className="form-control" rows={3} placeholder={leaveComment}></textarea>
|
||||
<textarea className="form-control" rows={3} placeholder={staticContent?.leaveComment}></textarea>
|
||||
</form>
|
||||
|
||||
{items.map((comment, index) => <div key={index} className={`d-flex ${index < items.length - 1 ? 'mb-4' : ''}`}>
|
||||
@ -73,5 +50,5 @@ const CommentsComponent: FC<ICommentsComponent> = (props) => {
|
||||
}
|
||||
|
||||
export {
|
||||
CommentsComponent
|
||||
CommentsSection
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { FC, ReactNode } from 'react'
|
||||
import React, { FC, ReactNode, useEffect, useState } from 'react'
|
||||
|
||||
import './scss/loaders.scss'
|
||||
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'
|
||||
|
||||
|
||||
export interface IModalComponent {
|
||||
visible: boolean
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
const ModalComponent : FC<IModalComponent> = (props) => {
|
||||
|
||||
const { visible, title, text } = props
|
||||
|
||||
const [modal, setModal] = useState(visible)
|
||||
|
||||
const toggle = () => setModal(!modal)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <Button color="danger" onClick={toggle}>
|
||||
Click Me
|
||||
</Button> */}
|
||||
<Modal isOpen={modal} toggle={toggle}>
|
||||
<ModalHeader toggle={toggle}>{title}</ModalHeader>
|
||||
<ModalBody>{text}</ModalBody>
|
||||
{/*<ModalFooter>
|
||||
<Button color="primary" onClick={toggle}>
|
||||
Do Something
|
||||
</Button>{' '}
|
||||
<Button color="secondary" onClick={toggle}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>*/}
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalComponent
|
||||
@ -4,14 +4,14 @@ import { Pagination as ReactstrapPagination, PaginationItem, PaginationLink } fr
|
||||
import { findChunk, intToArray, splitInChunks } from './utils'
|
||||
|
||||
|
||||
export interface IPaginationComponent {
|
||||
interface PaginationProps {
|
||||
maxVisiblePages?: number,
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
onClick: (page: number) => void
|
||||
}
|
||||
|
||||
const Pagination: FC<IPaginationComponent> = ({
|
||||
const Pagination: FC<PaginationProps> = ({
|
||||
maxVisiblePages = 5,
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
@ -80,14 +80,14 @@ const Pagination: FC<IPaginationComponent> = ({
|
||||
</nav>
|
||||
}
|
||||
|
||||
export interface ISSRPaginationComponent {
|
||||
interface SSRPaginationProps {
|
||||
maxVisiblePages?: number,
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
linksPath?: string
|
||||
}
|
||||
|
||||
const SSRPagination: FC<ISSRPaginationComponent> = ({
|
||||
const SSRPagination: FC<SSRPaginationProps> = ({
|
||||
maxVisiblePages = 5,
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
|
||||
@ -1,25 +1,14 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
||||
|
||||
export interface ICategory {
|
||||
href: string,
|
||||
anchorText: string
|
||||
}
|
||||
import { CategoryModel } from '../../models'
|
||||
|
||||
interface ICategories {
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
items?: ICategory []
|
||||
items?: CategoryModel []
|
||||
}
|
||||
|
||||
export interface ICategoriesComponent extends ICategories {}
|
||||
|
||||
const Categories: FC<ICategoriesComponent> = (props) => {
|
||||
|
||||
const { items = [] } = props
|
||||
|
||||
const Categories: FC<ICategories> = ({
|
||||
items = []
|
||||
}) => {
|
||||
const middleIndex = Math.ceil(items.length / 2)
|
||||
|
||||
const firstHalf = items.splice(0, middleIndex)
|
||||
@ -32,12 +21,12 @@ const Categories: FC<ICategoriesComponent> = (props) => {
|
||||
|
||||
<Col sm="6">
|
||||
<ul className="list-unstyled mb-0">
|
||||
{firstHalf.map((item, index) => <li key={index}><Link to={item.href}>{item.anchorText}</Link></li>)}
|
||||
{firstHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
|
||||
</ul>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<ul className="list-unstyled mb-0">
|
||||
{secondHalf.map((item, index) => <li key={index}><Link to={item.href}>{item.anchorText}</Link></li>)}
|
||||
{secondHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
|
||||
</ul>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -47,4 +36,4 @@ const Categories: FC<ICategoriesComponent> = (props) => {
|
||||
|
||||
export {
|
||||
Categories
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
const cloneObject = <T>(obj : T) => {
|
||||
const json = JSON.stringify(obj)
|
||||
const newObj: T = JSON.parse(json)
|
||||
|
||||
return newObj
|
||||
}
|
||||
|
||||
export {
|
||||
cloneObject
|
||||
}
|
||||
@ -1,24 +1,16 @@
|
||||
import { IRoute } from "../App"
|
||||
import { RouteModel } from "../models"
|
||||
|
||||
interface ComponentRoutesModel {
|
||||
targets: string [],
|
||||
component: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Usege const path = findRoutes(content?.routes, 'ShopCatalog')[0]?.targets[0]
|
||||
* @param routes
|
||||
* @param component
|
||||
* @param parentTarget
|
||||
* @param result
|
||||
* @returns
|
||||
*/
|
||||
const findRoutes = (routes: IRoute[] = [], component: string | undefined, parentTarget: string [] = [], result: ComponentRoutesModel [] = []): ComponentRoutesModel [] => {
|
||||
const findRoutes = (routes: RouteModel[] = [], component: string | undefined, parentTarget: string [] = [], result: ComponentRoutesModel [] = []): ComponentRoutesModel [] => {
|
||||
|
||||
if(!Array.isArray(routes))
|
||||
return []
|
||||
|
||||
routes.forEach((route: IRoute) => {
|
||||
routes.forEach((route: RouteModel) => {
|
||||
const targets: string [] = []
|
||||
if(parentTarget) {
|
||||
parentTarget.forEach(item => {
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
import { dateFormat, timeFormat } from './dateTimeFormat'
|
||||
import { findRoutes } from './findRoutes'
|
||||
import { getKeyValue } from './getKeyValue'
|
||||
import { cloneObject } from './cloneObject'
|
||||
import { isSuccessStatusCode } from './isSuccessStatusCode'
|
||||
|
||||
export {
|
||||
getKeyValue,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
findRoutes,
|
||||
cloneObject,
|
||||
isSuccessStatusCode
|
||||
findRoutes
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
const isSuccessStatusCode = (statusCode: number) => {
|
||||
return statusCode >= 200 && statusCode <= 299
|
||||
}
|
||||
|
||||
export {
|
||||
isSuccessStatusCode
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
|
||||
//new shared interfaces
|
||||
interface IHeaderLink {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
interface IMeta {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface IHeader {
|
||||
title: string,
|
||||
link: IHeaderLink,
|
||||
meta: IMeta
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface IParams {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
export interface IRequest<T, TT> {
|
||||
pathParams?: T
|
||||
searchParams?: TT,
|
||||
}
|
||||
|
||||
export interface IResponse {}
|
||||
|
||||
|
||||
export interface IPagination<T> {
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
items: T []
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export interface PaginationModel<T> {
|
||||
// totalPages: number,
|
||||
// currentPage: number,
|
||||
// items: T []
|
||||
// }
|
||||
|
||||
|
||||
// export interface AuthorModel extends PersonModel {
|
||||
// nickName: string
|
||||
// }
|
||||
|
||||
// export interface BlogItemModel extends PostItemModel {
|
||||
// readTime?: number,
|
||||
// likes?: number
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export interface FeatureModel {
|
||||
// icon: string,
|
||||
// title: string,
|
||||
// text: string
|
||||
// }
|
||||
|
||||
// export interface FormItemModel {
|
||||
// title?: string,
|
||||
// optional?: string,
|
||||
// placeHolder?: string,
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export interface LinkModel {
|
||||
// target: string,
|
||||
// anchorText: string
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export interface ReviewerModel extends PersonModel {
|
||||
// fullName: string,
|
||||
// position: string
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// export interface ShopItemModel extends PostItemModel {
|
||||
// images?: ImageModel [],
|
||||
// sku: string,
|
||||
// brandName: string,
|
||||
// rating?: number,
|
||||
// price: number,
|
||||
// newPrice?: number,
|
||||
// quantity?: number
|
||||
// }
|
||||
|
||||
// export interface TestimonialModel {
|
||||
// text: string,
|
||||
// reviewer: ReviewerModel
|
||||
// }
|
||||
@ -6,15 +6,6 @@ import { Collapse, Nav, NavItem, NavLink } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
|
||||
export interface ISideMenuItem {
|
||||
icon?: string,
|
||||
title?: string,
|
||||
target?: string
|
||||
childItems?: ISideMenuItem []
|
||||
}
|
||||
|
||||
|
||||
interface ISubMenu {
|
||||
icon?: string,
|
||||
title: string,
|
||||
|
||||
@ -4,15 +4,9 @@ import { useSelector } from 'react-redux'
|
||||
import { Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import { ApplicationState } from '../../../store'
|
||||
import { MenuItemModel } from '../../../models'
|
||||
|
||||
|
||||
export interface INavMenuItem {
|
||||
icon?: string,
|
||||
title?: string,
|
||||
target?: string
|
||||
childItems?: INavMenuItem []
|
||||
}
|
||||
|
||||
const NavMenu : FC = () => {
|
||||
const { content, shopCart } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
@ -39,7 +33,7 @@ const NavMenu : FC = () => {
|
||||
<NavbarToggler onClick={toggle} className="mr-2"/>
|
||||
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={state.isOpen} navbar>
|
||||
<ul className="navbar-nav flex-grow">
|
||||
{content?.topMenu ? content.topMenu.map((item: INavMenuItem, index: number) => {
|
||||
{content?.topMenu ? content.topMenu.map((item: MenuItemModel, index: number) => {
|
||||
return <NavItem key={index}>
|
||||
<NavLink tag={Link} className="text-dark" to={item.target}><>
|
||||
{item.icon ? <><FeatherIcon icon={item.icon}/> </> : ''}{titleFormatter(item.title)}</>
|
||||
|
||||
53
src/ClientApp/src/models/abstractions.ts
Normal file
53
src/ClientApp/src/models/abstractions.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { AuthorModel, FormItemModel, HeaderModel, ImageModel } from "./"
|
||||
import { TitleSectionModel } from "./pageSections"
|
||||
|
||||
export interface Params {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
export interface RequestModel<T, TT> {
|
||||
pathParams?: T
|
||||
searchParams?: TT,
|
||||
|
||||
}
|
||||
|
||||
export interface ResponseModel {}
|
||||
|
||||
export interface AddressPageSectionModel extends PageSectionModel {
|
||||
firstName: FormItemModel,
|
||||
lastName: FormItemModel,
|
||||
address: FormItemModel,
|
||||
address2: FormItemModel,
|
||||
country: FormItemModel,
|
||||
state: FormItemModel,
|
||||
city: FormItemModel,
|
||||
zip: FormItemModel
|
||||
}
|
||||
|
||||
export interface PageModel {
|
||||
header: HeaderModel,
|
||||
titleSection?: TitleSectionModel
|
||||
}
|
||||
|
||||
export interface PageSectionModel {
|
||||
title?: string
|
||||
text?: string
|
||||
}
|
||||
|
||||
export interface PersonModel {
|
||||
id: string,
|
||||
image?: ImageModel
|
||||
}
|
||||
|
||||
export interface PostItemModel {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: ImageModel,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText?: string,
|
||||
text?: string,
|
||||
author: AuthorModel,
|
||||
created: string,
|
||||
tags: string []
|
||||
}
|
||||
106
src/ClientApp/src/models/index.ts
Normal file
106
src/ClientApp/src/models/index.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { PersonModel, PostItemModel } from "./abstractions"
|
||||
|
||||
|
||||
export interface PaginationModel<T> {
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
items: T []
|
||||
}
|
||||
|
||||
|
||||
export interface AuthorModel extends PersonModel {
|
||||
nickName: string
|
||||
}
|
||||
|
||||
export interface BlogItemModel extends PostItemModel {
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
export interface CategoryModel {
|
||||
id: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface CommentModel {
|
||||
author: AuthorModel,
|
||||
comment: string,
|
||||
responses?: CommentModel []
|
||||
}
|
||||
|
||||
export interface FeatureModel {
|
||||
icon: string,
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface FormItemModel {
|
||||
title?: string,
|
||||
optional?: string,
|
||||
placeHolder?: string,
|
||||
}
|
||||
|
||||
export interface HeaderLink {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
[key: string]: string
|
||||
}
|
||||
export interface HeaderModel {
|
||||
title: string,
|
||||
link: HeaderLink,
|
||||
meta: Meta
|
||||
}
|
||||
|
||||
export interface ImageModel {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
export interface LinkModel {
|
||||
target: string,
|
||||
anchorText: string
|
||||
}
|
||||
|
||||
export interface LocalizationModel {
|
||||
timeZone: string,
|
||||
locale: string,
|
||||
dateFormat: string,
|
||||
timeFormat: string,
|
||||
currency: string,
|
||||
currencySymbol: string
|
||||
}
|
||||
|
||||
export interface MenuItemModel {
|
||||
icon?: string,
|
||||
title?: string,
|
||||
target?: string
|
||||
childItems?: MenuItemModel []
|
||||
}
|
||||
|
||||
export interface ReviewerModel extends PersonModel {
|
||||
fullName: string,
|
||||
position: string
|
||||
}
|
||||
|
||||
export interface RouteModel {
|
||||
target: string
|
||||
component?: string
|
||||
childRoutes?: RouteModel []
|
||||
}
|
||||
|
||||
export interface ShopItemModel extends PostItemModel {
|
||||
images?: ImageModel [],
|
||||
sku: string,
|
||||
brandName: string,
|
||||
rating?: number,
|
||||
price: number,
|
||||
newPrice?: number,
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
export interface TestimonialModel {
|
||||
text: string,
|
||||
reviewer: ReviewerModel
|
||||
}
|
||||
78
src/ClientApp/src/models/pageSections.ts
Normal file
78
src/ClientApp/src/models/pageSections.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { FeatureModel, FormItemModel, ImageModel, LinkModel, MenuItemModel, TestimonialModel } from "./"
|
||||
import { AddressPageSectionModel, PageSectionModel } from "./abstractions"
|
||||
|
||||
export interface BillingAddressSectionModel extends AddressPageSectionModel { }
|
||||
|
||||
export interface CallToActionSectionModel extends PageSectionModel {
|
||||
privacyDisclaimer?: string
|
||||
email?: FormItemModel
|
||||
}
|
||||
|
||||
export interface CartProductsSectionModel extends PageSectionModel {
|
||||
product: string,
|
||||
price: string,
|
||||
quantity: string,
|
||||
subtotal: string,
|
||||
continueShopping: LinkModel,
|
||||
checkout: LinkModel
|
||||
}
|
||||
|
||||
export interface CheckoutSettingsSectionModel extends PageSectionModel {
|
||||
shippingAddressSameAsBillingAddress: string,
|
||||
saveThisInformation: string
|
||||
}
|
||||
|
||||
export interface CheckoutSummarySectionModel extends PageSectionModel {
|
||||
title: string,
|
||||
total: string,
|
||||
|
||||
promoCode: FormItemModel,
|
||||
submit: FormItemModel
|
||||
}
|
||||
|
||||
export interface CommentsSectionModel extends PageSectionModel {
|
||||
leaveComment: string
|
||||
}
|
||||
|
||||
export interface FeaturedBlogSectionModel extends PageSectionModel {
|
||||
readTime: string
|
||||
}
|
||||
|
||||
export interface FeaturedBlogsSectionModel extends PageSectionModel {}
|
||||
|
||||
export interface FeaturesSectionModel extends PageSectionModel {
|
||||
items: FeatureModel []
|
||||
}
|
||||
|
||||
export interface PaymentSectionModel extends PageSectionModel {
|
||||
nameOnCard: FormItemModel,
|
||||
cardNumber: FormItemModel,
|
||||
expiration: FormItemModel,
|
||||
cvv: FormItemModel
|
||||
}
|
||||
|
||||
export interface ProductSectionModel extends PageSectionModel {
|
||||
availableQuantity: string,
|
||||
addToCart: string
|
||||
}
|
||||
|
||||
export interface RelatedProductsSectionModel extends PageSectionModel {
|
||||
addToCart: string
|
||||
}
|
||||
|
||||
export interface ShippingAddressSectionModel extends AddressPageSectionModel { }
|
||||
|
||||
export interface TestimonialsSectionModel extends PageSectionModel {
|
||||
items: TestimonialModel []
|
||||
}
|
||||
|
||||
export interface TitleSectionModel extends PageSectionModel {
|
||||
image?: ImageModel,
|
||||
primaryLink?: MenuItemModel,
|
||||
secondaryLink?: MenuItemModel,
|
||||
postedOnBy?: string
|
||||
}
|
||||
|
||||
export interface ShopItemsSectionModel extends PageSectionModel {
|
||||
addToCart: string
|
||||
}
|
||||
60
src/ClientApp/src/models/pages.ts
Normal file
60
src/ClientApp/src/models/pages.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { FormItemModel, LinkModel } from "."
|
||||
import { PageModel } from "./abstractions"
|
||||
import * as PageSection from "./pageSections"
|
||||
|
||||
export interface BlogCatalogPageModel extends PageModel {
|
||||
featuredBlogSection: PageSection.FeaturedBlogSectionModel
|
||||
}
|
||||
|
||||
export interface BlogItemPageModel extends PageModel {
|
||||
commentsSection: PageSection.CommentsSectionModel
|
||||
}
|
||||
|
||||
export interface HomePageModel extends PageModel {
|
||||
featuresSection: PageSection.FeaturesSectionModel,
|
||||
testimonialsSection: PageSection.TestimonialsSectionModel,
|
||||
featuredBlogsSection: PageSection.FeaturedBlogsSectionModel,
|
||||
callToActionSection: PageSection.CallToActionSectionModel
|
||||
}
|
||||
|
||||
export interface ShopCartPageModel extends PageModel {
|
||||
productsSection: PageSection.CartProductsSectionModel
|
||||
}
|
||||
|
||||
export interface ShopCatalogPageModel extends PageModel {
|
||||
shopItemsSection: PageSection.ShopItemsSectionModel
|
||||
}
|
||||
|
||||
export interface ShopCheckoutPageModel extends PageModel {
|
||||
billingAddressSection: PageSection.BillingAddressSectionModel,
|
||||
shippingAddressSection: PageSection.ShippingAddressSectionModel,
|
||||
settingsSection: PageSection.CheckoutSettingsSectionModel,
|
||||
summarySection: PageSection.CheckoutSummarySectionModel,
|
||||
paymentSection: PageSection.PaymentSectionModel,
|
||||
submit: FormItemModel
|
||||
}
|
||||
|
||||
export interface ShopItemPageModel extends PageModel {
|
||||
productSection: PageSection.ProductSectionModel
|
||||
relatedProductsSection: PageSection.RelatedProductsSectionModel
|
||||
}
|
||||
|
||||
export interface SignInPageModel extends PageModel {
|
||||
title: string,
|
||||
email: FormItemModel,
|
||||
password: FormItemModel,
|
||||
dontHaveAnAccount: string,
|
||||
signUpLink: LinkModel,
|
||||
submit: FormItemModel
|
||||
}
|
||||
|
||||
export interface SignUpPageModel extends PageModel {
|
||||
title: string,
|
||||
username: FormItemModel,
|
||||
email: FormItemModel,
|
||||
reEmail: FormItemModel
|
||||
password: FormItemModel,
|
||||
rePassword: FormItemModel,
|
||||
acceptTermsAndConditions: string,
|
||||
submit: FormItemModel
|
||||
}
|
||||
107
src/ClientApp/src/models/requests.ts
Normal file
107
src/ClientApp/src/models/requests.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { Params, RequestModel } from "./abstractions"
|
||||
|
||||
|
||||
// Blog requests -----------------------------------------------------
|
||||
export interface GetBlogcatalogPathParams extends Params {}
|
||||
export interface GeBlogCatalogSearchParams extends Params {}
|
||||
|
||||
|
||||
export interface GetBlogCatalogRequestModel extends RequestModel<GetBlogcatalogPathParams, GeBlogCatalogSearchParams> {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
export interface GetBlogCategoriesPathParams extends Params {}
|
||||
export interface GetBlogCategoriesSearchParams extends Params {}
|
||||
export interface GetBlogCategoriesRequestModel extends RequestModel<GetBlogCategoriesPathParams, GetBlogCategoriesSearchParams> { }
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
export interface GetBlogItemPathParams extends Params {}
|
||||
export interface GetBlogItemSearchParams extends Params {
|
||||
slug: string
|
||||
}
|
||||
export interface GetBlogItemRequestModel extends RequestModel<GetBlogItemPathParams, GetBlogItemSearchParams> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
export interface GetBlogFeaturedPathParams extends Params {}
|
||||
export interface GetBlogFeaturedSearchParams extends Params {}
|
||||
export interface GetBlogFeaturedRequestModel extends RequestModel<GetBlogFeaturedPathParams, GetBlogFeaturedSearchParams> { }
|
||||
|
||||
|
||||
|
||||
// Static content -------------------------------------------------
|
||||
export interface GetContentPathParams extends Params {}
|
||||
|
||||
export interface GetContentSearchParams extends Params {
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export interface GetContentRequestModel extends RequestModel<GetContentPathParams, GetContentSearchParams> { }
|
||||
|
||||
|
||||
|
||||
|
||||
// Shop requests -------------------------------------------------
|
||||
export interface GetShopCatalogPathParams extends Params {}
|
||||
export interface GetShopCatalogSearchParams extends Params {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
export interface GetShopCatalogRequestModel extends RequestModel<GetShopCatalogPathParams, GetShopCatalogSearchParams> { }
|
||||
|
||||
// Shop cart items ------------------------------------------------
|
||||
export interface GetShopCartItemsPathParams extends Params {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface GetShopCartItemsSearchParams extends Params {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface GetShopCartItemsRequestModel extends RequestModel<GetShopCartItemsPathParams, GetShopCartItemsSearchParams> {}
|
||||
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export interface GetShopCategoriesPathParams extends Params {}
|
||||
export interface GetShopCategoriesSearchParams extends Params {}
|
||||
export interface GetShopCategoriesRequestModel extends RequestModel<GetShopCategoriesPathParams, GetShopCategoriesSearchParams> { }
|
||||
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
export interface GetShopFeaturedSearchParams extends Params {}
|
||||
export interface GetShopFeaturedPathParams extends Params {}
|
||||
export interface GetShopFeaturedRequestModel extends RequestModel<GetShopFeaturedSearchParams, GetShopFeaturedPathParams> { }
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
export interface GetShopItemPathParams extends Params {}
|
||||
export interface GetShopItemSearchParams extends Params {
|
||||
slug: string
|
||||
}
|
||||
export interface GetShopItemRequestModel extends RequestModel<GetShopItemPathParams, GetShopItemSearchParams> {
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
export interface GetShopRelatedPathParams extends Params {}
|
||||
export interface GetShopRelatedSearchParams extends Params {}
|
||||
export interface GetShopRelatedRequestModel extends RequestModel<GetShopRelatedPathParams, GetShopRelatedSearchParams> { }
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
export interface GetShopCartItemPathParams extends Params {}
|
||||
export interface GetShopCartItemSearchParams extends Params {}
|
||||
|
||||
export interface GetShopCartItemRequestModel extends RequestModel<GetShopCartItemPathParams, GetShopCartItemSearchParams> {}
|
||||
93
src/ClientApp/src/models/responses.ts
Normal file
93
src/ClientApp/src/models/responses.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { BlogItemModel, CategoryModel, CommentModel, HeaderModel, ImageModel, LocalizationModel, MenuItemModel, PaginationModel, RouteModel, ShopItemModel } from "./"
|
||||
import { ResponseModel } from "./abstractions"
|
||||
import * as Pages from "./pages"
|
||||
|
||||
|
||||
|
||||
|
||||
// Shop response models
|
||||
export interface GetShopCatalogResponseModel extends PaginationModel<ShopItemModel>, ResponseModel {}
|
||||
|
||||
export interface GetShopCategoriesResponseModel extends ResponseModel {
|
||||
items: CategoryModel []
|
||||
}
|
||||
|
||||
export interface GetShopFeaturedResponseModel extends ResponseModel {
|
||||
items: ShopItemModel []
|
||||
}
|
||||
|
||||
export interface GetShopItemResponseModel extends ShopItemModel, ResponseModel {
|
||||
comments: CommentModel []
|
||||
}
|
||||
|
||||
export interface GetShopRelatedResponseModel extends ResponseModel {
|
||||
items: ShopItemModel []
|
||||
}
|
||||
|
||||
|
||||
export interface GetShopCartItemResponseModel {
|
||||
slug: string
|
||||
sku: string,
|
||||
image: ImageModel,
|
||||
title: string,
|
||||
brandName: string,
|
||||
shortText: string,
|
||||
created: string,
|
||||
price: number,
|
||||
newPrice?: number,
|
||||
quantity: number
|
||||
}
|
||||
|
||||
|
||||
// Static content response model
|
||||
export interface GetContentResponseModel extends ResponseModel {
|
||||
|
||||
siteName: string,
|
||||
siteUrl: string,
|
||||
|
||||
header: HeaderModel,
|
||||
|
||||
localization: LocalizationModel,
|
||||
|
||||
routes: RouteModel [],
|
||||
adminRoutes: RouteModel [],
|
||||
serviceRoutes: RouteModel [],
|
||||
|
||||
topMenu: MenuItemModel [],
|
||||
sideMenu: MenuItemModel [],
|
||||
|
||||
homePage: Pages.HomePageModel,
|
||||
|
||||
shopCatalog: Pages.ShopCatalogPageModel,
|
||||
shopItem: Pages.ShopItemPageModel,
|
||||
shopCart: Pages.ShopCartPageModel,
|
||||
shopCheckout: Pages.ShopCheckoutPageModel,
|
||||
|
||||
blogCatalog: Pages.BlogCatalogPageModel,
|
||||
blogItem: Pages.BlogItemPageModel,
|
||||
|
||||
signIn: Pages.SignInPageModel,
|
||||
signUp: Pages.SignUpPageModel
|
||||
}
|
||||
|
||||
// Blog response models
|
||||
export interface GetBlogCatalogResponseModel extends PaginationModel<BlogItemModel>, ResponseModel { }
|
||||
|
||||
export interface GetBlogCategoriesResponseModel extends ResponseModel {
|
||||
items: CategoryModel []
|
||||
}
|
||||
export interface GetBlogFeaturedResponseModel extends ResponseModel {
|
||||
items: BlogItemModel []
|
||||
}
|
||||
|
||||
export interface GetBlogItemResponseModel extends BlogItemModel, ResponseModel {
|
||||
comments: CommentModel []
|
||||
}
|
||||
|
||||
// Weather forecasts
|
||||
export interface GetWeatherForecastResponseModel extends ResponseModel {
|
||||
date: string,
|
||||
temperatireC: number,
|
||||
temperatureF: number,
|
||||
summary?: string
|
||||
}
|
||||
@ -1,10 +1,6 @@
|
||||
import React, { FC } from 'react'
|
||||
import * as React from 'react'
|
||||
|
||||
interface IAdminHomeComponent {
|
||||
|
||||
}
|
||||
|
||||
const AdminHome : FC<IAdminHomeComponent> = () => {
|
||||
const AdminHome = () => {
|
||||
|
||||
return <div>Admin Home</div>
|
||||
}
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
// Reducers
|
||||
import { actionCreators as blogCatalogActionCreators } from '../../../store/reducers/BlogCatalog'
|
||||
|
||||
// Reactstrap
|
||||
import { Card, CardBody, CardImg, Col } from 'reactstrap'
|
||||
|
||||
// Components
|
||||
import { ISSRPaginationComponent, SSRPagination } from '../../../components/Pagination'
|
||||
|
||||
// Functions
|
||||
import { dateFormat } from '../../../functions'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
export interface IBlogItem {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText?: string,
|
||||
text?: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
export interface IBlogItems {
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: IBlogItem []
|
||||
}
|
||||
|
||||
export interface IBlogItemsSection {
|
||||
readMore: string
|
||||
}
|
||||
|
||||
export interface IBlogItemsComponent extends IBlogItemsSection, IBlogItems {
|
||||
path: string
|
||||
}
|
||||
|
||||
const BlogItemsComponent: FC<IBlogItemsComponent> = (props) => {
|
||||
|
||||
const { path, totalPages, currentPage, items = [], readMore } = props
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
|
||||
|
||||
return <>
|
||||
{items.map((item, index) => <Col key={index} className="lg-6 mb-3">
|
||||
<Card>
|
||||
|
||||
<CardImg top {...item.image} />
|
||||
|
||||
<CardBody>
|
||||
{item.badges.map((badge, index) => <div key={index} className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>)}
|
||||
<div className="small text-muted">{dateFormat(item.created)}</div>
|
||||
<h2 className="card-title h4">{item.title}</h2>
|
||||
<p className="card-text">{item.shortText}</p>
|
||||
<Link to={`${path}/${currentPage}/${item.slug}`} className="btn btn-primary">{readMore}</Link>
|
||||
</CardBody>
|
||||
|
||||
</Card>
|
||||
</Col>)}
|
||||
|
||||
<SSRPagination {...{
|
||||
totalPages: totalPages,
|
||||
currentPage: currentPage,
|
||||
// onClick: (nextPage) => {
|
||||
// dispatch(blogCatalogActionCreators.requestBlogCatalog({
|
||||
// searchParams: {
|
||||
// currentPage: nextPage + ""
|
||||
// }
|
||||
// }))
|
||||
|
||||
// navigate(`${path}/${nextPage}`)
|
||||
// }
|
||||
linksPath: path
|
||||
} as ISSRPaginationComponent} />
|
||||
</>
|
||||
}
|
||||
|
||||
export {
|
||||
BlogItemsComponent
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Card, CardBody, CardFooter, CardImg } from 'reactstrap'
|
||||
import { dateFormat } from '../../../functions'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
export interface IFeaturedBlogItem {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
// static stuff from Content
|
||||
export interface IFeaturedBlogSection {
|
||||
readTime?: string
|
||||
}
|
||||
|
||||
// component props
|
||||
export interface IFeaturedBlogComponent extends IFeaturedBlogSection {
|
||||
path: string,
|
||||
currentPage: number
|
||||
item: IFeaturedBlogItem,
|
||||
}
|
||||
|
||||
const FeaturedBlogComponent: FC<IFeaturedBlogComponent> = (props) => {
|
||||
|
||||
const { currentPage, path, item } = props
|
||||
let { readTime } = props
|
||||
|
||||
if(readTime && item?.created && item?.readTime)
|
||||
readTime = readTime?.replace('{date}', dateFormat(item.created))
|
||||
.replace('{readTime}', `${item.readTime}`)
|
||||
|
||||
return <Card className="mb-4 shadow border-0">
|
||||
<CardImg top {...item.image} />
|
||||
<CardBody className="p-4">
|
||||
{item.badges.map((badge, index) => <div key={index} className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>)}
|
||||
|
||||
<div className="small text-muted">{dateFormat(item.created)}</div>
|
||||
<Link className="text-decoration-none link-dark stretched-link" to={`${path}/${currentPage}/${item.slug}`}>
|
||||
<h5 className="card-title mb-3">{item.title}</h5>
|
||||
</Link>
|
||||
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText }}></p>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
|
||||
<div className="d-flex align-items-end justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
<img className="rounded-circle me-3" {...item.author.image} />
|
||||
<div className="small">
|
||||
<div className="fw-bold">{item.author.nickName}</div>
|
||||
{readTime !=null ? <div className="text-muted">{readTime}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
}
|
||||
|
||||
export {
|
||||
FeaturedBlogComponent
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
|
||||
// Reactstrap
|
||||
import { Container } from 'reactstrap'
|
||||
|
||||
export interface ITitleSection {
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface ITitleComponent extends ITitleSection {}
|
||||
|
||||
const TitleSection: FC<ITitleComponent> = (props) => {
|
||||
|
||||
const { title, text } = props
|
||||
|
||||
return <header className="py-5 bg-light border-bottom mb-4">
|
||||
<Container fluid>
|
||||
<div className="text-center my-5">
|
||||
<h1 className="display-4 fw-bolder">{title}</h1>
|
||||
<p className="lead fw-normal text-50 mb-0">{text}</p>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
}
|
||||
|
||||
export {
|
||||
TitleSection
|
||||
}
|
||||
@ -1,103 +1,195 @@
|
||||
// React
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { ApplicationState } from '../../../store'
|
||||
|
||||
// Reducers
|
||||
import { actionCreators as contentActionCreators } from '../../../store/reducers/Content'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
import { actionCreators as blogCatalogActionCreators } from '../../../store/reducers/BlogCatalog'
|
||||
import { actionCreators as blogCategoriesActionCreators } from '../../../store/reducers/BlogCategories'
|
||||
import { actionCreators as blogFeaturedActionCreators } from '../../../store/reducers/BlogFeatured'
|
||||
import { actionCreators as blogCategoriesActionCreators } from '../../../store/reducers/BlogCategories'
|
||||
|
||||
// Reactstrap
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
|
||||
import { dateFormat, findRoutes } from '../../../functions'
|
||||
|
||||
// Components
|
||||
import { TitleSection, ITitleSection } from './TitleComponent'
|
||||
import { FeaturedBlogComponent, IFeaturedBlogComponent, IFeaturedBlogSection } from './FeaturedBlogComponent'
|
||||
import { BlogItemsComponent, IBlogItemsComponent, IBlogItemsSection } from './BlogItemsComponent'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
import { Pagination } from '../../../components/Pagination'
|
||||
|
||||
// Interfaces
|
||||
import { IHeader } from '../../../interfaces'
|
||||
import { cloneObject } from '../../../functions'
|
||||
import { BlogItemModel } from '../../../models'
|
||||
import { TitleSectionModel } from '../../../models/pageSections'
|
||||
|
||||
export interface IBlogCatalogPage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
blogItemsSection: IBlogItemsSection,
|
||||
featuredBlogSection: IFeaturedBlogSection
|
||||
const TitleSection: FC<TitleSectionModel> = (props) => {
|
||||
const { title, text } = props
|
||||
return <header className="py-5 bg-light border-bottom mb-4">
|
||||
<Container fluid>
|
||||
<div className="text-center my-5">
|
||||
<h1 className="fw-bolder">{title ? title : ''}</h1>
|
||||
<p className="lead mb-0">{text ? text : ''}</p>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
}
|
||||
|
||||
export interface IBlogCatalogComponent extends IBlogCatalogPage { }
|
||||
interface FeaturedBlog {
|
||||
path?: string,
|
||||
currentPage?: number
|
||||
item?: BlogItemModel,
|
||||
readTime?: string
|
||||
}
|
||||
|
||||
const BlogCatalog : FC<IBlogCatalogComponent> = () => {
|
||||
const location = useLocation()
|
||||
const FeaturedBlogSection: FC<FeaturedBlog> = ({
|
||||
currentPage = 1,
|
||||
path = "",
|
||||
item = {
|
||||
id: "",
|
||||
slug: "demo-post",
|
||||
|
||||
badges: [],
|
||||
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "",
|
||||
shortText: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
created: new Date().toString(),
|
||||
tags: [],
|
||||
likes: 0
|
||||
},
|
||||
readTime = ""
|
||||
}) => {
|
||||
|
||||
return <Card className="mb-4 shadow border-0">
|
||||
<CardImg top {...item.image} />
|
||||
<CardBody className="p-4">
|
||||
{item.badges.map((badge, index) => <div key={index} className="badge bg-primary bg-gradient rounded-pill mb-2">{badge}</div>)}
|
||||
|
||||
<Link className="text-decoration-none link-dark stretched-link" to={`${path}/${currentPage}/${item.slug}`}>
|
||||
<h5 className="card-title mb-3">{item.title}</h5>
|
||||
</Link>
|
||||
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText ? item.shortText : '' }}></p>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
|
||||
<div className="d-flex align-items-end justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
<img className="rounded-circle me-3" {...item.author.image} />
|
||||
<div className="small">
|
||||
<div className="fw-bold">{item.author.nickName}</div>
|
||||
<div className="text-muted">{readTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
}
|
||||
|
||||
interface BlogItems {
|
||||
path?: string
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: BlogItemModel []
|
||||
}
|
||||
|
||||
const BlogItemsSection: FC<BlogItems> = ({
|
||||
path = "",
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
items = []
|
||||
}) => {
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return <>
|
||||
{items.map((item, index) => <Col key={index} className="lg-6">
|
||||
<Card className="mb-4">
|
||||
|
||||
<CardImg top {...item.image} />
|
||||
|
||||
<CardBody>
|
||||
<div className="small text-muted">{dateFormat(item.created)}</div>
|
||||
<h2 className="card-title h4">{item.title}</h2>
|
||||
<p className="card-text">{item.shortText}</p>
|
||||
<Link to={`${path}/${currentPage}/${item.slug}`} className="btn btn-primary">Read more →</Link>
|
||||
</CardBody>
|
||||
|
||||
</Card>
|
||||
</Col>)}
|
||||
|
||||
<Pagination {...{
|
||||
totalPages: totalPages,
|
||||
currentPage: currentPage,
|
||||
onClick: (nextPage) => {
|
||||
dispatch(blogCatalogActionCreators.requestBlogCatalog({
|
||||
currentPage: nextPage + ""
|
||||
}))
|
||||
|
||||
navigate(`${path}/${nextPage}`)
|
||||
}
|
||||
}} />
|
||||
</>
|
||||
}
|
||||
|
||||
const BlogCatalog = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, blogCatalog, blogCategories, blogFeatured } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const { dateFormat, timeFormat } = content.localization
|
||||
|
||||
const { header, titleSection, blogItemsSection, featuredBlogSection } = content.blogCatalog
|
||||
|
||||
const page = content?.blogCatalog
|
||||
const path = findRoutes(content?.routes, 'BlogCatalog')[0]?.targets[0]
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(blogCatalogActionCreators.requestBlogCatalog({
|
||||
currentPage: params?.page ? params.page : "1"
|
||||
}))
|
||||
dispatch(blogFeaturedActionCreators.requestBlogFeatured())
|
||||
dispatch(blogCategoriesActionCreators.requestBlogCategories())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(blogCatalogActionCreators.requestBlogCatalog({
|
||||
searchParams: {
|
||||
category: params?.category,
|
||||
currentPage: params?.page
|
||||
}
|
||||
}))
|
||||
}, [params.category, params.page])
|
||||
|
||||
const updateBlogCategories = () => {
|
||||
const newBlogCategoies = cloneObject(blogCategories)
|
||||
|
||||
newBlogCategoies.items = newBlogCategoies.items.map(item => {
|
||||
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}`
|
||||
return item
|
||||
})
|
||||
|
||||
return newBlogCategoies
|
||||
}
|
||||
|
||||
const updateBlogLinks = () => {
|
||||
return location.pathname.split('/').slice(0, 3).join('/')
|
||||
}
|
||||
|
||||
blogCatalog?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [blogCatalog?.isLoading])
|
||||
|
||||
const blogItem = blogFeatured?.items[0]
|
||||
|
||||
const featuredBlog: FeaturedBlog = {
|
||||
path,
|
||||
currentPage: blogCatalog?.currentPage,
|
||||
item: blogItem,
|
||||
readTime: page?.featuredBlogSection?.readTime
|
||||
}
|
||||
|
||||
if(featuredBlog.readTime && blogItem?.created && blogItem?.readTime)
|
||||
featuredBlog.readTime = featuredBlog.readTime?.replace('{date}', dateFormat(blogItem.created))
|
||||
.replace('{readTime}', `${blogItem.readTime}`)
|
||||
|
||||
return <>
|
||||
<TitleSection {...titleSection} />
|
||||
<TitleSection {...page?.titleSection} />
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col>
|
||||
<FeaturedBlogComponent {...{
|
||||
path: location.pathname,
|
||||
currentPage: blogCatalog?.currentPage,
|
||||
item: blogItem,
|
||||
featuredBlogSection
|
||||
} as IFeaturedBlogComponent} />
|
||||
<FeaturedBlogSection {...featuredBlog} />
|
||||
<Row>
|
||||
<BlogItemsComponent {...{
|
||||
path: updateBlogLinks(),
|
||||
...blogCatalog,
|
||||
...blogItemsSection
|
||||
} as IBlogItemsComponent }/>
|
||||
<BlogItemsSection path={path} {...blogCatalog} />
|
||||
</Row>
|
||||
</Col>
|
||||
<Col lg="3">
|
||||
<Search />
|
||||
<Categories {...updateBlogCategories()} />
|
||||
<Col lg="4">
|
||||
{/*<Search />
|
||||
<Categories {...blogCategories} />*/}
|
||||
<Empty/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
export interface ITitleSection {
|
||||
text :string,
|
||||
}
|
||||
|
||||
export interface ITitleComponent extends ITitleSection {
|
||||
title: string,
|
||||
badges? : string[],
|
||||
image?: IImage
|
||||
}
|
||||
|
||||
const TitleSection: FC<ITitleComponent> = (props) => {
|
||||
|
||||
const { title, text, badges = [], image } = props
|
||||
|
||||
return <>
|
||||
<header className="mb-4">
|
||||
<h1 className="fw-bolder mb-1">{title}</h1>
|
||||
<div className="text-muted fst-italic mb-2">{text ? text : ''}</div>
|
||||
|
||||
{badges ? badges.map((badge, index) => <a key={index} className="badge bg-secondary text-decoration-none link-light" href="#!">{badge}</a>) : <></>}
|
||||
</header>
|
||||
|
||||
<figure className="mb-4">
|
||||
{image != null ? <img className="img-fluid rounded" {...image} /> : <></>}
|
||||
</figure>
|
||||
</>
|
||||
}
|
||||
|
||||
export {
|
||||
TitleSection
|
||||
}
|
||||
@ -4,63 +4,57 @@ import { useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
import { actionCreators as blogItemActionCreators } from '../../../store/reducers/BlogItem'
|
||||
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
|
||||
|
||||
import { CommentsSection } from '../../../components/Comments'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
|
||||
|
||||
import { ApplicationState } from '../../../store'
|
||||
|
||||
import { TitleSection, ITitleSection, ITitleComponent } from './TitleSection'
|
||||
import { CommentsComponent, ICommentsSection, ICommentsComponent } from './CommentsComponent'
|
||||
import { IHeader } from '../../../interfaces'
|
||||
import { dateFormat } from '../../../functions'
|
||||
import { ImageModel } from '../../../models'
|
||||
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
interface BlogItemTitle {
|
||||
title?: string,
|
||||
postedOnBy? :string,
|
||||
badges? : string[],
|
||||
image?: ImageModel
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
const BlogTitleSection: FC<BlogItemTitle> = ({
|
||||
title = "",
|
||||
postedOnBy = "",
|
||||
badges = [],
|
||||
image = {
|
||||
src: "",
|
||||
alt: ""
|
||||
}
|
||||
}) => {
|
||||
|
||||
nickName: string
|
||||
}
|
||||
export interface IBlogItem {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText?: string,
|
||||
text?: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
return <>
|
||||
<header className="mb-4">
|
||||
<h1 className="fw-bolder mb-1">{title}</h1>
|
||||
<div className="text-muted fst-italic mb-2">{postedOnBy ? postedOnBy : ''}</div>
|
||||
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
{badges ? badges.map((badge, index) => <a key={index} className="badge bg-secondary text-decoration-none link-light" href="#!">{badge}</a>) : <></>}
|
||||
</header>
|
||||
|
||||
<figure className="mb-4">
|
||||
<img className="img-fluid rounded" {...image} />
|
||||
</figure>
|
||||
</>
|
||||
}
|
||||
|
||||
export interface IBlogItemPage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
commentsSection: ICommentsSection
|
||||
}
|
||||
|
||||
export interface IBlogItemComponent extends IBlogItemPage {}
|
||||
|
||||
const BlogItem : FC<IBlogItemComponent> = () => {
|
||||
const BlogItem = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, blogItem, comments } = useSelector((state: ApplicationState) => state)
|
||||
const { titleSection, commentsSection } = content.blogItem
|
||||
const { content, blogItem } = useSelector((state: ApplicationState) => state)
|
||||
const page = content?.blogItem
|
||||
|
||||
useEffect(() => {
|
||||
if(params?.slug)
|
||||
@ -71,32 +65,40 @@ const BlogItem : FC<IBlogItemComponent> = () => {
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const blogItemTitle: ITitleComponent = {
|
||||
useEffect(() => {
|
||||
blogItem?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [blogItem?.isLoading])
|
||||
|
||||
const blogItemTitle: BlogItemTitle = {
|
||||
title: blogItem?.title,
|
||||
text: titleSection?.text,
|
||||
postedOnBy: page?.titleSection?.postedOnBy,
|
||||
badges: blogItem?.badges,
|
||||
image: blogItem?.image
|
||||
}
|
||||
|
||||
if(blogItemTitle.text && blogItem?.created)
|
||||
blogItemTitle.text = blogItemTitle.text?.replace('{date}', dateFormat(blogItem.created))
|
||||
if(blogItemTitle.postedOnBy && blogItem?.created)
|
||||
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{date}', dateFormat(blogItem.created))
|
||||
|
||||
if(blogItemTitle.text && blogItem?.author)
|
||||
blogItemTitle.text = blogItemTitle.text?.replace('{nickName}', dateFormat(blogItem.author.nickName))
|
||||
if(blogItemTitle.postedOnBy && blogItem?.author)
|
||||
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{nickName}', dateFormat(blogItem.author.nickName))
|
||||
|
||||
return <Container fluid className="mt-5">
|
||||
<Row>
|
||||
<Col lg="8">
|
||||
|
||||
<article>
|
||||
<TitleSection {...blogItemTitle} />
|
||||
<BlogTitleSection {...blogItemTitle} />
|
||||
<section className="mb-5" dangerouslySetInnerHTML={{ __html: blogItem?.text ? blogItem.text : '' }}></section>
|
||||
</article>
|
||||
|
||||
<CommentsComponent {...{
|
||||
...commentsSection,
|
||||
...comments
|
||||
} as ICommentsComponent} />
|
||||
<CommentsSection {...{
|
||||
staticContent: page?.commentsSection,
|
||||
items: blogItem?.comments
|
||||
}} />
|
||||
</Col>
|
||||
|
||||
<Col lg="4">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
@ -8,9 +8,7 @@ interface IReduxState {
|
||||
counter: CounterState
|
||||
}
|
||||
|
||||
export interface ICounterComponent {}
|
||||
|
||||
const Counter : FC<ICounterComponent> = () => {
|
||||
const Counter = () => {
|
||||
const dispatch = useDispatch()
|
||||
const counterState = useSelector((state: IReduxState) => state.counter)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// React
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
@ -15,9 +15,7 @@ type IParams = {
|
||||
startDateIndex: string
|
||||
}
|
||||
|
||||
export interface IFetchDataComponent {}
|
||||
|
||||
const FetchData : FC<IFetchDataComponent> = () => {
|
||||
const FetchData = () => {
|
||||
const location = useLocation()
|
||||
const params = useParams<IParams>()
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import React, { FC } from "react"
|
||||
import { Col, Container, Row } from "reactstrap"
|
||||
|
||||
interface IEmail {
|
||||
title: string,
|
||||
placeHolder: string
|
||||
}
|
||||
|
||||
export interface ICallToActionSection {
|
||||
title: string,
|
||||
text: string,
|
||||
privacyDisclaimer: string,
|
||||
email: IEmail
|
||||
}
|
||||
|
||||
const CallToActionSection: FC<ICallToActionSection> = (props) => {
|
||||
|
||||
const { title, text, privacyDisclaimer, email } = props
|
||||
|
||||
return <section className="py-5">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<aside className="bg-primary bg-gradient rounded-3 p-4 p-sm-5 mt-5">
|
||||
<div className="d-flex align-items-center justify-content-between flex-column flex-xl-row text-center text-xl-start">
|
||||
<div className="mb-4 mb-xl-0">
|
||||
<div className="fs-3 fw-bold text-white">{title}</div>
|
||||
<div className="text-white-50">{text}</div>
|
||||
</div>
|
||||
<div className="ms-xl-4">
|
||||
<div className="input-group mb-2">
|
||||
<input className="form-control" type="text" placeholder={email.placeHolder} aria-label={email.placeHolder} aria-describedby="button-newsletter" />
|
||||
<button className="btn btn-outline-light" id="button-newsletter" type="button">{email.title ? email.title : ''}</button>
|
||||
</div>
|
||||
<div className="small text-white-50">{privacyDisclaimer}</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
export {
|
||||
CallToActionSection
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
// Reactstrap
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
import { dateFormat } from '../../functions'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage,
|
||||
nickName: string
|
||||
}
|
||||
|
||||
export interface IFeaturedBlogItem {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
readTime: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
export interface IFeaturedBlogsSection {
|
||||
title: string,
|
||||
text: string,
|
||||
readTime: string
|
||||
}
|
||||
|
||||
export interface IFeaturedBlogsFull extends IFeaturedBlogsSection {
|
||||
items?: IFeaturedBlogItem []
|
||||
}
|
||||
|
||||
const FeaturedBlogsSection: FC<IFeaturedBlogsFull> = (props) => {
|
||||
|
||||
const { title, text, items = [] } = props
|
||||
|
||||
const readTimeString = (itemCreated: string, itemReadTime: number) : string => {
|
||||
let { readTime } = props
|
||||
|
||||
if(readTime && itemCreated && itemReadTime)
|
||||
readTime = readTime?.replace('{date}', dateFormat(itemCreated))
|
||||
.replace('{readTime}', `${itemReadTime}`)
|
||||
|
||||
return readTime
|
||||
}
|
||||
|
||||
return <section className="py-5">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<Row className="gx-5 justify-content-center">
|
||||
<Col className="lg-8 xl-6">
|
||||
<div className="text-center">
|
||||
<h2 className="fw-bolder">{title}</h2>
|
||||
<p className="lead fw-normal text-muted mb-5" dangerouslySetInnerHTML={{ __html: text }}></p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="gx-5">
|
||||
{items.map((item, index) => <Col key={index} className="lg-4 mb-5">
|
||||
<Card className="h-100 shadow border-0">
|
||||
<CardImg top {...item.image} />
|
||||
<CardBody className="p-4">
|
||||
<div className="badge bg-primary bg-gradient rounded-pill mb-2">{item.badges}</div>
|
||||
<Link className="text-decoration-none link-dark stretched-link" to="#!">
|
||||
<h5 className="card-title mb-3">{item.title}</h5>
|
||||
</Link>
|
||||
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: item.shortText }}></p>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
|
||||
<div className="d-flex align-items-end justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
<img className="rounded-circle me-3" {...item.author.image} />
|
||||
<div className="small">
|
||||
<div className="fw-bold">{item.author.nickName}</div>
|
||||
<div className="text-muted" dangerouslySetInnerHTML={{ __html: readTimeString(item.created, item.readTime)}}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>)}
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
export {
|
||||
FeaturedBlogsSection
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
|
||||
// Reactstrap
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
import { FeatherIcon } from '../../components/FeatherIcons'
|
||||
|
||||
//
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
interface IFeatureItem {
|
||||
icon: string,
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface IFeaturesSection {
|
||||
title: string,
|
||||
items?: IFeatureItem []
|
||||
}
|
||||
|
||||
const FeaturesSection: FC<IFeaturesSection> = (props) => {
|
||||
|
||||
const { title, items = [] } = props
|
||||
|
||||
return <section className="py-5" id="features">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<Row className="gx-5">
|
||||
<Col className="lg-4 mb-5 mb-lg-0">
|
||||
<h2 className="fw-bolder mb-0">{title}</h2>
|
||||
</Col>
|
||||
<Col className="lg-8">
|
||||
<Row className="gx-5 cols-1 cols-md-2">
|
||||
{items ? items.map((item, index) => <Col key={index} className="mb-5 h-100">
|
||||
<div className={`${style.feature} bg-primary bg-gradient text-white rounded-3 mb-3`}>
|
||||
<FeatherIcon icon={item.icon} />
|
||||
</div>
|
||||
<h2 className="h5">{item.title}</h2>
|
||||
<p className="mb-0" dangerouslySetInnerHTML={{ __html: item.text }}></p>
|
||||
</Col>) : ''}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
export {
|
||||
FeaturesSection
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import React, { FC } from "react"
|
||||
import { Col, Container, Row } from "reactstrap"
|
||||
|
||||
interface IImage {
|
||||
src: string
|
||||
alt?: string
|
||||
}
|
||||
|
||||
interface IReviewer {
|
||||
image: IImage,
|
||||
fullName: string,
|
||||
position: string
|
||||
}
|
||||
|
||||
interface ITestimonialItem {
|
||||
text: string,
|
||||
reviewer: IReviewer
|
||||
}
|
||||
|
||||
export interface ITestimonialsSection {
|
||||
items: ITestimonialItem []
|
||||
}
|
||||
|
||||
const TestimonialsSection: FC<ITestimonialsSection> = (props) => {
|
||||
const item = props.items[0]
|
||||
|
||||
return <section className="py-5 bg-light">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<Row className="gx-5 justify-content-center">
|
||||
<Col className="lg-10 xl-7">
|
||||
<div className="text-center">
|
||||
<div className="fs-4 mb-4 fst-italic" dangerouslySetInnerHTML={{ __html: item.text }}></div>
|
||||
<div className="d-flex align-items-center justify-content-center">
|
||||
<img className="rounded-circle me-3" {...item.reviewer.image} />
|
||||
<div className="fw-bold">{item.reviewer.fullName}<span className="fw-bold text-primary mx-1">/</span>{item.reviewer.position}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
export {
|
||||
TestimonialsSection
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import React, { FC } from "react"
|
||||
import { Col, Container, Row } from "reactstrap"
|
||||
|
||||
export interface ITitleSection {
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
const TitleSection : FC<ITitleSection> = (props) => {
|
||||
|
||||
const { title, text } = props
|
||||
|
||||
return <header className="py-5 bg-dark">
|
||||
<Container fluid className="px-5">
|
||||
<Row className="gx-5 align-items-center justify-content-center">
|
||||
<Col className="lg-8 xl-7 xxl-6">
|
||||
<div className="my-5 text-center text-xl-start">
|
||||
<h1 className="display-5 fw-bolder text-white mb-2">{title}</h1>
|
||||
<span className="lead fw-normal text-white-50 mb-4" dangerouslySetInnerHTML={{ __html: text }}>
|
||||
|
||||
</span>
|
||||
<div className="d-grid gap-3 d-sm-flex justify-content-sm-center justify-content-xl-start">
|
||||
<a className="btn btn-primary btn-lg px-4 me-sm-3" href="#features">Get Started</a>
|
||||
<a className="btn btn-outline-light btn-lg px-4" href="#!">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<div className="col-xl-5 col-xxl-6 d-none d-xl-block text-center"><img className="img-fluid rounded-3 my-5" src={`${process.env.REACT_APP_FRONTEND}/Image/600x400/343a40/6c757d`} alt="..." /></div>
|
||||
</Row>
|
||||
</Container>
|
||||
</header>
|
||||
}
|
||||
|
||||
export {
|
||||
TitleSection
|
||||
}
|
||||
@ -1,61 +1,240 @@
|
||||
// React
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../store'
|
||||
import { actionCreators as loaderActionCreators } from '../../store/reducers/Loader'
|
||||
import { actionCreators as blogFeaturedActionCreators } from '../../store/reducers/BlogFeatured'
|
||||
import { actionCreators as headerActionCreators } from '../../store/reducers/Header'
|
||||
|
||||
// Reactstrap
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
|
||||
// Models (interfaces)
|
||||
import { CallToActionSectionModel, FeaturedBlogsSectionModel, FeaturesSectionModel, TestimonialsSectionModel, TitleSectionModel } from '../../models/pageSections'
|
||||
import { BlogItemModel, FeatureModel, HeaderModel, TestimonialModel } from '../../models'
|
||||
|
||||
import { IHeader } from '../../interfaces'
|
||||
import { TitleSection, ITitleSection } from './TitleSection'
|
||||
import { FeaturesSection, IFeaturesSection } from './FeaturesSection'
|
||||
import { TestimonialsSection, ITestimonialsSection } from './TestimonialsSection'
|
||||
import { FeaturedBlogsSection, IFeaturedBlogsFull, IFeaturedBlogsSection } from './FeaturedBlogsSection'
|
||||
import { CallToActionSection, ICallToActionSection } from './CallToActionSection'
|
||||
// Custom components
|
||||
import { FeatherIcon } from '../../components/FeatherIcons'
|
||||
|
||||
// Functions
|
||||
import { dateFormat } from '../../functions'
|
||||
|
||||
export interface IHomePage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
featuresSection: IFeaturesSection,
|
||||
testimonialsSection: ITestimonialsSection,
|
||||
featuredBlogsSection: IFeaturedBlogsSection,
|
||||
callToActionSection: ICallToActionSection
|
||||
// CSS Modules
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
const TitleSection : FC<TitleSectionModel> = ({
|
||||
title = "",
|
||||
text = ""
|
||||
}) => {
|
||||
return <header className="py-5 bg-dark">
|
||||
<Container fluid className="px-5">
|
||||
<Row className="gx-5 align-items-center justify-content-center">
|
||||
<Col className="lg-8 xl-7 xxl-6">
|
||||
<div className="my-5 text-center text-xl-start">
|
||||
<h1 className="display-5 fw-bolder text-white mb-2">{title}</h1>
|
||||
<span className="lead fw-normal text-white-50 mb-4" dangerouslySetInnerHTML={{ __html: text }}>
|
||||
|
||||
</span>
|
||||
<div className="d-grid gap-3 d-sm-flex justify-content-sm-center justify-content-xl-start">
|
||||
<a className="btn btn-primary btn-lg px-4 me-sm-3" href="#features">Get Started</a>
|
||||
<a className="btn btn-outline-light btn-lg px-4" href="#!">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<div className="col-xl-5 col-xxl-6 d-none d-xl-block text-center"><img className="img-fluid rounded-3 my-5" src={`${process.env.REACT_APP_API}/Image/600x400/343a40/6c757d`} alt="..." /></div>
|
||||
</Row>
|
||||
</Container>
|
||||
</header>
|
||||
}
|
||||
|
||||
export interface IHomePageComponent extends IHomePage { }
|
||||
interface Fetures {
|
||||
title?: string,
|
||||
items?: FeatureModel []
|
||||
}
|
||||
|
||||
const Home : FC<IHomePageComponent> = () => {
|
||||
const FeaturesSection: FC<Fetures> = ({
|
||||
title = "",
|
||||
items = []
|
||||
}) => {
|
||||
return <section className="py-5" id="features">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<Row className="gx-5">
|
||||
<Col className="lg-4 mb-5 mb-lg-0">
|
||||
<h2 className="fw-bolder mb-0">{title ? title : ''}</h2>
|
||||
</Col>
|
||||
<Col className="lg-8">
|
||||
<Row className="gx-5 cols-1 cols-md-2">
|
||||
{items ? items.map((item, index) => <Col key={index} className="mb-5 h-100">
|
||||
<div className={`${style.feature} bg-primary bg-gradient text-white rounded-3 mb-3`}>
|
||||
<FeatherIcon icon={item.icon} />
|
||||
</div>
|
||||
<h2 className="h5">{item.title}</h2>
|
||||
<p className="mb-0" dangerouslySetInnerHTML={{ __html: item.text }}></p>
|
||||
</Col>) : ''}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
interface Testimonials {
|
||||
items?: TestimonialModel []
|
||||
}
|
||||
|
||||
const TestimonialsSection: FC<Testimonials> = ({
|
||||
items = []
|
||||
}) => {
|
||||
const item = items[0]
|
||||
|
||||
return <section className="py-5 bg-light">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<Row className="gx-5 justify-content-center">
|
||||
<Col className="lg-10 xl-7">
|
||||
{ item
|
||||
? <div className="text-center">
|
||||
<div className="fs-4 mb-4 fst-italic" dangerouslySetInnerHTML={{ __html: item.text }}></div>
|
||||
<div className="d-flex align-items-center justify-content-center">
|
||||
<img className="rounded-circle me-3" {...item.reviewer.image} />
|
||||
<div className="fw-bold">{item.reviewer.fullName}<span className="fw-bold text-primary mx-1">/</span>{item.reviewer.position}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: '' }
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
}
|
||||
|
||||
interface FeaturedBlogs extends FeaturedBlogsSectionModel {
|
||||
items?: BlogItemModel []
|
||||
}
|
||||
const FeaturedBlogsSection: FC<FeaturedBlogs> = ({
|
||||
title = "",
|
||||
text = "",
|
||||
items = [] }) => <section className="py-5">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<Row className="gx-5 justify-content-center">
|
||||
<Col className="lg-8 xl-6">
|
||||
<div className="text-center">
|
||||
<h2 className="fw-bolder">{title}</h2>
|
||||
<p className="lead fw-normal text-muted mb-5" dangerouslySetInnerHTML={{ __html: text }}></p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="gx-5">
|
||||
{items.map((item, index) => <Col key={index} className="lg-4 mb-5">
|
||||
<Card className="h-100 shadow border-0">
|
||||
<CardImg top {...item.image} />
|
||||
<CardBody className="p-4">
|
||||
<div className="badge bg-primary bg-gradient rounded-pill mb-2">{item.badges}</div>
|
||||
<Link className="text-decoration-none link-dark stretched-link" to="#!">
|
||||
<h5 className="card-title mb-3">{item.title}</h5>
|
||||
</Link>
|
||||
<p className="card-text mb-0" dangerouslySetInnerHTML={{ __html: text ? text : '' }}></p>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 bg-transparent border-top-0">
|
||||
<div className="d-flex align-items-end justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
<img className="rounded-circle me-3" {...item.author.image} />
|
||||
<div className="small">
|
||||
<div className="fw-bold">{item.author.nickName}</div>
|
||||
<div className="text-muted">{dateFormat(item.created)} · {item.readTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>)}
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
const CallToActionSection: FC<CallToActionSectionModel> = ({
|
||||
title,
|
||||
text,
|
||||
privacyDisclaimer,
|
||||
email = {
|
||||
placeHolder: "",
|
||||
title: ""
|
||||
}
|
||||
}) => {
|
||||
|
||||
return <section className="py-5">
|
||||
<Container fluid className="px-5 my-5">
|
||||
<aside className="bg-primary bg-gradient rounded-3 p-4 p-sm-5 mt-5">
|
||||
<div className="d-flex align-items-center justify-content-between flex-column flex-xl-row text-center text-xl-start">
|
||||
<div className="mb-4 mb-xl-0">
|
||||
<div className="fs-3 fw-bold text-white">{title}</div>
|
||||
<div className="text-white-50">{text}</div>
|
||||
</div>
|
||||
<div className="ms-xl-4">
|
||||
<div className="input-group mb-2">
|
||||
<input className="form-control" type="text" placeholder={email.placeHolder ? email.placeHolder : ''} aria-label={email.placeHolder ? email.placeHolder : ''} aria-describedby="button-newsletter" />
|
||||
<button className="btn btn-outline-light" id="button-newsletter" type="button">{email.title ? email.title : ''}</button>
|
||||
</div>
|
||||
<div className="small text-white-50">{privacyDisclaimer}</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
const Home = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { content, blogFeatured } = useSelector((state: ApplicationState) => state)
|
||||
const page = content?.homePage
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(blogFeaturedActionCreators.requestBlogFeatured())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(headerActionCreators.updateHeader(content?.homePage.header))
|
||||
}, [content?.homePage.header])
|
||||
content?.isLoading || blogFeatured?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading, blogFeatured?.isLoading])
|
||||
|
||||
if(content?.homePage) {
|
||||
const { titleSection, featuresSection, testimonialsSection, featuredBlogsSection, callToActionSection } = content.homePage
|
||||
const {
|
||||
header = {},
|
||||
titleSection = {
|
||||
title: "",
|
||||
text: ""
|
||||
},
|
||||
featuresSection = {},
|
||||
testimonialsSection = {},
|
||||
featuredBlogsSection = {},
|
||||
callToActionSection = {
|
||||
title: "",
|
||||
text: "",
|
||||
privacyDisclaimer: "",
|
||||
email: {
|
||||
placeHolder: "",
|
||||
title: ""
|
||||
}
|
||||
}
|
||||
} = content?.homePage ? content.homePage : {}
|
||||
|
||||
return <>
|
||||
<TitleSection {...titleSection} />
|
||||
<FeaturesSection {...featuresSection} />
|
||||
<TestimonialsSection {...testimonialsSection} />
|
||||
<FeaturedBlogsSection {...{
|
||||
items: blogFeatured?.items,
|
||||
...featuredBlogsSection} as IFeaturedBlogsFull} />
|
||||
<CallToActionSection {...callToActionSection} />
|
||||
</>
|
||||
}
|
||||
else {
|
||||
return <></>
|
||||
}
|
||||
useEffect(() => {
|
||||
dispatch(headerActionCreators.updateHeader(header as HeaderModel))
|
||||
}, [header])
|
||||
|
||||
|
||||
return <>
|
||||
<TitleSection {...titleSection} />
|
||||
<FeaturesSection {...featuresSection} />
|
||||
<TestimonialsSection {...testimonialsSection} />
|
||||
<FeaturedBlogsSection items={blogFeatured?.items} {...featuredBlogsSection} />
|
||||
<CallToActionSection {...callToActionSection} />
|
||||
</>
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// React
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
import { actionCreators as shopCartActionCreators } from '../../../store/reducers/ShopCart'
|
||||
import { ApplicationState } from '../../../store'
|
||||
|
||||
@ -13,32 +14,7 @@ import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import style from './scss/style.module.scss'
|
||||
import { ReservedWords } from '../../../enumerations'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
export interface IShopCartItem {
|
||||
slug: string
|
||||
sku: string,
|
||||
image: IImage,
|
||||
title: string,
|
||||
brandName: string,
|
||||
shortText: string,
|
||||
created: string,
|
||||
price: number,
|
||||
newPrice?: number,
|
||||
quantity: number
|
||||
}
|
||||
|
||||
|
||||
export interface IShopCartPage {
|
||||
|
||||
}
|
||||
|
||||
export interface IShopCartComponent {}
|
||||
|
||||
const Cart : FC<IShopCartComponent> = () => {
|
||||
const Cart = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { content, shopCart } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
@ -47,11 +23,40 @@ const Cart : FC<IShopCartComponent> = () => {
|
||||
dispatch(shopCartActionCreators.requestCart({ pathParams: { userId: "fdc5aa50-ee68-4bae-a8e6-b8ae2c258f60" }}))
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading])
|
||||
|
||||
const {
|
||||
currencySymbol = ""
|
||||
} = content?.localization ? content.localization : {}
|
||||
|
||||
//const { titleSection, productsSection } = content.shopCart
|
||||
const {
|
||||
titleSection = {
|
||||
title: "",
|
||||
text: ""
|
||||
},
|
||||
productsSection = {
|
||||
product: "",
|
||||
price: "",
|
||||
quantity: "",
|
||||
subtotal: "",
|
||||
continueShopping: {
|
||||
target: "!#",
|
||||
anchorText: ""
|
||||
},
|
||||
checkout: {
|
||||
target: "!#",
|
||||
anchorText: ""
|
||||
}
|
||||
}
|
||||
|
||||
} = content?.shopCart ? content.shopCart : {}
|
||||
|
||||
const [subtotal, setSubtotal] = useState<number>(0)
|
||||
|
||||
@ -81,7 +86,7 @@ const Cart : FC<IShopCartComponent> = () => {
|
||||
}
|
||||
|
||||
return <Container fluid>
|
||||
{/* <section className="pt-5 pb-5">
|
||||
<section className="pt-5 pb-5">
|
||||
<div className="row w-100">
|
||||
<div className="col-lg-12 col-md-12 col-12">
|
||||
<h3 className="display-5 mb-2 text-center">{titleSection.title}</h3>
|
||||
@ -100,7 +105,7 @@ const Cart : FC<IShopCartComponent> = () => {
|
||||
<td data-th="Product">
|
||||
<div className="row">
|
||||
<div className="col-md-3 text-left">
|
||||
<img src={`${process.env.REACT_APP_FRONTEND}/Image/250x250/ced4da/6c757d`} alt="" className="img-fluid d-none d-md-block rounded mb-2 shadow" />
|
||||
<img src={`${process.env.REACT_APP_API}/Image/250x250/ced4da/6c757d`} alt="" className="img-fluid d-none d-md-block rounded mb-2 shadow" />
|
||||
</div>
|
||||
<div className="col-md-9 text-left mt-sm-2">
|
||||
<h4>{item.title}</h4>
|
||||
@ -142,7 +147,7 @@ const Cart : FC<IShopCartComponent> = () => {
|
||||
<Link to={productsSection.continueShopping.target}><FeatherIcon icon="arrow-left" /> {productsSection.continueShopping.anchorText}</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</section> */}
|
||||
</section>
|
||||
</Container>
|
||||
}
|
||||
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
// Reducers
|
||||
import { actionCreators as shopCatalogActionCreators } from '../../../store/reducers/ShopCatalog'
|
||||
|
||||
// Reactstrap
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
|
||||
// Components
|
||||
import { FeatherRating } from '../../../components/FeatherRating'
|
||||
import { SSRPagination, ISSRPaginationComponent } from '../../../components/Pagination'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
export interface IShopItem {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText?: string,
|
||||
text?: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
images?: IImage [],
|
||||
sku: string,
|
||||
brandName: string,
|
||||
rating?: number,
|
||||
price: number,
|
||||
newPrice?: number,
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
interface IShopItems {
|
||||
path: string
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: IShopItem []
|
||||
}
|
||||
|
||||
export interface IShopItemsSection {
|
||||
addToCart: string
|
||||
}
|
||||
|
||||
export interface IShopItemComponent extends IShopItemsSection, IShopItems {
|
||||
currencySymbol: string,
|
||||
}
|
||||
|
||||
const ShopItemsSection: FC<IShopItemComponent> = (props) => {
|
||||
|
||||
const { currencySymbol, addToCart, path = "", totalPages, currentPage, items = [] } = props
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return <>
|
||||
{items.map((item, index) => <Col key={index} className="lg-6 mb-3">
|
||||
<Card className="h-100">
|
||||
<div className="position-absolute" style={{top: "0.5rem", right: "0.5rem"}}>
|
||||
{(item?.badges ? item.badges : []).map((badge, index) => <div key={index} className="badge bg-dark text-white" style={{marginLeft: "0.5rem"}}>{badge}</div>) }
|
||||
</div>
|
||||
|
||||
<Link to={`${path}/${currentPage}/${item.slug}`}>
|
||||
<CardImg top {...item.image} />
|
||||
</Link>
|
||||
|
||||
<CardBody>
|
||||
<div className="text-center">
|
||||
<h5 className="fw-bolder">{item.title}</h5>
|
||||
|
||||
<FeatherRating {...{
|
||||
value: item?.rating ? item.rating : 0
|
||||
}} />
|
||||
|
||||
{item.newPrice
|
||||
? <><span className="text-muted text-decoration-line-through">{currencySymbol}{item.price.toFixed(2)}</span> <span>{currencySymbol}{item.newPrice.toFixed(2)}</span></>
|
||||
: <span>{currencySymbol}{item.price.toFixed(2)}</span>}
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 border-top-0 bg-transparent">
|
||||
<div className="text-center"><a className="btn btn-outline-dark mt-auto" href="#">{addToCart}</a></div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>)}
|
||||
|
||||
|
||||
<SSRPagination {...{
|
||||
totalPages: totalPages,
|
||||
currentPage: currentPage,
|
||||
// onClick: (nextPage) => {
|
||||
// dispatch(shopCatalogActionCreators.requestShopCatalog({
|
||||
// searchParams: {
|
||||
// currentPage: nextPage + ""
|
||||
// }
|
||||
// }))
|
||||
|
||||
// navigate(`${path}/${nextPage}`)
|
||||
// }
|
||||
linksPath: path
|
||||
} as ISSRPaginationComponent} />
|
||||
</>
|
||||
}
|
||||
|
||||
export {
|
||||
ShopItemsSection
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
// React
|
||||
import React, { FC } from 'react'
|
||||
|
||||
// Reactstrap
|
||||
import { Container } from 'reactstrap'
|
||||
|
||||
export interface ITitleSection {
|
||||
title: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
interface ITitleComponent extends ITitleSection { }
|
||||
|
||||
const TitleSection: FC<ITitleComponent> = (props) => {
|
||||
|
||||
const { title, text } = props
|
||||
|
||||
return <header className="py-5 bg-dark border-bottom mb-4">
|
||||
<Container fluid>
|
||||
<div className="text-center text-white my-5">
|
||||
<h1 className="display-4 fw-bolder">{title}</h1>
|
||||
<p className="lead fw-normal text-white-50 mb-0">{text}</p>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
}
|
||||
|
||||
export {
|
||||
TitleSection
|
||||
}
|
||||
@ -1,103 +1,156 @@
|
||||
// React
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../store'
|
||||
|
||||
// Reducers
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
import { actionCreators as shopCatalogActionCreators } from '../../../store/reducers/ShopCatalog'
|
||||
import { actionCreators as shopCategoriesActionCreators } from '../../../store/reducers/ShopCategories'
|
||||
import { actionCreators as shopFeaturedActionCreators } from '../../../store/reducers/ShopFeatured'
|
||||
|
||||
// Reactstrap
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
|
||||
// Components
|
||||
import { TitleSection, ITitleSection } from './TitleSection'
|
||||
import { ShopItemsSection, IShopItemsSection, IShopItemComponent } from './ShopItemsSection'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
// Models (interfaces)
|
||||
import { ShopItemModel } from '../../../models'
|
||||
import { TitleSectionModel } from '../../../models/pageSections'
|
||||
|
||||
// Interfaces
|
||||
import { IHeader } from '../../../interfaces'
|
||||
import { cloneObject } from '../../../functions'
|
||||
// Custom components
|
||||
import { FeatherRating } from '../../../components/FeatherRating'
|
||||
import { Pagination } from '../../../components/Pagination'
|
||||
|
||||
export interface IShopCatalogPage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
shopItemsSection: IShopItemsSection,
|
||||
// Functions
|
||||
import { findRoutes } from '../../../functions'
|
||||
|
||||
const TitleSection: FC<TitleSectionModel> = ({
|
||||
title = "",
|
||||
text = ""
|
||||
}) => <header className="bg-dark py-5">
|
||||
<Container fluid className="px-4 px-lg-5 my-5">
|
||||
<div className="text-center text-white">
|
||||
<h1 className="display-4 fw-bolder">{title}</h1>
|
||||
<p className="lead fw-normal text-white-50 mb-0">{text}</p>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
|
||||
|
||||
interface ShopItems {
|
||||
currencySymbol: string,
|
||||
addToCart: string,
|
||||
path: string
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: ShopItemModel []
|
||||
}
|
||||
|
||||
export interface IShopCatalogComponent extends IShopCatalogPage { }
|
||||
const ShopItemsSection: FC<ShopItems> = ({
|
||||
currencySymbol = "",
|
||||
addToCart = "",
|
||||
path = "",
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
items = []
|
||||
}) => {
|
||||
|
||||
const ShopCatalog : FC<IShopCatalogComponent> = () => {
|
||||
const location = useLocation()
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return <section className="py-5">
|
||||
<Container fluid className="px-4 px-lg-5 mt-5">
|
||||
<Row className="gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4 justify-content-center">
|
||||
{items.map((item, index) => <Col key={index} className="mb-5">
|
||||
<Card className="h-100">
|
||||
<div className="position-absolute" style={{top: "0.5rem", right: "0.5rem"}}>
|
||||
{(item?.badges ? item.badges : []).map((badge, index) => <div key={index} className="badge bg-dark text-white" style={{marginLeft: "0.5rem"}}>{badge}</div>) }
|
||||
</div>
|
||||
|
||||
<Link to={`${path}/${currentPage}/${item.slug}`}>
|
||||
<CardImg top {...item.image} />
|
||||
</Link>
|
||||
|
||||
<CardBody>
|
||||
<div className="text-center">
|
||||
<h5 className="fw-bolder">{item.title}</h5>
|
||||
|
||||
<FeatherRating {...{
|
||||
value: item?.rating ? item.rating : 0
|
||||
}} />
|
||||
|
||||
{item.newPrice
|
||||
? <><span className="text-muted text-decoration-line-through">{currencySymbol}{item.price.toFixed(2)}</span> <span>{currencySymbol}{item.newPrice.toFixed(2)}</span></>
|
||||
: <span>{currencySymbol}{item.price.toFixed(2)}</span>}
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="p-4 pt-0 border-top-0 bg-transparent">
|
||||
<div className="text-center"><a className="btn btn-outline-dark mt-auto" href="#">{addToCart}</a></div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>)}
|
||||
</Row>
|
||||
|
||||
<Pagination {...{
|
||||
totalPages: totalPages,
|
||||
currentPage: currentPage,
|
||||
onClick: (nextPage) => {
|
||||
dispatch(shopCatalogActionCreators.requestShopCatalog({
|
||||
searchParams: {
|
||||
currentPage: nextPage + ""
|
||||
}
|
||||
}))
|
||||
|
||||
navigate(`${path}/${nextPage}`)
|
||||
}
|
||||
}} />
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
|
||||
|
||||
const ShopCatalog = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, shopCatalog, shopCategories } = useSelector((state: ApplicationState) => state)
|
||||
const { content, shopCatalog } = useSelector((state: ApplicationState) => state)
|
||||
const page = content?.shopCatalog
|
||||
const path = findRoutes(content?.routes, 'ShopCatalog')[0]?.targets[0]
|
||||
|
||||
const { dateFormat, timeFormat, currencySymbol } = content.localization
|
||||
const { header, titleSection, shopItemsSection } = content.shopCatalog
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
dispatch(shopCategoriesActionCreators.requestShopCategories())
|
||||
dispatch(shopFeaturedActionCreators.requestShopFeatured())
|
||||
}, [])
|
||||
const {
|
||||
currencySymbol = ""
|
||||
} = content?.localization ? content.localization : {}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(shopCatalogActionCreators.requestShopCatalog({
|
||||
searchParams: {
|
||||
category: params?.category,
|
||||
currentPage: params?.page
|
||||
currentPage: params?.page ? params.page : "1"
|
||||
}
|
||||
}))
|
||||
}, [params.category, params.page])
|
||||
}, [])
|
||||
|
||||
const updateShopCategories = () => {
|
||||
const newShopCategoies = cloneObject(shopCategories)
|
||||
useEffect(() => {
|
||||
shopCatalog?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [shopCatalog?.isLoading])
|
||||
|
||||
newShopCategoies.items = newShopCategoies.items.map(item => {
|
||||
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}`
|
||||
return item
|
||||
})
|
||||
const {
|
||||
shopItemsSection = {
|
||||
addToCart: ""
|
||||
}
|
||||
} = content?.shopCatalog ? content?.shopCatalog : {}
|
||||
|
||||
return newShopCategoies
|
||||
}
|
||||
|
||||
const updateShopLinks = () => {
|
||||
return location.pathname.split('/').slice(0, 3).join('/')
|
||||
const shopItems: ShopItems = {
|
||||
currencySymbol,
|
||||
path,
|
||||
...shopItemsSection,
|
||||
...shopCatalog
|
||||
}
|
||||
|
||||
return <>
|
||||
<TitleSection {...titleSection} />
|
||||
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col>
|
||||
<Row>
|
||||
<ShopItemsSection {...{
|
||||
currencySymbol,
|
||||
path: updateShopLinks(),
|
||||
...shopItemsSection,
|
||||
...shopCatalog
|
||||
} as IShopItemComponent} />
|
||||
</Row>
|
||||
</Col>
|
||||
<Col lg="3">
|
||||
<Search />
|
||||
<Categories {...updateShopCategories()} />
|
||||
<Empty/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<TitleSection {...page?.titleSection} />
|
||||
<ShopItemsSection {...shopItems} />
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
import React, { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { Container } from 'reactstrap'
|
||||
|
||||
// CSS Modules
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
export interface IShopCheckoutPage {
|
||||
|
||||
}
|
||||
|
||||
export interface IShopCheckoutComponent extends IShopCheckoutPage {}
|
||||
|
||||
const Checkout : FC<IShopCheckoutComponent> = () => {
|
||||
const Checkout = () => {
|
||||
return <Container fluid className={`py-5 ${style.container}`}>
|
||||
<main>
|
||||
<div className="text-center">
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Card, CardBody } from 'reactstrap'
|
||||
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
|
||||
export interface IComment {
|
||||
author: IAuthor,
|
||||
comment: string,
|
||||
responses?: IComment []
|
||||
}
|
||||
|
||||
export interface IComments {
|
||||
path?: string
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: IComment []
|
||||
}
|
||||
|
||||
export interface ICommentsSection {
|
||||
leaveComment: string
|
||||
}
|
||||
|
||||
export interface ICommentsComponent extends ICommentsSection, IComments { }
|
||||
|
||||
const CommentsComponent: FC<ICommentsComponent> = (props) => {
|
||||
|
||||
const { leaveComment, items = [] } = props
|
||||
|
||||
return <section className="mb-5">
|
||||
<Card className="card bg-light">
|
||||
<CardBody className="card-body">
|
||||
<form className="mb-4">
|
||||
<textarea className="form-control" rows={3} placeholder={leaveComment}></textarea>
|
||||
</form>
|
||||
|
||||
{items.map((comment, index) => <div key={index} className={`d-flex ${index < items.length - 1 ? 'mb-4' : ''}`}>
|
||||
<div className="flex-shrink-0">
|
||||
<img className="rounded-circle" {...comment.author.image} />
|
||||
</div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">{comment.author.nickName}</div>
|
||||
{comment.comment}
|
||||
|
||||
{comment.responses? comment.responses.map((response, index) => <div key={index} className="d-flex mt-4">
|
||||
<div className="flex-shrink-0">
|
||||
<img className="rounded-circle" {...response.author.image} />
|
||||
</div>
|
||||
<div className="ms-3">
|
||||
<div className="fw-bold">{response.author.nickName}</div>
|
||||
{response.comment}
|
||||
</div>
|
||||
</div>) : ''}
|
||||
</div>
|
||||
</div>)}
|
||||
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
CommentsComponent
|
||||
}
|
||||
@ -4,8 +4,8 @@ import { useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { ApplicationState } from '../../../store'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
import { actionCreators as shopItemActionCreators } from '../../../store/reducers/ShopItem'
|
||||
|
||||
// Reactstrap
|
||||
@ -13,71 +13,13 @@ import { Container } from 'reactstrap'
|
||||
|
||||
// Components
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import { IRelatedProductsComponent, IRelatedProductsSection, RelatedProducts } from './RelatedProducts'
|
||||
import { RelatedProducts } from '../RelatedProducts'
|
||||
|
||||
|
||||
import { IHeader } from '../../../interfaces'
|
||||
import { CommentsComponent, ICommentsComponent, ICommentsSection } from './CommentsComponent'
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
export interface IShopItem {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText?: string,
|
||||
text?: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
images?: IImage [],
|
||||
sku: string,
|
||||
brandName: string,
|
||||
rating?: number,
|
||||
price: number,
|
||||
newPrice?: number,
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
|
||||
interface IProductSection {
|
||||
title?: string
|
||||
text?: string
|
||||
|
||||
availableQuantity: string,
|
||||
addToCart: string
|
||||
}
|
||||
|
||||
export interface IShopItemPage {
|
||||
header: IHeader,
|
||||
productSection: IProductSection
|
||||
relatedProductsSection: IRelatedProductsSection,
|
||||
commentsSection: ICommentsSection
|
||||
}
|
||||
|
||||
export interface IShopItemComponent extends IShopItemPage {}
|
||||
|
||||
const ShopItem : FC<IShopItemComponent> = () => {
|
||||
const ShopItem : FC = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, shopItem, comments } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
|
||||
const { commentsSection } = content.shopItem
|
||||
const { content, shopItem } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const {
|
||||
currencySymbol = ""
|
||||
@ -99,6 +41,17 @@ const ShopItem : FC<IShopItemComponent> = () => {
|
||||
}))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading || shopItem?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading, shopItem?.isLoading])
|
||||
|
||||
|
||||
|
||||
|
||||
return <>
|
||||
<section className="py-5">
|
||||
<Container fluid className="px-4 px-lg-5 my-5">
|
||||
@ -128,15 +81,7 @@ const ShopItem : FC<IShopItemComponent> = () => {
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<RelatedProducts {...{
|
||||
|
||||
} as IRelatedProductsComponent} />
|
||||
|
||||
|
||||
<CommentsComponent {...{
|
||||
...commentsSection,
|
||||
...comments
|
||||
} as ICommentsComponent} />
|
||||
<RelatedProducts />
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import React, { FC, useEffect } from "react"
|
||||
// Reduc
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
import { ApplicationState } from "../../../store"
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
|
||||
// Reactstrap
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from "reactstrap"
|
||||
@ -11,73 +12,37 @@ import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from "reacts
|
||||
// Components
|
||||
import { FeatherRating } from "../../../components/FeatherRating"
|
||||
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
|
||||
export interface IRelatedProduct {
|
||||
id: string,
|
||||
slug: string,
|
||||
image: IImage,
|
||||
badges: string [],
|
||||
title: string,
|
||||
shortText?: string,
|
||||
text?: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
images?: IImage [],
|
||||
sku: string,
|
||||
brandName: string,
|
||||
rating?: number,
|
||||
price: number,
|
||||
newPrice?: number,
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
export interface IRelatedProducts {
|
||||
items?: IRelatedProduct []
|
||||
}
|
||||
|
||||
|
||||
export interface IRelatedProductsSection {
|
||||
title: string,
|
||||
addToCart: string
|
||||
}
|
||||
|
||||
|
||||
export interface IRelatedProductsComponent extends IRelatedProductsSection, IRelatedProducts {
|
||||
|
||||
}
|
||||
|
||||
|
||||
const RelatedProducts: FC<IRelatedProductsComponent> = (props) => {
|
||||
const RelatedProducts: FC = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, shopRelated } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const { currencySymbol } = content.localization
|
||||
const {
|
||||
currencySymbol = ""
|
||||
} = content?.localization ? content.localization : {}
|
||||
|
||||
const { relatedProductsSection } = content.shopItem
|
||||
const {
|
||||
relatedProductsSection = {
|
||||
title: "",
|
||||
addToCart: ""
|
||||
}
|
||||
} = content?.shopItem ? content?.shopItem : {}
|
||||
|
||||
const {
|
||||
items = []
|
||||
} = shopRelated ? shopRelated : {}
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading || shopRelated?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading, shopRelated?.isLoading])
|
||||
|
||||
return <section className="py-5 bg-light">
|
||||
<Container fluid className="px-4 px-lg-5">
|
||||
{/* <h2 className="fw-bolder mb-4">{relatedProductsSection.title}</h2>
|
||||
<h2 className="fw-bolder mb-4">{relatedProductsSection.title}</h2>
|
||||
|
||||
<Row className="gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4 justify-content-center">
|
||||
{items.map((item, index) => <Col key={index} className="mb-5">
|
||||
@ -104,7 +69,7 @@ const RelatedProducts: FC<IRelatedProductsComponent> = (props) => {
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>)}
|
||||
</Row> */}
|
||||
</Row>
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
@ -1,15 +1,14 @@
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
import { actionCreators as loaderActionCreators } from '../../store/reducers/Loader'
|
||||
|
||||
import { Link } from "react-router-dom"
|
||||
import { Button, Container, Form, FormGroup, Input, Label } from "reactstrap"
|
||||
import { ApplicationState } from "../../store"
|
||||
|
||||
import './scss/style.scss'
|
||||
import { IHeader } from "../../interfaces"
|
||||
import { Post } from "../../restClient"
|
||||
|
||||
interface IStateProp {
|
||||
[key: string]: string;
|
||||
@ -20,36 +19,38 @@ interface IState extends IStateProp {
|
||||
password: string
|
||||
}
|
||||
|
||||
interface IFormControl {
|
||||
title: string,
|
||||
placeHolder?: string
|
||||
}
|
||||
|
||||
interface ILink {
|
||||
target: string,
|
||||
anchorText: string
|
||||
}
|
||||
|
||||
export interface ISigninPage {
|
||||
header: IHeader,
|
||||
|
||||
title: string,
|
||||
email: IFormControl,
|
||||
password: IFormControl,
|
||||
dontHaveAnAccount: string,
|
||||
signUpLink: ILink,
|
||||
submit: IFormControl
|
||||
}
|
||||
|
||||
export interface ISigninComponent extends ISigninPage {
|
||||
|
||||
}
|
||||
|
||||
const Signin : FC<ISigninComponent> = () => {
|
||||
const Signin = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { content } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const { title, email, password, dontHaveAnAccount, signUpLink, submit } = content.signIn
|
||||
const {
|
||||
title = "",
|
||||
email = {
|
||||
title: "",
|
||||
placeHolder: ""
|
||||
},
|
||||
password = {
|
||||
title: "",
|
||||
placeHolder: ""
|
||||
},
|
||||
dontHaveAnAccount = "",
|
||||
signUpLink = {
|
||||
target: "#",
|
||||
anchorText: ""
|
||||
},
|
||||
submit = {
|
||||
title: ""
|
||||
}
|
||||
} = content?.signIn ? content.signIn : {}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading])
|
||||
|
||||
const [state, hookState] = useState<IState>({
|
||||
username: '',
|
||||
@ -68,18 +69,6 @@ const Signin : FC<ISigninComponent> = () => {
|
||||
setState({ [name]: value })
|
||||
}
|
||||
|
||||
const postSignIn = () => {
|
||||
Post<Promise<any>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_ACCOUNT}`, { account: 'Authenticate' }, {
|
||||
username: state.username,
|
||||
password: state.password
|
||||
}).then(response => response)
|
||||
.then((data) => {
|
||||
if(data) {
|
||||
console.log(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return <Container className="container">
|
||||
<h2>{title}</h2>
|
||||
<Form className="form">
|
||||
@ -106,7 +95,7 @@ const Signin : FC<ISigninComponent> = () => {
|
||||
<FormGroup>
|
||||
{dontHaveAnAccount} <Link to={signUpLink.target}>{signUpLink.anchorText}</Link>.
|
||||
</FormGroup>
|
||||
<Button onClick={postSignIn}>{submit.title}</Button>
|
||||
<Button>{submit.title}</Button>
|
||||
</Form>
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
import { actionCreators as loaderActionCreators } from '../../store/reducers/Loader'
|
||||
|
||||
import { Button, Container, Form, FormGroup, Input, Label } from "reactstrap"
|
||||
import { ApplicationState } from "../../store"
|
||||
|
||||
import './scss/style.scss'
|
||||
import { IHeader } from "../../interfaces"
|
||||
|
||||
interface IStateProp {
|
||||
[key: string]: string | boolean;
|
||||
@ -25,39 +25,46 @@ interface IState extends IStateProp {
|
||||
tnc: boolean
|
||||
}
|
||||
|
||||
interface IFormControl {
|
||||
title: string,
|
||||
placeHolder?: string
|
||||
}
|
||||
|
||||
export interface ISignupPage {
|
||||
header: IHeader,
|
||||
|
||||
title: string,
|
||||
username: IFormControl,
|
||||
email: IFormControl,
|
||||
reEmail: IFormControl,
|
||||
password: IFormControl,
|
||||
rePassword: IFormControl,
|
||||
acceptTermsAndConditions: string,
|
||||
submit: IFormControl
|
||||
}
|
||||
|
||||
export interface ISignupComponent extends ISignupPage { }
|
||||
|
||||
const Signup : FC<ISignupComponent> = () => {
|
||||
const Signup = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { content } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const {
|
||||
title, username,
|
||||
email,
|
||||
reEmail,
|
||||
password,
|
||||
rePassword,
|
||||
title = "",
|
||||
username = {
|
||||
title: "",
|
||||
placeHolder: ""
|
||||
},
|
||||
email = {
|
||||
title: "",
|
||||
placeHolder: ""
|
||||
},
|
||||
reEmail = {
|
||||
title: "Repeat email address",
|
||||
placeHolder: "Repeat email address..."
|
||||
},
|
||||
password = {
|
||||
title: "",
|
||||
placeHolder: ""
|
||||
},
|
||||
rePassword = {
|
||||
title: "Repeat password",
|
||||
placeHolder: "Repeat password..."
|
||||
},
|
||||
acceptTermsAndConditions = "",
|
||||
submit
|
||||
} = content.signUp
|
||||
submit = {
|
||||
title: ""
|
||||
}
|
||||
} = content?.signUp ? content.signUp : {}
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading])
|
||||
|
||||
|
||||
const [state, hookState] = useState<IState>({
|
||||
username: '',
|
||||
@ -89,7 +96,7 @@ const Signup : FC<ISignupComponent> = () => {
|
||||
}
|
||||
|
||||
return <Container className="container">
|
||||
{/* <h2>{title}</h2>
|
||||
<h2>{title}</h2>
|
||||
<Form className="form">
|
||||
<FormGroup>
|
||||
<Label for="username">{username.title}</Label>
|
||||
@ -146,7 +153,7 @@ const Signup : FC<ISignupComponent> = () => {
|
||||
<Label check>{acceptTermsAndConditions}</Label>
|
||||
</FormGroup>
|
||||
<Button>{submit.title}</Button>
|
||||
</Form> */}
|
||||
</Form>
|
||||
</Container>
|
||||
}
|
||||
|
||||
|
||||
@ -1,47 +1,18 @@
|
||||
import axios from "axios"
|
||||
import { IParams } from "./interfaces"
|
||||
import { Params, RequestModel } from "./models/abstractions"
|
||||
|
||||
export interface FetchResult<T> {
|
||||
|
||||
|
||||
|
||||
interface FetchData {
|
||||
status: number,
|
||||
data?: T
|
||||
text: string
|
||||
}
|
||||
|
||||
const Post = async <T>(apiUrl: string, pathParams?: IParams, data?: any) : Promise<FetchResult<T>> => {
|
||||
const url = new URL(apiUrl)
|
||||
const Post = () => {
|
||||
|
||||
if(pathParams) {
|
||||
Object.keys(pathParams).forEach(key => {
|
||||
if (typeof(pathParams[key]) !== undefined) {
|
||||
url.pathname += `/${pathParams[key]}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const requestParams = {
|
||||
method: 'POST',
|
||||
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
|
||||
data
|
||||
}
|
||||
|
||||
const fetchData = await axios(url.toString(), requestParams)
|
||||
.then(async fetchData => {
|
||||
return {
|
||||
status: fetchData.status,
|
||||
data: fetchData.data as T
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
|
||||
return {
|
||||
status: err.status
|
||||
}
|
||||
})
|
||||
|
||||
return fetchData
|
||||
}
|
||||
|
||||
const Get = async <T>(apiUrl: string, pathParams?: IParams, searchParams?: IParams): Promise<T | null> => {
|
||||
const Get = async <T>(apiUrl: string, pathParams?: Params, searchParams?: Params): Promise<T | null> => {
|
||||
const url = new URL(apiUrl)
|
||||
|
||||
if(pathParams) {
|
||||
@ -65,19 +36,21 @@ const Get = async <T>(apiUrl: string, pathParams?: IParams, searchParams?: IPara
|
||||
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
|
||||
}
|
||||
|
||||
const fetchData = await axios(url.toString(), requestParams)
|
||||
const fetchData = await fetch(url.toString(), requestParams)
|
||||
.then(async fetchData => {
|
||||
// console.log(fetchData)
|
||||
|
||||
return fetchData.data as T
|
||||
return {
|
||||
status: fetchData.status,
|
||||
text: await fetchData.text()
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
return fetchData
|
||||
|
||||
if (fetchData?.text)
|
||||
return JSON.parse((fetchData as FetchData).text) as T
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const Put = () => {
|
||||
@ -94,3 +67,6 @@ export {
|
||||
Put,
|
||||
Delete
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -5,8 +5,8 @@ import * as BlogItem from './reducers/BlogItem'
|
||||
|
||||
import * as Counter from './reducers/Counter'
|
||||
import * as Header from './reducers/Header'
|
||||
import * as Loader from './reducers/Loader'
|
||||
|
||||
import * as Comments from './reducers/Comments'
|
||||
import * as Content from './reducers/Content'
|
||||
|
||||
import * as ShopCatalog from './reducers/ShopCatalog'
|
||||
@ -20,25 +20,25 @@ import * as WeatherForecasts from './reducers/WeatherForecasts'
|
||||
|
||||
// The top-level state object
|
||||
export interface ApplicationState {
|
||||
blogCatalog: BlogCatalog.BlogCatalogState
|
||||
blogCategories: BlogCategories.BlogCategoriesState
|
||||
blogFeatured: BlogFeatured.BlogFeaturedState
|
||||
blogItem: BlogItem.BlogItemState
|
||||
blogCatalog: BlogCatalog.BlogCatalogState | undefined
|
||||
blogCategories: BlogCategories.BlogCategoriesState | undefined
|
||||
blogFeatured: BlogFeatured.BlogFeaturedState | undefined
|
||||
blogItem: BlogItem.BlogItemState | undefined
|
||||
|
||||
comments: Comments.CommentsState
|
||||
content: Content.ContentState
|
||||
content: Content.ContentState | undefined
|
||||
|
||||
counter: Counter.CounterState
|
||||
header: Header.HeaderState
|
||||
counter: Counter.CounterState | undefined
|
||||
header: Header.HeaderState | undefined
|
||||
loader: Loader.LoaderState | undefined
|
||||
|
||||
shopCatalog: ShopCatalog.ShopCatalogState
|
||||
shopCategories: ShopCategories.ShopCategoriesState
|
||||
shopFeatured: ShopFeatured.ShopFeaturedState
|
||||
shopItem: ShopItem.ShopItemState
|
||||
shopRelated: ShopRelated.ShopRelatedState
|
||||
shopCart: ShopCart.ShopCartState
|
||||
shopCatalog: ShopCatalog.ShopCatalogState | undefined
|
||||
shopCategories: ShopCategories.ShopCategoriesState | undefined
|
||||
shopFeatured: ShopFeatured.ShopFeaturedState | undefined
|
||||
shopItem: ShopItem.ShopItemState | undefined
|
||||
shopRelated: ShopRelated.ShopRelatedState | undefined
|
||||
shopCart: ShopCart.ShopCartState | undefined
|
||||
|
||||
weatherForecasts: WeatherForecasts.WeatherForecastsState
|
||||
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined
|
||||
}
|
||||
|
||||
// Whenever an action is dispatched, Redux will update each top-level application state property using
|
||||
@ -50,11 +50,11 @@ export const reducers = {
|
||||
blogFeatured: BlogFeatured.reducer,
|
||||
blogItem: BlogItem.reducer,
|
||||
|
||||
comments: Comments.reducer,
|
||||
content: Content.reducer,
|
||||
|
||||
counter: Counter.reducer,
|
||||
header: Header.reducer,
|
||||
loader: Loader.reducer,
|
||||
|
||||
shopCatalog: ShopCatalog.reducer,
|
||||
shopCategories: ShopCategories.reducer,
|
||||
|
||||
@ -1,46 +1,44 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IBlogItem } from '../../pages/Blog/Catalog/BlogItemsComponent'
|
||||
|
||||
import { GetBlogCatalogRequestModel } from '../../models/requests'
|
||||
import { GetBlogCatalogResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Request
|
||||
interface IGetBlogcatalogPathParams extends IParams {}
|
||||
|
||||
interface IGeBlogCatalogSearchParams extends IParams {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
interface IGetBlogCatalogRequestModel extends IRequest<IGetBlogcatalogPathParams, IGeBlogCatalogSearchParams> { }
|
||||
|
||||
// Response
|
||||
interface IGetBlogCatalogResponseModel extends IPagination<IBlogItem>, IResponse { }
|
||||
|
||||
|
||||
export interface BlogCatalogState extends IGetBlogCatalogResponseModel {
|
||||
export interface BlogCatalogState extends GetBlogCatalogResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetBlogCatalogRequestModel {
|
||||
interface RequestAction extends GetBlogCatalogRequestModel {
|
||||
type: 'REQUEST_BLOG_CATALOG'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetBlogCatalogResponseModel {
|
||||
interface ReceiveAction extends GetBlogCatalogResponseModel {
|
||||
type: 'RECEIVE_BLOG_CATALOG'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetBlogCatalogResponseModel = {
|
||||
totalPages: 100,
|
||||
export const actionCreators = {
|
||||
requestBlogCatalog: (props?: GetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
Get<Promise<GetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogCatalogState = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
@ -48,16 +46,17 @@ const mockData: IGetBlogCatalogResponseModel = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
text: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -71,16 +70,17 @@ const mockData: IGetBlogCatalogResponseModel = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
text: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -94,16 +94,17 @@ const mockData: IGetBlogCatalogResponseModel = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
text: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -117,16 +118,17 @@ const mockData: IGetBlogCatalogResponseModel = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
text: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -135,32 +137,7 @@ const mockData: IGetBlogCatalogResponseModel = {
|
||||
|
||||
likes: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogCatalog: (props?: IGetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
|
||||
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<IGetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogCatalogState = {
|
||||
...cloneObject(mockData),
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,73 +1,47 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { ICategory } from '../../components/SideWidgets/Categories'
|
||||
|
||||
import { GetBlogCategoriesRequestModel } from '../../models/requests'
|
||||
import { GetBlogCategoriesResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Request
|
||||
interface IGetBlogCategoriesPathParams extends IParams {}
|
||||
|
||||
interface IGetBlogCategoriesSearchParams extends IParams {}
|
||||
|
||||
interface IGetBlogCategoriesRequestModel extends IRequest<IGetBlogCategoriesPathParams, IGetBlogCategoriesSearchParams> { }
|
||||
|
||||
// Response
|
||||
interface IGetBlogCategoriesResponseModel extends IPagination<ICategory>, IResponse { }
|
||||
|
||||
|
||||
export interface BlogCategoriesState extends IGetBlogCategoriesResponseModel {
|
||||
export interface BlogCategoriesState extends GetBlogCategoriesResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetBlogCategoriesRequestModel {
|
||||
interface RequestAction extends GetBlogCategoriesRequestModel {
|
||||
type: 'REQUEST_BLOG_CATEGORIES'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetBlogCategoriesResponseModel {
|
||||
interface ReceiveAction extends GetBlogCategoriesResponseModel {
|
||||
type: 'RECEIVE_BLOG_CATEGORIES'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetBlogCategoriesResponseModel = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{ href: 'default', anchorText: "Default" },
|
||||
{ href: 'software', anchorText: "Software" },
|
||||
{ href: 'hardware', anchorText: "Hardware" }
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogCategories: (props?: IGetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_BLOG_CATEGORIES' })
|
||||
requestBlogCategories: (props?: GetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
console.log(mockData)
|
||||
dispatch({ type: 'RECEIVE_BLOG_CATEGORIES', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<IGetBlogCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
Get<Promise<GetBlogCategoriesResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_CATEGORIES', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_BLOG_CATEGORIES' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogCategoriesState = {
|
||||
...cloneObject(mockData),
|
||||
items: [
|
||||
{ id: "", text: "Software" },
|
||||
{ id: "", text: "Hardware" }
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,59 +1,59 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IFeaturedBlogItem } from '../../pages/Blog/Catalog/FeaturedBlogComponent'
|
||||
|
||||
import { GetBlogFeaturedRequestModel } from '../../models/requests'
|
||||
import { GetBlogFeaturedResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Request
|
||||
interface IGetBlogFeaturedPathParams extends IParams {}
|
||||
|
||||
interface IGetBlogFeaturedSearchParams extends IParams {}
|
||||
|
||||
interface IGetBlogFeaturedRequestModel extends IRequest<IGetBlogFeaturedPathParams, IGetBlogFeaturedSearchParams> { }
|
||||
|
||||
|
||||
// Response
|
||||
interface IGetBlogFeaturedResponseModel extends IResponse {
|
||||
items: IFeaturedBlogItem []
|
||||
}
|
||||
|
||||
|
||||
export interface BlogFeaturedState extends IGetBlogFeaturedResponseModel {
|
||||
export interface BlogFeaturedState extends GetBlogFeaturedResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetBlogFeaturedRequestModel {
|
||||
interface RequestAction extends GetBlogFeaturedRequestModel {
|
||||
type: 'REQUEST_BLOG_FEATURED'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetBlogFeaturedResponseModel {
|
||||
interface ReceiveAction extends GetBlogFeaturedResponseModel {
|
||||
type: 'RECEIVE_BLOG_FEATURED'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetBlogFeaturedResponseModel = {
|
||||
export const actionCreators = {
|
||||
requestBlogFeatured: (props?: GetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
Get<Promise<GetBlogFeaturedResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_BLOGITEMS_FEAUTERED}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_BLOG_FEATURED' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogFeaturedState = {
|
||||
items: [
|
||||
{
|
||||
id: "",
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -68,16 +68,16 @@ const mockData: IGetBlogFeaturedResponseModel = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -92,16 +92,16 @@ const mockData: IGetBlogFeaturedResponseModel = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
shortText: "This is a blog short text...",
|
||||
shortText: "",
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -111,32 +111,7 @@ const mockData: IGetBlogFeaturedResponseModel = {
|
||||
readTime: 10,
|
||||
likes: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogFeatured: (props?: IGetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_BLOG_FEATURED' })
|
||||
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<IGetBlogFeaturedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS_FEAUTERED}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_FEATURED', ...data })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogFeaturedState = {
|
||||
...cloneObject(mockData),
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,46 +1,47 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IBlogItem } from '../../pages/Blog/Item'
|
||||
|
||||
import { GetBlogItemRequestModel } from '../../models/requests'
|
||||
import { GetBlogItemResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Request
|
||||
interface IGetBlogItemPathParams extends IParams { }
|
||||
|
||||
interface IGetBlogItemSearchParams extends IParams {
|
||||
slug: string
|
||||
}
|
||||
|
||||
interface IGetBlogItemRequestModel extends IRequest<IGetBlogItemPathParams, IGetBlogItemSearchParams> { }
|
||||
|
||||
// Response
|
||||
interface IGetBlogItemResponseModel extends IBlogItem, IResponse { }
|
||||
|
||||
|
||||
export interface BlogItemState extends IGetBlogItemResponseModel {
|
||||
export interface BlogItemState extends GetBlogItemResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetBlogItemRequestModel {
|
||||
interface RequestAction extends GetBlogItemRequestModel {
|
||||
type: 'REQUEST_BLOG_ITEM'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetBlogItemResponseModel {
|
||||
interface ReceiveAction extends GetBlogItemResponseModel {
|
||||
type: 'RECEIVE_BLOG_ITEM'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetBlogItemResponseModel = {
|
||||
export const actionCreators = {
|
||||
requestBlogItem: (props?: GetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
Get<Promise<GetBlogItemResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_BLOGITEM}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...data })
|
||||
})
|
||||
|
||||
// dispatch({ type: 'REQUEST_BLOG_ITEM', slug: props.slug })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogItemState = {
|
||||
id: "",
|
||||
slug: "demo-post",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/900x400/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/900x400/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
badges: [
|
||||
@ -59,37 +60,63 @@ const mockData: IGetBlogItemResponseModel = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
created: new Date().toString(),
|
||||
tags: [ "react", "redux", "webapi" ]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogItem: (props?: IGetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_BLOG_ITEM' })
|
||||
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...cloneObject(mockData) })
|
||||
return
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
comments: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 1",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.",
|
||||
|
||||
responses: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 4",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors."
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 3",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When you put money directly to a problem, it makes a good headline."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 2",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence."
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
Get<Promise<IGetBlogItemResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEM}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_ITEM', ...data })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: BlogItemState = {
|
||||
...cloneObject(mockData),
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
//Redux
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IComment } from '../../pages/Blog/Item/CommentsComponent'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
// Request
|
||||
interface IGetCommentsPathParams extends IParams { }
|
||||
|
||||
interface IGetCommentsSearchParams extends IParams { }
|
||||
|
||||
interface IGetCommentsRequestModel extends IRequest<IGetCommentsPathParams, IGetCommentsSearchParams> { }
|
||||
|
||||
// Response
|
||||
interface IGetCommentsResponseModel extends IPagination<IComment>, IResponse { }
|
||||
|
||||
export interface CommentsState extends IGetCommentsResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetCommentsRequestModel {
|
||||
type: 'REQUEST_COMMENTS'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetCommentsResponseModel {
|
||||
type: 'RECEIVE_COMMENTS'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetCommentsResponseModel = {
|
||||
totalPages: 100,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 1",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.",
|
||||
|
||||
responses: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 4",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors."
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 3",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When you put money directly to a problem, it makes a good headline."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 2",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestComments: (props?: IGetCommentsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_COMMENTS', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Get<Promise<IGetBlogCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_BLOGITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_BLOG_CATALOG', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_BLOG_CATALOG' })
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: CommentsState = {
|
||||
...cloneObject(mockData),
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
export const reducer: Reducer<CommentsState> = (state: CommentsState | undefined, incomingAction: Action): CommentsState => {
|
||||
if (state === undefined) {
|
||||
return unloadedState
|
||||
}
|
||||
|
||||
const action = incomingAction as KnownAction
|
||||
switch (action.type) {
|
||||
case 'REQUEST_COMMENTS':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true
|
||||
}
|
||||
|
||||
case 'RECEIVE_COMMENTS':
|
||||
return {
|
||||
...action,
|
||||
isLoading: false
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
@ -2,100 +2,42 @@ import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
import { ReservedWords } from '../../enumerations'
|
||||
|
||||
|
||||
// Interfaces
|
||||
import { IHeader, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
|
||||
import { IRoute } from '../../App'
|
||||
|
||||
import { INavMenuItem } from '../../layouts/public/NavMenu'
|
||||
import { ISideMenuItem } from '../../layouts/admin/SideMenu'
|
||||
|
||||
import { IHomePage } from '../../pages/Home'
|
||||
|
||||
import { IShopCatalogPage } from '../../pages/Shop/Catalog'
|
||||
import { IShopItemPage } from '../../pages/Shop/Item'
|
||||
|
||||
import { IBlogCatalogPage } from '../../pages/Blog/Catalog'
|
||||
import { IBlogItemPage } from '../../pages/Blog/Item'
|
||||
|
||||
import { ISigninPage } from '../../pages/Signin'
|
||||
import { ISignupPage } from '../../pages/Signup'
|
||||
|
||||
import { IShopCartPage } from '../../pages/Shop/Cart'
|
||||
import { IShopCheckoutPage } from '../../pages/Shop/Checkout'
|
||||
|
||||
|
||||
import { GetContentRequestModel } from '../../models/requests'
|
||||
import { GetContentResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Request
|
||||
interface IGetContentPathParams extends IParams {}
|
||||
|
||||
interface IGetContentSearchParams extends IParams {
|
||||
locale?: string
|
||||
}
|
||||
|
||||
interface IGetContentRequestModel extends IRequest<IGetContentPathParams, IGetContentSearchParams> { }
|
||||
|
||||
interface ILocalization {
|
||||
timeZone: string,
|
||||
locale: string,
|
||||
dateFormat: string,
|
||||
timeFormat: string,
|
||||
currency: string,
|
||||
currencySymbol: string
|
||||
}
|
||||
|
||||
// Response
|
||||
interface IGetContentResponseModel extends IResponse {
|
||||
|
||||
siteName: string,
|
||||
siteUrl: string,
|
||||
|
||||
header: IHeader,
|
||||
|
||||
localization: ILocalization,
|
||||
|
||||
routes: IRoute [],
|
||||
adminRoutes: IRoute [],
|
||||
serviceRoutes: IRoute [],
|
||||
|
||||
topMenu: INavMenuItem [],
|
||||
sideMenu: ISideMenuItem [],
|
||||
|
||||
homePage: IHomePage,
|
||||
|
||||
shopCatalog: IShopCatalogPage,
|
||||
shopItem: IShopItemPage,
|
||||
shopCart: IShopCartPage,
|
||||
shopCheckout: IShopCheckoutPage,
|
||||
|
||||
blogCatalog: IBlogCatalogPage,
|
||||
blogItem: IBlogItemPage,
|
||||
|
||||
signIn: ISigninPage,
|
||||
signUp: ISignupPage
|
||||
}
|
||||
|
||||
export interface ContentState extends IGetContentResponseModel {
|
||||
export interface ContentState extends GetContentResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetContentRequestModel {
|
||||
interface RequestAction extends GetContentRequestModel {
|
||||
type: 'REQUEST_CONTENT'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetContentResponseModel {
|
||||
interface ReceiveAction extends GetContentResponseModel {
|
||||
type: 'RECEIVE_CONTENT'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction;
|
||||
|
||||
const mockData: IGetContentResponseModel = {
|
||||
siteName: "Contoso",
|
||||
siteUrl: "https://contoso.com",
|
||||
export const actionCreators = {
|
||||
requestContent: (props?: GetContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
Get<Promise<GetContentResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_CONTENT}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then((data) => {
|
||||
if(data) {
|
||||
dispatch({ type: 'RECEIVE_CONTENT', ...data })
|
||||
}
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_CONTENT' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ContentState = {
|
||||
siteName: "MAKS-IT",
|
||||
siteUrl: "https://maks-it.com",
|
||||
|
||||
header: {
|
||||
title: `${ReservedWords.siteName}`,
|
||||
@ -115,42 +57,29 @@ const mockData: IGetContentResponseModel = {
|
||||
currency: "EUR",
|
||||
currencySymbol: "€"
|
||||
},
|
||||
|
||||
routes: [
|
||||
{ target: "/", component: "Home" },
|
||||
{ target: "/home", component: "Home" },
|
||||
{ target: "/shop", childRoutes: [
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: "", component: "ShopCatalog" },
|
||||
{ target: ":page", component: "ShopCatalog" }
|
||||
] },
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "ShopItem" }
|
||||
]}
|
||||
] },
|
||||
|
||||
{ target: "", component: "ShopCatalog" },
|
||||
{ target: ":page", component: "ShopCatalog" },
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "ShopItem" }
|
||||
]},
|
||||
{ target:"cart", childRoutes: [
|
||||
{ target: "", component: "Cart" },
|
||||
{ target: "checkout", component: "Checkout" }
|
||||
]}
|
||||
]},
|
||||
|
||||
{ target: "/blog", childRoutes: [
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: "", component: "BlogCatalog" },
|
||||
{ target: ":page", component: "BlogCatalog" }
|
||||
] },
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "BlogItem" }
|
||||
]}
|
||||
] }
|
||||
{ target: "", component: "BlogCatalog" },
|
||||
{ target: ":page", component: "BlogCatalog" },
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "BlogItem" }
|
||||
]}
|
||||
]}
|
||||
],
|
||||
|
||||
adminRoutes: [],
|
||||
|
||||
serviceRoutes: [
|
||||
{ target: "/signin", component: "Signin" },
|
||||
{ target: "/signup", component: "Signup" }
|
||||
@ -158,15 +87,14 @@ const mockData: IGetContentResponseModel = {
|
||||
|
||||
topMenu: [
|
||||
{ target: "/", title: "Home" },
|
||||
{ target: "/shop/default", title: "Shop" },
|
||||
{ target: "/blog/default", title: "Blog" },
|
||||
{ target: "/shop", title: "Shop" },
|
||||
{ target: "/blog", title: "Blog" },
|
||||
|
||||
{ target: "/signin", title: "Sing in" },
|
||||
{ target: "/signup", title: "Sign up" },
|
||||
|
||||
{ target: "/shop/cart", icon: "shopping-cart", title: `Cart (${ReservedWords.quantity})` }
|
||||
],
|
||||
|
||||
sideMenu: [],
|
||||
|
||||
homePage: {
|
||||
@ -213,7 +141,8 @@ const mockData: IGetContentResponseModel = {
|
||||
{
|
||||
text: "The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>yarn</code> commands such as <code>yarn test</code> or <code>yarn install</code>.",
|
||||
reviewer: {
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
id: "",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
fullName: "Admin",
|
||||
position: "CEO, MAKS-IT"
|
||||
}
|
||||
@ -221,9 +150,7 @@ const mockData: IGetContentResponseModel = {
|
||||
]
|
||||
},
|
||||
featuredBlogsSection: {
|
||||
title: "Featured blogs",
|
||||
text: "Chek our best blog posts",
|
||||
readTime: `${ReservedWords.date} · Time to read: ${ReservedWords.readTime} min`
|
||||
title: "Featured blogs"
|
||||
},
|
||||
callToActionSection: {
|
||||
title: "New products, delivered to you.",
|
||||
@ -272,9 +199,6 @@ const mockData: IGetContentResponseModel = {
|
||||
relatedProductsSection: {
|
||||
title: "Related products",
|
||||
addToCart: "Add to cart"
|
||||
},
|
||||
commentsSection: {
|
||||
leaveComment: "Join the discussion and leave a comment!"
|
||||
}
|
||||
},
|
||||
|
||||
@ -447,9 +371,6 @@ const mockData: IGetContentResponseModel = {
|
||||
title: "Welcome to Blog Home!",
|
||||
text: "A Bootstrap 5 starter layout for your next blog homepage"
|
||||
},
|
||||
blogItemsSection: {
|
||||
readMore: 'Read more →'
|
||||
},
|
||||
featuredBlogSection: {
|
||||
readTime: `${ReservedWords.date} Time to read: ${ReservedWords.readTime} min`
|
||||
},
|
||||
@ -466,7 +387,7 @@ const mockData: IGetContentResponseModel = {
|
||||
}
|
||||
},
|
||||
titleSection: {
|
||||
text: `Posted on ${ReservedWords.date} by ${ReservedWords.nickName}`
|
||||
postedOnBy: `Posted on ${ReservedWords.date} by ${ReservedWords.nickName}`
|
||||
},
|
||||
commentsSection: {
|
||||
leaveComment: "Join the discussion and leave a comment!"
|
||||
@ -540,42 +461,14 @@ const mockData: IGetContentResponseModel = {
|
||||
submit: {
|
||||
title: "Sing up"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
export const actionCreators = {
|
||||
requestContent: (props?: IGetContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_CONTENT' })
|
||||
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_CONTENT', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
const siteId = process.env.REACT_APP_SITEID
|
||||
const pathParams = { ...props?.pathParams, siteId }
|
||||
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
const searchParams = { ...props?.searchParams, locale }
|
||||
|
||||
Get<Promise<IGetContentResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CONTENT}`, pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then((data) => {
|
||||
if(data) {
|
||||
dispatch({ type: 'RECEIVE_CONTENT', ...data })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ContentState = {
|
||||
...cloneObject(mockData),
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
export const reducer: Reducer<ContentState> = (state: ContentState | undefined, incomingAction: Action): ContentState => {
|
||||
if (state === undefined) {
|
||||
console.log(unloadedState)
|
||||
return unloadedState
|
||||
}
|
||||
|
||||
|
||||
@ -2,19 +2,19 @@ import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '..'
|
||||
import { ReservedWords } from '../../enumerations'
|
||||
|
||||
import { IHeader } from '../../interfaces'
|
||||
import { HeaderLink, HeaderModel } from '../../models'
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
|
||||
export interface HeaderState extends IHeader {}
|
||||
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 IHeader { type: 'RECEIVE_UPDATE_HEADER' }
|
||||
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
|
||||
@ -27,7 +27,7 @@ export type KnownAction = ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
|
||||
updateHeader: (props: IHeader): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
updateHeader: (props: HeaderModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const siteName = getState().content?.siteName
|
||||
const baseHeader = getState().content?.header
|
||||
|
||||
52
src/ClientApp/src/store/reducers/Loader.ts
Normal file
52
src/ClientApp/src/store/reducers/Loader.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
|
||||
export interface LoaderState {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
interface RequestAction {
|
||||
type: 'SHOW_LOADER'
|
||||
}
|
||||
|
||||
interface ReceiveAction {
|
||||
type: 'HIDE_LOADER'
|
||||
}
|
||||
|
||||
// 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 = RequestAction | 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 = {
|
||||
show: () => ({ type: 'SHOW_LOADER' } as RequestAction),
|
||||
hide: () => ({ type: 'HIDE_LOADER' } as ReceiveAction)
|
||||
}
|
||||
|
||||
// ----------------
|
||||
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
|
||||
|
||||
const unloadedState: LoaderState = {
|
||||
visible: false
|
||||
}
|
||||
|
||||
export const reducer: Reducer<LoaderState> = (state: LoaderState | undefined, incomingAction: Action): LoaderState => {
|
||||
if (state === undefined) {
|
||||
return unloadedState
|
||||
}
|
||||
|
||||
const action = incomingAction as KnownAction
|
||||
switch (action.type) {
|
||||
case 'SHOW_LOADER':
|
||||
return { visible: true }
|
||||
case 'HIDE_LOADER':
|
||||
return { visible: false }
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
@ -1,35 +1,22 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '..'
|
||||
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IShopCartItem } from '../../pages/Shop/Cart'
|
||||
|
||||
import { GetShopCartItemsRequestModel } from '../../models/requests'
|
||||
import { GetShopCartItemResponseModel } from '../../models/responses'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
// Request
|
||||
interface IGetShopCartItemsPathParams extends IParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
interface IGetShopCartItemsSearchParams extends IParams { }
|
||||
|
||||
interface IGetShopCartItemsRequestModel extends IRequest<IGetShopCartItemsPathParams, IGetShopCartItemsSearchParams> {}
|
||||
|
||||
// Response
|
||||
interface IGetShopCartItemsResponseModel extends IPagination<IShopCartItem>, IResponse {}
|
||||
|
||||
|
||||
export interface ShopCartState extends IGetShopCartItemsResponseModel {
|
||||
export interface ShopCartState {
|
||||
items: GetShopCartItemResponseModel [],
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export interface RequestAction extends IGetShopCartItemsRequestModel {
|
||||
export interface RequestAction {
|
||||
type: 'REQUEST_CART_ITEMS'
|
||||
}
|
||||
|
||||
export interface ReceiveAction extends IGetShopCartItemsResponseModel {
|
||||
export interface ReceiveAction {
|
||||
items: GetShopCartItemResponseModel [],
|
||||
type: 'RECEIVE_CART_ITEMS'
|
||||
}
|
||||
|
||||
@ -44,16 +31,14 @@ export interface ReceiveAction extends IGetShopCartItemsResponseModel {
|
||||
export type KnownAction = RequestAction | ReceiveAction //| IncrementItemQuantityAction | DecrementItmeQuantityAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestCart: (props?: IGetShopCartItemsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestCart: (props?: GetShopCartItemsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<IGetShopCartItemsResponseModel>>('https://localhost:7151/api/ShopCartItems/404c8232-9048-4519-bfba-6e78dc7005ca', props?.pathParams, props?.searchParams)
|
||||
Get<Promise<GetShopCartItemResponseModel []>>('https://localhost:7151/api/ShopCartItems/404c8232-9048-4519-bfba-6e78dc7005ca', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_CART_ITEMS', ...data })
|
||||
dispatch({ type: 'RECEIVE_CART_ITEMS', items: [...data] })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_CART_ITEMS' })
|
||||
@ -90,13 +75,11 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: ShopCartState = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
@ -108,7 +91,7 @@ const unloadedState: ShopCartState = {
|
||||
{
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
@ -120,7 +103,7 @@ const unloadedState: ShopCartState = {
|
||||
{
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
|
||||
@ -1,28 +1,9 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IShopItem } from '../../pages/Shop/Catalog/ShopItemsSection'
|
||||
|
||||
import { GetShopCatalogRequestModel } from '../../models/requests'
|
||||
import { GetShopCatalogResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Request
|
||||
interface IGetShopcatalogPathParams extends IParams {}
|
||||
|
||||
interface IGeShopCatalogSearchParams extends IParams {}
|
||||
|
||||
interface GetShopCatalogRequestModel extends IRequest<IGetShopcatalogPathParams, IGeShopCatalogSearchParams> {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
// Response
|
||||
interface GetShopCatalogResponseModel extends IPagination<IShopItem>, IResponse {}
|
||||
|
||||
export interface ShopCatalogState extends GetShopCatalogResponseModel {
|
||||
isLoading: boolean
|
||||
@ -38,15 +19,33 @@ interface ReceiveAction extends GetShopCatalogResponseModel {
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: GetShopCatalogResponseModel = {
|
||||
totalPages: 100,
|
||||
export const actionCreators = {
|
||||
requestShopCatalog: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
|
||||
const searchParams = { ...props?.searchParams, locale }
|
||||
|
||||
Get<Promise<GetShopCatalogResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_SHOPITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_SHOP_CATALOG', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_SHOP_CATALOG' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ShopCatalogState = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -55,7 +54,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -70,7 +69,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -79,7 +78,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -94,7 +93,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "out of stock" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -103,7 +102,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -118,7 +117,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -127,7 +126,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -138,32 +137,7 @@ const mockData: GetShopCatalogResponseModel = {
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopCatalog: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_SHOP_CATALOG' })
|
||||
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
const searchParams = { ...props?.searchParams, locale }
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_SHOP_CATALOG', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<GetShopCatalogResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SITEID}/${process.env.REACT_APP_SHOPITEMS}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_SHOP_CATALOG', ...data })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ShopCatalogState = {
|
||||
...cloneObject(mockData),
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,24 +1,9 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { ICategory } from '../../components/SideWidgets/Categories'
|
||||
|
||||
import { GetShopCategoriesRequestModel } from '../../models/requests'
|
||||
import { GetShopCategoriesResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
// Request
|
||||
interface GetShopCategoriesPathParams extends IParams { }
|
||||
|
||||
interface GetShopCategoriesSearchParams extends IParams { }
|
||||
|
||||
interface GetShopCategoriesRequestModel extends IRequest<GetShopCategoriesPathParams, GetShopCategoriesSearchParams> { }
|
||||
|
||||
|
||||
// Response
|
||||
interface GetShopCategoriesResponseModel extends IPagination<ICategory>, IResponse { }
|
||||
|
||||
|
||||
export interface ShopCategoriesState extends GetShopCategoriesResponseModel {
|
||||
isLoading: boolean
|
||||
@ -34,36 +19,25 @@ interface ReceiveAction extends GetShopCategoriesResponseModel {
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: GetShopCategoriesResponseModel = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{ href: 'default', anchorText: "Default" },
|
||||
{ href: 'software', anchorText: "Software" },
|
||||
{ href: 'hardware', anchorText: "Hardware" }
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopCategories: (props?: GetShopCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_SHOP_CATEGORIES' })
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_SHOP_CATEGORIES', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<GetShopCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
|
||||
Get<Promise<GetShopCategoriesResponseModel>>('https://localhost:7151/api/ShopCategories', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_SHOP_CATEGORIES', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_SHOP_CATEGORIES' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ShopCategoriesState = {
|
||||
...cloneObject(mockData),
|
||||
items: [
|
||||
{ id: "", text: "Software" },
|
||||
{ id: "", text: "Hardware" }
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,79 +1,28 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IRelatedProduct, IRelatedProducts } from '../../pages/Shop/Item/RelatedProducts'
|
||||
|
||||
|
||||
import { GetShopFeaturedRequestModel, } from '../../models/requests'
|
||||
import { GetShopFeaturedResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
// Request
|
||||
interface IGetShopFeaturedSearchParams extends IParams { }
|
||||
|
||||
interface IGetShopFeaturedPathParams extends IParams { }
|
||||
|
||||
interface IGetShopFeaturedRequestModel extends IRequest<IGetShopFeaturedSearchParams, IGetShopFeaturedPathParams> { }
|
||||
|
||||
// Response
|
||||
interface IGetShopFeaturedResponseModel extends IResponse {
|
||||
items: IRelatedProduct []
|
||||
}
|
||||
|
||||
|
||||
export interface ShopFeaturedState extends IGetShopFeaturedResponseModel {
|
||||
export interface ShopFeaturedState extends GetShopFeaturedResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetShopFeaturedRequestModel {
|
||||
interface RequestAction extends GetShopFeaturedRequestModel {
|
||||
type: 'REQUEST_SHOP_FEATURED'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetShopFeaturedResponseModel {
|
||||
interface ReceiveAction extends GetShopFeaturedResponseModel {
|
||||
type: 'RECEIVE_SHOP_FEATURED'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetShopFeaturedResponseModel = {
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopFeatured: (props?: IGetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestShopFeatured: (props?: GetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_SHOP_FEATURED', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<IGetShopFeaturedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SHOPITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
|
||||
Get<Promise<GetShopFeaturedResponseModel>>('https://localhost:7151/api/ShopFeatured', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
@ -85,7 +34,32 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: ShopFeaturedState = {
|
||||
...cloneObject(mockData),
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
}
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,26 +1,9 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
// Interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IShopItem } from '../../pages/Shop/Item'
|
||||
|
||||
import { GetShopItemRequestModel } from '../../models/requests'
|
||||
import { GetShopItemResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
|
||||
// Reuest
|
||||
interface IGetShopItemPathParams extends IParams { }
|
||||
|
||||
interface IGetShopItemSearchParams extends IParams {
|
||||
slug: string
|
||||
}
|
||||
|
||||
interface GetShopItemRequestModel extends IRequest<IGetShopItemPathParams, IGetShopItemSearchParams> { }
|
||||
|
||||
|
||||
// Response
|
||||
interface GetShopItemResponseModel extends IShopItem, IResponse { }
|
||||
|
||||
|
||||
export interface ShopItemState extends GetShopItemResponseModel {
|
||||
@ -37,11 +20,25 @@ interface ReceiveAction extends GetShopItemResponseModel {
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: GetShopItemResponseModel = {
|
||||
export const actionCreators = {
|
||||
requestShopItem: (props?: GetShopItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
Get<Promise<GetShopItemResponseModel>>('https://localhost:7151/api/ShopItem', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_SHOP_ITEM', ...data })
|
||||
})
|
||||
|
||||
// dispatch({ type: 'REQUEST_SHOP_ITEM', slug: props.slug })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ShopItemState = {
|
||||
id: "",
|
||||
slug: "demo-post",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/600x700/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/600x700/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
|
||||
@ -59,7 +56,7 @@ const mockData: GetShopItemResponseModel = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -69,29 +66,58 @@ const mockData: GetShopItemResponseModel = {
|
||||
price: 20,
|
||||
newPrice: 10,
|
||||
|
||||
quantity: 10
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopItem: (props?: GetShopItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_SHOP_ITEM' })
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_SHOP_ITEM', ...cloneObject(mockData) })
|
||||
return
|
||||
quantity: 10,
|
||||
|
||||
comments: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 1",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.",
|
||||
|
||||
responses: [
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 4",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors."
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 3",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When you put money directly to a problem, it makes a good headline."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
author: {
|
||||
id: "",
|
||||
nickName: "Commenter Name 2",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/50x50/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
comment: "When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence."
|
||||
}
|
||||
|
||||
Get<Promise<GetShopItemResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SHOPITEM}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_SHOP_ITEM', ...data })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ShopItemState = {
|
||||
...cloneObject(mockData),
|
||||
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,160 +1,137 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IRelatedProduct, IRelatedProducts } from '../../pages/Shop/Item/RelatedProducts'
|
||||
|
||||
import { GetShopRelatedRequestModel } from '../../models/requests'
|
||||
import { GetShopRelatedResponseModel } from '../../models/responses'
|
||||
import { Get } from '../../restClient'
|
||||
import { cloneObject } from '../../functions'
|
||||
|
||||
// Resquest
|
||||
interface IGetShopRelatedPathParams extends IParams {}
|
||||
|
||||
interface IGetShopRelatedSearchParams extends IParams {}
|
||||
|
||||
interface IGetShopRelatedRequestModel extends IRequest<IGetShopRelatedPathParams, IGetShopRelatedSearchParams> { }
|
||||
|
||||
// Response
|
||||
interface IGetShopRelatedResponseModel extends IResponse {
|
||||
items: IRelatedProduct []
|
||||
}
|
||||
|
||||
|
||||
export interface ShopRelatedState extends IGetShopRelatedResponseModel {
|
||||
export interface ShopRelatedState extends GetShopRelatedResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends IGetShopRelatedRequestModel {
|
||||
interface RequestAction extends GetShopRelatedRequestModel {
|
||||
type: 'REQUEST_SHOP_RELATED'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends IGetShopRelatedResponseModel {
|
||||
interface ReceiveAction extends GetShopRelatedResponseModel {
|
||||
type: 'RECEIVE_SHOP_RELATED'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
const mockData: IGetShopRelatedResponseModel = {
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopRelated: (props?: IGetShopRelatedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
dispatch({ type: 'REQUEST_SHOP_RELATED' })
|
||||
requestShopRelated: (props?: GetShopRelatedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y') {
|
||||
dispatch({ type: 'RECEIVE_SHOP_RELATED', ...cloneObject(mockData) })
|
||||
return
|
||||
}
|
||||
|
||||
Get<Promise<IGetShopRelatedResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_SITEID}/${process.env.REACT_APP_SHOPITEMS}`, props?.pathParams, props?.searchParams)
|
||||
Get<Promise<GetShopRelatedResponseModel>>('https://localhost:7151/api/ShopRelated', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
dispatch({ type: 'RECEIVE_SHOP_RELATED', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_SHOP_RELATED' })
|
||||
}
|
||||
}
|
||||
|
||||
const unloadedState: ShopRelatedState = {
|
||||
...cloneObject(mockData),
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
|
||||
shortText: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque fugit ratione dicta mollitia. Officiis ad...",
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
tags: [ "react", "redux", "webapi" ],
|
||||
|
||||
rating: 4.5,
|
||||
price: 20,
|
||||
newPrice: 10
|
||||
}
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,6 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
|
||||
|
||||
// export interface GetWeatherForecastResponseModel extends ResponseModel {
|
||||
// date: string,
|
||||
// temperatireC: number,
|
||||
// temperatureF: number,
|
||||
// summary?: string
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
|
||||
|
||||
@ -4,14 +4,14 @@ using DomainResults.Common;
|
||||
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
using DomainObjects.Documents;
|
||||
using DataProviders.Collections.Abstractions;
|
||||
using DomainObjects.Documents.Content;
|
||||
|
||||
namespace DataProviders.Collections
|
||||
{
|
||||
|
||||
public interface IContentDataProvider {
|
||||
(ContentDocument?, IDomainResult) Get(Guid siteId);
|
||||
(List<ContentDocument>?, IDomainResult) Get(Guid siteId);
|
||||
}
|
||||
|
||||
public class ContentDataProvider : CollectionDataProviderBase<ContentDocument>, IContentDataProvider {
|
||||
@ -25,13 +25,6 @@ namespace DataProviders.Collections
|
||||
ISessionService sessionService) : base(logger, client, idGenerator, sessionService, _databaseName, _collectionName) {
|
||||
}
|
||||
|
||||
public (ContentDocument?, IDomainResult) Get(Guid siteId) {
|
||||
var (list, result) = GetWithPredicate(x => x.Id == siteId, x => x);
|
||||
|
||||
if (!result.IsSuccess || list == null)
|
||||
return (null, result);
|
||||
|
||||
return (list.First(), result);
|
||||
}
|
||||
public (List<ContentDocument>?, IDomainResult) Get(Guid siteId) => GetWithPredicate(x => x.SiteId == siteId, x => x);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,10 +15,6 @@ using DomainObjects.Documents.Sites;
|
||||
using DomainObjects.Documents.Categories;
|
||||
using DomainObjects.Documents.Categories.L10n;
|
||||
using DomainObjects.Documents.Posts;
|
||||
using DomainObjects.Documents.Content;
|
||||
using DomainObjects.Abstractions.Posts.L10n;
|
||||
using DomainObjects.Documents.Content.L10n;
|
||||
using Core.Enumerations;
|
||||
|
||||
namespace DataProviders
|
||||
{
|
||||
@ -36,8 +32,19 @@ namespace DataProviders
|
||||
// https://kevsoft.net/2020/07/02/how-to-store-decimal-fields-in-mongodb-with-csharp.html
|
||||
BsonSerializer.RegisterSerializer(new DecimalSerializer(BsonType.Decimal128));
|
||||
|
||||
#region L10n
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(PostItemL10n))) {
|
||||
BsonClassMap.RegisterClassMap<PostItemL10n>(cm => {
|
||||
cm.AutoMap();
|
||||
|
||||
cm.GetMemberMap(c => c.Locale)
|
||||
.SetSerializer(new EnumerationSerializer<Locales>());
|
||||
|
||||
cm.GetMemberMap(c => c.TextFormat)
|
||||
.SetSerializer(new EnumerationSerializer<TextFormat>());
|
||||
});
|
||||
}
|
||||
|
||||
#region MediaAttachments
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(MediaAttachmentL10n))) {
|
||||
BsonClassMap.RegisterClassMap<MediaAttachmentL10n>(cm => {
|
||||
cm.AutoMap();
|
||||
@ -47,21 +54,6 @@ namespace DataProviders
|
||||
});
|
||||
}
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(MediaAttachment))) {
|
||||
BsonClassMap.RegisterClassMap<MediaAttachment>(cm => {
|
||||
cm.AutoMap();
|
||||
|
||||
cm.GetMemberMap(c => c.MediaType)
|
||||
.SetSerializer(new EnumerationSerializer<MediaTypes>());
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region L10n
|
||||
|
||||
|
||||
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(CategoryL10n))) {
|
||||
BsonClassMap.RegisterClassMap<CategoryL10n>(cm => {
|
||||
cm.AutoMap();
|
||||
@ -126,7 +118,14 @@ namespace DataProviders
|
||||
});
|
||||
}
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(Localization))) {
|
||||
BsonClassMap.RegisterClassMap<Localization>(cm => {
|
||||
cm.AutoMap();
|
||||
|
||||
cm.GetMemberMap(c => c.Locale)
|
||||
.SetSerializer(new EnumerationSerializer<Locales>());
|
||||
});
|
||||
}
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(MenuItem))) {
|
||||
BsonClassMap.RegisterClassMap<MenuItem>(cm => {
|
||||
@ -159,25 +158,15 @@ namespace DataProviders
|
||||
#endregion
|
||||
|
||||
|
||||
#region Blog and Shop item
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(PostItemL10n))) {
|
||||
BsonClassMap.RegisterClassMap<PostItemL10n>(cm => {
|
||||
cm.AutoMap();
|
||||
|
||||
cm.GetMemberMap(c => c.Locale)
|
||||
.SetSerializer(new EnumerationSerializer<Locales>());
|
||||
|
||||
cm.GetMemberMap(c => c.TextFormat)
|
||||
.SetSerializer(new EnumerationSerializer<TextFormat>());
|
||||
});
|
||||
}
|
||||
|
||||
#region BlogItem
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(BlogDocument))) {
|
||||
BsonClassMap.RegisterClassMap<BlogDocument>(cm => {
|
||||
cm.AutoMap();
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ShopItem
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(ShopDocument))) {
|
||||
BsonClassMap.RegisterClassMap<ShopDocument>(cm => {
|
||||
cm.AutoMap();
|
||||
@ -342,21 +331,6 @@ namespace DataProviders
|
||||
|
||||
|
||||
#region Content
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(Settings))) {
|
||||
BsonClassMap.RegisterClassMap<Settings>(cm => {
|
||||
cm.AutoMap();
|
||||
});
|
||||
}
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(ContentL10n))) {
|
||||
BsonClassMap.RegisterClassMap<ContentL10n>(cm => {
|
||||
cm.AutoMap();
|
||||
|
||||
cm.GetMemberMap(c => c.Locale)
|
||||
.SetSerializer(new EnumerationSerializer<Locales>());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(ContentDocument))) {
|
||||
BsonClassMap.RegisterClassMap<ContentDocument>(cm => {
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
|
||||
public abstract class PersonBase<T> : DomainObjectBase<T> {
|
||||
public Guid Id { get; set; }
|
||||
public MediaAttachment? Image { get; set; }
|
||||
public Image? Image { get; set; }
|
||||
}
|
||||
|
||||
25
src/DomainObjects/Abstractions/PostItemBase.cs
Normal file
25
src/DomainObjects/Abstractions/PostItemBase.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using DomainObjects.L10n;
|
||||
|
||||
namespace DomainObjects.Abstractions;
|
||||
|
||||
public abstract class PostItemBase<T> : DomainObjectDocumentBase<T> {
|
||||
|
||||
public Guid SiteId { get; set; }
|
||||
|
||||
public List<PostItemL10n> L10n { get; set; }
|
||||
|
||||
public List<MediaAttachment>? MediaAttachments { get; set; }
|
||||
|
||||
public Guid Author { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public DateTime? Published { get; set; }
|
||||
|
||||
|
||||
public List<string>? Tags { get; set; }
|
||||
|
||||
public List<Guid> Categories { get; set; }
|
||||
|
||||
public bool? FamilyFriendly { get; set; }
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
using DomainObjects.Enumerations;
|
||||
|
||||
namespace DomainObjects.Abstractions.Posts.L10n;
|
||||
|
||||
public class PostItemL10n : DomainObjectBase<PostItemL10n> {
|
||||
public Locales Locale { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Text { get; set; }
|
||||
public TextFormat TextFormat { get; set; }
|
||||
public string PlainText { get; set; }
|
||||
public string ShortText { get; set; }
|
||||
public List<string>? Badges { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
|
||||
hash = hash * 23 + Locale.GetHashCode();
|
||||
hash = hash * 23 + Slug.GetHashCode();
|
||||
hash = hash * 23 + Description.GetHashCode();
|
||||
hash = hash * 23 + Title.GetHashCode();
|
||||
hash = hash * 23 + Text.GetHashCode();
|
||||
hash = hash * 23 + TextFormat.GetHashCode();
|
||||
hash = hash * 23 + ShortText.GetHashCode();
|
||||
|
||||
if (Badges != null)
|
||||
hash = hash * 23 + Badges.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
using DomainObjects.Abstractions.Posts.L10n;
|
||||
|
||||
namespace DomainObjects.Abstractions.Posts;
|
||||
|
||||
public abstract class PostItemBase<T> : DomainObjectDocumentBase<T> {
|
||||
|
||||
public Guid SiteId { get; set; }
|
||||
|
||||
public List<PostItemL10n> L10n { get; set; }
|
||||
|
||||
public List<MediaAttachment>? MediaAttachments { get; set; }
|
||||
|
||||
public Guid Author { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public DateTime? Published { get; set; }
|
||||
|
||||
|
||||
public List<string>? Tags { get; set; }
|
||||
|
||||
public List<Guid> Categories { get; set; }
|
||||
|
||||
public bool? FamilyFriendly { get; set; }
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
using DomainObjects.Abstractions;
|
||||
using DomainObjects.Documents.Content.L10n;
|
||||
|
||||
namespace DomainObjects.Documents.Content;
|
||||
|
||||
public class ContentDocument : DomainObjectDocumentBase<ContentDocument> {
|
||||
public List<ContentL10n> L10n { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
using DomainObjects.Enumerations;
|
||||
using DomainObjects.Pages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DomainObjects.Documents.Content.L10n {
|
||||
public class ContentL10n {
|
||||
public Locales Locale { get; set; }
|
||||
|
||||
public string SiteName { get; set; }
|
||||
public string SiteUrl { get; set; }
|
||||
|
||||
public Header Header { get; set; }
|
||||
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
public List<Route> Routes { get; set; }
|
||||
public List<Route> AdminRoutes { get; set; }
|
||||
public List<Route> ServiceRoutes { get; set; }
|
||||
|
||||
public List<MenuItem> TopMenu { get; set; }
|
||||
public List<MenuItem> SideMenu { get; set; }
|
||||
|
||||
public HomePage HomePage { get; set; }
|
||||
|
||||
public ShopCatalogPage ShopCatalog { get; set; }
|
||||
public ShopItemPage ShopItem { get; set; }
|
||||
public ShopCartPage ShopCart { get; set; }
|
||||
public ShopCheckoutPage ShopCheckout { get; set; }
|
||||
|
||||
|
||||
public BlogCatalogPage BlogCatalog { get; set; }
|
||||
public BlogItemPage BlogItem { get; set; }
|
||||
|
||||
public SignInPage SignIn { get; set; }
|
||||
public SignUpPage SignUp { get; set; }
|
||||
}
|
||||
}
|
||||
41
src/DomainObjects/Documents/ContentDocument.cs
Normal file
41
src/DomainObjects/Documents/ContentDocument.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using DomainObjects.Abstractions;
|
||||
using DomainObjects.Pages;
|
||||
|
||||
namespace DomainObjects.Documents;
|
||||
|
||||
public class ContentDocument : DomainObjectDocumentBase<ContentDocument> {
|
||||
|
||||
public Guid SiteId { get; set; }
|
||||
|
||||
public string SiteName { get; set; }
|
||||
public string SiteUrl { get; set; }
|
||||
|
||||
public Header Header { get; set; }
|
||||
|
||||
public Localization Localization { get; set; }
|
||||
|
||||
public List<Route> Routes { get; set; }
|
||||
public List<Route> AdminRoutes { get; set; }
|
||||
public List<Route> ServiceRoutes { get; set; }
|
||||
|
||||
public List<MenuItem> TopMenu { get; set; }
|
||||
public List<MenuItem> SideMenu { get; set; }
|
||||
|
||||
public HomePage HomePage { get; set; }
|
||||
|
||||
public ShopCatalogPage ShopCatalog { get; set; }
|
||||
public ShopItemPage ShopItem { get; set; }
|
||||
public ShopCartPage ShopCart { get; set; }
|
||||
public ShopCheckoutPage ShopCheckout { get; set; }
|
||||
|
||||
|
||||
public BlogCatalogPage BlogCatalog { get; set; }
|
||||
public BlogItemPage BlogItem { get; set; }
|
||||
|
||||
public SignInPage SignIn { get; set; }
|
||||
public SignUpPage SignUp { get; set; }
|
||||
|
||||
public override int GetHashCode() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using DomainObjects.Abstractions.Posts;
|
||||
using DomainObjects.Abstractions;
|
||||
|
||||
namespace DomainObjects.Documents.Posts;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using DomainObjects.Abstractions.Posts;
|
||||
using DomainObjects.Abstractions;
|
||||
|
||||
namespace DomainObjects.Documents.Posts;
|
||||
|
||||
|
||||
17
src/DomainObjects/Image.cs
Normal file
17
src/DomainObjects/Image.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using DomainObjects.Abstractions;
|
||||
|
||||
namespace DomainObjects;
|
||||
|
||||
public class Image : DomainObjectBase<MediaAttachment> {
|
||||
public string Src { get; set; }
|
||||
|
||||
public string Alt { get; set; }
|
||||
|
||||
public override int GetHashCode() {
|
||||
int hash = 17;
|
||||
hash = hash * 23 + Src.GetHashCode();
|
||||
hash = hash * 23 + Alt.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
36
src/DomainObjects/L10n/PostItemL10n.cs
Normal file
36
src/DomainObjects/L10n/PostItemL10n.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using DomainObjects.Abstractions;
|
||||
using DomainObjects.Enumerations;
|
||||
|
||||
namespace DomainObjects.L10n;
|
||||
|
||||
public class PostItemL10n : DomainObjectBase<PostItemL10n> {
|
||||
public Locales Locale { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Text { get; set; }
|
||||
public TextFormat TextFormat { get; set; }
|
||||
public string PlainText { get; set; }
|
||||
public string ShortText { get; set; }
|
||||
public List<string>? Badges { get; set; }
|
||||
|
||||
public override int GetHashCode() {
|
||||
unchecked {
|
||||
int hash = 17;
|
||||
|
||||
hash = hash * 23 + Locale.GetHashCode();
|
||||
hash = hash * 23 + Slug.GetHashCode();
|
||||
hash = hash * 23 + Description.GetHashCode();
|
||||
hash = hash * 23 + Title.GetHashCode();
|
||||
hash = hash * 23 + Text.GetHashCode();
|
||||
hash = hash * 23 + TextFormat.GetHashCode();
|
||||
hash = hash * 23 + ShortText.GetHashCode();
|
||||
|
||||
if(Badges != null)
|
||||
hash = hash * 23 + Badges.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,12 @@ using DomainObjects.Enumerations;
|
||||
|
||||
namespace DomainObjects;
|
||||
|
||||
public class Settings : DomainObjectBase<Settings> {
|
||||
public class Localization : DomainObjectBase<Localization> {
|
||||
|
||||
public string? TimeZone { get; set; }
|
||||
|
||||
public Locales Locale { get; set; }
|
||||
|
||||
public string? DateFormat { get; set; }
|
||||
|
||||
public string? TimeFormat { get; set; }
|
||||
@ -3,7 +3,7 @@
|
||||
namespace DomainObjects.PageSections;
|
||||
|
||||
public class TitleSection : PageSectionBase<TitleSection> {
|
||||
public MediaAttachment? Image { get; set; }
|
||||
public Image? Image { get; set; }
|
||||
public Link? PrimaryLink { get; set; }
|
||||
public Link? SecondaryLink { get; set; }
|
||||
public string? PostedOnBy { get; set; }
|
||||
|
||||
@ -82,46 +82,5 @@ namespace Extensions {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static readonly string[] suffixes = { "Bytes", "KB", "MB", "GB", "TB", "PB" };
|
||||
|
||||
private static decimal FormatSize(int bytes, int index = 1) => (decimal)bytes / 1024 * index;
|
||||
|
||||
public static string ToKB(this int bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes, 1), suffixes[1]);
|
||||
|
||||
public static string ToKB(this byte[] bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes.Length, 1), suffixes[1]);
|
||||
|
||||
|
||||
public static string ToMB(this int bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes, 2), suffixes[2]);
|
||||
|
||||
public static string ToMB(this byte[] bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes.Length, 2), suffixes[2]);
|
||||
|
||||
|
||||
public static string ToGB(this int bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes, 3), suffixes[3]);
|
||||
|
||||
public static string ToGB(this byte[] bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes.Length, 3), suffixes[3]);
|
||||
|
||||
|
||||
public static string ToTB(this int bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes, 4), suffixes[4]);
|
||||
|
||||
public static string ToTB(this byte[] bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes.Length, 4), suffixes[4]);
|
||||
|
||||
|
||||
public static string ToPB(this int bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes, 5), suffixes[5]);
|
||||
|
||||
public static string ToPB(this byte[] bytes) =>
|
||||
string.Format("{0:n1}{1}", FormatSize(bytes.Length, 5), suffixes[5]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,6 +83,8 @@ namespace ImageProvider {
|
||||
image.Mutate(ctx => {
|
||||
ctx.BackgroundColor(backgroundColor);
|
||||
ctx.ApplyScalingWaterMark(font, $"{width}x{height}", foregroundColor, 5, false);
|
||||
//x.Fill(, new Rectangle(0, 0, width, height));
|
||||
//x.DrawText($"{width}x{height}", font, Color.Black, new PointF(10, 10));
|
||||
});
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"route2": {
|
||||
"ClusterId": "cluster1",
|
||||
"Match": {
|
||||
"Path": "Image/{**catchall}"
|
||||
"Path": "image/{**catchall}"
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathPattern": "/api/Image/{**catchall}" }
|
||||
@ -48,7 +48,7 @@
|
||||
"cluster2": {
|
||||
"Destinations": {
|
||||
"destination1": {
|
||||
"Address": "http://clientapp:3000/"
|
||||
"Address": "http://localhost:3000/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,11 +8,13 @@ EXPOSE 443
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["WeatherForecast/WeatherForecast.csproj", "WeatherForecast/"]
|
||||
COPY ["FileSecurityService/FileSecurityService.csproj", "Services/FileSecurityService/"]
|
||||
COPY ["Services/FileSecurityService/FileSecurityService.csproj", "Services/FileSecurityService/"]
|
||||
COPY ["Extensions/Extensions.csproj", "Extensions/"]
|
||||
COPY ["Core/Core.csproj", "Core/"]
|
||||
COPY ["ImageProvider/ImageProvider.csproj", "Services/ImageProvider/"]
|
||||
COPY ["Services/ImageProvider/ImageProvider.csproj", "Services/ImageProvider/"]
|
||||
COPY ["DataProviders/DataProviders.csproj", "DataProviders/"]
|
||||
COPY ["Services/JWTService/JWTService.csproj", "Services/JWTService/"]
|
||||
COPY ["Services/HashService/HashService.csproj", "Services/HashService/"]
|
||||
RUN dotnet restore "WeatherForecast/WeatherForecast.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/WeatherForecast"
|
||||
|
||||
@ -7,7 +7,6 @@ using DomainObjects;
|
||||
|
||||
using Core.Enumerations;
|
||||
using Core.Abstractions.Models;
|
||||
using DomainObjects.Abstractions.Posts.L10n;
|
||||
|
||||
namespace WeatherForecast.Models.Blog.Requests {
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using DomainObjects.Abstractions.Posts.L10n;
|
||||
using DomainObjects.Documents.Posts;
|
||||
using DomainObjects.Enumerations;
|
||||
using DomainObjects.L10n;
|
||||
@ -9,7 +8,6 @@ using DomainObjects;
|
||||
using Core.Enumerations;
|
||||
using Core.Abstractions.Models;
|
||||
|
||||
|
||||
namespace WeatherForecast.Models.Blog.Requests {
|
||||
|
||||
/// <summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user