Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions spago.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@ to generate this file without the comments in this block.
[ "aff"
, "aff-promise"
, "console"
, "datetime"
, "effect"
, "either"
, "foldable-traversable"
, "foreign"
, "foreign-object"
, "functions"
, "identity"
, "maybe"
, "nullable"
, "options"
, "partial"
, "prelude"
, "react-basic"
, "react-basic-hooks"
, "transformers"
, "typelevel-prelude"
, "unsafe-coerce"
, "yoga-json"
]
, packages = ./packages.dhall
, sources = [ "src/**/*.purs", "test/**/*.purs" ]
Expand Down
5 changes: 5 additions & 0 deletions src/Next/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ export var _html = document.Html;
export var _head = document.Head;
export var _main = document.Main;
export var _nextScript = document.NextScript;

export const unsafeDocument = component => () => {
component.getInitialProps = nextDoc.getInitialProps
return component
}
7 changes: 7 additions & 0 deletions src/Next/Document.purs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Next.Document where

import Effect (Effect)
import Prim.Row (class Union)
import React.Basic.Hooks (JSX, ReactComponent, element)

Expand Down Expand Up @@ -59,3 +60,9 @@ nextScript
nextScript attrs = element _nextScript attrs

foreign import _nextScript :: forall attrs. ReactComponent attrs

-- | Document
foreign import unsafeDocument
∷ ∀ props
. ReactComponent props
→ Effect (ReactComponent props)
81 changes: 81 additions & 0 deletions src/Next/GetServerSideProps.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Next.GetServerSideProps where

import Prelude hiding (top)

import Control.Promise (Promise)
import Control.Promise as Promise
import Data.Either (Either(..))
import Data.Maybe (Maybe)
import Data.Semigroup.Foldable (intercalateMap)
import Effect.Aff (Aff)
import Effect.Uncurried (EffectFn1, mkEffectFn1)
import Foreign (Foreign, renderForeignError)
import Foreign.Object (Object)
import Next.SerialisedProps (SerialisedProps)
import Partial.Unsafe (unsafeCrashWith)
import Prim.Row (class Union)
import Unsafe.Coerce (unsafeCoerce)
import Yoga.JSON (class ReadForeign, class WriteForeign, read, write, writeJSON)

type ServerSidePropsContext params =
{ params ∷ Maybe { | params }
, req ∷ Foreign -- IncomingMessage
, res ∷ Foreign -- HTTP response object
, query ∷ Object String
, preview ∷ Maybe Boolean
, resolvedUrl ∷ String
, locale ∷ Maybe Locale
, locales ∷ Maybe (Array Locale)
, defaultLocale ∷ Maybe Locale
}

-- [TODO]: Move to a separate file
newtype Locale = Locale String

derive newtype instance ReadForeign Locale
derive newtype instance WriteForeign Locale

type ServerSideProps props =
( props ∷ props
, redirect ∷ { destination ∷ String, permanent ∷ Boolean }
, notFound ∷ Boolean
)

foreign import data GetServerSideProps ∷ Type → Type → Type

toGetServerSideProps
∷ ∀ partialProps componentProps params
. (EffectFn1 Foreign (Promise partialProps))
→ GetServerSideProps params componentProps
toGetServerSideProps = unsafeCoerce

decodeContextOrCrash ∷ ∀ a. ReadForeign a ⇒ Foreign → a
decodeContextOrCrash fgn =
case read fgn of
Right value → value
Left errors →
unsafeCrashWith
$ "Invalid context received in getServerSideProps:\n"
<> intercalateMap "\n" renderForeignError errors

mkGetServerSideProps
∷ ∀ props p p_ params
. Union p p_ (ServerSideProps props)
⇒ ReadForeign { | params }
⇒ ReadForeign { | p }
⇒ WriteForeign props
⇒ WriteForeign { props ∷ { serialisedProps ∷ String } | p }
⇒ (ServerSidePropsContext params → Aff { props ∷ props | p })
→ GetServerSideProps { | params } (SerialisedProps props)
mkGetServerSideProps constructProps =
toGetServerSideProps
$ mkEffectFn1
( Promise.fromAff
<<< (map write)
<<<
( map \x →
(x { props = { serialisedProps: writeJSON x.props } ∷ SerialisedProps props })
)
<<< constructProps
<<< decodeContextOrCrash
)
78 changes: 78 additions & 0 deletions src/Next/GetStaticProps.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module Next.GetStaticProps where

import Prelude hiding (top)

import Control.Promise (Promise)
import Control.Promise as Promise
import Data.Either (Either(..))
import Data.Maybe (Maybe)
import Data.Semigroup.Foldable (intercalateMap)
import Data.Time.Duration (Seconds)
import Effect.Aff (Aff)
import Effect.Uncurried (EffectFn1, mkEffectFn1)
import Foreign (Foreign, renderForeignError)
import Next.SerialisedProps (SerialisedProps)
import Partial.Unsafe (unsafeCrashWith)
import Prim.Row (class Union)
import Unsafe.Coerce (unsafeCoerce)
import Yoga.JSON (class ReadForeign, class WriteForeign, read, write, writeJSON)

