Skip to content

Commit

Permalink
Merge branch 'main' into dt-realtime-defer-stream
Browse files Browse the repository at this point in the history
  • Loading branch information
dthyresson authored Oct 20, 2023
2 parents 471cb34 + daaa199 commit 8bb557b
Show file tree
Hide file tree
Showing 19 changed files with 779 additions and 346 deletions.
4 changes: 2 additions & 2 deletions packages/eslint-plugin/src/unsupported-route-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ESLintUtils } from '@typescript-eslint/utils'
const createRule = ESLintUtils.RuleCreator.withoutDocs

function isAllowedElement(name: string) {
const allowedElements = ['Router', 'Route', 'Set', 'Private']
const allowedElements = ['Router', 'Route', 'Set', 'PrivateSet', 'Private']
return allowedElements.includes(name)
}

Expand All @@ -16,7 +16,7 @@ export const unsupportedRouteComponents = createRule({
},
messages: {
unexpected:
'Unexpected JSX element <{{name}}>. Only <Router>, <Route>, <Set> and <Private> are allowed in the Routes file.',
'Unexpected JSX element <{{name}}>. Only <Router>, <Route>, <Set>, <PrivateSet> and <Private> are allowed in the Routes file.',
},
schema: [], // No additional configuration needed
},
Expand Down
20 changes: 16 additions & 4 deletions packages/fastify/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fg from 'fast-glob'
import type {
FastifyInstance,
FastifyReply,
FastifyRequest,
HookHandlerDoneFunction,
} from 'fastify'

