diff --git a/package-lock.json b/package-lock.json index f694320..f732bb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,10 @@ "@types/node": "^16.18.29", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "axios": "^1.7.9", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.24.0", + "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", "stenciljs-components": "^1.0.3", "typescript": "^4.9.5", @@ -3175,9 +3176,10 @@ } }, "node_modules/@remix-run/router": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz", - "integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -4915,6 +4917,31 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -13986,6 +14013,12 @@ "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==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14281,11 +14314,12 @@ } }, "node_modules/react-router": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", - "integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.17.0" + "@remix-run/router": "1.21.0" }, "engines": { "node": ">=14.0.0" @@ -14295,12 +14329,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz", - "integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.17.0", - "react-router": "6.24.0" + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" }, "engines": { "node": ">=14.0.0" diff --git a/package.json b/package.json index 109208b..9c4bfc1 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,10 @@ "@types/node": "^16.18.29", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "axios": "^1.7.9", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.24.0", + "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", "stenciljs-components": "^1.0.3", "typescript": "^4.9.5", diff --git a/src/App.tsx b/src/App.tsx index 12d2ed0..2bce597 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,29 +1,66 @@ import React from 'react'; //import logo from './logo.svg'; +//https://stackoverflow.com/questions/69843615/switch-is-not-exported-from-react-router-dom +//https://www.w3schools.com/react/react_router.asp +import { BrowserRouter as Router, Routes,Route, Link } from "react-router-dom"; +//Routes instead of Switch from v6 +//elements instead of component +//CSS or CSS-in-JS (if you prefer inline styles or styled-components). import './App.css'; -//import Header from './components/Header'; -//import DarkMode from "./components/DarkMode"; -//import Counter from './components/Counter'; -//import Timer from './components/Timer'; +import './Navbar.css'; +import Header from './components/Header'; +import DarkMode from "./components/DarkMode"; +import Counter from './components/Counter'; +import Timer from './components/Timer'; +import LibraryComponents from './components/LibraryComponents'; +import FluidForm from './components/FluidForm'; +import FluidUpload from './components/FluidUpload'; +import AxiosCall from './components/AxiosCall'; function App() { return (
-

from 'stenciljs-components' npm package

