From cf530c284508836a4f1d62ec0a81312cc547938f Mon Sep 17 00:00:00 2001 From: themooses Date: Sun, 10 Mar 2024 15:47:28 +0100 Subject: [PATCH 1/2] many many fixes --- package-lock.json | 113 ++++++- package.json | 3 +- public/683424-200.png | Bin 0 -> 5957 bytes public/lifecycle.png | Bin 0 -> 7193 bytes public/volvo.svg | 2 + src/components/Global/NavbarGlobal.jsx | 2 +- src/components/Global/SearchBar.jsx | 37 +-- src/components/UI/Forms/UserDropdown.jsx | 10 +- src/components/UI/global/SearchResultCard.jsx | 23 +- src/components/app/profile/Additionals.jsx | 48 +++ src/components/app/profile/Layout.jsx | 88 +++++ .../app/profile/UserAffiliation.jsx | 26 ++ src/components/app/profile/UserHeader.jsx | 17 + src/components/app/profile/UserHero.jsx | 24 ++ .../app/profile/UserVerifications.jsx | 27 ++ .../v1/product/{[productId].js => [cid].js} | 46 ++- src/pages/api/v1/search.js | 42 ++- .../api/v1/users/by-id/[userid]/index.js | 35 ++ src/pages/index.jsx | 4 +- src/pages/product/[cid].js | 313 ++++++++++++++++++ src/pages/product/[productId].js | 219 ------------ src/pages/user/[userid]/index.js | 46 ++- src/utils/server/helpers.js | 9 + src/utils/server/ipfs-api-helpers.js | 57 ++++ 24 files changed, 885 insertions(+), 306 deletions(-) create mode 100644 public/683424-200.png create mode 100644 public/lifecycle.png create mode 100644 public/volvo.svg create mode 100644 src/components/app/profile/Additionals.jsx create mode 100644 src/components/app/profile/Layout.jsx create mode 100644 src/components/app/profile/UserAffiliation.jsx create mode 100644 src/components/app/profile/UserHeader.jsx create mode 100644 src/components/app/profile/UserHero.jsx create mode 100644 src/components/app/profile/UserVerifications.jsx rename src/pages/api/v1/product/{[productId].js => [cid].js} (57%) create mode 100644 src/pages/api/v1/users/by-id/[userid]/index.js create mode 100644 src/pages/product/[cid].js delete mode 100644 src/pages/product/[productId].js create mode 100644 src/utils/server/ipfs-api-helpers.js diff --git a/package-lock.json b/package-lock.json index 5e5e199a..a5893e5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@yudiel/react-qr-scanner": "^1.2.6", - "axios": "^1.6.2", + "axios": "^1.6.7", "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "clsx": "^2.0.0", @@ -24,6 +24,7 @@ "next-auth": "^4.24.5", "next-progress": "^2.3.1", "react": "^18", + "react-datepicker": "^6.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18", "react-modal": "^3.16.1", @@ -129,6 +130,54 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.9.tgz", + "integrity": "sha512-p86wynZJVEkEq2BBjY/8p2g3biQ6TlgT4o/3KgFKyTWoJLU1GZ8wpctwRqtkEl2tseYA+kw7dBAIDFcednfI5w==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.8", + "@floating-ui/utils": "^0.2.1", + "tabbable": "^6.0.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@headlessui/react": { "version": "1.7.17", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", @@ -1001,11 +1050,11 @@ } }, "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -1280,6 +1329,11 @@ "node": ">=10" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1413,6 +1467,15 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2350,9 +2413,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -4663,6 +4726,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.2.0.tgz", + "integrity": "sha512-GzEOiE6yLfp9P6XNkOhXuYtZHzoAx3tirbi7/dj2WHlGM+NGE1lefceqGR0ZrYsYaqsNJhIJFTgwUpzVzA+mjw==", + "dependencies": { + "@floating-ui/react": "^0.26.2", + "classnames": "^2.2.6", + "date-fns": "^3.3.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, "node_modules/react-device-detect": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-device-detect/-/react-device-detect-2.2.3.tgz", @@ -4715,6 +4794,19 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" } }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, "node_modules/react-spinners": { "version": "0.13.8", "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz", @@ -5361,6 +5453,11 @@ "react": "^16.11.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/tailwindcss": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", diff --git a/package.json b/package.json index 0b2eff84..65984078 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", "@yudiel/react-qr-scanner": "^1.2.6", - "axios": "^1.6.2", + "axios": "^1.6.7", "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "clsx": "^2.0.0", @@ -25,6 +25,7 @@ "next-auth": "^4.24.5", "next-progress": "^2.3.1", "react": "^18", + "react-datepicker": "^6.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18", "react-modal": "^3.16.1", diff --git a/public/683424-200.png b/public/683424-200.png new file mode 100644 index 0000000000000000000000000000000000000000..b06972ace639334c423812d4670463c80411a70b GIT binary patch literal 5957 zcmV-L7rN+)P)m3E7)FOtR0pN*>M|%*gchy3chn#i_i?KkagSCANf5cq z?Ibz7f4pZ;cJ^6&?{#^<%Ub*UJw*80Bhy+9xk2m}IwVz4hT4rp_& zNFd-F{|Jy8{rL-KQ;y)1#Se^a;!@rV3jy;kV=^W9OOjP zCLz2*^3D3dy}+HoP{+CylHef4;85UA;JdCBDH_2+io-giN^x)ucZI#{iRED-tL!8v)-( zCc{%nwrAmkbOGlAeY6KA(44dZyCa_`ujBp(*OJ$P9TmqZ&_uKWdjQt~ALIUJHYF>8 zD+s}lftZW}E&!(E{^qqQA^0&+lXl?Sz%#hNQC&+2ehmCSLxHo9U_aM4rcW{hI55Mw z26D~_;6H)IxW5^FmW$F3a3G&_BA*alxWD=GAam0Vb0BXF0WL>&lU?6*d6HYw40a$t z^Z|YZ%*XxBmM8fP_*X&@YoXZ}3C?qUv*k@DBI&$=f-)R<0QWaq-eevUKBGklWATg@ z;5fqPR2sP-i7?h+9c1n@9oSFln1PIR0+VomqvcIz0f(m;?^+xJd{lLg351PDryPNN z9=N{Q@+P+vHa1)YmoktcTNmd^Mp8~UcWHGYC z--Sfgj#E76yGTog#}tm$3LFodkG#dx@BwfzlHC|kW+v{R#BpRkazkE$q_}R6tnkF) z6eS&2A`t`wVxG4Vnx{U|MknLMr!$CA#C9DCQftgU9 zpDO7X=%KRvl52SdI0s2v)tr20aggQcHrv@2csYYUabyCLlM_(j3%GTPDTq^yHfUp7 z2YDITMoEW$NM_~ol)5C9M+j-ZYP^qKr&xx3KJ8|~Hc0LuOM%nTD*(6)@K!<{G~{Vu zkdh5e%>?XRuvtj4wRLS6RY4L5c^b*1*PsJQB#-R45B!-V)^d=!$exNV1|y$bwkGP;@)UY|I8Dd(=v;(5(W|De#mV&6 z^FE}LlnX{9iBv9}6q-rDm_0&H|!(}Mg93$_)G zp`;t^Zr~UAAIr5jk{3SMwIWpv1@6MWlbm70IA7@meh&O|!r#3{vO)d`c{&HOa}2VQ zX3Igkfp1x{%ro|8_1E?x34VJO@dx-}U_CXh0|1+1J`AN&76MyZvCIQnfFC0fr39D8ilZ=XOf?>dljHlwnc*`M9KVBA-@cDvzrAuGnhk0EDXk%w*)qrk+XK@`XZYp`&hso1z?b`{Nx>L~ z`KG5Uk^Gh1aV*(#n3HT8?niJAU)ccjt+J<}mtt(3zDWJ0tVaLdw&DR}Y(npnq}p