type StaticPropsContext params props =
{ params ∷ Maybe { | params }
, preview ∷ Maybe Boolean
, previewData ∷ Maybe { | props }
, locale ∷ Maybe Locale
, locales ∷ Maybe (Array Locale)
, defaultLocale ∷ Maybe Locale
}

-- [TODO]: Move to a separate file
newtype Locale = Locale String

derive newtype instance ReadForeign Locale
derive newtype instance WriteForeign Locale

type StaticProps props =
( props ∷ props
, revalidate ∷ Seconds
, notFound ∷ Boolean
)

foreign import data GetStaticProps ∷ Type → Type → Type

toGetStaticProps
∷ ∀ partialProps componentProps params
. (EffectFn1 Foreign (Promise partialProps))
→ GetStaticProps params componentProps
toGetStaticProps = unsafeCoerce

decodeContextOrCrash ∷ ∀ a. ReadForeign a ⇒ Foreign → a
decodeContextOrCrash fgn =
case read fgn of
Right value → value
Left errors →
unsafeCrashWith
$ "Invalid context received in getStaticProps:\n"
<> intercalateMap "\n" renderForeignError errors

mkGetStaticProps
∷ ∀ props p p_ params
. Union p p_ (StaticProps props)
⇒ ReadForeign { | params }
⇒ ReadForeign { | p }
⇒ WriteForeign props
⇒ WriteForeign { props ∷ { serialisedProps ∷ String } | p }
⇒ (StaticPropsContext params p → Aff { props ∷ props | p })
→ GetStaticProps { | params } (SerialisedProps props)
mkGetStaticProps constructProps =
toGetStaticProps
$ mkEffectFn1
( Promise.fromAff
<<< (map write)
<<<
( map \x →
(x { props = { serialisedProps: writeJSON x.props } ∷ SerialisedProps props })
)
<<< constructProps
<<< decodeContextOrCrash
)
3 changes: 3 additions & 0 deletions src/Next/Image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import image from "next/image.js"

export const imageImpl = image;
26 changes: 26 additions & 0 deletions src/Next/Image.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Next.Image where

import Prim.Row (class Union)
import React.Basic.Hooks (ReactComponent)

foreign import data StaticallyImportedImage ∷ Type

-- | Components
type Props_static_image =
( src ∷ StaticallyImportedImage
, alt ∷ String
, layout ∷ String
, objectFit ∷ String
, objectPosition ∷ String
, quality ∷ Int
, placeholder ∷ String
, className ∷ String
)

staticImage
∷ ∀ attrs attrs_
. Union attrs attrs_ Props_static_image
⇒ ReactComponent { src ∷ StaticallyImportedImage | attrs }
staticImage = imageImpl

foreign import imageImpl ∷ ∀ attrs. ReactComponent attrs
105 changes: 95 additions & 10 deletions src/Next/Router.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,112 @@
export { useRouter as useRouter_ } from "next/router";
import router from "next/router";
import nextRouter, { useRouter } from "next/router.js";

export function _on(event) {
export const onImpl = function (event) {
return function (cb) {
return function () {
router.events.on(event, cb);
nextRouter.events.on(event, cb);
};
};
}
};

export function _off(event) {
export const offImpl = function (event) {
return function (cb) {
return function () {
router.events.off(event, cb);
nextRouter.events.off(event, cb);
};
};
};

export const useRouterImpl = () => useRouter();

export const queryImpl = (nr) => nr.query;

export function pathnameImpl(nr) {
return () => nr.pathname;
}

export const asPathImpl = (nr) => nr.asPath;

export const pushImpl = (path) => (as) => (options) => (routerInstance) => () =>
routerInstance.push(path, as, options);

export function pushImplNoAs(path) {
return (options) => (routerInstance) => () => {
routerInstance.push(path, undefined, options);
};
}

export const query = (router) => router.query;
export const historyPushState = (href) => (as) => () => {
window.history.pushState(
_objectSpread(
_objectSpread({}, window.history.state),
{},
{
as: as,
url: href,
}
),
"",
href
);
};

// Babel stuff
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}

export const push = (router) => (route) => () => router.push(route);
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key)
);
});
}
}
return target;
}

function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}

export const route = (router) => router.route;
export const mkOnRouteChangeStartHandler = (fn) => () => (url, opts) =>
fn(url)(opts)();
export const addOnRouteChangeStartHandler = (handler) => (router) => () => {
router.events.on("routeChangeStart", handler);
return () => router.events.off("routeChangeStart", handler);
};

export const replaceImpl = (router) => (route) => (as) => (options) => () =>
router.replace(route, as, options);
Loading