(refactor): loader interceptor and overall code reordering

This commit is contained in:
Maksym Sadovnychyy 2023-04-07 16:55:52 +02:00
parent 5ad87b6d48
commit eb5334917d
92 changed files with 2546 additions and 2024 deletions

View File

@ -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 Bootstraps 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": []
}

View File

@ -1,3 +0,0 @@
*
!obj\Docker\publish\*
!obj\Docker\empty\

View File

@ -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

View File

@ -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" />

View File

@ -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" ]

View File

@ -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",

View File

@ -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",

View File

@ -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 }} />}
</>
}

View File

@ -1,4 +1,4 @@
import React, { FC, ReactNode, useEffect, useState } from 'react'
import React, { FC, ReactNode } from 'react'
import './scss/loaders.scss'

View 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

View File

@ -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,

View File

@ -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
}
}

View File

@ -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 => {

View 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
// }

View File

@ -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,

View File

@ -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)}</>

View File

@ -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 []
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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> {}

View File

@ -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
}

View File

@ -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>
}

View 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
}

View File

@ -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
}

View 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
}

View File

@ -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>

View File

@ -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
}

View 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
}

View File

@ -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">

View File

@ -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)

View File

@ -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>()

View 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
}

View 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)} &middot; {item.readTime}</div>
</div>
</div>
</div>
</CardFooter>
</Card>
</Col>)}
</Row>
</Container>
</section>
}
export {
FeaturedBlogsSection
}

View 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
}

View 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
}

View 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
}

View File

@ -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)} &middot; {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 {

View File

@ -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>
}

View 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
}

View 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
}

View File

@ -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>
</>
}

View File

@ -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">

View File

@ -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>
}

View File

@ -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} />
</>
}

View File

@ -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>

View File

@ -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>
}

View File

@ -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
}

View File

@ -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,

View File

@ -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: "..."
}
},

View File

@ -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
}

View File

@ -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: "..."
}
},

View File

@ -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
}

View 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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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...",

View File

@ -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(),

View File

@ -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
}

View File

@ -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(),

View File

@ -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
}

View File

@ -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(),

View File

@ -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.

View File

@ -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; }
}

View File

@ -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;
}
}

View File

@ -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; }

View File

@ -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();

View File

@ -18,7 +18,7 @@
"route2": {
"ClusterId": "cluster1",
"Match": {
"Path": "image/{**catchall}"
"Path": "Image/{**catchall}"
},
"Transforms": [
{ "PathPattern": "/api/Image/{**catchall}" }

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -31,13 +31,6 @@ services:
networks:
- "my-network"
#clientapp:
# environment:
# - ASPNETCORE_ENVIRONMENT=Development
# ports:
# - "3000:3000"
# networks:
# - "my-network"
mongo:
image: mongo

View File

@ -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

View File

@ -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)

View File

@ -7,8 +7,7 @@
"mongo": "StartWithoutDebugging",
"mongo-express": "StartWithoutDebugging",
"reverseproxy": "StartWithoutDebugging",
"weatherforecast": "StartDebugging",
// "clientapp": "StartDebugging"
"weatherforecast": "StartDebugging"
}
}
}