(refactor): loader interceptor and overall code reordering
This commit is contained in:
parent
5ad87b6d48
commit
eb5334917d
@ -1,8 +1,386 @@
|
||||
{
|
||||
"_id": "b3f39a82-6a1b-46a4-85cc-04c3b4315511",
|
||||
"siteId": "404c8232-9048-4519-bfba-6e78dc7005ca",
|
||||
"siteName": "MAKS-IT",
|
||||
"siteUrl": "https://maks-it.com",
|
||||
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"siteName": "Contoso",
|
||||
"siteUrl": "https://maks-it.com",
|
||||
"settings": {
|
||||
"timeZone": "+1",
|
||||
"dateFormat": "MMMM YYYY, dddd",
|
||||
"timeFormat": "HH:mm",
|
||||
"currency": "EUR",
|
||||
"currencySymbol": "€"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"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..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"header": {
|
||||
"title": "{siteName}",
|
||||
"meta": {
|
||||
@ -14,14 +392,7 @@
|
||||
"canonical": "{siteUrl}"
|
||||
}
|
||||
},
|
||||
"localization": {
|
||||
"timeZone": "+1",
|
||||
"locale": "en-US",
|
||||
"dateFormat": "MMMM YYYY, dddd",
|
||||
"timeFormat": "HH:mm",
|
||||
"currency": "EUR",
|
||||
"currencySymbol": "€"
|
||||
},
|
||||
|
||||
"routes": [
|
||||
{
|
||||
"target": "/",
|
||||
@ -103,394 +474,60 @@
|
||||
"topMenu": [
|
||||
{
|
||||
"target": "/",
|
||||
"title": "Home"
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"title": "Home"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "/shop",
|
||||
"title": "Shop"
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"title": "Shop"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "/blog",
|
||||
"title": "Blog"
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"title": "Blog"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "/signin",
|
||||
"title": "Sing in"
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"title": "Sign in"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "/signup",
|
||||
"title": "Sign up"
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"title": "Sign up"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "/shop/cart",
|
||||
"icon": "shopping-cart",
|
||||
"title": "Cart ({quantity})"
|
||||
"l10n": [
|
||||
{
|
||||
"locale": 0,
|
||||
"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": [
|
||||
{
|
||||
"icon": "navigation",
|
||||
"title": "Client-side navigation",
|
||||
"text": "For example, click <em>Counter</em> then <em>Back</em> to return here."
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"_id": "c5295208-8950-441f-8217-bd7c4a907a0f",
|
||||
"image": {
|
||||
"src": "https://dummyimage.com/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..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"sideMenu": []
|
||||
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
*
|
||||
!obj\Docker\publish\*
|
||||
!obj\Docker\empty\
|
||||
@ -1,7 +1,12 @@
|
||||
BROWSER=none
|
||||
|
||||
REACT_APP_API=https://localhost:7151/api
|
||||
REACT_APP_LOCAL_ONLY=Y
|
||||
|
||||
REACT_APP_FRONTEND=https://localhost:7174
|
||||
REACT_APP_API=https://localhost:7174/api
|
||||
|
||||
REACT_APP_SITEID=404c8232-9048-4519-bfba-6e78dc7005ca
|
||||
REACT_APP_LOCALE=en-US
|
||||
|
||||
REACT_APP_ACCOUNT=Account
|
||||
|
||||
|
||||
@ -32,11 +32,6 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include=".dockerignore">
|
||||
<SubType>Content</SubType>
|
||||
<DependentUpon>Dockerfile</DependentUpon>
|
||||
</Content>
|
||||
<None Include="Dockerfile" />
|
||||
<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" />
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
# ==== CONFIGURE =====
|
||||
# Use a Node 16 base image
|
||||
FROM node:16-alpine
|
||||
# Set the working directory to /app inside the container
|
||||
WORKDIR /app
|
||||
# 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
|
||||
# Start the app
|
||||
CMD [ "npx", "serve", "build" ]
|
||||
78
src/ClientApp/package-lock.json
generated
78
src/ClientApp/package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "react-redux-template",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"bootstrap": "5.1.3",
|
||||
"classnames": "^2.3.1",
|
||||
"dayjs": "^1.11.2",
|
||||
@ -4713,8 +4714,7 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
@ -4767,6 +4767,29 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
@ -5607,7 +5630,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@ -6379,7 +6401,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@ -7984,7 +8005,6 @@
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -10723,7 +10743,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@ -10732,7 +10751,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@ -12841,6 +12859,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
@ -19526,8 +19549,7 @@
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
@ -19555,6 +19577,28 @@
|
||||
"integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
@ -20213,7 +20257,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
@ -20760,8 +20803,7 @@
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
@ -21994,8 +22036,7 @@
|
||||
"follow-redirects": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin": {
|
||||
"version": "6.5.2",
|
||||
@ -24041,14 +24082,12 @@
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
@ -25442,6 +25481,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"bootstrap": "5.1.3",
|
||||
"classnames": "^2.3.1",
|
||||
"dayjs": "^1.11.2",
|
||||
|
||||
@ -1,28 +1,37 @@
|
||||
// React
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import { Route, Routes, useLocation } from 'react-router'
|
||||
|
||||
// Redux
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
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: RouteModel[], tag: string | undefined = undefined) => {
|
||||
const NestedRoutes = (routes: IRoute[], tag: string | undefined = undefined) => {
|
||||
if(!Array.isArray(routes)) return
|
||||
|
||||
return routes.map((route: RouteModel, index: number) => {
|
||||
return routes.map((route: IRoute, index: number) => {
|
||||
const routeProps: IRouteProp = {
|
||||
path: route.target
|
||||
}
|
||||
@ -32,17 +41,48 @@ const NestedRoutes = (routes: RouteModel[], 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, loader } = useSelector((state: ApplicationState) => state)
|
||||
const { content, header } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(settingsActionCreators.requestContent())
|
||||
@ -56,11 +96,7 @@ const App = () => {
|
||||
}, [pathname])
|
||||
|
||||
|
||||
const {
|
||||
title = "",
|
||||
link = {},
|
||||
meta = {}
|
||||
} = header ? header : {}
|
||||
const { title, link, meta } = header
|
||||
|
||||
return <>
|
||||
<Helmet>
|
||||
@ -76,7 +112,13 @@ const App = () => {
|
||||
{content?.serviceRoutes ? NestedRoutes(content.serviceRoutes) : ''}
|
||||
</Routes>
|
||||
|
||||
{loader ? <Loader {...loader} /> : ''}
|
||||
{<ModalComponent {...{
|
||||
visible: false,
|
||||
title: 'Message title',
|
||||
text: 'Some message'
|
||||
}} />}
|
||||
|
||||
{<Loader {...{visible: loaderCounter > 0 }} />}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { FC, ReactNode, useEffect, useState } from 'react'
|
||||
import React, { FC, ReactNode } from 'react'
|
||||
|
||||
import './scss/loaders.scss'
|
||||
|
||||
|
||||
40
src/ClientApp/src/components/Modal/index.tsx
Normal file
40
src/ClientApp/src/components/Modal/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
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'
|
||||
|
||||
|
||||
interface PaginationProps {
|
||||
export interface IPaginationComponent {
|
||||
maxVisiblePages?: number,
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
onClick: (page: number) => void
|
||||
}
|
||||
|
||||
const Pagination: FC<PaginationProps> = ({
|
||||
const Pagination: FC<IPaginationComponent> = ({
|
||||
maxVisiblePages = 5,
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
@ -80,14 +80,14 @@ const Pagination: FC<PaginationProps> = ({
|
||||
</nav>
|
||||
}
|
||||
|
||||
interface SSRPaginationProps {
|
||||
export interface ISSRPaginationComponent {
|
||||
maxVisiblePages?: number,
|
||||
totalPages: number,
|
||||
currentPage: number,
|
||||
linksPath?: string
|
||||
}
|
||||
|
||||
const SSRPagination: FC<SSRPaginationProps> = ({
|
||||
const SSRPagination: FC<ISSRPaginationComponent> = ({
|
||||
maxVisiblePages = 5,
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
|
||||
import { CategoryModel } from '../../models'
|
||||
|
||||
export interface ICategory {
|
||||
href: string,
|
||||
anchorText: string
|
||||
}
|
||||
|
||||
interface ICategories {
|
||||
items?: CategoryModel []
|
||||
items?: ICategory []
|
||||
}
|
||||
|
||||
const Categories: FC<ICategories> = ({
|
||||
@ -21,12 +27,12 @@ const Categories: FC<ICategories> = ({
|
||||
|
||||
<Col sm="6">
|
||||
<ul className="list-unstyled mb-0">
|
||||
{firstHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
|
||||
{firstHalf.map((item, index) => <li key={index}><Link to={item.href}>{item.anchorText}</Link></li>)}
|
||||
</ul>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<ul className="list-unstyled mb-0">
|
||||
{secondHalf.map((item, index) => <li key={index}><a href="#!">{item.text}</a></li>)}
|
||||
{secondHalf.map((item, index) => <li key={index}><Link to={item.href}>{item.anchorText}</Link></li>)}
|
||||
</ul>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -36,4 +42,4 @@ const Categories: FC<ICategories> = ({
|
||||
|
||||
export {
|
||||
Categories
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,24 @@
|
||||
import { RouteModel } from "../models"
|
||||
import { IRoute } from "../App"
|
||||
|
||||
interface ComponentRoutesModel {
|
||||
targets: string [],
|
||||
component: string
|
||||
}
|
||||
|
||||
const findRoutes = (routes: RouteModel[] = [], component: string | undefined, parentTarget: string [] = [], result: ComponentRoutesModel [] = []): ComponentRoutesModel [] => {
|
||||
/**
|
||||
* 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 [] => {
|
||||
|
||||
if(!Array.isArray(routes))
|
||||
return []
|
||||
|
||||
routes.forEach((route: RouteModel) => {
|
||||
routes.forEach((route: IRoute) => {
|
||||
const targets: string [] = []
|
||||
if(parentTarget) {
|
||||
parentTarget.forEach(item => {
|
||||
|
||||
123
src/ClientApp/src/interfaces/index.ts
Normal file
123
src/ClientApp/src/interfaces/index.ts
Normal file
@ -0,0 +1,123 @@
|
||||
|
||||
//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,6 +6,15 @@ 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,9 +4,15 @@ 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)
|
||||
|
||||
@ -33,7 +39,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: MenuItemModel, index: number) => {
|
||||
{content?.topMenu ? content.topMenu.map((item: INavMenuItem, index: number) => {
|
||||
return <NavItem key={index}>
|
||||
<NavLink tag={Link} className="text-dark" to={item.target}><>
|
||||
{item.icon ? <><FeatherIcon icon={item.icon}/> </> : ''}{titleFormatter(item.title)}</>
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
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 []
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
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> {}
|
||||
@ -1,93 +0,0 @@
|
||||
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,6 +1,10 @@
|
||||
import * as React from 'react'
|
||||
import React, { FC } from 'react'
|
||||
|
||||
const AdminHome = () => {
|
||||
interface IAdminHomeComponent {
|
||||
|
||||
}
|
||||
|
||||
const AdminHome : FC<IAdminHomeComponent> = () => {
|
||||
|
||||
return <div>Admin Home</div>
|
||||
}
|
||||
|
||||
101
src/ClientApp/src/pages/Blog/Catalog/BlogItemsComponent.tsx
Normal file
101
src/ClientApp/src/pages/Blog/Catalog/BlogItemsComponent.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
// 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 { IPaginationComponent, Pagination } 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 {
|
||||
path?: string
|
||||
totalPages?: number,
|
||||
currentPage?: number,
|
||||
items?: IBlogItem []
|
||||
}
|
||||
|
||||
export interface IBlogItemsSection {
|
||||
readMore: string
|
||||
}
|
||||
|
||||
export interface IBlogItemsComponent extends IBlogItemsSection, IBlogItems { }
|
||||
|
||||
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>)}
|
||||
|
||||
<Pagination {...{
|
||||
totalPages: totalPages,
|
||||
currentPage: currentPage,
|
||||
onClick: (nextPage) => {
|
||||
dispatch(blogCatalogActionCreators.requestBlogCatalog({
|
||||
currentPage: nextPage + ""
|
||||
}))
|
||||
|
||||
navigate(`${path}/${nextPage}`)
|
||||
}
|
||||
} as IPaginationComponent} />
|
||||
</>
|
||||
}
|
||||
|
||||
export {
|
||||
BlogItemsComponent
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
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,
|
||||
text?: 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>)}
|
||||
|
||||
<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>
|
||||
{readTime !=null ? <div className="text-muted">{readTime}</div> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
}
|
||||
|
||||
export {
|
||||
FeaturedBlogComponent
|
||||
}
|
||||
30
src/ClientApp/src/pages/Blog/Catalog/TitleComponent.tsx
Normal file
30
src/ClientApp/src/pages/Blog/Catalog/TitleComponent.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
// 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,195 +1,81 @@
|
||||
// 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'
|
||||
|
||||
import { actionCreators as contentActionCreators } from '../../../store/reducers/Content'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
// Reducers
|
||||
import { actionCreators as blogCatalogActionCreators } from '../../../store/reducers/BlogCatalog'
|
||||
import { actionCreators as blogFeaturedActionCreators } from '../../../store/reducers/BlogFeatured'
|
||||
import { actionCreators as blogCategoriesActionCreators } from '../../../store/reducers/BlogCategories'
|
||||
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
|
||||
import { dateFormat, findRoutes } from '../../../functions'
|
||||
// Reactstrap
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
|
||||
// Components
|
||||
import { TitleSection, ITitleSection } from './TitleComponent'
|
||||
import { FeaturedBlogComponent, IFeaturedBlogComponent, IFeaturedBlogSection } from './FeaturedBlogComponent'
|
||||
import { BlogItemsComponent, IBlogItemsSection } from './BlogItemsComponent'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
import { Pagination } from '../../../components/Pagination'
|
||||
|
||||
import { BlogItemModel } from '../../../models'
|
||||
import { TitleSectionModel } from '../../../models/pageSections'
|
||||
// Interfaces
|
||||
import { IHeader } from '../../../interfaces'
|
||||
|
||||
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 IBlogCatalogPage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
blogItemsSection: IBlogItemsSection,
|
||||
featuredBlogSection: IFeaturedBlogSection
|
||||
}
|
||||
|
||||
interface FeaturedBlog {
|
||||
path?: string,
|
||||
currentPage?: number
|
||||
item?: BlogItemModel,
|
||||
readTime?: string
|
||||
}
|
||||
export interface IBlogCatalogComponent extends IBlogCatalogPage { }
|
||||
|
||||
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 BlogCatalog : FC<IBlogCatalogComponent> = () => {
|
||||
const location = useLocation()
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, blogCatalog, blogCategories, blogFeatured } = useSelector((state: ApplicationState) => state)
|
||||
const page = content?.blogCatalog
|
||||
const path = findRoutes(content?.routes, 'BlogCatalog')[0]?.targets[0]
|
||||
|
||||
const {header, titleSection, featuredBlogSection } = content.blogCatalog
|
||||
|
||||
// update categories slugs
|
||||
blogCategories.items = blogCategories.items.map(item => {
|
||||
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}`
|
||||
return item
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(blogCatalogActionCreators.requestBlogCatalog({
|
||||
currentPage: params?.page ? params.page : "1"
|
||||
searchParams: {
|
||||
currentPage: params?.page ? params.page : "1"
|
||||
}
|
||||
}))
|
||||
dispatch(blogFeaturedActionCreators.requestBlogFeatured())
|
||||
dispatch(blogCategoriesActionCreators.requestBlogCategories())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
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 {...page?.titleSection} />
|
||||
<TitleSection {...titleSection} />
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col>
|
||||
<FeaturedBlogSection {...featuredBlog} />
|
||||
<FeaturedBlogComponent {...{
|
||||
path: location.pathname,
|
||||
currentPage: blogCatalog?.currentPage,
|
||||
item: blogItem,
|
||||
featuredBlogSection
|
||||
} as IFeaturedBlogComponent} />
|
||||
<Row>
|
||||
<BlogItemsSection path={path} {...blogCatalog} />
|
||||
<BlogItemsComponent path={location.pathname} {...blogCatalog} readMore={''} />
|
||||
</Row>
|
||||
</Col>
|
||||
<Col lg="4">
|
||||
{/*<Search />
|
||||
<Categories {...blogCategories} />*/}
|
||||
<Col lg="3">
|
||||
<Search />
|
||||
<Categories {...blogCategories} />
|
||||
<Empty/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@ -1,25 +1,43 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Card, CardBody } from 'reactstrap'
|
||||
import { CommentModel } from '../../models'
|
||||
import { CommentsSectionModel } from '../../models/pageSections'
|
||||
|
||||
|
||||
|
||||
interface Comments {
|
||||
staticContent?: CommentsSectionModel,
|
||||
items?: CommentModel []
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
const CommentsSection: FC<Comments> = ({
|
||||
staticContent,
|
||||
items = []
|
||||
}) => {
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
|
||||
export interface IComment {
|
||||
author: IAuthor,
|
||||
comment: string,
|
||||
responses?: IComment []
|
||||
}
|
||||
|
||||
export interface ICommentsSection {
|
||||
leaveComment: string
|
||||
}
|
||||
|
||||
export interface ICommentsComponent extends ICommentsSection {
|
||||
items?: IComment []
|
||||
}
|
||||
|
||||
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={staticContent?.leaveComment}></textarea>
|
||||
<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' : ''}`}>
|
||||
@ -50,5 +68,5 @@ const CommentsSection: FC<Comments> = ({
|
||||
}
|
||||
|
||||
export {
|
||||
CommentsSection
|
||||
CommentsComponent
|
||||
}
|
||||
40
src/ClientApp/src/pages/Blog/Item/TitleSection.tsx
Normal file
40
src/ClientApp/src/pages/Blog/Item/TitleSection.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
// 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,52 +4,58 @@ 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 BlogItemTitle {
|
||||
title?: string,
|
||||
postedOnBy? :string,
|
||||
badges? : string[],
|
||||
image?: ImageModel
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
const BlogTitleSection: FC<BlogItemTitle> = ({
|
||||
title = "",
|
||||
postedOnBy = "",
|
||||
badges = [],
|
||||
image = {
|
||||
src: "",
|
||||
alt: ""
|
||||
}
|
||||
}) => {
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
return <>
|
||||
<header className="mb-4">
|
||||
<h1 className="fw-bolder mb-1">{title}</h1>
|
||||
<div className="text-muted fst-italic mb-2">{postedOnBy ? postedOnBy : ''}</div>
|
||||
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 []
|
||||
|
||||
{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>
|
||||
</>
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
const BlogItem = () => {
|
||||
export interface IBlogItemPage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
commentsSection: ICommentsSection
|
||||
}
|
||||
|
||||
export interface IBlogItemComponent extends IBlogItemPage {}
|
||||
|
||||
const BlogItem : FC<IBlogItemComponent> = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
@ -65,40 +71,32 @@ const BlogItem = () => {
|
||||
}))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
blogItem?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [blogItem?.isLoading])
|
||||
|
||||
const blogItemTitle: BlogItemTitle = {
|
||||
const blogItemTitle: ITitleComponent = {
|
||||
title: blogItem?.title,
|
||||
postedOnBy: page?.titleSection?.postedOnBy,
|
||||
text: page?.titleSection?.text,
|
||||
badges: blogItem?.badges,
|
||||
image: blogItem?.image
|
||||
}
|
||||
|
||||
if(blogItemTitle.postedOnBy && blogItem?.created)
|
||||
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{date}', dateFormat(blogItem.created))
|
||||
if(blogItemTitle.text && blogItem?.created)
|
||||
blogItemTitle.text = blogItemTitle.text?.replace('{date}', dateFormat(blogItem.created))
|
||||
|
||||
if(blogItemTitle.postedOnBy && blogItem?.author)
|
||||
blogItemTitle.postedOnBy = blogItemTitle.postedOnBy?.replace('{nickName}', dateFormat(blogItem.author.nickName))
|
||||
if(blogItemTitle.text && blogItem?.author)
|
||||
blogItemTitle.text = blogItemTitle.text?.replace('{nickName}', dateFormat(blogItem.author.nickName))
|
||||
|
||||
return <Container fluid className="mt-5">
|
||||
<Row>
|
||||
<Col lg="8">
|
||||
|
||||
<article>
|
||||
<BlogTitleSection {...blogItemTitle} />
|
||||
<TitleSection {...blogItemTitle} />
|
||||
<section className="mb-5" dangerouslySetInnerHTML={{ __html: blogItem?.text ? blogItem.text : '' }}></section>
|
||||
</article>
|
||||
|
||||
<CommentsSection {...{
|
||||
staticContent: page?.commentsSection,
|
||||
items: blogItem?.comments
|
||||
}} />
|
||||
<CommentsComponent {...{
|
||||
...page?.commentsSection,
|
||||
//items: blogItem?.comments
|
||||
} as ICommentsComponent} />
|
||||
</Col>
|
||||
|
||||
<Col lg="4">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { FC } from 'react'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
@ -8,7 +8,9 @@ interface IReduxState {
|
||||
counter: CounterState
|
||||
}
|
||||
|
||||
const Counter = () => {
|
||||
export interface ICounterComponent {}
|
||||
|
||||
const Counter : FC<ICounterComponent> = () => {
|
||||
const dispatch = useDispatch()
|
||||
const counterState = useSelector((state: IReduxState) => state.counter)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// React
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
@ -15,7 +15,9 @@ type IParams = {
|
||||
startDateIndex: string
|
||||
}
|
||||
|
||||
const FetchData = () => {
|
||||
export interface IFetchDataComponent {}
|
||||
|
||||
const FetchData : FC<IFetchDataComponent> = () => {
|
||||
const location = useLocation()
|
||||
const params = useParams<IParams>()
|
||||
|
||||
|
||||
43
src/ClientApp/src/pages/Home/CallToActionSection.tsx
Normal file
43
src/ClientApp/src/pages/Home/CallToActionSection.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
||||
}
|
||||
90
src/ClientApp/src/pages/Home/FeaturedBlogsSection.tsx
Normal file
90
src/ClientApp/src/pages/Home/FeaturedBlogsSection.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
// 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,
|
||||
text?: string,
|
||||
author: IAuthor,
|
||||
created: string,
|
||||
tags: string []
|
||||
|
||||
readTime?: number,
|
||||
likes?: number
|
||||
}
|
||||
|
||||
export interface IFeaturedBlogsSection {
|
||||
title: string,
|
||||
text?: string
|
||||
}
|
||||
|
||||
interface IFeaturedBlogsFull extends IFeaturedBlogsSection {
|
||||
items?: IFeaturedBlogItem []
|
||||
}
|
||||
|
||||
const FeaturedBlogsSection: FC<IFeaturedBlogsFull> = (props) => {
|
||||
|
||||
const { title, text = "", items = [] } = props
|
||||
|
||||
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: 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>
|
||||
}
|
||||
|
||||
export {
|
||||
FeaturedBlogsSection
|
||||
}
|
||||
50
src/ClientApp/src/pages/Home/FeaturesSection.tsx
Normal file
50
src/ClientApp/src/pages/Home/FeaturesSection.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
// 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
|
||||
}
|
||||
47
src/ClientApp/src/pages/Home/TestimonialsSection.tsx
Normal file
47
src/ClientApp/src/pages/Home/TestimonialsSection.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
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
|
||||
}
|
||||
36
src/ClientApp/src/pages/Home/TitleSection.tsx
Normal file
36
src/ClientApp/src/pages/Home/TitleSection.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
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,240 +1,59 @@
|
||||
// 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'
|
||||
|
||||
// Custom components
|
||||
import { FeatherIcon } from '../../components/FeatherIcons'
|
||||
import { IHeader } from '../../interfaces'
|
||||
import { TitleSection, ITitleSection } from './TitleSection'
|
||||
import { FeaturesSection, IFeaturesSection } from './FeaturesSection'
|
||||
import { TestimonialsSection, ITestimonialsSection } from './TestimonialsSection'
|
||||
import { FeaturedBlogsSection, IFeaturedBlogsSection } from './FeaturedBlogsSection'
|
||||
import { CallToActionSection, ICallToActionSection } from './CallToActionSection'
|
||||
|
||||
// Functions
|
||||
import { dateFormat } from '../../functions'
|
||||
|
||||
// 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 IHomePage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
featuresSection: IFeaturesSection,
|
||||
testimonialsSection: ITestimonialsSection,
|
||||
featuredBlogsSection: IFeaturedBlogsSection,
|
||||
callToActionSection: ICallToActionSection
|
||||
}
|
||||
|
||||
interface Fetures {
|
||||
title?: string,
|
||||
items?: FeatureModel []
|
||||
}
|
||||
export interface IHomePageComponent extends IHomePage { }
|
||||
|
||||
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 Home : FC<IHomePageComponent> = () => {
|
||||
const dispatch = useDispatch()
|
||||
const { content, blogFeatured } = useSelector((state: ApplicationState) => state)
|
||||
const page = content?.homePage
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(blogFeaturedActionCreators.requestBlogFeatured())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading || blogFeatured?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading, blogFeatured?.isLoading])
|
||||
dispatch(headerActionCreators.updateHeader(content?.homePage.header))
|
||||
}, [content?.homePage.header])
|
||||
|
||||
const {
|
||||
header = {},
|
||||
titleSection = {
|
||||
title: "",
|
||||
text: ""
|
||||
},
|
||||
featuresSection = {},
|
||||
testimonialsSection = {},
|
||||
featuredBlogsSection = {},
|
||||
callToActionSection = {
|
||||
title: "",
|
||||
text: "",
|
||||
privacyDisclaimer: "",
|
||||
email: {
|
||||
placeHolder: "",
|
||||
title: ""
|
||||
}
|
||||
}
|
||||
} = content?.homePage ? content.homePage : {}
|
||||
if(content?.homePage) {
|
||||
const { titleSection, featuresSection, testimonialsSection, featuredBlogsSection, callToActionSection } = content.homePage
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(headerActionCreators.updateHeader(header as HeaderModel))
|
||||
}, [header])
|
||||
|
||||
|
||||
return <>
|
||||
<TitleSection {...titleSection} />
|
||||
<FeaturesSection {...featuresSection} />
|
||||
<TestimonialsSection {...testimonialsSection} />
|
||||
<FeaturedBlogsSection items={blogFeatured?.items} {...featuredBlogsSection} />
|
||||
<CallToActionSection {...callToActionSection} />
|
||||
</>
|
||||
return <>
|
||||
<TitleSection {...titleSection} />
|
||||
<FeaturesSection {...featuresSection} />
|
||||
<TestimonialsSection {...testimonialsSection} />
|
||||
<FeaturedBlogsSection items={blogFeatured?.items} {...featuredBlogsSection} />
|
||||
<CallToActionSection {...callToActionSection} />
|
||||
</>
|
||||
}
|
||||
else {
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
// React
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { FC, 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'
|
||||
|
||||
@ -14,7 +13,32 @@ import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import style from './scss/style.module.scss'
|
||||
import { ReservedWords } from '../../../enumerations'
|
||||
|
||||
const Cart = () => {
|
||||
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 dispatch = useDispatch()
|
||||
const { content, shopCart } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
@ -23,40 +47,11 @@ const Cart = () => {
|
||||
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 = {
|
||||
title: "",
|
||||
text: ""
|
||||
},
|
||||
productsSection = {
|
||||
product: "",
|
||||
price: "",
|
||||
quantity: "",
|
||||
subtotal: "",
|
||||
continueShopping: {
|
||||
target: "!#",
|
||||
anchorText: ""
|
||||
},
|
||||
checkout: {
|
||||
target: "!#",
|
||||
anchorText: ""
|
||||
}
|
||||
}
|
||||
|
||||
} = content?.shopCart ? content.shopCart : {}
|
||||
//const { titleSection, productsSection } = content.shopCart
|
||||
|
||||
const [subtotal, setSubtotal] = useState<number>(0)
|
||||
|
||||
@ -86,7 +81,7 @@ const Cart = () => {
|
||||
}
|
||||
|
||||
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>
|
||||
@ -105,7 +100,7 @@ const Cart = () => {
|
||||
<td data-th="Product">
|
||||
<div className="row">
|
||||
<div className="col-md-3 text-left">
|
||||
<img src={`${process.env.REACT_APP_API}/Image/250x250/ced4da/6c757d`} alt="" className="img-fluid d-none d-md-block rounded mb-2 shadow" />
|
||||
<img src={`${process.env.REACT_APP_FRONTEND}/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>
|
||||
@ -147,7 +142,7 @@ const Cart = () => {
|
||||
<Link to={productsSection.continueShopping.target}><FeatherIcon icon="arrow-left" /> {productsSection.continueShopping.anchorText}</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</section> */}
|
||||
</Container>
|
||||
}
|
||||
|
||||
|
||||
123
src/ClientApp/src/pages/Shop/Catalog/ShopItemsSection.tsx
Normal file
123
src/ClientApp/src/pages/Shop/Catalog/ShopItemsSection.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
// 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.split('/').slice(0, 3).join('/')
|
||||
} as ISSRPaginationComponent} />
|
||||
</>
|
||||
}
|
||||
|
||||
export {
|
||||
ShopItemsSection
|
||||
}
|
||||
30
src/ClientApp/src/pages/Shop/Catalog/TitleSection.tsx
Normal file
30
src/ClientApp/src/pages/Shop/Catalog/TitleSection.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
// 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,156 +1,87 @@
|
||||
// React
|
||||
import React, { FC, useEffect } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
|
||||
// Redux
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../store'
|
||||
import { actionCreators as loaderActionCreators } from '../../../store/reducers/Loader'
|
||||
|
||||
// Reducers
|
||||
import { actionCreators as shopCatalogActionCreators } from '../../../store/reducers/ShopCatalog'
|
||||
import { actionCreators as shopCategoriesActionCreators } from '../../../store/reducers/ShopCategories'
|
||||
|
||||
// Reactstrap
|
||||
import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from 'reactstrap'
|
||||
import { Col, Container, Row } from 'reactstrap'
|
||||
|
||||
// Models (interfaces)
|
||||
import { ShopItemModel } from '../../../models'
|
||||
import { TitleSectionModel } from '../../../models/pageSections'
|
||||
// Components
|
||||
import { TitleSection, ITitleSection } from './TitleSection'
|
||||
import { ShopItemsSection, IShopItemsSection, IShopItemComponent } from './ShopItemsSection'
|
||||
import { Categories, Empty, Search } from '../../../components/SideWidgets'
|
||||
|
||||
// Custom components
|
||||
import { FeatherRating } from '../../../components/FeatherRating'
|
||||
import { Pagination } from '../../../components/Pagination'
|
||||
// Interfaces
|
||||
import { IHeader } from '../../../interfaces'
|
||||
|
||||
// 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 IShopCatalogPage {
|
||||
header: IHeader,
|
||||
titleSection: ITitleSection,
|
||||
shopItemsSection: IShopItemsSection,
|
||||
}
|
||||
|
||||
const ShopItemsSection: FC<ShopItems> = ({
|
||||
currencySymbol = "",
|
||||
addToCart = "",
|
||||
path = "",
|
||||
totalPages = 1,
|
||||
currentPage = 1,
|
||||
items = []
|
||||
}) => {
|
||||
export interface IShopCatalogComponent extends IShopCatalogPage { }
|
||||
|
||||
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 ShopCatalog : FC<IShopCatalogComponent> = () => {
|
||||
const location = useLocation()
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { content, shopCatalog } = useSelector((state: ApplicationState) => state)
|
||||
const page = content?.shopCatalog
|
||||
const path = findRoutes(content?.routes, 'ShopCatalog')[0]?.targets[0]
|
||||
const { content, shopCatalog, shopCategories } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const {
|
||||
currencySymbol = ""
|
||||
} = content?.localization ? content.localization : {}
|
||||
const { currencySymbol } = content.localization
|
||||
const { header, titleSection, shopItemsSection } = content.shopCatalog
|
||||
|
||||
// update categories slugs
|
||||
shopCategories.items = shopCategories.items.map(item => {
|
||||
item.href = `${location.pathname.split('/').slice(0, 2).join('/')}/${item.href}`
|
||||
return item
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(shopCatalogActionCreators.requestShopCatalog({
|
||||
searchParams: {
|
||||
currentPage: params?.page ? params.page : "1"
|
||||
category: params?.category,
|
||||
currentPage: params?.page
|
||||
}
|
||||
}))
|
||||
dispatch(shopCategoriesActionCreators.requestShopCategories())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
shopCatalog?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [shopCatalog?.isLoading])
|
||||
|
||||
const {
|
||||
shopItemsSection = {
|
||||
addToCart: ""
|
||||
}
|
||||
} = content?.shopCatalog ? content?.shopCatalog : {}
|
||||
|
||||
const shopItems: ShopItems = {
|
||||
currencySymbol,
|
||||
path,
|
||||
...shopItemsSection,
|
||||
...shopCatalog
|
||||
}
|
||||
|
||||
return <>
|
||||
<TitleSection {...page?.titleSection} />
|
||||
<ShopItemsSection {...shopItems} />
|
||||
<TitleSection {...titleSection} />
|
||||
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col>
|
||||
<Row>
|
||||
<ShopItemsSection {...{
|
||||
currencySymbol,
|
||||
path: location.pathname,
|
||||
...shopItemsSection,
|
||||
...shopCatalog
|
||||
} as IShopItemComponent} />
|
||||
</Row>
|
||||
</Col>
|
||||
<Col lg="3">
|
||||
<Search />
|
||||
<Categories {...shopCategories} />
|
||||
<Empty/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import React from 'react'
|
||||
import React, { FC } from 'react'
|
||||
import { Container } from 'reactstrap'
|
||||
|
||||
// CSS Modules
|
||||
import style from './scss/style.module.scss'
|
||||
|
||||
const Checkout = () => {
|
||||
export interface IShopCheckoutPage {
|
||||
|
||||
}
|
||||
|
||||
export interface IShopCheckoutComponent extends IShopCheckoutPage {}
|
||||
|
||||
const Checkout : FC<IShopCheckoutComponent> = () => {
|
||||
return <Container fluid className={`py-5 ${style.container}`}>
|
||||
<main>
|
||||
<div className="text-center">
|
||||
|
||||
@ -4,7 +4,6 @@ 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"
|
||||
@ -12,37 +11,73 @@ import { Card, CardBody, CardFooter, CardImg, Col, Container, Row } from "reacts
|
||||
// Components
|
||||
import { FeatherRating } from "../../../components/FeatherRating"
|
||||
|
||||
const RelatedProducts: FC = () => {
|
||||
|
||||
interface IImage {
|
||||
src: string,
|
||||
alt: string
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: string,
|
||||
image?: IImage
|
||||
|
||||
nickName: string
|
||||
}
|
||||
|
||||
|
||||
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 dispatch = useDispatch()
|
||||
|
||||
const { content, shopRelated } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const {
|
||||
currencySymbol = ""
|
||||
} = content?.localization ? content.localization : {}
|
||||
const { currencySymbol } = content.localization
|
||||
|
||||
const {
|
||||
relatedProductsSection = {
|
||||
title: "",
|
||||
addToCart: ""
|
||||
}
|
||||
} = content?.shopItem ? content?.shopItem : {}
|
||||
const { relatedProductsSection } = 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">
|
||||
@ -69,7 +104,7 @@ const RelatedProducts: FC = () => {
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Col>)}
|
||||
</Row>
|
||||
</Row> */}
|
||||
</Container>
|
||||
</section>
|
||||
}
|
||||
@ -5,7 +5,6 @@ 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,9 +12,66 @@ import { Container } from 'reactstrap'
|
||||
|
||||
// Components
|
||||
import { FeatherIcon } from '../../../components/FeatherIcons'
|
||||
import { RelatedProducts } from '../RelatedProducts'
|
||||
import { IRelatedProductsComponent, IRelatedProductsSection, RelatedProducts } from './RelatedProducts'
|
||||
|
||||
const ShopItem : FC = () => {
|
||||
|
||||
import { IHeader } from '../../../interfaces'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export interface IShopItemComponent extends IShopItemPage {}
|
||||
|
||||
const ShopItem : FC<IShopItemComponent> = () => {
|
||||
const params = useParams()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
@ -41,17 +97,6 @@ const ShopItem : FC = () => {
|
||||
}))
|
||||
}, [])
|
||||
|
||||
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">
|
||||
@ -81,7 +126,9 @@ const ShopItem : FC = () => {
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<RelatedProducts />
|
||||
<RelatedProducts {...{
|
||||
|
||||
} as IRelatedProductsComponent} />
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import React, { FC, 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"
|
||||
|
||||
interface IStateProp {
|
||||
[key: string]: string;
|
||||
@ -19,38 +19,42 @@ interface IState extends IStateProp {
|
||||
password: string
|
||||
}
|
||||
|
||||
const Signin = () => {
|
||||
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 dispatch = useDispatch()
|
||||
const { content } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
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])
|
||||
title,
|
||||
email,
|
||||
password,
|
||||
dontHaveAnAccount,
|
||||
signUpLink,
|
||||
submit
|
||||
} = content.signIn
|
||||
|
||||
const [state, hookState] = useState<IState>({
|
||||
username: '',
|
||||
@ -69,6 +73,10 @@ const Signin = () => {
|
||||
setState({ [name]: value })
|
||||
}
|
||||
|
||||
const postSignIn = () => {
|
||||
|
||||
}
|
||||
|
||||
return <Container className="container">
|
||||
<h2>{title}</h2>
|
||||
<Form className="form">
|
||||
@ -95,7 +103,7 @@ const Signin = () => {
|
||||
<FormGroup>
|
||||
{dontHaveAnAccount} <Link to={signUpLink.target}>{signUpLink.anchorText}</Link>.
|
||||
</FormGroup>
|
||||
<Button>{submit.title}</Button>
|
||||
<Button onClick={postSignIn}>{submit.title}</Button>
|
||||
</Form>
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import React, { FC, 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,46 +25,39 @@ interface IState extends IStateProp {
|
||||
tnc: boolean
|
||||
}
|
||||
|
||||
const Signup = () => {
|
||||
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 dispatch = useDispatch()
|
||||
const { content } = useSelector((state: ApplicationState) => state)
|
||||
|
||||
const {
|
||||
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..."
|
||||
},
|
||||
title, username,
|
||||
email,
|
||||
reEmail,
|
||||
password,
|
||||
rePassword,
|
||||
acceptTermsAndConditions = "",
|
||||
submit = {
|
||||
title: ""
|
||||
}
|
||||
} = content?.signUp ? content.signUp : {}
|
||||
|
||||
useEffect(() => {
|
||||
content?.isLoading
|
||||
? dispatch(loaderActionCreators.show())
|
||||
: setTimeout(() => {
|
||||
dispatch(loaderActionCreators.hide())
|
||||
}, 1000)
|
||||
}, [content?.isLoading])
|
||||
|
||||
submit
|
||||
} = content.signUp
|
||||
|
||||
const [state, hookState] = useState<IState>({
|
||||
username: '',
|
||||
@ -96,7 +89,7 @@ const Signup = () => {
|
||||
}
|
||||
|
||||
return <Container className="container">
|
||||
<h2>{title}</h2>
|
||||
{/* <h2>{title}</h2>
|
||||
<Form className="form">
|
||||
<FormGroup>
|
||||
<Label for="username">{username.title}</Label>
|
||||
@ -153,7 +146,7 @@ const Signup = () => {
|
||||
<Label check>{acceptTermsAndConditions}</Label>
|
||||
</FormGroup>
|
||||
<Button>{submit.title}</Button>
|
||||
</Form>
|
||||
</Form> */}
|
||||
</Container>
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import { Params, RequestModel } from "./models/abstractions"
|
||||
import axios from "axios"
|
||||
import { IParams } from "./interfaces"
|
||||
|
||||
|
||||
|
||||
|
||||
interface FetchData {
|
||||
status: number,
|
||||
text: string
|
||||
text: any
|
||||
}
|
||||
|
||||
const Post = () => {
|
||||
|
||||
}
|
||||
|
||||
const Get = async <T>(apiUrl: string, pathParams?: Params, searchParams?: Params): Promise<T | null> => {
|
||||
const Get = async <T>(apiUrl: string, pathParams?: IParams, searchParams?: IParams): Promise<T | null> => {
|
||||
const url = new URL(apiUrl)
|
||||
|
||||
if(pathParams) {
|
||||
@ -36,21 +37,19 @@ const Get = async <T>(apiUrl: string, pathParams?: Params, searchParams?: Params
|
||||
headers: { 'accept': 'application/json', 'content-type': 'application/json' },
|
||||
}
|
||||
|
||||
const fetchData = await fetch(url.toString(), requestParams)
|
||||
const fetchData = await axios(url.toString(), requestParams)
|
||||
.then(async fetchData => {
|
||||
return {
|
||||
status: fetchData.status,
|
||||
text: await fetchData.text()
|
||||
}
|
||||
// console.log(fetchData)
|
||||
|
||||
return fetchData.data as T
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
if (fetchData?.text)
|
||||
return JSON.parse((fetchData as FetchData).text) as T
|
||||
|
||||
return null
|
||||
return null
|
||||
})
|
||||
|
||||
return fetchData
|
||||
}
|
||||
|
||||
const Put = () => {
|
||||
@ -67,6 +66,3 @@ export {
|
||||
Put,
|
||||
Delete
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ 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 Content from './reducers/Content'
|
||||
|
||||
@ -20,25 +19,24 @@ import * as WeatherForecasts from './reducers/WeatherForecasts'
|
||||
|
||||
// The top-level state object
|
||||
export interface ApplicationState {
|
||||
blogCatalog: BlogCatalog.BlogCatalogState | undefined
|
||||
blogCategories: BlogCategories.BlogCategoriesState | undefined
|
||||
blogFeatured: BlogFeatured.BlogFeaturedState | undefined
|
||||
blogItem: BlogItem.BlogItemState | undefined
|
||||
blogCatalog: BlogCatalog.BlogCatalogState
|
||||
blogCategories: BlogCategories.BlogCategoriesState
|
||||
blogFeatured: BlogFeatured.BlogFeaturedState
|
||||
blogItem: BlogItem.BlogItemState
|
||||
|
||||
content: Content.ContentState | undefined
|
||||
content: Content.ContentState
|
||||
|
||||
counter: Counter.CounterState | undefined
|
||||
header: Header.HeaderState | undefined
|
||||
loader: Loader.LoaderState | undefined
|
||||
counter: Counter.CounterState
|
||||
header: Header.HeaderState
|
||||
|
||||
shopCatalog: ShopCatalog.ShopCatalogState | undefined
|
||||
shopCategories: ShopCategories.ShopCategoriesState | undefined
|
||||
shopFeatured: ShopFeatured.ShopFeaturedState | undefined
|
||||
shopItem: ShopItem.ShopItemState | undefined
|
||||
shopRelated: ShopRelated.ShopRelatedState | undefined
|
||||
shopCart: ShopCart.ShopCartState | undefined
|
||||
shopCatalog: ShopCatalog.ShopCatalogState
|
||||
shopCategories: ShopCategories.ShopCategoriesState
|
||||
shopFeatured: ShopFeatured.ShopFeaturedState
|
||||
shopItem: ShopItem.ShopItemState
|
||||
shopRelated: ShopRelated.ShopRelatedState
|
||||
shopCart: ShopCart.ShopCartState
|
||||
|
||||
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined
|
||||
weatherForecasts: WeatherForecasts.WeatherForecastsState
|
||||
}
|
||||
|
||||
// Whenever an action is dispatched, Redux will update each top-level application state property using
|
||||
@ -54,7 +52,6 @@ export const reducers = {
|
||||
|
||||
counter: Counter.reducer,
|
||||
header: Header.reducer,
|
||||
loader: Loader.reducer,
|
||||
|
||||
shopCatalog: ShopCatalog.reducer,
|
||||
shopCategories: ShopCategories.reducer,
|
||||
|
||||
@ -1,32 +1,54 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetBlogCatalogRequestModel } from '../../models/requests'
|
||||
import { GetBlogCatalogResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IBlogItem } from '../../pages/Blog/Catalog/BlogItemsComponent'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface BlogCatalogState extends GetBlogCatalogResponseModel {
|
||||
|
||||
// Request
|
||||
interface IGetBlogcatalogPathParams extends IParams {}
|
||||
|
||||
interface IGeBlogCatalogSearchParams extends IParams {}
|
||||
|
||||
interface IGetBlogCatalogRequestModel extends IRequest<IGetBlogcatalogPathParams, IGeBlogCatalogSearchParams> {
|
||||
category?: string,
|
||||
searchText?: string,
|
||||
currentPage?: string,
|
||||
itemsPerPage?: string
|
||||
}
|
||||
|
||||
// Response
|
||||
interface IGetBlogCatalogResponseModel extends IPagination<IBlogItem>, IResponse { }
|
||||
|
||||
|
||||
export interface BlogCatalogState extends IGetBlogCatalogResponseModel {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetBlogCatalogRequestModel {
|
||||
interface RequestAction extends IGetBlogCatalogRequestModel {
|
||||
type: 'REQUEST_BLOG_CATALOG'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetBlogCatalogResponseModel {
|
||||
interface ReceiveAction extends IGetBlogCatalogResponseModel {
|
||||
type: 'RECEIVE_BLOG_CATALOG'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogCatalog: (props?: GetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestBlogCatalog: (props?: IGetBlogCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
const locale = process.env.REACT_APP_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)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
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)
|
||||
@ -38,7 +60,7 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: BlogCatalogState = {
|
||||
totalPages: 1,
|
||||
totalPages: 100,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
@ -46,7 +68,7 @@ const unloadedState: BlogCatalogState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -56,7 +78,7 @@ const unloadedState: BlogCatalogState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -70,7 +92,7 @@ const unloadedState: BlogCatalogState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -80,7 +102,7 @@ const unloadedState: BlogCatalogState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -94,7 +116,7 @@ const unloadedState: BlogCatalogState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -104,7 +126,7 @@ const unloadedState: BlogCatalogState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -118,7 +140,7 @@ const unloadedState: BlogCatalogState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -128,7 +150,7 @@ const unloadedState: BlogCatalogState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,32 +1,49 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetBlogCategoriesRequestModel } from '../../models/requests'
|
||||
import { GetBlogCategoriesResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { ICategory } from '../../components/SideWidgets/Categories'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface BlogCategoriesState extends GetBlogCategoriesResponseModel {
|
||||
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetBlogCategoriesRequestModel {
|
||||
interface RequestAction extends IGetBlogCategoriesRequestModel {
|
||||
type: 'REQUEST_BLOG_CATEGORIES'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetBlogCategoriesResponseModel {
|
||||
interface ReceiveAction extends IGetBlogCategoriesResponseModel {
|
||||
type: 'RECEIVE_BLOG_CATEGORIES'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogCategories: (props?: GetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestBlogCategories: (props?: IGetBlogCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
const locale = process.env.REACT_APP_LOCALE
|
||||
|
||||
const searchParams = {...props?.searchParams, locale}
|
||||
|
||||
Get<Promise<GetBlogCategoriesResponseModel>>(`${process.env.REACT_APP_API}/Image/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<IGetBlogCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
@ -38,9 +55,12 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: BlogCategoriesState = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{ id: "", text: "Software" },
|
||||
{ id: "", text: "Hardware" }
|
||||
{ href: 'default', anchorText: "Default" },
|
||||
{ href: 'software', anchorText: "Software" },
|
||||
{ href: 'hardware', anchorText: "Hardware" }
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
@ -1,32 +1,52 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetBlogFeaturedRequestModel } from '../../models/requests'
|
||||
import { GetBlogFeaturedResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IFeaturedBlogItem } from '../../pages/Blog/Catalog/FeaturedBlogComponent'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface BlogFeaturedState extends GetBlogFeaturedResponseModel {
|
||||
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetBlogFeaturedRequestModel {
|
||||
interface RequestAction extends IGetBlogFeaturedRequestModel {
|
||||
type: 'REQUEST_BLOG_FEATURED'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetBlogFeaturedResponseModel {
|
||||
interface ReceiveAction extends IGetBlogFeaturedResponseModel {
|
||||
type: 'RECEIVE_BLOG_FEATURED'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogFeatured: (props?: GetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestBlogFeatured: (props?: IGetBlogFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
const locale = process.env.REACT_APP_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)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
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)
|
||||
@ -44,7 +64,7 @@ const unloadedState: BlogFeaturedState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -53,7 +73,7 @@ const unloadedState: BlogFeaturedState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -68,7 +88,7 @@ const unloadedState: BlogFeaturedState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -77,7 +97,7 @@ const unloadedState: BlogFeaturedState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -92,7 +112,7 @@ const unloadedState: BlogFeaturedState = {
|
||||
slug: "demo-post",
|
||||
badges: [ "demo" ],
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/850x350/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/850x350/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
title: "Lorem ipsum",
|
||||
@ -101,7 +121,7 @@ const unloadedState: BlogFeaturedState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,32 +1,51 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetBlogItemRequestModel } from '../../models/requests'
|
||||
import { GetBlogItemResponseModel } from '../../models/responses'
|
||||
// interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IBlogItem } from '../../pages/Blog/Item'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface BlogItemState extends GetBlogItemResponseModel {
|
||||
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetBlogItemRequestModel {
|
||||
interface RequestAction extends IGetBlogItemRequestModel {
|
||||
type: 'REQUEST_BLOG_ITEM'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetBlogItemResponseModel {
|
||||
interface ReceiveAction extends IGetBlogItemResponseModel {
|
||||
type: 'RECEIVE_BLOG_ITEM'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestBlogItem: (props?: GetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestBlogItem: (props?: IGetBlogItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
const locale = process.env.REACT_APP_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)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
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)
|
||||
@ -41,7 +60,7 @@ const unloadedState: BlogItemState = {
|
||||
id: "",
|
||||
slug: "demo-post",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/900x400/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/900x400/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
badges: [
|
||||
@ -60,63 +79,13 @@ const unloadedState: BlogItemState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
created: new Date().toString(),
|
||||
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."
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
132
src/ClientApp/src/store/reducers/Comments.ts
Normal file
132
src/ClientApp/src/store/reducers/Comments.ts
Normal file
@ -0,0 +1,132 @@
|
||||
//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'
|
||||
|
||||
// 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
|
||||
|
||||
export const actionCreators = {
|
||||
requestComments: (props?: IGetCommentsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
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 = {
|
||||
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."
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
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,28 +2,103 @@ import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
import { ReservedWords } from '../../enumerations'
|
||||
|
||||
import { GetContentRequestModel } from '../../models/requests'
|
||||
import { GetContentResponseModel } from '../../models/responses'
|
||||
|
||||
// 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 { Get } from '../../restClient'
|
||||
|
||||
export interface ContentState extends GetContentResponseModel {
|
||||
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetContentRequestModel {
|
||||
interface RequestAction extends IGetContentRequestModel {
|
||||
type: 'REQUEST_CONTENT'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetContentResponseModel {
|
||||
interface ReceiveAction extends IGetContentResponseModel {
|
||||
type: 'RECEIVE_CONTENT'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction;
|
||||
|
||||
export const actionCreators = {
|
||||
requestContent: (props?: GetContentRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestContent: (props?: IGetContentRequestModel): 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)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<IGetContentResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CONTENT}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then((data) => {
|
||||
if(data) {
|
||||
@ -36,8 +111,8 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: ContentState = {
|
||||
siteName: "MAKS-IT",
|
||||
siteUrl: "https://maks-it.com",
|
||||
siteName: "Contoso",
|
||||
siteUrl: "https://contoso.com",
|
||||
|
||||
header: {
|
||||
title: `${ReservedWords.siteName}`,
|
||||
@ -57,29 +132,42 @@ const unloadedState: ContentState = {
|
||||
currency: "EUR",
|
||||
currencySymbol: "€"
|
||||
},
|
||||
|
||||
routes: [
|
||||
{ target: "/", component: "Home" },
|
||||
{ target: "/home", component: "Home" },
|
||||
{ target: "/shop", childRoutes: [
|
||||
{ target: "", component: "ShopCatalog" },
|
||||
{ target: ":page", component: "ShopCatalog" },
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "ShopItem" }
|
||||
]},
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: "", component: "ShopCatalog" },
|
||||
{ target: ":page", component: "ShopCatalog" }
|
||||
] },
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "ShopItem" }
|
||||
]}
|
||||
] },
|
||||
|
||||
{ target:"cart", childRoutes: [
|
||||
{ target: "", component: "Cart" },
|
||||
{ target: "checkout", component: "Checkout" }
|
||||
]}
|
||||
]},
|
||||
|
||||
{ target: "/blog", childRoutes: [
|
||||
{ target: "", component: "BlogCatalog" },
|
||||
{ target: ":page", component: "BlogCatalog" },
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "BlogItem" }
|
||||
]}
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: "", component: "BlogCatalog" },
|
||||
{ target: ":page", component: "BlogCatalog" }
|
||||
] },
|
||||
{ target: ":category", childRoutes: [
|
||||
{ target: ":page" , childRoutes: [
|
||||
{ target: ":slug", component: "BlogItem" }
|
||||
]}
|
||||
] }
|
||||
]}
|
||||
],
|
||||
|
||||
adminRoutes: [],
|
||||
|
||||
serviceRoutes: [
|
||||
{ target: "/signin", component: "Signin" },
|
||||
{ target: "/signup", component: "Signup" }
|
||||
@ -87,14 +175,15 @@ const unloadedState: ContentState = {
|
||||
|
||||
topMenu: [
|
||||
{ target: "/", title: "Home" },
|
||||
{ target: "/shop", title: "Shop" },
|
||||
{ target: "/blog", title: "Blog" },
|
||||
{ target: "/shop/default", title: "Shop" },
|
||||
{ target: "/blog/default", title: "Blog" },
|
||||
|
||||
{ target: "/signin", title: "Sing in" },
|
||||
{ target: "/signup", title: "Sign up" },
|
||||
|
||||
{ target: "/shop/cart", icon: "shopping-cart", title: `Cart (${ReservedWords.quantity})` }
|
||||
],
|
||||
|
||||
sideMenu: [],
|
||||
|
||||
homePage: {
|
||||
@ -141,8 +230,7 @@ const unloadedState: ContentState = {
|
||||
{
|
||||
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: "",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
fullName: "Admin",
|
||||
position: "CEO, MAKS-IT"
|
||||
}
|
||||
@ -371,6 +459,9 @@ const unloadedState: ContentState = {
|
||||
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`
|
||||
},
|
||||
@ -387,7 +478,7 @@ const unloadedState: ContentState = {
|
||||
}
|
||||
},
|
||||
titleSection: {
|
||||
postedOnBy: `Posted on ${ReservedWords.date} by ${ReservedWords.nickName}`
|
||||
text: `Posted on ${ReservedWords.date} by ${ReservedWords.nickName}`
|
||||
},
|
||||
commentsSection: {
|
||||
leaveComment: "Join the discussion and leave a comment!"
|
||||
@ -468,7 +559,6 @@ const unloadedState: ContentState = {
|
||||
|
||||
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 { HeaderLink, HeaderModel } from '../../models'
|
||||
import { IHeader } from '../../interfaces'
|
||||
|
||||
// -----------------
|
||||
// STATE - This defines the type of data maintained in the Redux store.
|
||||
|
||||
export interface HeaderState extends HeaderModel {}
|
||||
export interface HeaderState extends IHeader {}
|
||||
|
||||
// -----------------
|
||||
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
|
||||
// They do not themselves have any side-effects they just describe something that is going to happen.
|
||||
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
|
||||
|
||||
export interface ReceiveAction extends HeaderModel { type: 'RECEIVE_UPDATE_HEADER' }
|
||||
export interface ReceiveAction extends IHeader { 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: HeaderModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
updateHeader: (props: IHeader): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const siteName = getState().content?.siteName
|
||||
const baseHeader = getState().content?.header
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
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,22 +1,35 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '..'
|
||||
|
||||
import { GetShopCartItemsRequestModel } from '../../models/requests'
|
||||
import { GetShopCartItemResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IShopCartItem } from '../../pages/Shop/Cart'
|
||||
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface ShopCartState {
|
||||
items: GetShopCartItemResponseModel [],
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export interface RequestAction {
|
||||
export interface RequestAction extends IGetShopCartItemsRequestModel {
|
||||
type: 'REQUEST_CART_ITEMS'
|
||||
}
|
||||
|
||||
export interface ReceiveAction {
|
||||
items: GetShopCartItemResponseModel [],
|
||||
export interface ReceiveAction extends IGetShopCartItemsResponseModel {
|
||||
type: 'RECEIVE_CART_ITEMS'
|
||||
}
|
||||
|
||||
@ -31,14 +44,16 @@ export interface ReceiveAction {
|
||||
export type KnownAction = RequestAction | ReceiveAction //| IncrementItemQuantityAction | DecrementItmeQuantityAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestCart: (props?: GetShopCartItemsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestCart: (props?: IGetShopCartItemsRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<GetShopCartItemResponseModel []>>('https://localhost:7151/api/ShopCartItems/404c8232-9048-4519-bfba-6e78dc7005ca', props?.pathParams, props?.searchParams)
|
||||
Get<Promise<IGetShopCartItemsResponseModel>>('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', items: [...data] })
|
||||
dispatch({ type: 'RECEIVE_CART_ITEMS', ...data })
|
||||
})
|
||||
|
||||
dispatch({ type: 'REQUEST_CART_ITEMS' })
|
||||
@ -75,11 +90,13 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: ShopCartState = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/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...",
|
||||
@ -91,7 +108,7 @@ const unloadedState: ShopCartState = {
|
||||
{
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/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...",
|
||||
@ -103,7 +120,7 @@ const unloadedState: ShopCartState = {
|
||||
{
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/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,10 +1,29 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetShopCatalogRequestModel } from '../../models/requests'
|
||||
import { GetShopCatalogResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IShopItem } from '../../pages/Shop/Catalog/ShopItemsSection'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -22,11 +41,14 @@ type KnownAction = RequestAction | ReceiveAction
|
||||
export const actionCreators = {
|
||||
requestShopCatalog: (props?: GetShopCatalogRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
const locale = getState().content?.localization.locale
|
||||
const locale = process.env.REACT_APP_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)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
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)
|
||||
@ -38,14 +60,14 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: ShopCatalogState = {
|
||||
totalPages: 1,
|
||||
totalPages: 100,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -54,7 +76,7 @@ const unloadedState: ShopCatalogState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -69,7 +91,7 @@ const unloadedState: ShopCatalogState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -78,7 +100,7 @@ const unloadedState: ShopCatalogState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -93,7 +115,7 @@ const unloadedState: ShopCatalogState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "out of stock" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -102,7 +124,7 @@ const unloadedState: ShopCatalogState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -117,7 +139,7 @@ const unloadedState: ShopCatalogState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -126,7 +148,7 @@ const unloadedState: ShopCatalogState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
@ -1,10 +1,24 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetShopCategoriesRequestModel } from '../../models/requests'
|
||||
import { GetShopCategoriesResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IPagination, IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { ICategory } from '../../components/SideWidgets/Categories'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -22,7 +36,10 @@ type KnownAction = RequestAction | ReceiveAction
|
||||
export const actionCreators = {
|
||||
requestShopCategories: (props?: GetShopCategoriesRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
Get<Promise<GetShopCategoriesResponseModel>>('https://localhost:7151/api/ShopCategories', props?.pathParams, props?.searchParams)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<GetShopCategoriesResponseModel>>(`${process.env.REACT_APP_API}/${process.env.REACT_APP_CATEGORYITEMS}/${process.env.REACT_APP_SITEID}`, props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
@ -34,9 +51,12 @@ export const actionCreators = {
|
||||
}
|
||||
|
||||
const unloadedState: ShopCategoriesState = {
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
items: [
|
||||
{ id: "", text: "Software" },
|
||||
{ id: "", text: "Hardware" }
|
||||
{ href: 'default', anchorText: "Default" },
|
||||
{ href: 'software', anchorText: "Software" },
|
||||
{ href: 'hardware', anchorText: "Hardware" }
|
||||
],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
@ -1,28 +1,47 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetShopFeaturedRequestModel, } from '../../models/requests'
|
||||
import { GetShopFeaturedResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IRelatedProduct } from '../../pages/Shop/Item/RelatedProducts'
|
||||
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface ShopFeaturedState extends GetShopFeaturedResponseModel {
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetShopFeaturedRequestModel {
|
||||
interface RequestAction extends IGetShopFeaturedRequestModel {
|
||||
type: 'REQUEST_SHOP_FEATURED'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetShopFeaturedResponseModel {
|
||||
interface ReceiveAction extends IGetShopFeaturedResponseModel {
|
||||
type: 'RECEIVE_SHOP_FEATURED'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopFeatured: (props?: GetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestShopFeatured: (props?: IGetShopFeaturedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
Get<Promise<GetShopFeaturedResponseModel>>('https://localhost:7151/api/ShopFeatured', props?.pathParams, props?.searchParams)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<IGetShopFeaturedResponseModel>>('https://localhost:7151/api/ShopFeatured', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
@ -39,7 +58,7 @@ const unloadedState: ShopFeaturedState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -48,7 +67,7 @@ const unloadedState: ShopFeaturedState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
@ -1,11 +1,27 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetShopItemRequestModel } from '../../models/requests'
|
||||
import { GetShopItemResponseModel } from '../../models/responses'
|
||||
// Interfaces
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IShopItem } from '../../pages/Shop/Item'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
@ -22,7 +38,10 @@ type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopItem: (props?: GetShopItemRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<GetShopItemResponseModel>>('https://localhost:7151/api/ShopItem', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
@ -38,7 +57,7 @@ const unloadedState: ShopItemState = {
|
||||
id: "",
|
||||
slug: "demo-post",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/600x700/dee2e6/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/600x700/dee2e6/6c757d`,
|
||||
alt: "..."
|
||||
},
|
||||
|
||||
@ -56,7 +75,7 @@ const unloadedState: ShopItemState = {
|
||||
id: "",
|
||||
nickName: "Admin",
|
||||
image: {
|
||||
src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`,
|
||||
src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`,
|
||||
alt: "..."
|
||||
}
|
||||
},
|
||||
@ -68,56 +87,56 @@ const unloadedState: ShopItemState = {
|
||||
|
||||
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.",
|
||||
// comments: [
|
||||
// {
|
||||
// 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_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."
|
||||
}
|
||||
// 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."
|
||||
// }
|
||||
|
||||
],
|
||||
// ],
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
|
||||
@ -1,28 +1,45 @@
|
||||
import { Action, Reducer } from 'redux'
|
||||
import { AppThunkAction } from '../'
|
||||
|
||||
import { GetShopRelatedRequestModel } from '../../models/requests'
|
||||
import { GetShopRelatedResponseModel } from '../../models/responses'
|
||||
import { IParams, IRequest, IResponse } from '../../interfaces'
|
||||
import { IRelatedProduct } from '../../pages/Shop/Item/RelatedProducts'
|
||||
|
||||
import { Get } from '../../restClient'
|
||||
|
||||
export interface ShopRelatedState extends GetShopRelatedResponseModel {
|
||||
// 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 {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
interface RequestAction extends GetShopRelatedRequestModel {
|
||||
interface RequestAction extends IGetShopRelatedRequestModel {
|
||||
type: 'REQUEST_SHOP_RELATED'
|
||||
}
|
||||
|
||||
interface ReceiveAction extends GetShopRelatedResponseModel {
|
||||
interface ReceiveAction extends IGetShopRelatedResponseModel {
|
||||
type: 'RECEIVE_SHOP_RELATED'
|
||||
}
|
||||
|
||||
type KnownAction = RequestAction | ReceiveAction
|
||||
|
||||
export const actionCreators = {
|
||||
requestShopRelated: (props?: GetShopRelatedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
requestShopRelated: (props?: IGetShopRelatedRequestModel): AppThunkAction<KnownAction> => (dispatch, getState) => {
|
||||
|
||||
Get<Promise<GetShopRelatedResponseModel>>('https://localhost:7151/api/ShopRelated', props?.pathParams, props?.searchParams)
|
||||
if(process.env.REACT_APP_LOCAL_ONLY == 'Y')
|
||||
return
|
||||
|
||||
Get<Promise<IGetShopRelatedResponseModel>>('https://localhost:7151/api/ShopRelated', props?.pathParams, props?.searchParams)
|
||||
.then(response => response)
|
||||
.then(data => {
|
||||
if(data)
|
||||
@ -39,7 +56,7 @@ const unloadedState: ShopRelatedState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -48,7 +65,7 @@ const unloadedState: ShopRelatedState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -63,7 +80,7 @@ const unloadedState: ShopRelatedState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -72,7 +89,7 @@ const unloadedState: ShopRelatedState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -87,7 +104,7 @@ const unloadedState: ShopRelatedState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -96,7 +113,7 @@ const unloadedState: ShopRelatedState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
@ -111,7 +128,7 @@ const unloadedState: ShopRelatedState = {
|
||||
id: '',
|
||||
slug: "shop-catalog-item",
|
||||
sku: "SKU-0",
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/450x300/dee2e6/6c757d`, alt: "..." },
|
||||
badges: [ "sale", "best offer" ],
|
||||
title: "Shop item title",
|
||||
brandName: "Brand & Name",
|
||||
@ -120,7 +137,7 @@ const unloadedState: ShopRelatedState = {
|
||||
text: "",
|
||||
author: {
|
||||
id: '',
|
||||
image: { src: `${process.env.REACT_APP_API}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
image: { src: `${process.env.REACT_APP_FRONTEND}/Image/40x40/ced4da/6c757d`, alt: "..." },
|
||||
nickName: "Admin"
|
||||
},
|
||||
created: (new Date).toString(),
|
||||
|
||||
@ -1,6 +1,17 @@
|
||||
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.
|
||||
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
|
||||
public abstract class PersonBase<T> : DomainObjectBase<T> {
|
||||
public Guid Id { get; set; }
|
||||
public Image? Image { get; set; }
|
||||
public MediaAttachment? Image { get; set; }
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
namespace DomainObjects.PageSections;
|
||||
|
||||
public class TitleSection : PageSectionBase<TitleSection> {
|
||||
public Image? Image { get; set; }
|
||||
public MediaAttachment? Image { get; set; }
|
||||
public Link? PrimaryLink { get; set; }
|
||||
public Link? SecondaryLink { get; set; }
|
||||
public string? PostedOnBy { get; set; }
|
||||
|
||||
@ -83,8 +83,6 @@ 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}" }
|
||||
|
||||
@ -1,23 +1,25 @@
|
||||
using Core.Abstractions.Models;
|
||||
using Core.Enumerations;
|
||||
using DomainObjects;
|
||||
using DomainObjects.Enumerations;
|
||||
using DomainObjects.L10n;
|
||||
|
||||
namespace WeatherForecast.Models.Content.Responses {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MediaAttachmentL10nRequestModel : RequestModelBase {
|
||||
public class MediaAttachmentL10nReponsetModel : ResponseModelBase {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Locales Locale { get; set; } = Locales.Unknown;
|
||||
public Locales Locale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Alt { get; set; } = string.Empty;
|
||||
public string Alt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -33,6 +35,18 @@ namespace WeatherForecast.Models.Content.Responses {
|
||||
///
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="l10n"></param>
|
||||
public MediaAttachmentL10nReponsetModel(MediaAttachmentL10n l10n) {
|
||||
Locale = l10n.Locale;
|
||||
Alt = l10n.Alt;
|
||||
Target = l10n.Target;
|
||||
Title = l10n.Title;
|
||||
Description = l10n.Description;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -53,7 +67,18 @@ namespace WeatherForecast.Models.Content.Responses {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<MediaAttachmentL10nRequestModel> L10n { get; set; } = new List<MediaAttachmentL10nRequestModel>();
|
||||
public List<MediaAttachmentL10nReponsetModel> L10n { get; set; } = new List<MediaAttachmentL10nReponsetModel>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="mediaAttachment"></param>
|
||||
public MediaAttachmentResponseModel(MediaAttachment mediaAttachment) {
|
||||
Src= mediaAttachment.Src;
|
||||
MediaType= mediaAttachment.MediaType;
|
||||
|
||||
L10n = mediaAttachment.L10n.Select(x => new MediaAttachmentL10nReponsetModel(x)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,8 +19,6 @@ namespace WeatherForecast.Models.Content.Responses.PageSections {
|
||||
/// </summary>
|
||||
public MediaAttachmentResponseModel? Image { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@ -39,8 +37,8 @@ namespace WeatherForecast.Models.Content.Responses.PageSections {
|
||||
FullName = reviewer.FullName;
|
||||
Position = reviewer.Position;
|
||||
|
||||
//if (reviewer.Image != null)
|
||||
// Image = new ImageModel(reviewer.Image);
|
||||
if (reviewer.Image != null)
|
||||
Image = new MediaAttachmentResponseModel(reviewer.Image);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,13 +31,6 @@ services:
|
||||
networks:
|
||||
- "my-network"
|
||||
|
||||
#clientapp:
|
||||
# environment:
|
||||
# - ASPNETCORE_ENVIRONMENT=Development
|
||||
# ports:
|
||||
# - "3000:3000"
|
||||
# networks:
|
||||
# - "my-network"
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
|
||||
@ -10,10 +10,4 @@ services:
|
||||
image: ${DOCKER_REGISTRY-}reverseproxy
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ReverseProxy/Dockerfile
|
||||
#clientapp:
|
||||
# image: ${DOCKER_REGISTRY-}clientapp
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ClientApp/Dockerfile
|
||||
|
||||
dockerfile: ReverseProxy/Dockerfile
|
||||
@ -3,4 +3,4 @@ WiredTiger 10.0.2: (December 21, 2021)
|
||||
WiredTiger version
|
||||
major=10,minor=0,patch=2
|
||||
file:WiredTiger.wt
|
||||
access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.124102=(addr="018381e487d81b1f8481e4bf0e4b478581e4ba5850aa808080e301ffc0e3010fc0",order=124102,time=1680030284,size=81920,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=29,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=372999,run_write_gen=372970)),checkpoint_backup_info=,checkpoint_lsn=(73,16000)
|
||||
access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.126190=(addr="018381e452c7a52b8481e4d7b9659f8581e46348db2e808080e3023fc0e3010fc0",order=126190,time=1680809094,size=81920,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=1524,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=379335,run_write_gen=377166)),checkpoint_backup_info=,checkpoint_lsn=(78,597248)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -7,8 +7,7 @@
|
||||
"mongo": "StartWithoutDebugging",
|
||||
"mongo-express": "StartWithoutDebugging",
|
||||
"reverseproxy": "StartWithoutDebugging",
|
||||
"weatherforecast": "StartDebugging",
|
||||
// "clientapp": "StartDebugging"
|
||||
"weatherforecast": "StartDebugging"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user