From 7c0b915e737036128eddf811ff1ba49ab32b5814 Mon Sep 17 00:00:00 2001
From: tarjelavik <Tarje.Lavik@ub.uib.no>
Date: Mon, 13 Sep 2021 13:54:46 +0200
Subject: [PATCH] Fix lint errors and missing PT component

---
 web/components/Image/image.ts                 |  45 ++++++
 web/components/Image/index.js                 |  36 -----
 web/components/Image/index.tsx                | 133 ++++++++++++++++++
 web/components/Layout/Footer.js               |  16 ++-
 web/components/Layout/Meta.js                 |  32 +++--
 .../Sections/IllustrationWithCaption.js       |   3 +-
 web/pages/[...slug].js                        |   2 +-
 web/pages/actors/index.js                     |   2 +-
 web/pages/nansen-i-biblioteket/index.js       |   2 +-
 9 files changed, 220 insertions(+), 51 deletions(-)
 create mode 100644 web/components/Image/image.ts
 delete mode 100644 web/components/Image/index.js
 create mode 100644 web/components/Image/index.tsx

diff --git a/web/components/Image/image.ts b/web/components/Image/image.ts
new file mode 100644
index 0000000..a12a396
--- /dev/null
+++ b/web/components/Image/image.ts
@@ -0,0 +1,45 @@
+/* eslint-disable no-unused-vars */
+/**
+ * ! Important in optimizing images
+ *
+ *  Keep the values in sync between:
+ * - `deviceSizes` in `next.config.js`
+ * - `deviceSizes` in `image.ts`
+ *
+ * ! Recommended
+ * NextJs optimize images according to your viewport. This is wonderful for mobile, but for desktop with a 4k screen, NextJs would
+ * download the 3840px version of your image.
+ *
+ * To workaround this unfortunate situation, I highly recommend you pass `size` to images with the highest width value being the
+ * max width an image can have on your site.
+ *
+ * For example, content on my site is centered and cannot be more than 960px wide, thus I make sure that 960 is in `deviceSizes` and
+ * I use `Sizes.main` to limit the image to only 960px. This considerably reduce the size in KB of my images and they're much
+ * better optimized on screens larger than 960px.
+ *
+ * This file is a way to generate the strings to pass to Image's `size` property and put the results in an Enum for easier consumption
+ */
+const deviceSizes = [320, 480, 640, 750, 828, 960, 1080, 1200, 1440, 1920, 2048, 2560, 3840]
+const deviceSizesMax = Math.max(...deviceSizes)
+
+/**
+ * ? `generateSizes` will create the strings necessary for `Sizes` enum
+ *
+ * ? Simply uncomment the `console.log` and adjust values
+ */
+
+const generateSizes = (upperLimit: number = deviceSizesMax): string => {
+  const sizes = [...deviceSizes.filter((v) => v < upperLimit), upperLimit]
+  return sizes
+    .map((v, i) => {
+      return i < sizes.length - 1 ? ` (max-width: ${v}px) ${v}px` : ` ${v}px`
+    })
+    .join()
+}
+// console.log(generateSizes(960)) // I use a variable, but since it's easier to understand with a real number...
+// console.log(generateSizes())
+
+export enum Sizes {
+  main = '(max-width: 320px) 320px, (max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 750px) 750px, (max-width: 828px) 828px, 960px',
+  full = '(max-width: 320px) 320px, (max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 750px) 750px, (max-width: 828px) 828px, (max-width: 960px) 960px, (max-width: 1080px) 1080px, (max-width: 1200px) 1200px, (max-width: 1440px) 1440px, (max-width: 1920px) 1920px, (max-width: 2048px) 2048px, (max-width: 2560px) 2560px, 3840px',
+}
diff --git a/web/components/Image/index.js b/web/components/Image/index.js
deleted file mode 100644
index f8482df..0000000
--- a/web/components/Image/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Box, chakra } from '@chakra-ui/react'
-import NextImage from 'next/image'
-
-const CoverImg = chakra(NextImage, {
-  shouldForwardProp: (prop) =>
-    ['width', 'height', 'src', 'alt', 'quality', 'placeholder', 'blurDataURL', 'loader '].includes(
-      prop,
-    ),
-})
-
-const myLoader = ({ src, width, quality }) => {
-  return `${src}?w=${width}&q=${quality}`
-}
-
-export const Image = (props) => {
-  const { src, alt, ...rest } = props
-  return (
-    <Box pos="relative" cursor="pointer" className="group" {...rest}>
-      <CoverImg
-        w="auto"
-        h="auto"
-        loader={myLoader}
-        width={600}
-        quality={50}
-        height={384}
-        placeholder="blur"
-        src={src}
-        alt={alt}
-        transition="all 0.2s"
-        _groupHover={{
-          transform: 'scale(1.05)',
-        }}
-      />
-    </Box>
-  )
-}
diff --git a/web/components/Image/index.tsx b/web/components/Image/index.tsx
new file mode 100644
index 0000000..ba8c146
--- /dev/null
+++ b/web/components/Image/index.tsx
@@ -0,0 +1,133 @@
+import { chakra, ThemingProps, useStyleConfig } from '@chakra-ui/react'
+import NextImage, { ImageProps as NextImageProps } from 'next/image'
+import { ReactElement } from 'react'
+import { Sizes } from './image'
+
+// TODO review props when NextJs is updated so we don't have to defined it here
+/**
+ * ? Because NextJs typing is preventing auto-suggest for layout, width and height,
+ * ? we declare the styles differently in this component and will manage the switch
+ * ? to NextJs typings when calling NextJs `next/image` component
+ */
+type LayoutValue = 'fixed' | 'intrinsic' | 'responsive' | undefined
+
+type LayoutAndSize = { layout: 'fill' } | { layout: LayoutValue; height: number; width: number }
+
+/**
+ * Types for the Image component itself
+ */
+type ImageProps = Pick<
+  NextImageProps,
+  | 'className'
+  | 'loading'
+  | 'objectFit'
+  | 'objectPosition'
+  | 'priority'
+  | 'quality'
+  | 'src'
+  | 'unoptimized'
+> &
+  Pick<Required<NextImageProps>, 'alt'> &
+  Pick<ThemingProps, 'variant'> & {
+    dimensions?: [number, number]
+    layout?: 'fill' | LayoutValue
+    sizes?: Sizes // could be a string too, this one is just a way to make it easier
+  }
+
+/**
+ * Wraps NextJs `next/image` component in Chakra's factory function
+ * This is what will allow to use the theme and the styling properties on the component
+ */
+const ImageWithChakra = chakra(
+  ({
+    className,
+    dimensions = [0, 0],
+    layout = 'fill',
+    loading,
+    objectFit,
+    objectPosition,
+    priority,
+    quality,
+    sizes,
+    src,
+    unoptimized,
+    ...nextjsInternals
+  }: ImageProps): ReactElement => {
+    /**
+     * ? As explained earlier, NextJs typing is preventing auto-suggest for layout, width and height
+     * ? Here we actually convert our component typing to NextJs typing
+     */
+    const [width, height] = dimensions
+
+    const layoutAndSize: LayoutAndSize =
+      height > 0 || width > 0
+        ? { height, layout: layout === 'fill' ? 'intrinsic' : layout, width }
+        : { layout: 'fill' }
+
+    return (
+      <NextImage
+        className={className}
+        loading={loading}
+        objectFit={objectFit}
+        objectPosition={objectPosition}
+        priority={priority}
+        quality={quality}
+        sizes={sizes}
+        src={src}
+        unoptimized={unoptimized}
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        {...layoutAndSize}
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        {...nextjsInternals}
+      />
+    )
+  },
+)
+
+export const Image = ({ variant, ...props }: ImageProps): ReactElement => {
+  /**
+   * ? This components serves as an interface to pass Chakra's styles
+   * ? You can use the theme and/or styling properties (eg. backgroundColor='red.200')
+   */
+  const styles = useStyleConfig('Image', { variant })
+  // eslint-disable-next-line react/jsx-props-no-spreading
+  return <ImageWithChakra sx={styles} {...props} />
+}
+
+/* import { Box, chakra } from '@chakra-ui/react'
+import NextImage from 'next/image'
+
+const CoverImg = chakra(NextImage, {
+  shouldForwardProp: (prop) =>
+    ['width', 'height', 'src', 'alt', 'quality', 'placeholder', 'blurDataURL', 'loader '].includes(
+      prop,
+    ),
+})
+
+const myLoader = ({ src, width, quality }) => {
+  return `${src}?w=${width}&q=${quality}`
+}
+
+export const Image = (props) => {
+  const { src, alt, ...rest } = props
+  return (
+    <Box pos="relative" cursor="pointer" className="group" {...rest}>
+      <CoverImg
+        w="auto"
+        h="auto"
+        loader={myLoader}
+        width={600}
+        quality={50}
+        height={384}
+        placeholder="blur"
+        src={src}
+        alt={alt}
+        transition="all 0.2s"
+        _groupHover={{
+          transform: 'scale(1.05)',
+        }}
+      />
+    </Box>
+  )
+}
+ */
diff --git a/web/components/Layout/Footer.js b/web/components/Layout/Footer.js
index e1d8f3e..87b3720 100644
--- a/web/components/Layout/Footer.js
+++ b/web/components/Layout/Footer.js
@@ -5,7 +5,9 @@ import RenderSections from '../Sections/RenderSection'
 import License from '../License'
 import React from 'react'
 import Publisher from '../Publisher'