- - - - - - Click me! - Number: - - - - - - - -
+ + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); } diff --git a/src/Navbar.css b/src/Navbar.css new file mode 100644 index 0000000..6c70bdf --- /dev/null +++ b/src/Navbar.css @@ -0,0 +1,54 @@ +/* Nav container */ +nav { + background-color: #2c3e50; /* Dark background */ + padding: 10px 20px; /* Space around the nav */ + border-radius: 8px; /* Rounded corners */ + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */ + } + + /* Unordered list styles */ + nav ul { + list-style: none; /* Remove bullets */ + display: flex; /* Align items horizontally */ + justify-content: center; /* Center items horizontally */ + margin: 0; /* Remove default margin */ + padding: 0; /* Remove default padding */ + } + + /* List item styles */ + nav ul li { + margin: 0 15px; /* Space between links */ + } + + /* Link styles */ + nav ul li a { + text-decoration: none; /* Remove underline */ + color: white; /* White text */ + font-size: 18px; /* Larger font size */ + font-weight: bold; /* Bold font */ + padding: 8px 16px; /* Space around the text */ + border-radius: 4px; /* Rounded corners for each link */ + transition: background-color 0.3s ease, transform 0.3s ease; /* Smooth transition */ + } + + /* Link hover styles */ + nav ul li a:hover { + background-color: #3498db; /* Blue background on hover */ + transform: scale(1.1); /* Slightly enlarge on hover */ + } + + /* Link active styles */ + nav ul li a.active { + color: #e74c3c; /* Red color when active */ + border-bottom: 3px solid #e74c3c; /* Red underline when active */ + } +/* +Explanation of the Styles: +------------- +1.nav: The background-color is set to a dark shade for a modern look, and box-shadow adds a subtle depth effect. +2.ul: Flexbox is used to horizontally align the items and justify-content: center centers the links. +3.li: Margin is used to space the links apart. +4.a: The links have no underline by default, and the text is white for contrast against the dark background. The border-radius adds rounded corners to the links for a soft look. +5.Hover Effect: When you hover over a link, it changes the background color and slightly enlarges to create a "pop" effect. +6.Active Link: If you want the active link to be highlighted, you can apply the .active class in React based on the current route (this will be added dynamically as the user navigates). +*/ \ No newline at end of file diff --git a/src/components/AxiosCall.tsx b/src/components/AxiosCall.tsx new file mode 100644 index 0000000..5f900f2 --- /dev/null +++ b/src/components/AxiosCall.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import { useCustomElementRef, fluid } from './../fluid'; // Replaced by @lmig/fluid-react-utils +import { useEffect,useState } from 'react'; +//built-in fetch API or a library like axios +//npm i axios +import axios from 'axios'; + +function AxiosCall() { + + // ---- Initialise FLUID +let env:any = fluid.environments.external; +useEffect(() => fluid.init(env)); +//since its typescript, cannot use with empty dependency +//useEffect(() => fluid.init(env), []); +// ---- Start Building +const [response, setResponse] = useState(null); + +const validation = [ + { + type: 'required', + value: true, + message: 'This is a required field.', + }, + ]; +const fluidFormRef = useCustomElementRef( + { valueChanged: (event:any) => { + console.log('value changed'); + }, + formChanged: (event:any) => { + console.log(event.detail); + }, + actionClicked: (event:any) => { + console.log(event.detail.data.redactedValue); + const sendData = async () => { + try { + const res = await axios.post('https://jsonplaceholder.typicode.com/posts', event.detail.data.redactedValue, { + headers: { + 'Content-Type': 'application/json', // Ensures JSON content + }, + }); + + console.log(res.data); + setResponse(res.data); + } catch (error) { + console.error('Error:', error); + } + }; + sendData(); + }, + }, + { + config: { + layout: 'vertical', + elements: [ + { + dataPath: 'textInput', + label: 'My Text Input', + type: 'input', + controlName: 'textInput', + validation, + }, + { + dataPath: 'array', + label: 'Array Section', + elementType: 'array', + + constraints: { + minimumEntries: 2, + maximumEntries: 8, + }, + + initialValue: [ + { phoneNumber: '(801) 999-9999' }, + { phoneNumber: '(801) 222-2222' }, + ], + + controlName: 'array', + formConfig: { + allowDeleteForm: (data: { + redactedValue?: { phoneNumber: number }; + }) => { + const test = !data?.redactedValue?.phoneNumber; + //console.log('data::', data, test); + return test; + }, + + elements: [ + { + dataPath: 'phoneNumber', + label: 'phone', + type: 'input', + controlName: 'phoneNumber', + }, + ], + }, + }, + ], + submitConfig: { + actionText: 'Save', + actionKey: 'formSubmitted', + }, + cancelConfig: { + actionText: 'Cancel', + actionKey: 'formCancelled', + displayDialogBeforeAction: true, + }, + }, + } + ); + console.log('fluidFormRef', fluidFormRef); + +// const btRef = useCustomElementRef( +// { +// click: async () => { +// console.log('click', fluidFormRef); +// const test = await fluidFormRef.current.markTouchedAndValidate(); +// console.log('test'); +// }, +// }, +// { label: 'Test useCustomElementRef.current' } +// ); +return ( +
+ Your Code Here + + {response &&
Response: {JSON.stringify(response, null, 2)}
} +
+); +} + +export default AxiosCall; \ No newline at end of file diff --git a/src/components/FluidForm.tsx b/src/components/FluidForm.tsx new file mode 100644 index 0000000..846cae6 --- /dev/null +++ b/src/components/FluidForm.tsx @@ -0,0 +1,227 @@ +import React from 'react'; +import { useCustomElementRef, fluid } from './../fluid'; // Replaced by @lmig/fluid-react-utils +import { useEffect,useState } from 'react'; +//built-in fetch API or a library like axios +function FluidForm() { + + // ---- Initialise FLUID +let env:any = fluid.environments.external; +useEffect(() => fluid.init(env)); +//since its typescript, cannot use with empty dependency +//useEffect(() => fluid.init(env), []); +// ---- Start Building +const [response, setResponse] = useState(null); + +const validation = [ + { + type: 'required', + value: true, + message: 'This is a required field.', + }, + ]; +const fluidFormRef = useCustomElementRef( + { valueChanged: (event:any) => { + console.log('value changed'); + }, + formChanged: (event:any) => { + console.log(event.detail); + }, + actionClicked: (event:any) => { + console.log(event.detail.data.redactedValue); + const sendData = async () => { + try { + const res = await fetch('https://jsonplaceholder.typicode.com/posts', { + method: 'POST', // HTTP method + headers: { + 'Content-Type': 'application/json', // Ensures JSON content + }, + body: JSON.stringify(event.detail.data.redactedValue), // Convert JavaScript object to JSON string + }); + + const result = await res.json(); // Parse the response JSON + console.log(result); + setResponse(result); + } catch (error) { + console.error('Error:', error); + } + }; + sendData(); + }, + }, + { + config: { + layout: 'vertical', + elements: [ + { + dataPath: 'textInput', + label: 'My Text Input', + type: 'input', + validation, + controlName: 'textInput', + }, + { + dataPath: 'currencyInput', + label: 'My Currency Input', + type: 'currency', + controlName: 'currencyInput', + }, + { + dataPath: 'dateInput', + label: 'My Date Input', + type: 'date', + controlName: 'dateInput', + }, + { + dataPath: 'numberInput', + label: 'My Number Input', + type: 'number', + validation, + controlName: 'numberInput', + }, + { + dataPath: 'phoneNumbers', + controlName: 'phoneNumberForm', + elementType: 'array', + controlConfig: { + add: { + label: 'Add Number', + }, + }, + disableAddUntilValid: false, + entryLabel: () => '', + + formConfig: { + formName: 'phoneNumbersForm', + /* + layout: 'row', + rowOptions: [ + { + rowId: 1, + columnOptions: [ + { columnId: 1, colSpan: 3 }, + { columnId: 2, colSpan: 3 }, + { columnId: 3, colSpan: 1 }, + { columnId: 4, colSpan: 1 }, + { columnId: 5, colSpan: 3 }, + { columnId: 6, colSpan: 1 }, + { columnId: 7, colSpan: 0 }, + ], + }, + ], + */ + elements: [ + // PhoneNumberTypeSelect(1, 1), + // CountrySelect(1, 2), + { + dataPath: 'countryPhoneCode', + controlName: 'countryPhoneCode', + label: 'Country Code', + elementType: `input`, + type: `number`, + displayStepperButtons: false, + readonly: true, + wholeOnly: true, + inlineField: true, + initialValue: 'Country Code', + //dynamicValue: '55', + maskingConfig: { + blurDelay: 0, + maskingFn: (value:any) => `+ ${value}`, + }, + gridRow: 1, + gridColumn: 3, + }, + { + dataPath: 'areaCode', + controlName: 'areaCode', + label: 'Area Code', + elementType: `input`, + type: `number`, + displayStepperButtons: false, + wholeOnly: true, + inlineField: true, + allowCancel: true, + //dynamicConfig: areaValidation, + gridRow: 1, + gridColumn: 4, + }, + { + dataPath: 'phoneNumber', + controlName: 'phoneNumber', + label: 'Phone Number', + elementType: `input`, + type: `number`, + displayStepperButtons: false, + //dynamicConfig: dynamicValidation, + allowCancel: true, + wholeOnly: true, + gridRow: 1, + gridColumn: 5, + }, + { + dataPath: 'extension', + controlName: 'extension', + label: 'Ext', + elementType: `input`, + type: `number`, + displayStepperButtons: false, + allowCancel: true, + wholeOnly: true, + gridRow: 1, + gridColumn: 6, + }, + { + label: 'Hidden Input type', + dataPath: 'id', + controlName: 'id', + elementType: `hidden`, + type: `number`, + gridRow: 1, + gridColumn: 7, + }, + { + dataPath: 'isDeletedInd', + controlName: 'isDeletedInd', + initialValue: false, + elementType: `hidden`, + gridRow: 1, + gridColumn: 7, + }, + ], + }, + }, + ], + submitConfig: { + actionText: 'Save', + actionKey: 'formSubmitted', + }, + cancelConfig: { + actionText: 'Cancel', + actionKey: 'formCancelled', + displayDialogBeforeAction: true, + }, + }, + } + ); + console.log('fluidFormRef', fluidFormRef); + +// const btRef = useCustomElementRef( +// { +// click: async () => { +// console.log('click', fluidFormRef); +// const test = await fluidFormRef.current.markTouchedAndValidate(); +// console.log('test'); +// }, +// }, +// { label: 'Test useCustomElementRef.current' } +// ); +return ( +
+ Your Code Here + + {response &&
Response: {JSON.stringify(response, null, 2)}
} +
+); +} + +export default FluidForm; \ No newline at end of file diff --git a/src/components/FluidUpload.tsx b/src/components/FluidUpload.tsx new file mode 100644 index 0000000..a44054f --- /dev/null +++ b/src/components/FluidUpload.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { useEffect } from 'react'; +import { useCustomElementRef,fluid } from './../fluid'; // Replaced by @lmig/fluid-react-utils + +function FluidUpload(config:any) { + + // ---- Initialise FLUID +let env:any = fluid.environments.external; +useEffect(() => fluid.init(env)); +// ---- Start Building + +const selectRef = useCustomElementRef( + { + fileListUpdated: (eventData:any) => fileUploaded(eventData), + }, + { + ...config, + // initialValue: config.initialValue.favFlavor, + acceptMultiple: false, + acceptFileTypes: ['application/json'], + } +); +const fileUploaded = (eventData:any) => { + if (eventData.detail) { + console.log(eventData.detail); + const uFile = eventData.detail[0]; + if (uFile.type !== 'application/json') { + alert('Only JSON file accepted'); + } else { + console.log(uFile); + } + } +}; +// useEffect(() => { +// const { favFlavor } = config.initialValue; +// if (selectRef?.current?.setValue && !!favFlavor) { +// selectRef.current +// .setValue(favFlavor) +// .then(() => selectRef.current.markTouchedAndValidate()); +// } +// }, [config.initialValue]); + + return ( +
+ + + {/* */} + You are on search tab! + + + {/**/} + You are on upload tab! + + + +
+ ); +}; + +export default FluidUpload; \ No newline at end of file diff --git a/src/components/LibraryComponents.tsx b/src/components/LibraryComponents.tsx new file mode 100644 index 0000000..59cc054 --- /dev/null +++ b/src/components/LibraryComponents.tsx @@ -0,0 +1,31 @@ +//import * as React from 'react'; +import React from 'react'; + +//import { useNavigate,useParams } from 'react-router-dom'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + function LibraryComponents() { + + return ( +
+

