diff --git a/apps/id.saladeaula.digital/app/routes/forgot.tsx b/apps/id.saladeaula.digital/app/routes/forgot.tsx index d394ac8..508a798 100644 --- a/apps/id.saladeaula.digital/app/routes/forgot.tsx +++ b/apps/id.saladeaula.digital/app/routes/forgot.tsx @@ -62,8 +62,8 @@ export default function Forgot({}: Route.ComponentProps) { Redefinir senha

- Digite seu email e lhe enviaremos um email com as instruções para - redefinir sua senha. + Digite seu endereço de email ou cpf e lhe enviaremos um email com as + instruções para redefinir sua senha.

@@ -101,7 +101,7 @@ export default function Forgot({}: Route.ComponentProps) {

Lembrou da senha?{' '} - Faça login + Fazer login .

diff --git a/apps/id.saladeaula.digital/app/routes/layout.tsx b/apps/id.saladeaula.digital/app/routes/layout.tsx index 8cd78af..eb43853 100644 --- a/apps/id.saladeaula.digital/app/routes/layout.tsx +++ b/apps/id.saladeaula.digital/app/routes/layout.tsx @@ -11,7 +11,7 @@ export default function Layout() { Página inicial -
+
diff --git a/apps/id.saladeaula.digital/app/routes/register/cpf.tsx b/apps/id.saladeaula.digital/app/routes/register/cpf.tsx index 9d84f38..879ca64 100644 --- a/apps/id.saladeaula.digital/app/routes/register/cpf.tsx +++ b/apps/id.saladeaula.digital/app/routes/register/cpf.tsx @@ -3,8 +3,15 @@ import { useRequest } from 'ahooks' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' +import { AlertCircleIcon } from 'lucide-react' +import { Link } from 'react-router' import { Button } from '@repo/ui/components/ui/button' +import { + Alert, + AlertDescription, + AlertTitle +} from '@repo/ui/components/ui/alert' import { Form, FormControl, @@ -17,7 +24,7 @@ import { Input } from '@repo/ui/components/ui/input' import { Spinner } from '@repo/ui/components/ui/spinner' import { cpf, type RegisterContextProps, type User } from './data' import { RegisterContext } from './data' -import { use } from 'react' +import { use, useState } from 'react' const formSchema = z.object({ cpf: cpf @@ -27,10 +34,11 @@ type Schema = z.infer export function Cpf() { const { setUser } = use(RegisterContext) as RegisterContextProps + const [isOnboarded, setIsOnboarded] = useState(false) const form = useForm({ resolver: zodResolver(formSchema) }) - const { control, handleSubmit, formState } = form + const { control, handleSubmit, formState, setError } = form const { runAsync } = useRequest( async ({ cpf }) => { return await fetch(`/lookup?cpf=${cpf}`, { @@ -43,8 +51,15 @@ export function Cpf() { const onSubmit = async ({ cpf }: Schema) => { const r = await runAsync({ cpf }) - const user = (await r.json()) as any - setUser({ cpf, ...user }) + const data = (await r.json()) as any + + if (r.ok) { + setUser({ cpf, ...data }) + } + + if (r.status === 409) { + setIsOnboarded(true) + } } return ( @@ -52,6 +67,27 @@ export function Cpf() {
+ {isOnboarded && ( + + + Você já está cadastrado. + +

+ Por favor, siga as instruções abaixo para acessar sua conta: +

+
    +
  • + Você lembra da sua senha? Fazer login +
  • +
  • + Esqueceu a senha?{' '} + Recuperar minha senha +
  • +
+
+
+ )} + data.password === data.confirm_password, { + message: 'As senhas não coincidem', + path: ['confirm_password'] + }) export type Schema = z.infer diff --git a/apps/id.saladeaula.digital/app/routes/register/index.tsx b/apps/id.saladeaula.digital/app/routes/register/index.tsx index e784129..891929c 100644 --- a/apps/id.saladeaula.digital/app/routes/register/index.tsx +++ b/apps/id.saladeaula.digital/app/routes/register/index.tsx @@ -2,7 +2,8 @@ import type { Route } from '../+types' import { PatternFormat } from 'react-number-format' import { zodResolver } from '@hookform/resolvers/zod' -import { useState, createContext, type ReactNode, use } from 'react' +import { useState } from 'react' +import { CheckCircle2Icon } from 'lucide-react' import { useForm } from 'react-hook-form' import { Button } from '@repo/ui/components/ui/button' @@ -20,6 +21,11 @@ import { Label } from '@repo/ui/components/ui/label' import { Cpf } from './cpf' import { formSchema, type Schema, RegisterContext, type User } from './data' +import { + Alert, + AlertDescription, + AlertTitle +} from '@repo/ui/components/ui/alert' export function meta({}: Route.MetaArgs) { return [{ title: 'Criar conta · EDUSEG®' }] @@ -44,6 +50,17 @@ export default function Signup({}: Route.ComponentProps) { {user ? ( + {user?.never_logged && ( + + + Confirme seus dados + + Revise suas informações e edite o que precisar antes de + continuar. + + + )} + + + + + )} + /> + + ( + + Confirmar senha + + diff --git a/enrollments-events/app/events/enroll_scheduled.py b/enrollments-events/app/events/enroll_scheduled.py new file mode 100644 index 0000000..3134691 --- /dev/null +++ b/enrollments-events/app/events/enroll_scheduled.py @@ -0,0 +1,25 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.data_classes import ( + EventBridgeEvent, + event_source, +) +from aws_lambda_powertools.utilities.typing import LambdaContext +from layercake.dynamodb import DynamoDBPersistenceLayer + +from boto3clients import dynamodb_client +from config import ( + ENROLLMENT_TABLE, +) + +logger = Logger(__name__) +dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) + + +@event_source(data_class=EventBridgeEvent) +@logger.inject_lambda_context +def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: + old_image = event.detail['old_image'] + # Key pattern `SCHEDULED#ORG#{org_id}` + *_, org_id = old_image['id'].split('#') + + return True diff --git a/id.saladeaula.digital/app/routes/lookup.py b/id.saladeaula.digital/app/routes/lookup.py index ab18544..630a1eb 100644 --- a/id.saladeaula.digital/app/routes/lookup.py +++ b/id.saladeaula.digital/app/routes/lookup.py @@ -1,6 +1,11 @@ +from http import HTTPStatus from typing import Annotated from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.exceptions import ( + BadRequestError, + ServiceError, +) from aws_lambda_powertools.event_handler.openapi.params import Path from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey from layercake.extra_types import CnpjStr, CpfStr @@ -14,36 +19,55 @@ router = Router() dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client) +class UserAlreadyOnboardedError(ServiceError): + def __init__(self, msg: str | dict): + super().__init__(HTTPStatus.CONFLICT, msg) + + @router.get('/lookup') def lookup( - email: Annotated[EmailStr, Path] = 'unknown', - cpf: Annotated[CpfStr, Path] = 'unknown', - cnpj: Annotated[CnpjStr, Path] = 'unknown', + email: Annotated[EmailStr | None, Path] = None, + cpf: Annotated[CpfStr | None, Path] = None, + cnpj: Annotated[CnpjStr | None, Path] = None, ): + if not any([email, cpf, cnpj]): + raise BadRequestError('Malformed body request') + r = dyn.collection.get_items( KeyPair( pk='email', - sk=SortKey(email, path_spec='user_id'), - rename_key='id', + sk=SortKey(email, path_spec='user_id'), # type: ignore + rename_key='user_id', ) + KeyPair( pk='cpf', - sk=SortKey(cpf, path_spec='user_id'), - rename_key='id', + sk=SortKey(cpf, path_spec='user_id'), # type: ignore + rename_key='user_id', ) + KeyPair( pk='cnpj', - sk=SortKey(cnpj, path_spec='user_id'), + sk=SortKey(cnpj, path_spec='user_id'), # type: ignore rename_key='org_id', ), flatten_top=False, ) - if 'id' in r: - user = dyn.collection.get_items( - TransactKey(r['id']) + SortKey('0') + SortKey('FRESH_USER') + if cnpj: + return pick(('org_id',), r) + + if 'user_id' not in r: + return {} + + user = dyn.collection.get_items( + TransactKey(r['user_id']) + + SortKey('0') + + SortKey( + sk='NEVER_LOGGED', + rename_key='never_logged', ) + ) - return r | pick(('name', 'email'), user) if 'FRESH_USER' in user else {} + if 'never_logged' not in user: + raise UserAlreadyOnboardedError('User is already onboarded') - return r + return {'never_logged': True} | pick(('id', 'name', 'email'), user) diff --git a/id.saladeaula.digital/app/routes/register.py b/id.saladeaula.digital/app/routes/register.py index eea1a5f..9b7ba98 100644 --- a/id.saladeaula.digital/app/routes/register.py +++ b/id.saladeaula.digital/app/routes/register.py @@ -32,10 +32,7 @@ def register( email: Annotated[EmailStr, Body(embed=True)], password: Annotated[str, Body(min_length=6, embed=True)], cpf: Annotated[CpfStr, Body(embed=True)], - user_id: Annotated[str | None, Body(embed=True)] = None, + id: Annotated[str | None, Body(embed=True)] = None, org: Annotated[Org | None, Body(embed=True)] = None, ): - if user_id: - ... - return {}