-import Image from '../Image'
+// import { Image } from '../Image'
+
+// import footerBorder from '../../public/img/taakeheimen-footer.svg'
 
 /* const MenuItem = ({ children }) => (
   <Text
@@ -35,10 +37,14 @@ export default function Footer(props) {
       gridArea="footer"
       maxW="full"
       minH="100px"
-      py="2"
+      mt="10"
+      py="6"
       px="0"
       zIndex="100"
       color={color}
+      backgroundImage={`url('${process.env.NEXT_PUBLIC_BASE_PATH}/img/taakeheimen-footer.svg')`}
+      backgroundPosition="50% 4%"
+      backgroundRepeat="no-repeat"
     >
       <Flex pl="5">
         <Button variant="link" onClick={toggleColorMode}>
@@ -46,7 +52,11 @@ export default function Footer(props) {
         </Button>
       </Flex>
 
-      <Image src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/taakeheimen-footer.svg`} alt="" />
+      {/* <Image
+        layout="responsive"
+        src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/taakeheimen-footer.svg`}
+        alt=""
+      /> */}
 
       <Container maxW="4xl" p="0" sx={{ perspective: '492px' }}>
         {/* <Flex pb="0">
diff --git a/web/components/Layout/Meta.js b/web/components/Layout/Meta.js
index 45c7952..33d32df 100644
--- a/web/components/Layout/Meta.js
+++ b/web/components/Layout/Meta.js
@@ -1,20 +1,36 @@
 import * as React from 'react'
 import Head from 'next/head'
 
+const basePath = process.env.NEXT_PUBLIC_BASE_PATH
+
 export default function Meta() {
   return (
     <React.Fragment>
       <Head>
-        <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
-        <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
-        <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
-        <link rel="manifest" href="/favicon/site.webmanifest" />
-        <link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#000000" />
-        <link rel="shortcut icon" href="/favicon/favicon.ico" />
+        <link
+          rel="apple-touch-icon"
+          sizes="180x180"
+          href={`${basePath}/favicon/apple-touch-icon.png`}
+        />
+        <link
+          rel="icon"
+          type="image/png"
+          sizes="32x32"
+          href={`${basePath}/favicon/favicon-32x32.png`}
+        />
+        <link
+          rel="icon"
+          type="image/png"
+          sizes="16x16"
+          href={`${basePath}/favicon/favicon-16x16.png`}
+        />
+        <link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} />
+        <link rel="mask-icon" href={`${basePath}/favicon/safari-pinned-tab.svg`} color="#000000" />
+        <link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} />
         <meta name="msapplication-TileColor" content="#000000" />
-        <meta name="msapplication-config" content="/favicon/browserconfig.xml" />
+        <meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} />
         <meta name="theme-color" content="#000" />
-        <link rel="alternate" type="application/rss+xml" href="/feed.xml" />
+        <link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} />
         <meta name="description" content={'A statically generated blog example using Next.js.'} />
         {/* <meta property="og:image" content={HOME_OG_IMAGE_URL} /> */}
       </Head>
