update cloudflare context

This commit is contained in:
2025-12-17 23:52:08 -03:00
parent d683feb85f
commit be1ae656d3
25 changed files with 222 additions and 94 deletions

View File

@@ -1,7 +1,7 @@
import type { Route } from './+types/api' import type { Route } from './+types/api'
import type { User } from '@repo/auth/auth' import type { User } from '@repo/auth/auth'
import { userContext } from '@repo/auth/context' import { userContext, cloudflareContext } from '@repo/auth/context'
export const loader = proxy export const loader = proxy
export const action = proxy export const action = proxy
@@ -11,8 +11,12 @@ async function proxy({
context context
}: Route.ActionArgs): Promise<Response> { }: Route.ActionArgs): Promise<Response> {
const pathname = new URL(request.url).pathname.replace(/^\/api\//, '') const pathname = new URL(request.url).pathname.replace(/^\/api\//, '')
const cloudflare = context.get(cloudflareContext) as {
env: Env
ctx: ExecutionContext
}
const user = context.get(userContext) as User const user = context.get(userContext) as User
const url = new URL(pathname, context.cloudflare.env.API_URL) const url = new URL(pathname, cloudflare.env.API_URL)
const headers = new Headers(request.headers) const headers = new Headers(request.headers)
headers.set('Authorization', `Bearer ${user.accessToken}`) headers.set('Authorization', `Bearer ${user.accessToken}`)

View File

@@ -3,30 +3,29 @@ import type { Route } from './+types/login'
import { redirect } from 'react-router' import { redirect } from 'react-router'
import { createAuth, type User } from '@repo/auth/auth' import { createAuth, type User } from '@repo/auth/auth'
import { requestIdContext } from '@repo/auth/context' import { requestIdContext, cloudflareContext } from '@repo/auth/context'
import { createSessionStorage } from '@repo/auth/session' import { createSessionStorage } from '@repo/auth/session'
export async function loader({ request, context }: Route.ActionArgs) { export async function loader({ request, context }: Route.LoaderArgs) {
const url = new URL(request.url) const url = new URL(request.url)
const sessionStorage = createSessionStorage(context.cloudflare.env) const cloudflare = context.get(cloudflareContext)
const sessionStorage = createSessionStorage(cloudflare.env)
const session = await sessionStorage.getSession(request.headers.get('cookie')) const session = await sessionStorage.getSession(request.headers.get('cookie'))
const requestId = context.get(requestIdContext) const requestId = context.get(requestIdContext)
const user = session.get('user') as User | null const user = session.get('user')
const returnTo = ( const returnTo = (session.get('returnTo') as string | undefined) ?? '/'
session.has('returnTo') ? session.get('returnTo') : '/'
) as string
if (user) { if (user) {
return redirect(returnTo) return redirect(returnTo)
} }
try { try {
const authenticator = createAuth( const authenticator = createAuth(cloudflare.env, `${url.origin}/login`)
context.cloudflare.env, const authenticatedUser = (await authenticator.authenticate(
`${url?.origin}/login` 'oidc',
) request
const user = await authenticator.authenticate('oidc', request) )) as User
session.set('user', user) session.set('user', authenticatedUser)
console.log(`[${requestId}] Redirecting the user to ${returnTo}`) console.log(`[${requestId}] Redirecting the user to ${returnTo}`)

View File

@@ -5,12 +5,14 @@ import type { OAuth2Strategy } from 'remix-auth-oauth2'
import { createAuth, type User } from '@repo/auth/auth' import { createAuth, type User } from '@repo/auth/auth'
import { createSessionStorage } from '@repo/auth/session' import { createSessionStorage } from '@repo/auth/session'
import { cloudflareContext } from '@repo/auth/context'
export async function loader({ request, context }: Route.LoaderArgs) { export async function loader({ request, context }: Route.LoaderArgs) {
const authenticator = createAuth(context.cloudflare.env) const cloudflare = context.get(cloudflareContext)
const sessionStorage = createSessionStorage(context.cloudflare.env) const authenticator = createAuth(cloudflare.env)
const sessionStorage = createSessionStorage(cloudflare.env)
const session = await sessionStorage.getSession(request.headers.get('cookie')) const session = await sessionStorage.getSession(request.headers.get('cookie'))
const user = session.get('user') as User const user = session.get('user')
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc') const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
if (user?.accessToken && strategy) { if (user?.accessToken && strategy) {

View File

@@ -5,7 +5,7 @@ import { Await, Link } from 'react-router'
import { Suspense } from 'react' import { Suspense } from 'react'
import { type User } from '@repo/auth/auth' import { type User } from '@repo/auth/auth'
import { userContext } from '@repo/auth/context' import { userContext, cloudflareContext } from '@repo/auth/context'
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
@@ -26,6 +26,7 @@ export function meta({}: Route.MetaArgs) {
} }
export async function loader({ request, context }: Route.ActionArgs) { export async function loader({ request, context }: Route.ActionArgs) {
const cloudflare = context.get(cloudflareContext)
const user = context.get(userContext) as User const user = context.get(userContext) as User
const { searchParams } = new URL(request.url) const { searchParams } = new URL(request.url)
const status = searchParams.getAll('status') || [] const status = searchParams.getAll('status') || []
@@ -45,15 +46,17 @@ export async function loader({ request, context }: Route.ActionArgs) {
sort: [sort], sort: [sort],
page, page,
hitsPerPage, hitsPerPage,
env: context.cloudflare.env env: cloudflare.env
}) })
return { return {
data: payments payments
} }
} }
export default function Component({ loaderData: { data } }) { export default function Component({
loaderData: { payments }
}: Route.ComponentProps) {
return ( return (
<Container className="space-y-2.5"> <Container className="space-y-2.5">
<Suspense fallback={<Skeleton />}> <Suspense fallback={<Skeleton />}>
@@ -81,15 +84,17 @@ export default function Component({ loaderData: { data } }) {
</p> </p>
</div> </div>
<Await resolve={data}> <Await resolve={payments}>
{({ hits, page, hitsPerPage, totalHits }) => { {({ hits, page = 1, hitsPerPage, totalHits }) => {
return ( return (
<DataTable <DataTable
sort={[{ id: 'create_date', desc: true }]} sort={[{ id: 'create_date', desc: true }]}
columns={columns} columns={columns}
data={hits as Order[]} data={hits as Order[]}
pageIndex={page - 1} pageIndex={page - 1}
// @ts-ignore
pageSize={hitsPerPage} pageSize={hitsPerPage}
// @ts-ignore
rowCount={totalHits} rowCount={totalHits}
/> />
) )

View File

@@ -15,7 +15,7 @@ import { Suspense, useMemo, type ComponentProps } from 'react'
import { Await, NavLink, useSearchParams } from 'react-router' import { Await, NavLink, useSearchParams } from 'react-router'
import type { User } from '@repo/auth/auth' import type { User } from '@repo/auth/auth'
import { userContext } from '@repo/auth/context' import { userContext, cloudflareContext } from '@repo/auth/context'
import { FacetedFilter } from '@repo/ui/components/faceted-filter' import { FacetedFilter } from '@repo/ui/components/faceted-filter'
import { SearchForm } from '@repo/ui/components/search-form' import { SearchForm } from '@repo/ui/components/search-form'
import { Skeleton } from '@repo/ui/components/skeleton' import { Skeleton } from '@repo/ui/components/skeleton'
@@ -60,6 +60,7 @@ export function meta({}: Route.MetaArgs) {
} }
export async function loader({ request, context }: Route.ActionArgs) { export async function loader({ request, context }: Route.ActionArgs) {
const cloudflare = context.get(cloudflareContext)
const user = context.get(userContext) as User const user = context.get(userContext) as User
const { searchParams } = new URL(request.url) const { searchParams } = new URL(request.url)
const status = searchParams.getAll('status') || [] const status = searchParams.getAll('status') || []
@@ -74,7 +75,7 @@ export async function loader({ request, context }: Route.ActionArgs) {
filter: builder.build(), filter: builder.build(),
sort: ['created_at:desc'], sort: ['created_at:desc'],
hitsPerPage: 100, hitsPerPage: 100,
env: context.cloudflare.env env: cloudflare.env
}) })
return { return {

View File

@@ -5,11 +5,14 @@ import { redirect } from 'react-router'
import { userContext } from '@repo/auth/context' import { userContext } from '@repo/auth/context'
import type { User } from '@repo/auth/auth' import type { User } from '@repo/auth/auth'
import { cloudflareContext } from 'workers/app'
const konvivaApi = 'https://lms.saladeaula.digital' const konvivaApi = 'https://lms.saladeaula.digital'
export async function loader({ context }: Route.LoaderArgs) { export async function loader({ context }: Route.LoaderArgs) {
const cloudflare = context.get(cloudflareContext)
const user = context.get(userContext) as User const user = context.get(userContext) as User
const secretKey = context.cloudflare.env.KONVIVA_SECRET_KEY const secretKey = cloudflare.env.KONVIVA_SECRET_KEY
const token = await getToken(user.email, secretKey) const token = await getToken(user.email, secretKey)
const url = new URL('/action/acessoExterno', konvivaApi) const url = new URL('/action/acessoExterno', konvivaApi)

View File

@@ -2,11 +2,13 @@ import type { Route } from './+types/layout'
import { useToggle } from 'ahooks' import { useToggle } from 'ahooks'
import { MenuIcon } from 'lucide-react' import { MenuIcon } from 'lucide-react'
import { Link, NavLink, Outlet } from 'react-router' import { data, Link, NavLink, Outlet } from 'react-router'
import { toast } from 'sonner' import { toast } from 'sonner'
import { useEffect } from 'react'
import type { User } from '@repo/auth/auth'
import { Toaster } from '@repo/ui/components/ui/sonner' import { Toaster } from '@repo/ui/components/ui/sonner'
import { userContext } from '@repo/auth/context' import { userContext, cloudflareContext } from '@repo/auth/context'
import { authMiddleware } from '@repo/auth/middleware/auth' import { authMiddleware } from '@repo/auth/middleware/auth'
import { ModeToggle, ThemedImage } from '@repo/ui/components/dark-mode' import { ModeToggle, ThemedImage } from '@repo/ui/components/dark-mode'
import { NavUser } from '@repo/ui/components/nav-user' import { NavUser } from '@repo/ui/components/nav-user'
@@ -23,16 +25,14 @@ import {
SheetTitle, SheetTitle,
SheetTrigger SheetTrigger
} from '@repo/ui/components/ui/sheet' } from '@repo/ui/components/ui/sheet'
import type { User } from '@repo/auth/auth'
import { createSessionStorage } from '@repo/auth/session' import { createSessionStorage } from '@repo/auth/session'
import { data } from 'react-router'
import { useEffect } from 'react'
export const middleware: Route.MiddlewareFunction[] = [authMiddleware] export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
export async function loader({ context, request }: Route.ActionArgs) { export async function loader({ context, request }: Route.ActionArgs) {
const cloudflare = context.get(cloudflareContext)
const user = context.get(userContext) as User const user = context.get(userContext) as User
const sessionStorage = createSessionStorage(context.cloudflare.env) const sessionStorage = createSessionStorage(cloudflare.env)
const session = await sessionStorage.getSession(request.headers.get('cookie')) const session = await sessionStorage.getSession(request.headers.get('cookie'))
const flash = { const flash = {

View File

@@ -1,5 +1,7 @@
import type { Route } from './+types/proxy' import type { Route } from './+types/proxy'
import { cloudflareContext } from '@repo/auth/context'
// Mapping of extensions to MIME types // Mapping of extensions to MIME types
const mimeTypes: Record<string, string> = { const mimeTypes: Record<string, string> = {
// Images // Images
@@ -25,8 +27,9 @@ const mimeTypes: Record<string, string> = {
} }
export async function loader({ params, context }: Route.LoaderArgs) { export async function loader({ params, context }: Route.LoaderArgs) {
const bucketEndpoint = context.cloudflare.env.BUCKET_ENDPOINT const cloudflare = context.get(cloudflareContext)
const bucketName = context.cloudflare.env.BUCKET_NAME const bucketEndpoint = cloudflare.env.BUCKET_ENDPOINT
const bucketName = cloudflare.env.BUCKET_NAME
const param = params['*'] ?? '' const param = params['*'] ?? ''
const url = new URL(`${bucketName}/scorm/${param}`, bucketEndpoint) const url = new URL(`${bucketName}/scorm/${param}`, bucketEndpoint)
const upstream = await fetch(url.toString()) const upstream = await fetch(url.toString())

View File

@@ -2,15 +2,16 @@ import type { Route } from './+types/verify'
import { redirect } from 'react-router' import { redirect } from 'react-router'
import { userContext } from '@repo/auth/context' import { userContext, cloudflareContext } from '@repo/auth/context'
import { createSessionStorage } from '@repo/auth/session' import { createSessionStorage } from '@repo/auth/session'
import { HttpMethod, request as req } from '@repo/util/request' import { HttpMethod, request as req } from '@repo/util/request'
export async function loader({ params, request, context }: Route.LoaderArgs) { export async function loader({ params, request, context }: Route.LoaderArgs) {
const { code } = params const { code } = params
const sessionStorage = createSessionStorage(context.cloudflare.env) const cloudflare = context.get(cloudflareContext)
const sessionStorage = createSessionStorage(cloudflare.env)
const session = await sessionStorage.getSession(request.headers.get('cookie')) const session = await sessionStorage.getSession(request.headers.get('cookie'))
const user = context.get(userContext) const user = context.get(userContext)!
const r = await req({ const r = await req({
url: `/users/${user.sub}/emails/${code}/verify`, url: `/users/${user.sub}/emails/${code}/verify`,

View File

@@ -39,7 +39,7 @@ import { formSchema, type Schema } from './emails/data'
export async function action({ request, context }: Route.ActionArgs) { export async function action({ request, context }: Route.ActionArgs) {
const body = await request.json() const body = await request.json()
const user = context.get(userContext) const user = context.get(userContext)!
const r = await req({ const r = await req({
url: `/users/${user.sub}`, url: `/users/${user.sub}`,
method: HttpMethod.PATCH, method: HttpMethod.PATCH,

View File

@@ -1,12 +1,9 @@
import { createRequestHandler, RouterContextProvider } from 'react-router' import { createRequestHandler, RouterContextProvider } from 'react-router'
import { cloudflareContext } from '@repo/auth/context'
declare module 'react-router' { declare module '@repo/auth/context' {
export interface AppLoadContext { interface CloudflareEnv extends Env {}
cloudflare: { interface CloudflareCtx extends ExecutionContext {}
env: Env
ctx: ExecutionContext
}
}
} }
const requestHandler = createRequestHandler( const requestHandler = createRequestHandler(
@@ -16,11 +13,10 @@ const requestHandler = createRequestHandler(
export default { export default {
async fetch(request, env, ctx) { async fetch(request, env, ctx) {
const context = new RouterContextProvider() const context = new RouterContextProvider(
new Map([[cloudflareContext, { env, ctx }]])
return requestHandler(
request,
Object.assign(context, { cloudflare: { env, ctx } })
) )
return requestHandler(request, context)
} }
} satisfies ExportedHandler<Env> } satisfies ExportedHandler<Env>

View File

@@ -6,10 +6,8 @@ main = "./workers/app.ts"
[placement] [placement]
mode = "smart" mode = "smart"
[vars] [vars]
CLIENT_ID = "1a5483ab-4521-4702-9115-5857ac676851" CLIENT_ID = "1a5483ab-4521-4702-9115-5857ac676851"
REDIRECT_URI = "https://scorm.eduseg.workers.dev/login"
SCOPE = "openid profile email offline_access" SCOPE = "openid profile email offline_access"
API_URL = "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com" API_URL = "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com"
ISSUER_URL = "https://id.saladeaula.digital" ISSUER_URL = "https://id.saladeaula.digital"

65
package-lock.json generated
View File

@@ -4398,6 +4398,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/js-cookie": { "node_modules/@types/js-cookie": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@@ -4465,6 +4472,17 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/statuses": { "node_modules/@types/statuses": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz",
@@ -5880,6 +5898,29 @@
} }
} }
}, },
"node_modules/react-router": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-style-singleton": { "node_modules/react-style-singleton": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@@ -7309,29 +7350,6 @@
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
"packages/auth/node_modules/react-router": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz",
"integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"packages/ui": { "packages/ui": {
"name": "@repo/ui", "name": "@repo/ui",
"version": "0.0.0", "version": "0.0.0",
@@ -7390,6 +7408,7 @@
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@types/react-router": "^5.1.20",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }

View File

@@ -13,10 +13,10 @@
"remix-auth-oauth2": "^3.4.1" "remix-auth-oauth2": "^3.4.1"
}, },
"devDependencies": { "devDependencies": {
"react-router": "^7.10.1",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"react-router": "^7.10.1",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }

View File

@@ -13,13 +13,16 @@ export type User = {
refreshToken: string refreshToken: string
} }
export function createAuth(env, redirectURI = null) { export function createAuth(
env: Record<string, any>,
redirectURI: string | null = null
) {
const authenticator = new Authenticator() const authenticator = new Authenticator()
const strategy = new OAuth2Strategy( const strategy = new OAuth2Strategy(
{ {
clientId: env.CLIENT_ID, clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET, clientSecret: env.CLIENT_SECRET,
redirectURI: redirectURI ?? env.REDIRECT_URI, redirectURI: (env?.REDIRECT_URI ?? redirectURI) || undefined,
authorizationEndpoint: `${env.ISSUER_URL}/authorize`, authorizationEndpoint: `${env.ISSUER_URL}/authorize`,
tokenEndpoint: `${env.ISSUER_URL}/token`, tokenEndpoint: `${env.ISSUER_URL}/token`,
tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`, tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`,

View File

@@ -1,5 +1,18 @@
import type { User } from '@/auth'
import { createContext } from 'react-router' import { createContext } from 'react-router'
import type { User } from './auth'
export const userContext = createContext<User | null>(null) export const userContext = createContext<User | null>(null)
export const requestIdContext = createContext<string | null>(null) export const requestIdContext = createContext<string | null>(null)
export interface CloudflareEnv {}
export interface CloudflareCtx {}
export type CloudflareContextType = {
env: CloudflareEnv extends Record<string, never>
? Record<string, unknown>
: CloudflareEnv
ctx: CloudflareCtx extends Record<string, never> ? unknown : CloudflareCtx
}
export const cloudflareContext = createContext<CloudflareContextType>()

View File

@@ -1,17 +1,18 @@
import { requestIdContext, userContext } from '@/context'
import { createSessionStorage } from '@/session'
import { createAuth, type User } from '@/auth'
import { decodeJwt } from 'jose' import { decodeJwt } from 'jose'
import { redirect, type LoaderFunctionArgs } from 'react-router' import { redirect, type LoaderFunctionArgs } from 'react-router'
import type { OAuth2Strategy } from 'remix-auth-oauth2' import type { OAuth2Strategy } from 'remix-auth-oauth2'
import { requestIdContext, userContext, cloudflareContext } from '../context'
import { createSessionStorage } from '../session'
import { createAuth, type User } from '../auth'
export const authMiddleware = async ( export const authMiddleware = async (
{ request, context }: LoaderFunctionArgs, { request, context }: LoaderFunctionArgs,
next: () => Promise<Response> next: () => Promise<Response>
): Promise<Response> => { ): Promise<Response> => {
const sessionStorage = createSessionStorage(context.cloudflare.env) const cloudflare = context.get(cloudflareContext)
const authenticator = createAuth(context.cloudflare.env) const sessionStorage = createSessionStorage(cloudflare.env)
const authenticator = createAuth(cloudflare.env)
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc') const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
const session = await sessionStorage.getSession(request.headers.get('cookie')) const session = await sessionStorage.getSession(request.headers.get('cookie'))
const requestId = context.get(requestIdContext) const requestId = context.get(requestIdContext)
@@ -51,6 +52,7 @@ export const authMiddleware = async (
session.set('user', user) session.set('user', user)
} }
} catch (error) { } catch (error) {
// @ts-ignore
console.error(`[${requestId}]`, error?.stack) console.error(`[${requestId}]`, error?.stack)
// If refreshing the token fails, remove the user from the current session // If refreshing the token fails, remove the user from the current session

View File

@@ -1,10 +1,11 @@
import { requestIdContext } from '@/context'
import { type LoaderFunctionArgs } from 'react-router' import { type LoaderFunctionArgs } from 'react-router'
import { requestIdContext } from '../context'
export const loggingMiddleware = async ( export const loggingMiddleware = async (
{ request, context }: LoaderFunctionArgs, { request, context }: LoaderFunctionArgs,
next next: () => Promise<Response>
) => { ): Promise<Response> => {
const requestId = crypto.randomUUID() const requestId = crypto.randomUUID()
context.set(requestIdContext, requestId) context.set(requestIdContext, requestId)

View File

@@ -1,4 +1,5 @@
import { createCookieSessionStorage } from 'react-router' import { createCookieSessionStorage } from 'react-router'
import { type User } from './auth' import { type User } from './auth'
type SessionData = { type SessionData = {

View File

@@ -1,6 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022"],
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]

View File

@@ -0,0 +1,27 @@
import type { ReactNode } from 'react'
type CurrencyProps = {
children: number
options?: Intl.NumberFormatOptions
locale?: string
currency?: string
}
export function Currency({
children,
options,
locale = 'pt-BR',
currency = 'BRL'
}: CurrencyProps): ReactNode {
const optionsInit: Intl.NumberFormatOptions = {
style: 'currency',
currency
}
const formatter = new Intl.NumberFormat(locale, {
...optionsInit,
...options
})
return formatter.format(children)
}

View File

@@ -0,0 +1,33 @@
import type { ComponentPropsWithoutRef } from 'react'
type DateTimeProps = {
children: string
options?: Intl.DateTimeFormatOptions
locale?: string
} & ComponentPropsWithoutRef<'time'>
export function DateTime({
children,
options,
locale = 'pt-BR',
...props
}: DateTimeProps) {
const optionsInit: Intl.DateTimeFormatOptions = {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
}
const datetime = new Intl.DateTimeFormat(locale, {
...optionsInit,
...options
})
return (
<time dateTime={children} {...props}>
{datetime.format(new Date(children))}
</time>
)
}

View File

@@ -10,6 +10,7 @@
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@types/react-router": "^5.1.20",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }

View File

@@ -1,5 +1,8 @@
import type { User } from '@repo/auth/auth' import {
import { requestIdContext, userContext } from '@repo/auth/context' cloudflareContext,
requestIdContext,
userContext
} from '@repo/auth/context'
import type { LoaderFunctionArgs } from 'react-router' import type { LoaderFunctionArgs } from 'react-router'
@@ -28,9 +31,10 @@ export function request({
request: { signal }, request: { signal },
context context
}: RequestArgs): Promise<Response> { }: RequestArgs): Promise<Response> {
const requestId = context.get(requestIdContext) as string const requestId = context.get(requestIdContext)
const user = context.get(userContext) as User const user = context.get(userContext)
const url_ = new URL(url, context.cloudflare.env.API_URL) const cloudflare = context.get(cloudflareContext)
const url_ = new URL(url, cloudflare.env.API_URL)
const headers = new Headers({ const headers = new Headers({
Authorization: `Bearer ${user.accessToken}` Authorization: `Bearer ${user.accessToken}`
}) })

View File

@@ -1,6 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]