Expand Down Expand Up @@ -47,10 +48,21 @@ export async function redwoodFastifyWeb(
const indexPath = getFallbackIndexPath()

// For SPA routing, fallback on unmatched routes and let client-side routing take over.
fastify.setNotFoundHandler({}, function (_, reply: FastifyReply) {
reply.header('Content-Type', 'text/html; charset=UTF-8')
reply.sendFile(indexPath)
})
fastify.setNotFoundHandler(
{},
function (req: FastifyRequest, reply: FastifyReply) {
const requestedExtension = path.extname(req.url)
// If it's requesting some sort of asset, e.g. .js or .jpg files
// Html files should fallback to the index.html
if (requestedExtension !== '' && requestedExtension !== '.html') {
reply.code(404)
return reply.send('Not Found')
}

reply.header('Content-Type', 'text/html; charset=UTF-8')
return reply.sendFile(indexPath)
}
)

done()
}
Expand Down
29 changes: 9 additions & 20 deletions packages/router/src/AuthenticatedRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,16 @@ import type { GeneratedRoutesMap } from './util'
interface AuthenticatedRouteProps {
children: React.ReactNode
roles?: string | string[]
unauthenticated?: keyof GeneratedRoutesMap
unauthenticated: keyof GeneratedRoutesMap
whileLoadingAuth?: () => React.ReactElement | null
private?: boolean
}
export const AuthenticatedRoute: React.FC<AuthenticatedRouteProps> = (
props
) => {
const {
private: isPrivate,
unauthenticated,
roles,
whileLoadingAuth,
children,
} = props

export const AuthenticatedRoute: React.FC<AuthenticatedRouteProps> = ({
unauthenticated,
roles,
whileLoadingAuth,
children,
}) => {
const routerState = useRouterState()
const {
loading: authLoading,
Expand All @@ -34,14 +30,7 @@ export const AuthenticatedRoute: React.FC<AuthenticatedRouteProps> = (
}, [isAuthenticated, roles, hasRole])

// Make sure `wrappers` is always an array with at least one wrapper component
if (isPrivate && unauthorized()) {
if (!unauthenticated) {
throw new Error(
'Private Sets need to specify what route to redirect unauthorized ' +
'users to by setting the `unauthenticated` prop'
)
}

if (unauthorized()) {
if (authLoading) {
return whileLoadingAuth?.() || null
} else {
Expand Down
57 changes: 40 additions & 17 deletions packages/router/src/Set.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,44 @@ import type { ReactElement, ReactNode } from 'react'
import React from 'react'

export type WrapperType<WTProps> = (
props: WTProps & { children: ReactNode }
props: Omit<WTProps, 'wrap' | 'children'> & {
children: ReactNode
}
) => ReactElement | null

type SetProps<P> = P & {
// P is the interface for the props that are forwarded to the wrapper
// components. TypeScript will most likely infer this for you, but if you
// need to you can specify it yourself in your JSX like so:
// <Set<{theme: string}> wrap={ThemeableLayout} theme="dark">
/**
* P is the interface for the props that are forwarded to the wrapper
* components. TypeScript will most likely infer this for you, but if you
* need to you can specify it yourself in your JSX like so:
* <Set<{theme: string}> wrap={ThemableLayout} theme="dark">
*/
wrap?: WrapperType<P> | WrapperType<P>[]
/**
* `Routes` nested in a `<Set>` with `private` specified require
*`Routes` nested in a `<Set>` with `private` specified require
* authentication. When a user is not authenticated and attempts to visit
* the wrapped route they will be redirected to `unauthenticated` route.
*
* @deprecated Please use `<PrivateSet>` instead
*/
private?: boolean
/** The page name where a user will be redirected when not authenticated */
/**
* The page name where a user will be redirected when not authenticated
*
* @deprecated Please use `<PrivateSet>` instead and specify this prop there
*/
unauthenticated?: string
/**
* Route is permitted when authenticated and use has any of the provided
* Route is permitted when authenticated and user has any of the provided
* roles such as "admin" or ["admin", "editor"]
*/
roles?: string | string[]
/** Prerender all pages in the set */
prerender?: boolean
children: ReactNode
/** Loading state for auth to distinguish with whileLoading */
whileLoadingAuth?: () => React.ReactElement | null
whileLoadingPage?: () => React.ReactElement | null
whileLoadingAuth?: () => ReactElement | null
whileLoadingPage?: () => ReactElement | null
}

/**
Expand All @@ -38,14 +48,13 @@ type SetProps<P> = P & {
* JSX like so:
* <Set<{theme: string}> wrap={ThemeableLayout} theme="dark">
*/
export function Set<WrapperProps>(_props: SetProps<WrapperProps>) {
export function Set<WrapperProps>(props: SetProps<WrapperProps>) {
// @MARK: Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block

return null
return <>{props.children}</>
}

type PrivateProps<P> = Omit<
type PrivateSetProps<P> = Omit<
SetProps<P>,
'private' | 'unauthenticated' | 'wrap'
> & {
Expand All @@ -54,20 +63,34 @@ type PrivateProps<P> = Omit<
wrap?: WrapperType<P> | WrapperType<P>[]
}

export function Private<WrapperProps>(_props: PrivateProps<WrapperProps>) {
/** @deprecated Please use `<PrivateSet>` instead */
export function Private<WrapperProps>(props: PrivateSetProps<WrapperProps>) {
// @MARK Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block
return null
return <>{props.children}</>
}

export function PrivateSet<WrapperProps>(props: PrivateSetProps<WrapperProps>) {
// @MARK Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block
return <>{props.children}</>
}

export const isSetNode = (
node: ReactNode
): node is ReactElement<SetProps<any>> => {
return (
React.isValidElement(node) && (node.type === Set || node.type === Private)
React.isValidElement(node) &&
(node.type === Set || node.type === PrivateSet || node.type === Private)
)
}

export const isPrivateSetNode = (
node: ReactNode
): node is ReactElement<PrivateSetProps<unknown>> => {
return React.isValidElement(node) && node.type === PrivateSet
}

// Only identifies <Private> nodes, not <Set private> nodes
export const isPrivateNode = (
node: ReactNode
Expand Down
Loading

0 comments on commit 8bb557b

Please sign in to comment.