Ukd@Cmv`kFjb&z%=_)e`7bRJY>(5WSoN3 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 TtwQSl`;gIjXb7#=bc*^PX=VF8 literal 0 HcmV?d00001 diff --git a/public/volvo.svg b/public/volvo.svg new file mode 100644 index 00000000..1420c05c --- /dev/null +++ b/public/volvo.svg @@ -0,0 +1,2 @@ + +Volvo icon \ No newline at end of file diff --git a/src/components/Global/NavbarGlobal.jsx b/src/components/Global/NavbarGlobal.jsx index 49be9381..d926bf95 100644 --- a/src/components/Global/NavbarGlobal.jsx +++ b/src/components/Global/NavbarGlobal.jsx @@ -25,7 +25,7 @@ export function NavbarGlobal({ searchBar = true, navClassName }) { {/* Conditionally render SearchBar */} {searchBar && ( -

+
)} diff --git a/src/components/Global/SearchBar.jsx b/src/components/Global/SearchBar.jsx index 18f9f16b..71f571b0 100644 --- a/src/components/Global/SearchBar.jsx +++ b/src/components/Global/SearchBar.jsx @@ -14,12 +14,17 @@ export default function SearchBar({ className, ...props }) { const [showSearchResults, setShowSearchResults] = useState(false) const handleSearch = async () => { + if (searchTerm.trim() === '') { + setSearchResult([]) + return + } + setLoading(true) try { const response = await fetch(`/api/v1/search?query=${searchTerm}`) const data = await response.json() - setSearchResult(data) + setSearchResult(data.successful) } catch (error) { console.error('Error fetching search results:', error) setSearchResult([]) @@ -29,35 +34,25 @@ export default function SearchBar({ className, ...props }) { } useEffect(() => { - if (searchTerm.trim() !== '') { + const delayDebounce = setTimeout(() => { handleSearch() - } else { - setSearchResult([]) - } + }, 300) // Add a debounce to reduce API calls + + return () => clearTimeout(delayDebounce) }, [searchTerm]) const handleInputChange = event => { setSearchTerm(event.target.value) } - const filteredResults = searchResult.filter( - item => - item.name.toLowerCase().includes(searchTerm.toLowerCase()) || - (item.manufactured_by && - item.manufactured_by.owner_name - .toLowerCase() - .includes(searchTerm.toLowerCase())) || - item.dpp_class.toLowerCase().includes(searchTerm.toLowerCase()) - ) + const handleBlur = () => { + setShowSearchResults(false) + } const handleSearchBoxSelect = () => { setShowSearchResults(true) } - const handleBlur = () => { - setShowSearchResults(false) - } - const handleSearchSelect = url => { router.push(url) handleBlur() @@ -82,17 +77,17 @@ export default function SearchBar({ className, ...props }) {
{showSearchResults && ( <> - {filteredResults.length > 0 && ( + {searchResult.length > 0 && (
    - {filteredResults.map((item, index) => { + {searchResult.map((item, index) => { if (!item) { return null } const uniqueKey = item._id || item.name + item.dpp_class + index - console.log(item) + return (
  • {({ active }) => (
    - -

    Account settings

    + +

    My Profile

    )} @@ -85,8 +85,8 @@ export default function DropdownMenu() { )} >
    - -

    Example link

    + +

    Settings

    )} diff --git a/src/components/UI/global/SearchResultCard.jsx b/src/components/UI/global/SearchResultCard.jsx index 30b7d6a8..d002a7e3 100644 --- a/src/components/UI/global/SearchResultCard.jsx +++ b/src/components/UI/global/SearchResultCard.jsx @@ -1,19 +1,22 @@ import Link from 'next/link' export default function SearchResultCard({ item }) { + const cid = item.cid console.log(item) - const { name, dpp_class, manufactured_by, created_at } = item - return ( - +
    -
    -

    {name}

    -

    - {manufactured_by && manufactured_by.owner_name && ( - {manufactured_by.owner_name} - )} -

    +
    +
    +

    {item.ProductName}

    +

    + {item.Entrydate} +

    +
    +
    +

    CID

    +

    {item.cid}

    +
    diff --git a/src/components/app/profile/Additionals.jsx b/src/components/app/profile/Additionals.jsx new file mode 100644 index 00000000..595d0eb3 --- /dev/null +++ b/src/components/app/profile/Additionals.jsx @@ -0,0 +1,48 @@ +import Image from 'next/image' +import Link from 'next/link' + +export function Additionals({ user, session }) { + return ( + <> +
    + +
    +

    Trace the Journey

    +

    + {`Browse Millions of Products or Scan QR Codes for Detailed Lifecycles`} +

    +
    +
    +
    + lifecycle +
    + + +
    +

    + Apply for wider Access +

    +

    + {`Apply for a Role to Access More Features and Tools on the Platform.`} +

    +
    + Apply for Role +
    +
    + +
    + + ) +} diff --git a/src/components/app/profile/Layout.jsx b/src/components/app/profile/Layout.jsx new file mode 100644 index 00000000..6b7880f3 --- /dev/null +++ b/src/components/app/profile/Layout.jsx @@ -0,0 +1,88 @@ +import { classNames } from '@/utils/server/helpers' +import { Menu, Transition } from '@headlessui/react' +import { + AdjustmentsHorizontalIcon, + EllipsisHorizontalIcon, +} from '@heroicons/react/24/solid' +import Link from 'next/link' +import { Fragment, useState } from 'react' + +export function UserHeroLayout({ children, editable }) { + return ( +
    + {children} +
    + ) +} + +export function UserProfileCardLayout({ children, editable, user }) { + return ( +
    +
    + <> + + {children} + +
    +
    + ) +} + +function OptionsMenu({ editable }) { + return ( +
    +
    + +
    + + Alternative + +
    + + + +
    + {editable && ( + + {({ active }) => ( + + + )} +
    +
    +
    +
    +
    +
    + ) +} diff --git a/src/components/app/profile/UserAffiliation.jsx b/src/components/app/profile/UserAffiliation.jsx new file mode 100644 index 00000000..b93d7025 --- /dev/null +++ b/src/components/app/profile/UserAffiliation.jsx @@ -0,0 +1,26 @@ +import Image from 'next/image' + +export function UserAffiliation({ user, editable }) { + return ( +
    +
    +
    + volvo logo +
    + +
    +

    + Volvo Construction & Equipment +

    +

    Remanufacturing Engineer

    +
    +
    +
    + ) +} diff --git a/src/components/app/profile/UserHeader.jsx b/src/components/app/profile/UserHeader.jsx new file mode 100644 index 00000000..d059550c --- /dev/null +++ b/src/components/app/profile/UserHeader.jsx @@ -0,0 +1,17 @@ +import { FingerPrintIcon } from '@heroicons/react/24/solid' + +export function UserHeader({ user, editable, session }) { + return ( +
    +
    +

    + {user.name} +

    +
    + +

    {user.role}

    +
    +
    +
    + ) +} diff --git a/src/components/app/profile/UserHero.jsx b/src/components/app/profile/UserHero.jsx new file mode 100644 index 00000000..f170e18c --- /dev/null +++ b/src/components/app/profile/UserHero.jsx @@ -0,0 +1,24 @@ +import { useSession } from 'next-auth/react' +import { UserHeroLayout, UserProfileCardLayout } from './Layout' +import { UserHeader } from './UserHeader' +import { UserVerifications } from './UserVerifications' +import { UserAffiliation } from './UserAffiliation' +import { Additionals } from './Additionals' + +export default function UserHero({ user, editable }) { + const { data: session } = useSession() + return ( + +
    +
    + + + + + +
    +
    + {editable && } +
    + ) +} diff --git a/src/components/app/profile/UserVerifications.jsx b/src/components/app/profile/UserVerifications.jsx new file mode 100644 index 00000000..26a1ab65 --- /dev/null +++ b/src/components/app/profile/UserVerifications.jsx @@ -0,0 +1,27 @@ +import { CheckBadgeIcon } from '@heroicons/react/24/solid' + +export function UserVerifications({ user, editable }) { + return ( +
    +

    + {editable ? 'Your Verifications' : 'Verifications'} +

    + +
    +
    + +

    Email

    +
    + +
    + +

    Phone Number

    +
    +
    + +

    Identity

    +
    +
    +
    + ) +} diff --git a/src/pages/api/v1/product/[productId].js b/src/pages/api/v1/product/[cid].js similarity index 57% rename from src/pages/api/v1/product/[productId].js rename to src/pages/api/v1/product/[cid].js index 8b3b9edc..c8d72956 100644 --- a/src/pages/api/v1/product/[productId].js +++ b/src/pages/api/v1/product/[cid].js @@ -1,28 +1,40 @@ import { defaultHandler } from '@/utils/server/api-helpers' import { ObjectId } from 'mongodb' -import Product from '@/models/Product' +import KeyDocument from '@/models/KeyDocument' +import { fetchDataFromIPFS } from '@/utils/server/ipfs-api-helpers' -const getProductById = async (req, res) => { +const getProductByCid = async (req, res) => { console.log('heres the req', req.query) + const { cid } = req.query try { - const { productId } = req.query - console.log('heres the productid', productId) - if (!ObjectId.isValid(productId)) { - return res.status(400).json({ message: 'Invalid ID' }) - } + console.log('heres the productid', cid) + const documents = await KeyDocument.find({ cid: cid }) - const productData = await Product.findOne({ _id: new ObjectId(productId) }) + // Fetch data from IPFS for each document CID + const fetchPromises = documents.map(doc => { + return new Promise((resolve, reject) => { + fetchDataFromIPFS(doc.cid, (error, data) => { + if (error) { + reject(error) + } else { + // Attach the CID to the data object + const responseData = { + ...data, + cid: doc.cid, // Include the CID in the response + } + resolve(responseData) + } + }) + }) + }) - if (!productData) { - return res.status(404).json({ message: 'Product not found' }) - } + // Resolve all promises to get the IPFS data + const ipfsResults = await Promise.all(fetchPromises) - res.status(200).json(productData) + res.status(200).json(ipfsResults) } catch (error) { - console.error('Error fetching product:', error) - return res - .status(500) - .json({ message: 'Internal server error', error: error.message }) + console.error('Error during search:', error) + res.status(500).json({ error: 'An error occurred during the search.' }) } } @@ -69,7 +81,7 @@ const handler = async (req, res) => req, res, { - GET: getProductById, + GET: getProductByCid, PUT: addEventToProduct, }, { diff --git a/src/pages/api/v1/search.js b/src/pages/api/v1/search.js index 9a218670..64c61ccf 100644 --- a/src/pages/api/v1/search.js +++ b/src/pages/api/v1/search.js @@ -1,26 +1,32 @@ import { defaultHandler } from '@/utils/server/api-helpers' -import Product from '@/models/Product' +import KeyDocument from '@/models/KeyDocument' +import { fetchDataFromIPFS } from '@/utils/server/ipfs-api-helpers' -const searchProducts = async (req, res) => { +const searchByCID = async (req, res) => { const { query } = req.query try { - const products = await Product.find({ - $or: [ - { - name: { $regex: query, $options: 'i' }, - }, - { - 'manufactured_by.owner_name': { $regex: query, $options: 'i' }, - }, - ], - }).select('name dpp_class manufactured_by _id') - - res.status(200).json(products) + const regex = new RegExp('^' + query, 'i') + const documents = await KeyDocument.find({ cid: { $regex: regex } }) + + const fetchPromises = documents.map(doc => { + return fetchDataFromIPFS(doc.cid) + .then(data => ({ ...data, cid: doc.cid })) // Attach the CID here + .catch(error => ({ error: error.message, cid: doc.cid })) + }) + + const ipfsResults = await Promise.all(fetchPromises) + + const successfulResults = ipfsResults.filter(result => !result.error) + const errorResults = ipfsResults.filter(result => result.error) + + res.status(200).json({ + successful: successfulResults, + errors: errorResults, + }) } catch (error) { - res - .status(500) - .json({ error: 'An error occurred while fetching search results' }) + console.error('Error during search:', error) + res.status(500).json({ error: 'An error occurred during the search.' }) } } @@ -29,7 +35,7 @@ const handler = async (req, res) => req, res, { - GET: searchProducts, + GET: searchByCID, }, { requiresAuth: false, diff --git a/src/pages/api/v1/users/by-id/[userid]/index.js b/src/pages/api/v1/users/by-id/[userid]/index.js new file mode 100644 index 00000000..697cec58 --- /dev/null +++ b/src/pages/api/v1/users/by-id/[userid]/index.js @@ -0,0 +1,35 @@ +import User from '@/models/User' +import { defaultHandler } from '@/utils/server/api-helpers' + +const getById = async (req, res) => { + try { + const user = await User.findById(req.query.userid) + if (!user) { + return res.status(404).json({ message: 'User not found' }) + } + res.status(200).json({ + _id: user._id, + name: user.name, + email: user.email, + role: user.role, + }) + } catch (error) { + console.error('Error fetching user:', error) + res.status(500).json({ message: 'Server Error' }) + } +} + +const handler = async (req, res) => + defaultHandler( + req, + res, + { + GET: getById, + }, + { + requiresAuth: true, + requiresAdmin: false, + } + ) + +export default handler diff --git a/src/pages/index.jsx b/src/pages/index.jsx index dc561fbb..03e0eda0 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -9,10 +9,10 @@ const inter = Inter({ subsets: ['latin'] }) export default function Home() { return ( -
    +

    Project D0020E

    Digital Product Passport & Key Services

    -
    +

    Want to know more about a product?

    diff --git a/src/pages/product/[cid].js b/src/pages/product/[cid].js new file mode 100644 index 00000000..08e45af9 --- /dev/null +++ b/src/pages/product/[cid].js @@ -0,0 +1,313 @@ +import { useRouter } from 'next/router' +import LayoutGlobal from '@/components/Layout/LayoutGlobal' +import { Container } from '@/components/utils/Container' +import { useEffect, useState } from 'react' +import React from 'react' +import { formatDate, formatDimensions } from '@/utils/server/helpers' +import Link from 'next/link' +import TextField from '@/components/UI/Forms/TextField' +import { + addEventToIPFS, + fetchDataFromIPFS, + fetchEventsFromIPFS, +} from '@/utils/server/ipfs-api-helpers' +import { getSession, useSession } from 'next-auth/react' +import KeyDocument from '@/models/KeyDocument' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' + +export async function getServerSideProps(context) { + const session = await getSession(context) + const { cid } = context.params + + try { + const productData = await fetchDataFromIPFS(cid) + + let remanEvents = null + let shippingEvents = null + + if ( + session && + (session.user.role === 'Contributor' || session.user.isAdmin) + ) { + const keyDocument = await KeyDocument.findOne({ cid: cid }) + if (!keyDocument) { + throw new Error('Document not found') + } + + try { + // Attempt to fetch the last remanufacturing event + remanEvents = await fetchEventsFromIPFS( + keyDocument.remanufacturing_events.publickey, + 'LastEvent' + ) + } catch (error) { + // If no remanufacturing event found, handle it here (e.g., log the error) + console.log('No remanufacturing event found:', error.message) + remanEvents = null + } + + try { + // Attempt to fetch the last shipping event + shippingEvents = await fetchEventsFromIPFS( + keyDocument.shipping.publickey, + 'LastEvent' + ) + } catch (error) { + // If no shipping event found, handle it here (e.g., log the error) + console.log('No shipping event found:', error.message) + shippingEvents = null + } + + console.log('Events:', { remanEvents, shippingEvents }) + } + + // Return all fetched data + return { + props: { + product: { + ...productData, + remanEvents, + shippingEvents, + }, + }, + } + } catch (error) { + console.error('Error fetching data:', error) + return { notFound: true } + } +} + +function ProductDetails({ product }) { + const { data: session, status } = useSession() + const loading = status === 'loading' + + const [eventCategory, setEventCategory] = useState('remanufacturing') // default to 'remanufacturing' + const [eventDate, setEventDate] = useState(new Date()) + const [eventType, setEventType] = useState('') + const [eventData, setEventData] = useState('') + const [error, setError] = useState('') + + if (loading) { + return
    Loading...
    // Or any other loading indicator + } + + const handleInputChange = e => { + const { name, value } = e.target + switch (name) { + case 'eventType': + setEventType(value) + break + case 'eventData': + setEventData(value) + break + default: + break + } + console.log() + if (error) setError('') + } + + const handleSubmit = async e => { + e.preventDefault() // Don't forget to prevent default form submission + + if (!eventType || !eventData) { + setError('Please fill in all fields') + return + } + const publicKey = + eventCategory === 'remanufacturing' + ? product.remanufacturing_events + : product.shipping + + console.log(publicKey, eventType, eventDate.toISOString(), eventData) + try { + const result = await addEventToIPFS( + publicKey, + eventType, + eventDate.toISOString(), // Format the date to ISO string + eventData + ) + + console.log('Event submitted:', result) + + setEventType('') + setEventData('') + setEventCategory('remanufacturing') // Reset the dropdown + } catch (error) { + console.error('Error submitting event:', error) + setError(error.message) + } + } + console.log('heres the goddamn product', product) + + return ( + + + {session && session.user.role === 'Contributor' && ( +
    +
    +
    +

    + You are qualified to add events +

    +

    + { + "If you've performed something that's altered the product please state it and submit the form" + } +

    +
    +
    + + Event Category + + +
    + + +
    + + Date + + setEventDate(date)} + className='form-control block w-full appearance-none rounded-lg border bg-gray-50 px-[calc(theme(spacing.4)-1px)] py-[calc(theme(spacing.3)-1px)] text-gray-900 ring-0 ring-transparent transition duration-200 placeholder:text-gray-400 hover:bg-white hover:ring-4 hover:ring-blue-50 focus:border-blue-500 focus:bg-white focus:outline-none focus:ring-4 focus:ring-teal-500/10 active:border-blue-500 active:bg-white active:ring-0' + name='eventDate' + /> +
    +
    + + {/*

    + This event will be added to {product.name} 's event trail +

    */} +
    +
    +
    + )} +
    +
    +
    +

    + {product.ProductName} +

    +

    + CID: {product.cid} +

    +
    +
    +
    +
    +
    + Product Type: +
    +
    + {product.ProductType} +
    +
    +
    +
    + Creation time: +
    +
    + {formatDate(product.Entrydate)} +
    +
    +
    +
    + Plant: +
    +
    + {product.Plant} +
    +
    +
    +
    + Material ID: +
    +
    + {product.MaterialId} +
    +
    +
    +
    + Order ID: +
    +
    + {product.OrderId} +
    +
    +
    +
    + Dimensions{' '} + + (XX YY ZZ) + + :{' '} +
    +
    + {formatDimensions(product.Dimensions)} +
    +
    + +
    + {product.remanEvents && ( +
    + + Remanufacturing Events + +
    +
    +

    Data: {product.remanEvents.Data}

    +

    + Creation Time:{' '} + {formatDate(product.remanEvents.Datetime)} +

    +

    Event Type: {product.remanEvents.Eventtype}

    +
    +
    +
    +
    + )} +
    +
    +
    +
    +
    +
    +
    + ) +} + +export default ProductDetails diff --git a/src/pages/product/[productId].js b/src/pages/product/[productId].js deleted file mode 100644 index c8778fd1..00000000 --- a/src/pages/product/[productId].js +++ /dev/null @@ -1,219 +0,0 @@ -import { useRouter } from 'next/router' -import LayoutGlobal from '@/components/Layout/LayoutGlobal' -import { Container } from '@/components/utils/Container' -import { useEffect, useState } from 'react' -import React from 'react' -import { formatDate } from '@/utils/server/helpers' -import Link from 'next/link' -import TextField from '@/components/UI/Forms/TextField' -import { set } from 'mongoose' - -export async function getServerSideProps(context) { - const { productId } = context.params - - const fetchUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/product/${productId}` - - try { - const res = await fetch(fetchUrl) - - if (!res.ok) { - throw new Error(`Failed to fetch product, status: ${res.status}`) - } - - const productData = await res.json() - - return { - props: { product: productData }, - } - } catch (error) { - console.error('Error fetching product:', error) - return { - notFound: true, - } - } -} - -function ProductDetails({ product }) { - const [eventAction, setEventAction] = useState('') - const [error, setError] = useState('') - - const handleInputChange = e => { - setEventAction(e.target.value) - if (error) { - setError('') - } - } - - const handleSubmit = async e => { - if (!eventAction) { - setError('Please enter an event action') - return - } - - try { - const response = await fetch(`/api/v1/product/${product._id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - productId: product._id, - action: eventAction, - }), - }) - - if (!response.ok) { - throw new Error(`Failed to submit event, status: ${response.status}`) - } - - const data = await response.json() - console.log('Event submitted:', data) - setEventAction('') - } catch (error) { - setError(error.message) - console.error('Error submitting event:', error) - } - } - - return ( - - -
    -
    -
    -

    - You are qualified to add events -

    -

    - { - "If you've performed something that's altered the product please state it and submit the form" - } -

    - - - {/*

    - This event will be added to {product.name} 's event trail -

    */} -
    -
    -
    -
    -
    -
    -

    - {product.name} -

    -

    - ProductID: {product.id} -

    -
    -
    -
    -
    -
    - Manufactuter: -
    -
    - {product.manufactured_by.owner_name} -
    -
    -
    -
    - Creation time -
    -
    - {formatDate(product.created_at.creation_time)} -
    -
    - {product.carbon_footprint.effect != null && ( -
    -
    - Carbon footprint -
    -
    - {product.carbon_footprint.effect} -
    -
    - )} - {product.crm !== undefined && ( -
    -
    - Crm -
    -
    - Scania -
    -
    - )} -
    - {product.event_trail.events.length > 0 && ( -
    - - Events - -
    - {product.event_trail.events.map((event, index) => ( -
    -

    ID: {event.id}

    -

    - Creation Time: {formatDate(event.creation_time)} -

    -

    Action: {event.action}

    -

    -
    - ))} -
    -
    - )} - {product.key_components.components.length > 0 && ( -
    - - Key components - -
    - {product.key_components.components.map( - (component, index) => ( -
    -

    ID: {component.id}

    -

    DPP Class: {component.dpp_class}

    -

    - Name:  - - {component.name} - -

    -
    - ) - )} -
    -
    - )} -
    -
    -
    -
    -
    -
    -
    - ) -} - -export default ProductDetails diff --git a/src/pages/user/[userid]/index.js b/src/pages/user/[userid]/index.js index 9646d323..2286909c 100644 --- a/src/pages/user/[userid]/index.js +++ b/src/pages/user/[userid]/index.js @@ -6,25 +6,63 @@ import { Cog6ToothIcon } from '@heroicons/react/24/outline' import { ClipboardDocumentListIcon } from '@heroicons/react/24/outline' import { QrCodeIcon } from '@heroicons/react/24/outline' import { useSession } from 'next-auth/react' -import useSWR from 'swr' +import useSWR, { mutate } from 'swr' import { useRouter } from 'next/router' import Account from '@/components/Profile/account' import Settings from '@/components/Profile/settings' import Scans from '@/components/Profile/scans' import Event from '@/components/Profile/event' +import UserHero from '@/components/app/profile/UserHero' +import { FullPageLoader } from '@/components/Global/Loader' export default function Profile() { + const router = useRouter() const { data: session } = useSession() + const [user, setUser] = useState(null) - const router = useRouter() + const { + data: userResponse, + error: error, + status: isValidating, + } = useSWR(`/api/v1/users/by-id/${router.query.userid}`, { + onErrorRetry: ({ retryCount }) => { + if (retryCount >= 1) return window.location.reload() + }, + }) + useEffect(() => { + if (userResponse) { + setUser(userResponse) + } + }, [userResponse]) const [editable, setEditable] = useState(false) useEffect(() => { if (session && user) { - setEditable(session.user.id === user.id) + setEditable(session.user._id === user._id) + console.log('session:', session) + console.log('user:', user) } }, [session, user]) - return <>dasda + if (user) { + return ( + <> + + + + + + + ) + } + return ( + <> + + + ) } + +Profile.auth = true diff --git a/src/utils/server/helpers.js b/src/utils/server/helpers.js index 6d63434f..c455aafc 100644 --- a/src/utils/server/helpers.js +++ b/src/utils/server/helpers.js @@ -2,3 +2,12 @@ export function formatDate(dateString) { const options = { year: 'numeric', month: 'short', day: 'numeric' } return new Date(dateString).toLocaleDateString('en-GB', options) } + +export function classNames(...classes) { + return classes.filter(Boolean).join(' ') +} + +export function formatDimensions(dimensionString) { + // Split the string every two characters and join with 'x' + return dimensionString.match(/.{1,2}/g).join('x') +} diff --git a/src/utils/server/ipfs-api-helpers.js b/src/utils/server/ipfs-api-helpers.js new file mode 100644 index 00000000..d585a63d --- /dev/null +++ b/src/utils/server/ipfs-api-helpers.js @@ -0,0 +1,57 @@ +import axios from 'axios' + +export async function fetchDataFromIPFS(cid) { + try { + const response = await axios.request({ + url: 'http://35.178.146.101:80/retrieveData', + method: 'get', + data: { CID: cid }, // Sending body with GET request + headers: { 'Content-Type': 'application/json' }, + }) + + return response.data // This will be the JSON response from the server + } catch (error) { + console.error('Error fetching data from IPFS with Axios:', error) + throw error // Rethrow the error to be handled by the caller + } +} + +export async function fetchEventsFromIPFS(publicKey, eventType) { + try { + const response = await axios.request({ + url: 'http://35.178.146.101:80/retrieveEvent', + method: 'get', + data: { Key: publicKey, Type: eventType }, // "AllEvents" or "LastEvent" + headers: { 'Content-Type': 'application/json' }, + }) + + return response.data // This will be the JSON response containing events + } catch (error) { + console.error('Error fetching events from IPFS with Axios:', error) + throw error + } +} + +export async function addEventToIPFS(Key, Eventtype, Datetime, Data) { + try { + const response = await axios.post( + 'http://35.178.146.101:80/addEvent', + { + Key, + Eventtype, + Datetime, + Data, + }, + { + headers: { 'Content-Type': 'application/json' }, + } + ) + + return response.data + } catch (error) { + console.error('Error adding event to IPFS with Axios:', error) + throw new Error( + `Error adding event to IPFS: ${error.response?.data || error.message}` + ) + } +} From f791de435acf4e082c4a059ca9d5a43e339b3fe7 Mon Sep 17 00:00:00 2001 From: themooses Date: Sun, 10 Mar 2024 19:48:11 +0100 Subject: [PATCH 2/2] f --- src/pages/app/settings/account.jsx | 0 src/pages/app/settings/events.jsx | 0 src/pages/app/settings/profile.jsx | 0 src/pages/app/settings/security.jsx | 0 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/pages/app/settings/account.jsx delete mode 100644 src/pages/app/settings/events.jsx delete mode 100644 src/pages/app/settings/profile.jsx delete mode 100644 src/pages/app/settings/security.jsx diff --git a/src/pages/app/settings/account.jsx b/src/pages/app/settings/account.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/app/settings/events.jsx b/src/pages/app/settings/events.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/app/settings/profile.jsx b/src/pages/app/settings/profile.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/app/settings/security.jsx b/src/pages/app/settings/security.jsx deleted file mode 100644 index e69de29b..00000000