import S3 from "aws-sdk/clients/s3" ; import { NextPage } from "next" ; import Head from "next/head" ; import { useCallback , useRef , useState } from "react" ; import Edit from "../components/edit" ; import { ErrorDialog } from "../components/error" ; import { ShareLinkDialog } from "../components/home/ShareLinkDialog" ; import Malleable , { FieldEdit } from "../components/malleable" ; import Snapshot from "../components/snapshot" ; import { useScrollReset } from "../hooks/use-scroll-reset" ; import layoutStyles from "../styles/layout.module.css" ; const s3 = new S3 ( { credentials : { accessKeyId : process . env . AWS_S3_ACCESS_KEY_ID , secretAccessKey : process . env . AWS_S3_SECRET_ACCESS_KEY , } , } ) ; export async function getStaticProps ( { preview , previewData , } ) { if ( preview ) { const { snapshotId } = previewData as { snapshotId : string ; } ; try { const object = await s3 . getObject ( { Bucket : process . env . AWS_S3_BUCKET , Key : `${ snapshotId }.json` , } ) . promise ( ) ; const contents = JSON . parse ( object . Body . toString ( ) ) ; return { props : { isPreview : true , snapshotId , contents } , } ; } catch ( e ) { return { props : { isPreview : false , hasError : true , message : e . statusCode === 403 ? "The requested preview edit does not exist!" : "An error has occurred while connecting to S3. Please refresh the page to try again." , } , } ; } } return { props : { isPreview : false } } ; } const Home = ( props ) => { useScrollReset ( ) ; const [ currentSnapshotId , setSnapshotId ] = useState ( null ) ; const clearSnapshot = useCallback ( ( ) => setSnapshotId ( null ) , [ setSnapshotId ] ) ; const [ isEdit , setEdit ] = useState ( false ) ; const toggleEdit = useCallback ( ( ) => setEdit ( ! isEdit ) , [ isEdit ] ) ; const hasSaveRequest = useRef ( false ) ; const [ isSharingView , _setSharing ] = useState ( false ) ; const setSharing = useCallback ( ( sharing ) => { hasSaveRequest . current = sharing ; _setSharing ( sharing ) ; } , [ hasSaveRequest , _setSharing ] ) ; const [ currentError , setError ] = useState < Error > ( null ) ; const onClearError = useCallback ( ( ) => { setError ( null ) ; } , [ setError ] ) ; const share = useCallback ( ( ) => { if ( hasSaveRequest . current ) return ; setSharing ( true ) ; const els = document . querySelectorAll ( "[id] > [contenteditable=true]" ) ; const persistContents = [ ] . slice . call ( els ) . map ( ( { parentNode : { id } , innerText } ) => ( { id , innerText } ) ) ; self . fetch ( `/api/save` , { method : "POST" , body : JSON . stringify ( persistContents ) , headers : { "content-type" : "application/json" } , } ) . then ( ( res ) => { if ( res . ok ) return res . json ( ) ; return new Promise ( async ( _ , reject ) => reject ( new Error ( await res . text ( ) ) ) ) ; } ) . then ( ( { snapshotId } ) => { setSnapshotId ( snapshotId ) ; } ) . catch ( ( err ) => { setError ( err ) ; } ) . finally ( ( ) => { setSharing ( false ) ; } ) ; } , [ ] ) ; const edits = props . isPreview ? props . contents : [ ] ; return ( < > < Head > < title > Next . js | Preview Mode < /title> < meta name = "description" content = "This website demonstrates a static website generated using Next.js' new Static Site Generation (SSG)." > < /meta> < /Head> { currentError && ( < ErrorDialog onExit = { onClearError } > < p > An error occurred while saving your snapshot . Please try again in a bit . < /p> < pre > { currentError . message } < /pre> < /ErrorDialog>)} { currentSnapshotId && ( < ShareLinkDialog snapshotId = { currentSnapshotId } onExit = { clearSnapshot } / > ) } < div className = { layoutStyles . layout } > { ( props . isPreview || props . hasError ) && ( < aside role = "alert" > < a href = "/api/exit" > Preview Mode < /a> < /aside>)} { props . hasError ? ( < > < h1 > Oops < /h1> < h2 > Something unique to your preview went wrong . < /h2> < div className = "explanation" style = { { textAlign : "center" } } > < p > The production website is < strong > still available < /strong> and this does not affect other users . < /p> < /div> < hr / > < h2 > Reason < /h2> < div className = "explanation" style = { { textAlign : "center" } } > < p > { props . message } < /p> < /div> < />) : (<Content isEdit={isEdit} edits={edits}/ > ) } < /div> { isEdit ? ( < > < Snapshot onCancel = { toggleEdit } onShare = { share } isSharing = { isSharingView } / > < />) : (<Edit onClick={toggleEdit}/ > ) } < />); } ; function Content ( { isEdit , edits } ) { return ( < > < Malleable id = "title" as = "h1" isActive = { isEdit } edits = { edits } > My Static Site < /Malleable> < div className = "features" > < div className = "feature" > < Malleable id = "feature-1-emoji" as = "div" isActive = { isEdit } edits = { edits } > ⚡ < /Malleable> < Malleable id = "feature-1-text" as = "h4" isActive = { isEdit } edits = { edits } > Blazing fast < /Malleable> < /div> < div className = "feature" > < Malleable id = "feature-2-emoji" as = "div" isActive = { isEdit } edits = { edits } > 📡 < /Malleable> < Malleable id = "feature-2-text" as = "h4" isActive = { isEdit } edits = { edits } > Always available < /Malleable> < /div> < div className = "feature" > < Malleable id = "feature-3-emoji" as = "div" isActive = { isEdit } edits = { edits } > 🏎 < /Malleable> < Malleable id = "feature-3-text" as = "h4" isActive = { isEdit } edits = { edits } > Lighthouse 100 < /Malleable> < /div> < /div> < Malleable as = "h2" id = "title-2" isActive = { isEdit } edits = { edits } > This demonstrates a static website generated using { " " } < a target = "_blank" rel = "noopener" href = "https://nextjs.org" > Next . js ' < /a>{" "} new { " " } < a target = "_blank" rel = "noopener" href = "https://nextjs.org/docs/basic-features/pages" > Static Site Generation ( SSG ) < /a> . View the source on { " " } < a target = "_blank" rel = "noopener" href = "https://github.com/vercel/preview-mode-demo" > GitHub < /a> . < /Malleable> < div className = "explanation" > < div className = "p" > < Malleable id = "explanation-1-inspect" as = "span" isActive = { isEdit } edits = { edits } > To inspect the response from { " " } < a target = "_blank" rel = "noopener" href = "https://vercel.com/cdn" > our edge network < /a> , run : < /Malleable> < br / > < Malleable id = "explanation-1-pre-curl" as = "pre" isActive = { isEdit } edits = { edits } > curl - sI https : < /Malleable> < Malleable id = "explanation-1-pre-response" as = "pre" className = "light" isActive = { isEdit } edits = { edits } > x - vercel - cache : HIT < br / > x - vercel - id : cle1 : : zzq7g - 1604525989923 - a33b3946ccee < /Malleable> < /div> < Malleable id = "explanation-2" isActive = { isEdit } edits = { edits } > When people visit this site , the response always comes instantly from their { " " } < a target = "_blank" rel = "noopener" href = "https://vercel.com/cdn" > nearest location < /a> . < /Malleable> < Malleable id = "explanation-3" isActive = { isEdit } edits = { edits } > Unlike traditional static solutions , however , you can generate previews of edits that you can share with anyone you choose . To try it out , click the edit icon on the bottom right and edit the content . When you 're done, click the share icon on the bottom right to generate a shareable preview URL . < /Malleable> < Malleable id = "explanation-4" isActive = { isEdit } edits = { edits } > SSG and Preview Mode make Next . js the most optimal framework to integrate into your Headless CMS workflow . Learn more about the preview mode on { " " } < a target = "_blank" rel = "noopener" href = "https://nextjs.org/docs/advanced-features/preview-mode" > our documentation . < /a> < /Malleable> < /div> < />); } export default Home ;