diff --git a/web/components/Sections/IllustrationWithCaption.js b/web/components/Sections/IllustrationWithCaption.js
index eab2271..760f398 100644
--- a/web/components/Sections/IllustrationWithCaption.js
+++ b/web/components/Sections/IllustrationWithCaption.js
@@ -1,6 +1,7 @@
 import { imageBuilder } from '../../lib/sanity'
-import { Flex, Grid, Box, Image } from '@chakra-ui/react'
+import { Flex, Grid, Box } from '@chakra-ui/react'
 import Caption from './shared/caption'
+import { Image } from '../Image'
 
 export default function IllustrationWithCaption(props) {
   if ((!props && !props.illustration) || props.disabled === true) {
diff --git a/web/pages/[...slug].js b/web/pages/[...slug].js
index cdce62c..e91dd03 100644
--- a/web/pages/[...slug].js
+++ b/web/pages/[...slug].js
@@ -30,7 +30,7 @@ export default function Page({ data, preview }) {
           </Box>
         )}
 
-        <Text mt="5" color="gray.500" fontSize="sm">
+        <Text mt="10" color="gray.500" fontSize="sm">
           Oppdatert: <Date>{_updatedAt}</Date>
         </Text>
       </Container>
diff --git a/web/pages/actors/index.js b/web/pages/actors/index.js
index bf0ebdd..3feea2c 100644
--- a/web/pages/actors/index.js
+++ b/web/pages/actors/index.js
@@ -3,7 +3,7 @@ import { getAllActors } from '../../lib/api'
 import { Grid, Avatar, Box, Heading, Flex, Badge, Container } from '@chakra-ui/react'
 import Layout from '../../components/Layout'
 import Link from '../../components/Link'
-import PortableTextBlock from '../../components/PortableTextBlock'
+import PortableTextBlock from '../../components/PT/PortableTextBlock'
 
 export default function Actors({ data, preview }) {
   /* let actors = data.items.reduce((r, e) => {
diff --git a/web/pages/nansen-i-biblioteket/index.js b/web/pages/nansen-i-biblioteket/index.js
index f371ac2..edd1d59 100644
--- a/web/pages/nansen-i-biblioteket/index.js
+++ b/web/pages/nansen-i-biblioteket/index.js
@@ -1,7 +1,7 @@
 import { getPhysicalExhibitionCopy } from '../../lib/api'
 import Head from 'next/head'
 import { Container, Heading } from '@chakra-ui/react'
-import PortableTextBlock from '../../components/PortableTextBlock'
+import PortableTextBlock from '../../components/PT/PortableTextBlock'
 import Layout from '../../components/Layout'
 
 export default function PhysicalExhibition({ data, preview }) {
-- 
GitLab