diff --git a/apps/saladeaula.digital/app/routes/api.ts b/apps/saladeaula.digital/app/routes/api.ts
index 73feb5a..20e1515 100644
--- a/apps/saladeaula.digital/app/routes/api.ts
+++ b/apps/saladeaula.digital/app/routes/api.ts
@@ -1,7 +1,7 @@
import type { Route } from './+types/api'
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 action = proxy
@@ -11,8 +11,12 @@ async function proxy({
context
}: Route.ActionArgs): Promise {
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 url = new URL(pathname, context.cloudflare.env.API_URL)
+ const url = new URL(pathname, cloudflare.env.API_URL)
const headers = new Headers(request.headers)
headers.set('Authorization', `Bearer ${user.accessToken}`)
diff --git a/apps/saladeaula.digital/app/routes/auth/login.ts b/apps/saladeaula.digital/app/routes/auth/login.ts
index e9e21ef..d20fd84 100644
--- a/apps/saladeaula.digital/app/routes/auth/login.ts
+++ b/apps/saladeaula.digital/app/routes/auth/login.ts
@@ -3,30 +3,29 @@ import type { Route } from './+types/login'
import { redirect } from 'react-router'
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'
-export async function loader({ request, context }: Route.ActionArgs) {
+export async function loader({ request, context }: Route.LoaderArgs) {
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 requestId = context.get(requestIdContext)
- const user = session.get('user') as User | null
- const returnTo = (
- session.has('returnTo') ? session.get('returnTo') : '/'
- ) as string
+ const user = session.get('user')
+ const returnTo = (session.get('returnTo') as string | undefined) ?? '/'
if (user) {
return redirect(returnTo)
}
try {
- const authenticator = createAuth(
- context.cloudflare.env,
- `${url?.origin}/login`
- )
- const user = await authenticator.authenticate('oidc', request)
- session.set('user', user)
+ const authenticator = createAuth(cloudflare.env, `${url.origin}/login`)
+ const authenticatedUser = (await authenticator.authenticate(
+ 'oidc',
+ request
+ )) as User
+ session.set('user', authenticatedUser)
console.log(`[${requestId}] Redirecting the user to ${returnTo}`)
diff --git a/apps/saladeaula.digital/app/routes/auth/logout.ts b/apps/saladeaula.digital/app/routes/auth/logout.ts
index 7aaba94..5ecd0c3 100644
--- a/apps/saladeaula.digital/app/routes/auth/logout.ts
+++ b/apps/saladeaula.digital/app/routes/auth/logout.ts
@@ -5,12 +5,14 @@ import type { OAuth2Strategy } from 'remix-auth-oauth2'
import { createAuth, type User } from '@repo/auth/auth'
import { createSessionStorage } from '@repo/auth/session'
+import { cloudflareContext } from '@repo/auth/context'
export async function loader({ request, context }: Route.LoaderArgs) {
- const authenticator = createAuth(context.cloudflare.env)
- const sessionStorage = createSessionStorage(context.cloudflare.env)
+ const cloudflare = context.get(cloudflareContext)
+ const authenticator = createAuth(cloudflare.env)
+ const sessionStorage = createSessionStorage(cloudflare.env)
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>('oidc')
if (user?.accessToken && strategy) {
diff --git a/apps/saladeaula.digital/app/routes/history.tsx b/apps/saladeaula.digital/app/routes/history.tsx
index eaa37fe..8a2bebc 100644
--- a/apps/saladeaula.digital/app/routes/history.tsx
+++ b/apps/saladeaula.digital/app/routes/history.tsx
@@ -5,7 +5,7 @@ import { Await, Link } from 'react-router'
import { Suspense } from 'react'
import { type User } from '@repo/auth/auth'
-import { userContext } from '@repo/auth/context'
+import { userContext, cloudflareContext } from '@repo/auth/context'
import {
Breadcrumb,
BreadcrumbItem,
@@ -26,6 +26,7 @@ export function meta({}: Route.MetaArgs) {
}
export async function loader({ request, context }: Route.ActionArgs) {
+ const cloudflare = context.get(cloudflareContext)
const user = context.get(userContext) as User
const { searchParams } = new URL(request.url)
const status = searchParams.getAll('status') || []
@@ -45,15 +46,17 @@ export async function loader({ request, context }: Route.ActionArgs) {
sort: [sort],
page,
hitsPerPage,
- env: context.cloudflare.env
+ env: cloudflare.env
})
return {
- data: payments
+ payments
}
}
-export default function Component({ loaderData: { data } }) {
+export default function Component({
+ loaderData: { payments }
+}: Route.ComponentProps) {
return (
}>
@@ -81,15 +84,17 @@ export default function Component({ loaderData: { data } }) {
-
- {({ hits, page, hitsPerPage, totalHits }) => {
+
+ {({ hits, page = 1, hitsPerPage, totalHits }) => {
return (
)
diff --git a/apps/saladeaula.digital/app/routes/index.tsx b/apps/saladeaula.digital/app/routes/index.tsx
index 3cf73d4..7b2fce5 100644
--- a/apps/saladeaula.digital/app/routes/index.tsx
+++ b/apps/saladeaula.digital/app/routes/index.tsx
@@ -15,7 +15,7 @@ import { Suspense, useMemo, type ComponentProps } from 'react'
import { Await, NavLink, useSearchParams } from 'react-router'
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 { SearchForm } from '@repo/ui/components/search-form'
import { Skeleton } from '@repo/ui/components/skeleton'
@@ -60,6 +60,7 @@ export function meta({}: Route.MetaArgs) {
}
export async function loader({ request, context }: Route.ActionArgs) {
+ const cloudflare = context.get(cloudflareContext)
const user = context.get(userContext) as User
const { searchParams } = new URL(request.url)
const status = searchParams.getAll('status') || []
@@ -74,7 +75,7 @@ export async function loader({ request, context }: Route.ActionArgs) {
filter: builder.build(),
sort: ['created_at:desc'],
hitsPerPage: 100,
- env: context.cloudflare.env
+ env: cloudflare.env
})
return {
diff --git a/apps/saladeaula.digital/app/routes/konviva.ts b/apps/saladeaula.digital/app/routes/konviva.ts
index d885b4c..b80f946 100644
--- a/apps/saladeaula.digital/app/routes/konviva.ts
+++ b/apps/saladeaula.digital/app/routes/konviva.ts
@@ -5,11 +5,14 @@ import { redirect } from 'react-router'
import { userContext } from '@repo/auth/context'
import type { User } from '@repo/auth/auth'
+import { cloudflareContext } from 'workers/app'
+
const konvivaApi = 'https://lms.saladeaula.digital'
export async function loader({ context }: Route.LoaderArgs) {
+ const cloudflare = context.get(cloudflareContext)
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 url = new URL('/action/acessoExterno', konvivaApi)
diff --git a/apps/saladeaula.digital/app/routes/layout.tsx b/apps/saladeaula.digital/app/routes/layout.tsx
index 789fd6b..07160fa 100644
--- a/apps/saladeaula.digital/app/routes/layout.tsx
+++ b/apps/saladeaula.digital/app/routes/layout.tsx
@@ -2,11 +2,13 @@ import type { Route } from './+types/layout'
import { useToggle } from 'ahooks'
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 { useEffect } from 'react'
+import type { User } from '@repo/auth/auth'
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 { ModeToggle, ThemedImage } from '@repo/ui/components/dark-mode'
import { NavUser } from '@repo/ui/components/nav-user'
@@ -23,16 +25,14 @@ import {
SheetTitle,
SheetTrigger
} from '@repo/ui/components/ui/sheet'
-import type { User } from '@repo/auth/auth'
import { createSessionStorage } from '@repo/auth/session'
-import { data } from 'react-router'
-import { useEffect } from 'react'
export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
export async function loader({ context, request }: Route.ActionArgs) {
+ const cloudflare = context.get(cloudflareContext)
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 flash = {
diff --git a/apps/saladeaula.digital/app/routes/proxy.tsx b/apps/saladeaula.digital/app/routes/proxy.tsx
index 80a4850..6fc3fce 100644
--- a/apps/saladeaula.digital/app/routes/proxy.tsx
+++ b/apps/saladeaula.digital/app/routes/proxy.tsx
@@ -1,5 +1,7 @@
import type { Route } from './+types/proxy'
+import { cloudflareContext } from '@repo/auth/context'
+
// Mapping of extensions to MIME types
const mimeTypes: Record = {
// Images
@@ -25,8 +27,9 @@ const mimeTypes: Record = {
}
export async function loader({ params, context }: Route.LoaderArgs) {
- const bucketEndpoint = context.cloudflare.env.BUCKET_ENDPOINT
- const bucketName = context.cloudflare.env.BUCKET_NAME
+ const cloudflare = context.get(cloudflareContext)
+ const bucketEndpoint = cloudflare.env.BUCKET_ENDPOINT
+ const bucketName = cloudflare.env.BUCKET_NAME
const param = params['*'] ?? ''
const url = new URL(`${bucketName}/scorm/${param}`, bucketEndpoint)
const upstream = await fetch(url.toString())
diff --git a/apps/saladeaula.digital/app/routes/settings/emails/verify.tsx b/apps/saladeaula.digital/app/routes/settings/emails/verify.tsx
index 80ee959..287ccfc 100644
--- a/apps/saladeaula.digital/app/routes/settings/emails/verify.tsx
+++ b/apps/saladeaula.digital/app/routes/settings/emails/verify.tsx
@@ -2,15 +2,16 @@ import type { Route } from './+types/verify'
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 { HttpMethod, request as req } from '@repo/util/request'
export async function loader({ params, request, context }: Route.LoaderArgs) {
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 user = context.get(userContext)
+ const user = context.get(userContext)!
const r = await req({
url: `/users/${user.sub}/emails/${code}/verify`,
diff --git a/apps/saladeaula.digital/app/routes/settings/profile.tsx b/apps/saladeaula.digital/app/routes/settings/profile.tsx
index 3f4277e..1fe3e69 100644
--- a/apps/saladeaula.digital/app/routes/settings/profile.tsx
+++ b/apps/saladeaula.digital/app/routes/settings/profile.tsx
@@ -39,7 +39,7 @@ import { formSchema, type Schema } from './emails/data'
export async function action({ request, context }: Route.ActionArgs) {
const body = await request.json()
- const user = context.get(userContext)
+ const user = context.get(userContext)!
const r = await req({
url: `/users/${user.sub}`,
method: HttpMethod.PATCH,
diff --git a/apps/saladeaula.digital/workers/app.ts b/apps/saladeaula.digital/workers/app.ts
index 78f50ef..a6ca45a 100644
--- a/apps/saladeaula.digital/workers/app.ts
+++ b/apps/saladeaula.digital/workers/app.ts
@@ -1,12 +1,9 @@
import { createRequestHandler, RouterContextProvider } from 'react-router'
+import { cloudflareContext } from '@repo/auth/context'
-declare module 'react-router' {
- export interface AppLoadContext {
- cloudflare: {
- env: Env
- ctx: ExecutionContext
- }
- }
+declare module '@repo/auth/context' {
+ interface CloudflareEnv extends Env {}
+ interface CloudflareCtx extends ExecutionContext {}
}
const requestHandler = createRequestHandler(
@@ -16,11 +13,10 @@ const requestHandler = createRequestHandler(
export default {
async fetch(request, env, ctx) {
- const context = new RouterContextProvider()
-
- return requestHandler(
- request,
- Object.assign(context, { cloudflare: { env, ctx } })
+ const context = new RouterContextProvider(
+ new Map([[cloudflareContext, { env, ctx }]])
)
+
+ return requestHandler(request, context)
}
} satisfies ExportedHandler
diff --git a/apps/saladeaula.digital/wrangler.toml b/apps/saladeaula.digital/wrangler.toml
index 073d448..763c13c 100644
--- a/apps/saladeaula.digital/wrangler.toml
+++ b/apps/saladeaula.digital/wrangler.toml
@@ -6,10 +6,8 @@ main = "./workers/app.ts"
[placement]
mode = "smart"
-
[vars]
CLIENT_ID = "1a5483ab-4521-4702-9115-5857ac676851"
-REDIRECT_URI = "https://scorm.eduseg.workers.dev/login"
SCOPE = "openid profile email offline_access"
API_URL = "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com"
ISSUER_URL = "https://id.saladeaula.digital"
diff --git a/package-lock.json b/package-lock.json
index ebf7e03..c491182 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4398,6 +4398,13 @@
"dev": true,
"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": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@@ -4465,6 +4472,17 @@
"@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": {
"version": "2.0.6",
"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": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@@ -7309,29 +7350,6 @@
"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": {
"name": "@repo/ui",
"version": "0.0.0",
@@ -7390,6 +7408,7 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
+ "@types/react-router": "^5.1.20",
"typescript": "^5.9.3"
}
}
diff --git a/packages/auth/package.json b/packages/auth/package.json
index 8f17059..8c2a584 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -13,10 +13,10 @@
"remix-auth-oauth2": "^3.4.1"
},
"devDependencies": {
- "react-router": "^7.10.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
+ "react-router": "^7.10.1",
"typescript": "^5.9.3"
}
}
diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts
index 9985ce0..e76081b 100644
--- a/packages/auth/src/auth.ts
+++ b/packages/auth/src/auth.ts
@@ -13,13 +13,16 @@ export type User = {
refreshToken: string
}
-export function createAuth(env, redirectURI = null) {
+export function createAuth(
+ env: Record,
+ redirectURI: string | null = null
+) {
const authenticator = new Authenticator()
const strategy = new OAuth2Strategy(
{
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
- redirectURI: redirectURI ?? env.REDIRECT_URI,
+ redirectURI: (env?.REDIRECT_URI ?? redirectURI) || undefined,
authorizationEndpoint: `${env.ISSUER_URL}/authorize`,
tokenEndpoint: `${env.ISSUER_URL}/token`,
tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`,
diff --git a/packages/auth/src/context.ts b/packages/auth/src/context.ts
index d512d04..1538501 100644
--- a/packages/auth/src/context.ts
+++ b/packages/auth/src/context.ts
@@ -1,5 +1,18 @@
-import type { User } from '@/auth'
import { createContext } from 'react-router'
+import type { User } from './auth'
+
export const userContext = createContext(null)
export const requestIdContext = createContext(null)
+
+export interface CloudflareEnv {}
+export interface CloudflareCtx {}
+
+export type CloudflareContextType = {
+ env: CloudflareEnv extends Record
+ ? Record
+ : CloudflareEnv
+ ctx: CloudflareCtx extends Record ? unknown : CloudflareCtx
+}
+
+export const cloudflareContext = createContext()
diff --git a/packages/auth/src/middleware/auth.ts b/packages/auth/src/middleware/auth.ts
index 519f25b..1a86361 100644
--- a/packages/auth/src/middleware/auth.ts
+++ b/packages/auth/src/middleware/auth.ts
@@ -1,17 +1,18 @@
-import { requestIdContext, userContext } from '@/context'
-import { createSessionStorage } from '@/session'
-
-import { createAuth, type User } from '@/auth'
import { decodeJwt } from 'jose'
import { redirect, type LoaderFunctionArgs } from 'react-router'
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 (
{ request, context }: LoaderFunctionArgs,
next: () => Promise
): Promise => {
- const sessionStorage = createSessionStorage(context.cloudflare.env)
- const authenticator = createAuth(context.cloudflare.env)
+ const cloudflare = context.get(cloudflareContext)
+ const sessionStorage = createSessionStorage(cloudflare.env)
+ const authenticator = createAuth(cloudflare.env)
const strategy = authenticator.get>('oidc')
const session = await sessionStorage.getSession(request.headers.get('cookie'))
const requestId = context.get(requestIdContext)
@@ -51,6 +52,7 @@ export const authMiddleware = async (
session.set('user', user)
}
} catch (error) {
+ // @ts-ignore
console.error(`[${requestId}]`, error?.stack)
// If refreshing the token fails, remove the user from the current session
diff --git a/packages/auth/src/middleware/logging.ts b/packages/auth/src/middleware/logging.ts
index eddc97b..d19b8ec 100644
--- a/packages/auth/src/middleware/logging.ts
+++ b/packages/auth/src/middleware/logging.ts
@@ -1,10 +1,11 @@
-import { requestIdContext } from '@/context'
import { type LoaderFunctionArgs } from 'react-router'
+import { requestIdContext } from '../context'
+
export const loggingMiddleware = async (
{ request, context }: LoaderFunctionArgs,
- next
-) => {
+ next: () => Promise
+): Promise => {
const requestId = crypto.randomUUID()
context.set(requestIdContext, requestId)
diff --git a/packages/auth/src/session.ts b/packages/auth/src/session.ts
index 2dfc3ad..f53f9fb 100644
--- a/packages/auth/src/session.ts
+++ b/packages/auth/src/session.ts
@@ -1,4 +1,5 @@
import { createCookieSessionStorage } from 'react-router'
+
import { type User } from './auth'
type SessionData = {
diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json
index 95ce42c..c23c313 100644
--- a/packages/auth/tsconfig.json
+++ b/packages/auth/tsconfig.json
@@ -1,6 +1,12 @@
{
"compilerOptions": {
+ "module": "ESNext",
"moduleResolution": "bundler",
+ "target": "ES2022",
+ "lib": ["ES2022"],
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
diff --git a/packages/ui/src/components/currency.tsx b/packages/ui/src/components/currency.tsx
new file mode 100644
index 0000000..1fe9312
--- /dev/null
+++ b/packages/ui/src/components/currency.tsx
@@ -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)
+}
diff --git a/packages/ui/src/components/datetime.tsx b/packages/ui/src/components/datetime.tsx
new file mode 100644
index 0000000..df00b11
--- /dev/null
+++ b/packages/ui/src/components/datetime.tsx
@@ -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 (
+
+ )
+}
diff --git a/packages/util/package.json b/packages/util/package.json
index 603d7a2..9a62c0b 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -10,6 +10,7 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
+ "@types/react-router": "^5.1.20",
"typescript": "^5.9.3"
}
}
diff --git a/packages/util/src/request.ts b/packages/util/src/request.ts
index a7b3924..363bbb0 100644
--- a/packages/util/src/request.ts
+++ b/packages/util/src/request.ts
@@ -1,5 +1,8 @@
-import type { User } from '@repo/auth/auth'
-import { requestIdContext, userContext } from '@repo/auth/context'
+import {
+ cloudflareContext,
+ requestIdContext,
+ userContext
+} from '@repo/auth/context'
import type { LoaderFunctionArgs } from 'react-router'
@@ -28,9 +31,10 @@ export function request({
request: { signal },
context
}: RequestArgs): Promise {
- const requestId = context.get(requestIdContext) as string
- const user = context.get(userContext) as User
- const url_ = new URL(url, context.cloudflare.env.API_URL)
+ const requestId = context.get(requestIdContext)
+ const user = context.get(userContext)
+ const cloudflare = context.get(cloudflareContext)
+ const url_ = new URL(url, cloudflare.env.API_URL)
const headers = new Headers({
Authorization: `Bearer ${user.accessToken}`
})
diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json
index 95ce42c..07dd8d1 100644
--- a/packages/util/tsconfig.json
+++ b/packages/util/tsconfig.json
@@ -1,6 +1,12 @@
{
"compilerOptions": {
+ "module": "ESNext",
"moduleResolution": "bundler",
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM"],
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]