from 'stenciljs-components' npm package

+ + + + + + Click me! + Number: + + + + + + + + +
+ ); +} + +export default LibraryComponents; \ No newline at end of file diff --git a/src/fluid.ts b/src/fluid.ts index fd30061..8b9cb85 100644 --- a/src/fluid.ts +++ b/src/fluid.ts @@ -1,5 +1,6 @@ import { useEffect, useRef } from 'react'; - +//AWS S3 + CloudFront gives CORS errors when serving images from browser cache +//https://stackoverflow.com/questions/33858072/aws-s3-cloudfront-gives-cors-errors-when-serving-images-from-browser-cache/42028071#42028071 /** * Custom hook to enable complex properties on FLUID components. * @@ -41,11 +42,18 @@ export const fluid = { legacy: 'https://fluid-dev.lmig.com/fluid/build/fluid.js', css: 'https://fluid-dev.lmig.com/fluid/build/fluid.css', }, + // test: { + // label: 'Test', + // esm: 'https://fluid-test.lmig.com/fluid/build/fluid.esm.js', + // legacy: 'https://fluid-test.lmig.com/fluid/build/fluid.js', + // css: 'https://fluid-test.lmig.com/fluid/build/fluid.css', + // }, + //direct access link and via a non-LM test application using the pre-production environment (which is also externally facing). test: { label: 'Test', - esm: 'https://fluid-test.lmig.com/fluid/build/fluid.esm.js', - legacy: 'https://fluid-test.lmig.com/fluid/build/fluid.js', - css: 'https://fluid-test.lmig.com/fluid/build/fluid.css', + esm: 'https://d3vissazv1qzch.cloudfront.net/fluid/build/fluid.esm.js', + legacy: 'https://d3vissazv1qzch.cloudfront.net/fluid/build/fluid.js', + css: 'https://d3vissazv1qzch.cloudfront.net/fluid/build/fluid.css', }, prod: { label: 'Production', @@ -55,14 +63,15 @@ export const fluid = { }, local: { label: 'Local', - esm: 'http://localhost:3333/build/my-first-stencil-project.esm.js', - legacy: 'http://localhost:3333/build/my-first-stencil-project.js', - css: 'http://localhost:3333/build/my-first-stencil-project.css', + esm: 'http://localhost:3333/fluid/build/fluid.esm.js', + legacy: 'http://localhost:3333/fluid/build/fluid.js', + css: 'http://localhost:3333/fluid/build/fluid.css', }, external: { label: 'External', - esm: 'https://fluid.libertymutual.com/fluid/build/fluid.esm.js', - legacy: 'https://fluid.libertymutual.com/fluid/build/fluid.esm.js', + esm: 'https://dsfe50dspcxki.cloudfront.net/fluid/build/fluid.esm.js', + legacy: 'https://dsfe50dspcxki.cloudfront.net/fluid/build/fluid.js', + css: 'https://dsfe50dspcxki.cloudfront.net/fluid/build/fluid.css', }, }, init: (fluid: FluidEnvironment, opts?: any) => { @@ -77,7 +86,7 @@ export const fluid = { // ========== IGNORE - replicating NPM package. -const addScriptLoader = (fluid: FluidEnvironment) => { +const addScriptLoader = (fluid:any) => { const script = document.createElement('script'); script.id = 'fluid-loader-script'; if ('noModule' in script) { @@ -98,7 +107,7 @@ const addScriptLoader = (fluid: FluidEnvironment) => { } }; -const addStyleSheet = (fluid: FluidEnvironment) => { +const addStyleSheet = (fluid:any) => { const element = document.createElement('link'); element.setAttribute('rel', 'stylesheet'); element.setAttribute('href', fluid.css); diff --git a/src/index.tsx b/src/index.tsx index 1eb10ee..6d76db9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,15 +3,9 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -//import { useCustomElementRef, fluid } from './fluid'; // Replaced by @lmig/fluid-react-utils -//import { useEffect, useRef } from 'react'; import { defineCustomElements } from 'stenciljs-components/loader'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement - // ---- Initialise FLUID -// let env = fluid.environments.test; -// useEffect(() => fluid.init(env), []); -// ---- Start Building ); // Initialize the custom elements defineCustomElements(window);