This commit is contained in:
2025-11-06 10:06:10 -03:00
parent bb667b6636
commit e885f04303
27 changed files with 376 additions and 284 deletions

View File

@@ -1,9 +1,9 @@
import { useKeyPress } from '@/hooks/use-keypress'
import {
InputGroup,
InputGroupAddon,
InputGroupInput
} from '@repo/ui/components/ui/input-group'
import { useKeyPress } from '@/hooks/use-keypress'
import clsx from 'clsx'
import { debounce } from 'lodash'
import { SearchIcon } from 'lucide-react'

View File

@@ -1,22 +0,0 @@
import { throttle } from 'lodash'
import { useEffect } from 'react'
export function useKeyPress(targetKey, callback) {
useEffect(() => {
const onKeyDown = throttle((event) => {
if (event.key === targetKey) {
event.preventDefault()
callback(event)
}
}, 300)
window.addEventListener('keydown', onKeyDown)
return () => {
window.removeEventListener('keydown', onKeyDown)
onKeyDown.cancel?.()
}
}, [targetKey, callback])
return null
}

View File

@@ -1,43 +0,0 @@
import type { OAuth2Tokens } from 'arctic'
import { decodeJwt } from 'jose'
import { Authenticator } from 'remix-auth'
import { CodeChallengeMethod, OAuth2Strategy } from 'remix-auth-oauth2'
export type User = {
sub: string
email: string
name: string
scope: string
email_verified: boolean
accessToken: string
refreshToken: string
}
export function createAuth(env: Env) {
const authenticator = new Authenticator()
const strategy = new OAuth2Strategy(
{
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
redirectURI: env.REDIRECT_URI,
authorizationEndpoint: `${env.ISSUER_URL}/authorize`,
tokenEndpoint: `${env.ISSUER_URL}/token`,
tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`,
scopes: env.SCOPE.split(' '),
codeChallengeMethod: CodeChallengeMethod.S256
},
async ({ tokens }: { tokens: OAuth2Tokens }) => {
const user = decodeJwt(tokens.idToken())
return {
...user,
accessToken: tokens.accessToken(),
refreshToken: tokens.hasRefreshToken() ? tokens.refreshToken() : null
}
}
)
authenticator.use(strategy, 'oidc')
return authenticator
}

View File

@@ -1,4 +1,4 @@
import { userContext } from '@/context'
import { userContext } from '@repo/auth/context'
import type { LoaderFunctionArgs } from 'react-router'
enum Method {

View File

@@ -1,16 +0,0 @@
import { createCookieSessionStorage } from 'react-router'
export function createSessionStorage(env: Env) {
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
secure: false,
secrets: [env.SESSION_SECRET],
sameSite: 'lax',
path: '/',
maxAge: 86400 * 7 // 7 days
}
})
return sessionStorage
}

View File

@@ -8,6 +8,7 @@ import {
export default [
layout('routes/layout.tsx', [
index('routes/index.tsx'),
route('certs', 'routes/certs.tsx'),
route('payments', 'routes/payments.tsx'),
route('settings', 'routes/settings.tsx'),
route('player/:course', 'routes/player.tsx'),

View File

@@ -10,10 +10,10 @@ import {
} from '@repo/ui/components/ui/empty'
import {
BanIcon,
BookCopyIcon,
CircleCheckIcon,
CircleIcon,
CircleOffIcon,
CirclePlusIcon,
CircleXIcon,
TimerIcon,
type LucideIcon
@@ -131,7 +131,7 @@ export default function Component({
/>
</div>
<FacetedFilter
icon={BookCopyIcon}
icon={CirclePlusIcon}
value={searchParams.getAll('status')}
onChange={(statuses) => {
setSearchParams((searchParams) => {

View File

@@ -1,12 +1,18 @@
import type { Route } from './+types'
import { Outlet } from 'react-router'
import { Link, NavLink, Outlet } from 'react-router'
import { userContext } 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'
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList
} from '@repo/ui/components/ui/navigation-menu'
import { useIsMobile } from '@repo/ui/hooks/use-mobile'
export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
@@ -16,6 +22,7 @@ export async function loader({ context }: Route.ActionArgs) {
}
export default function Component({ loaderData }: Route.ComponentProps) {
const isMobile = useIsMobile()
const { user } = loaderData
return (
@@ -25,7 +32,25 @@ export default function Component({ loaderData }: Route.ComponentProps) {
px-4 py-2 lg:py-4 sticky top-0 z-5"
>
<div className="container mx-auto flex items-center">
<ThemedImage />
<div className="flex gap-5">
<Link to="/">
<ThemedImage />
</Link>
<NavigationMenu viewport={isMobile}>
<NavigationMenuList>
<NavigationMenuItem>
<NavMenuLink to="/">Meus cursos</NavMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavMenuLink to="/certs">Certificados</NavMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavMenuLink to="/payments">Histórico de compras</NavMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
<div className="ml-auto flex gap-2.5 items-center">
<ModeToggle />
@@ -42,3 +67,14 @@ export default function Component({ loaderData }: Route.ComponentProps) {
</div>
)
}
function NavMenuLink({ children, ...props }) {
return (
<NavigationMenuLink
className="font-medium aria-[current=page]:bg-muted"
asChild
>
<NavLink {...props}>{children}</NavLink>
</NavigationMenuLink>
)
}

View File

@@ -2,9 +2,10 @@ import type { Route } from './+types'
import { Link } from 'react-router'
import { userContext } from '@/context'
import { request as req } from '@/lib/request'
import type { User } from '@/middleware/auth'
import type { User } from '@repo/auth/auth'
import { userContext } from '@repo/auth/context'
import {
Breadcrumb,
BreadcrumbItem,