diff --git a/http-api/course.py b/http-api/course.py deleted file mode 100644 index c00b543..0000000 --- a/http-api/course.py +++ /dev/null @@ -1,57 +0,0 @@ -from layercake.dateutils import now -from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems - -from models import Course, Org - - -def create_course( - course: Course, - org: Org, - /, - persistence_layer: DynamoDBPersistenceLayer, -): - now_ = now() - transact = TransactItems(persistence_layer.table_name) - transact.put( - item={ - 'sk': '0', - 'tenant__org_id': {org.id}, - 'create_date': now_, - **course.model_dump(), - } - ) - transact.put( - item={ - 'id': course.id, - 'sk': 'tenant', - 'org_id': org.id, - 'name': org.name, - 'create_date': now_, - } - ) - return persistence_layer.transact_write_items(transact) - - -def update_course( - id: str, - course: Course, - /, - persistence_layer: DynamoDBPersistenceLayer, -): - now_ = now() - transact = TransactItems(persistence_layer.table_name) - transact.update( - key=KeyPair(id, '0'), - update_expr='SET #name = :name, access_period = :access_period, cert = :cert, update_date = :update_date', - expr_attr_names={ - '#name': 'name', - }, - expr_attr_values={ - ':name': course.name, - ':cert': course.cert.model_dump() if course.cert else None, - ':access_period': course.access_period, - ':update_date': now_, - }, - cond_expr='attribute_exists(sk)', - ) - return persistence_layer.transact_write_items(transact) diff --git a/http-api/enrollment.py b/http-api/enrollment.py deleted file mode 100644 index 0fa4338..0000000 --- a/http-api/enrollment.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import TypedDict -from uuid import uuid4 - -from layercake.dateutils import now -from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems - -from settings import ORDER_TABLE - - -class Author(TypedDict): - id: str - name: str - - -class Course(TypedDict): - id: str - name: str - time_in_days: int - - -def set_status_as_canceled( - id: str, - *, - lock_hash: str, - author: Author, - course: Course | None = None, - vacancy_key: KeyPair | None = None, - persistence_layer: DynamoDBPersistenceLayer, -): - """Cancel the enrollment if there's a `cancel_policy` - and put its vacancy back if `vacancy_key` is provided.""" - now_ = now() - transact = TransactItems(persistence_layer.table_name) - transact.update( - key=KeyPair(id, '0'), - update_expr='SET #status = :canceled, update_date = :update', - expr_attr_names={ - '#status': 'status', - }, - expr_attr_values={ - ':canceled': 'CANCELED', - ':update': now_, - }, - ) - transact.put( - item={ - 'id': id, - 'sk': 'canceled_date', - 'author': author, - 'create_date': now_, - }, - ) - transact.delete( - key=KeyPair(id, 'cancel_policy'), - cond_expr='attribute_exists(sk)', - ) - # Remove schedules lifecycle events, referencies and locks - transact.delete(key=KeyPair(id, 'schedules#archive_it')) - transact.delete(key=KeyPair(id, 'schedules#no_activity')) - transact.delete(key=KeyPair(id, 'schedules#access_period_ends')) - transact.delete(key=KeyPair(id, 'schedules#does_not_access')) - transact.delete(key=KeyPair(id, 'parent_vacancy')) - transact.delete(key=KeyPair(id, 'lock')) - transact.delete(key=KeyPair('lock', lock_hash)) - - if vacancy_key and course: - vacancy_pk, vacancy_sk = vacancy_key.values() - org_id = vacancy_pk.removeprefix('vacancies#') - order_id, enrollment_id = vacancy_sk.split('#') - - transact.condition( - key=KeyPair(order_id, '0'), - cond_expr='attribute_exists(id)', - table_name=ORDER_TABLE, - ) - # Put the vacancy back and assign a new ID - transact.put( - item={ - 'id': f'vacancies#{org_id}', - 'sk': f'{order_id}#{uuid4()}', - 'course': course, - 'create_date': now_, - }, - cond_expr='attribute_not_exists(sk)', - ) - # Set the status of `generated_items` to `ROLLBACK` to know - # which vacancy is available for reuse - transact.update( - key=KeyPair(order_id, f'generated_items#{enrollment_id}'), - update_expr='SET #status = :status, update_date = :update', - expr_attr_names={ - '#status': 'status', - }, - expr_attr_values={ - ':status': 'ROLLBACK', - ':update': now_, - }, - cond_expr='attribute_exists(sk)', - table_name=ORDER_TABLE, - ) - - return persistence_layer.transact_write_items(transact) diff --git a/http-api/org.py b/http-api/org.py deleted file mode 100644 index 3415ce0..0000000 --- a/http-api/org.py +++ /dev/null @@ -1,40 +0,0 @@ -from layercake.dateutils import now -from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, TransactItems - - -def update_policies( - id: str, - /, - payment_policy: dict = {}, - billing_policy: dict = {}, - *, - persistence_layer: DynamoDBPersistenceLayer, -): - now_ = now() - transact = TransactItems(persistence_layer.table_name) - - if payment_policy: - transact.put( - item={ - 'id': id, - 'sk': 'payment_policy', - 'create_date': now_, - } - | payment_policy - ) - else: - transact.delete(key=KeyPair(id, 'payment_policy')) - - if billing_policy: - transact.put( - item={ - 'id': id, - 'sk': 'billing_policy', - 'create_date': now_, - } - | billing_policy - ) - else: - transact.delete(key=KeyPair(id, 'billing_policy')) - - return persistence_layer.transact_write_items(transact) diff --git a/http-api/routes/courses/__init__.py b/http-api/routes/courses/__init__.py index f0f217c..800d372 100644 --- a/http-api/routes/courses/__init__.py +++ b/http-api/routes/courses/__init__.py @@ -7,9 +7,9 @@ from meilisearch import Client as Meilisearch from api_gateway import JSONResponse from boto3clients import dynamodb_client -from course import create_course, update_course from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware from models import Course, Org +from rules.course import create_course, update_course from settings import ( COURSE_TABLE, MEILISEARCH_API_KEY, diff --git a/http-api/routes/enrollments/__init__.py b/http-api/routes/enrollments/__init__.py index 9085eb7..4ed8549 100644 --- a/http-api/routes/enrollments/__init__.py +++ b/http-api/routes/enrollments/__init__.py @@ -13,9 +13,9 @@ from pydantic import UUID4, BaseModel import elastic from boto3clients import dynamodb_client -from enrollment import set_status_as_canceled from middlewares.audit_log_middleware import AuditLogMiddleware from middlewares.authentication_middleware import User +from rules.enrollment import set_status_as_canceled from settings import ELASTIC_CONN, ENROLLMENT_TABLE, USER_TABLE router = Router() diff --git a/http-api/routes/orgs/policies.py b/http-api/routes/orgs/policies.py index 1ae7ce5..05eb5e2 100644 --- a/http-api/routes/orgs/policies.py +++ b/http-api/routes/orgs/policies.py @@ -15,7 +15,7 @@ from pydantic.main import BaseModel from typing_extensions import Literal from boto3clients import dynamodb_client -from org import update_policies +from rules.org import update_policies from settings import USER_TABLE router = Router() diff --git a/http-api/routes/users/emails.py b/http-api/routes/users/emails.py index 1bb7f5b..f697ec7 100644 --- a/http-api/routes/users/emails.py +++ b/http-api/routes/users/emails.py @@ -16,8 +16,8 @@ from pydantic import BaseModel, EmailStr from api_gateway import JSONResponse from boto3clients import dynamodb_client from middlewares import AuditLogMiddleware +from rules.user import add_email, del_email, set_email_as_primary from settings import USER_TABLE -from user import add_email, del_email, set_email_as_primary class BadRequestError(MissingError, PowertoolsBadRequestError): ... diff --git a/http-api/routes/users/orgs.py b/http-api/routes/users/orgs.py index dd379af..f510f1d 100644 --- a/http-api/routes/users/orgs.py +++ b/http-api/routes/users/orgs.py @@ -17,8 +17,8 @@ from pydantic import BaseModel from api_gateway import JSONResponse from boto3clients import dynamodb_client from middlewares.audit_log_middleware import AuditLogMiddleware +from rules.user import del_org_member from settings import USER_TABLE -from user import del_org_member class BadRequestError(MissingError, PowertoolsBadRequestError): ... diff --git a/http-api/user.py b/http-api/user.py deleted file mode 100644 index 5111f4b..0000000 --- a/http-api/user.py +++ /dev/null @@ -1,155 +0,0 @@ -from aws_lambda_powertools.event_handler.exceptions import ( - BadRequestError, -) -from botocore.exceptions import ClientError -from layercake.dateutils import now -from layercake.dynamodb import ( - ComposeKey, - DynamoDBPersistenceLayer, - KeyPair, - TransactItems, -) - - -def add_email( - id: str, - email: str, - /, - *, - persistence_layer: DynamoDBPersistenceLayer, -): - now_ = now() - transact = TransactItems(persistence_layer.table_name) - transact.update( - key=KeyPair(id, '0'), - update_expr='ADD emails :email', - expr_attr_values={ - ':email': {email}, - }, - ) - transact.put( - item={ - 'id': id, - 'sk': f'emails#{email}', - 'email_primary': False, - 'email_verified': False, - 'create_date': now_, - }, - cond_expr='attribute_not_exists(sk)', - ) - transact.put( - item={ - 'id': 'email', - 'sk': email, - 'user_id': id, - 'create_date': now_, - }, - cond_expr='attribute_not_exists(sk)', - ) - - try: - return persistence_layer.transact_write_items(transact) - except ClientError: - raise BadRequestError('Email already exists.') - - -def del_email( - id: str, - email: str, - /, - *, - persistence_layer: DynamoDBPersistenceLayer, -) -> bool: - """Delete any email except the primary email.""" - transact = TransactItems(persistence_layer.table_name) - transact.delete( - key=KeyPair('email', email), - ) - transact.delete( - key=KeyPair(id, ComposeKey(email, prefix='emails')), - cond_expr='email_primary <> :primary', - expr_attr_values={':primary': True}, - ) - transact.update( - key=KeyPair(id, '0'), - update_expr='DELETE emails :email', - expr_attr_values={ - ':email': {email}, - }, - ) - - try: - return persistence_layer.transact_write_items(transact) - except ClientError: - raise BadRequestError('Cannot remove the primary email.') - - -def set_email_as_primary( - id: str, - new_email: str, - old_email: str, - /, - *, - email_verified: bool = False, - persistence_layer: DynamoDBPersistenceLayer, -): - now_ = now() - expr = 'SET email_primary = :email_primary, update_date = :update_date' - transact = TransactItems(persistence_layer.table_name) - # Set the old email as non-primary - transact.update( - key=KeyPair(id, ComposeKey(old_email, 'emails')), - update_expr=expr, - expr_attr_values={ - ':email_primary': False, - ':update_date': now_, - }, - ) - # Set the new email as primary - transact.update( - key=KeyPair(id, ComposeKey(new_email, 'emails')), - update_expr=expr, - expr_attr_values={ - ':email_primary': True, - ':update_date': now_, - }, - ) - transact.update( - key=KeyPair(id, '0'), - update_expr=( - 'SET email = :email, email_verified = :email_verified, ' - 'update_date = :update_date' - ), - expr_attr_values={ - ':email': new_email, - ':email_verified': email_verified, - ':update_date': now_, - }, - ) - - return persistence_layer.transact_write_items(transact) - - -def del_org_member( - id: str, - *, - org_id: str, - persistence_layer: DynamoDBPersistenceLayer, -) -> bool: - transact = TransactItems(persistence_layer.table_name) - - # Remove the user's relationship with the organization and their privileges - transact.delete(key=KeyPair(id, f'acls#{org_id}')) - transact.delete(key=KeyPair(id, f'orgs#{org_id}')) - transact.update( - key=KeyPair(id, '0'), - update_expr='DELETE #tenant :org_id', - expr_attr_names={'#tenant': 'tenant__org_id'}, - expr_attr_values={':org_id': {org_id}}, - ) - - # Remove the user from the organization's admins and members list - transact.delete(key=KeyPair(org_id, f'admins#{id}')) - transact.delete(key=KeyPair(f'orgmembers#{org_id}', id)) - - return persistence_layer.transact_write_items(transact) diff --git a/superpage/.prettierrc b/superpage/.prettierrc new file mode 100644 index 0000000..6fed3d0 --- /dev/null +++ b/superpage/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true +} diff --git a/superpage/astro.config.mjs b/superpage/astro.config.mjs index 5d07d1d..767600b 100644 --- a/superpage/astro.config.mjs +++ b/superpage/astro.config.mjs @@ -1,14 +1,18 @@ // @ts-check -import { defineConfig } from 'astro/config'; +import { defineConfig } from 'astro/config' -import react from '@astrojs/react'; -import tailwindcss from '@tailwindcss/vite'; +import react from '@astrojs/react' +import tailwindcss from '@tailwindcss/vite' // https://astro.build/config export default defineConfig({ integrations: [react()], vite: { - plugins: [tailwindcss()] - } -}); \ No newline at end of file + plugins: [tailwindcss()], + }, + server: { + host: '0.0.0.0', + allowedHosts: ['7aaa-187-57-7-239.ngrok-free.app'], + }, +}) diff --git a/superpage/package-lock.json b/superpage/package-lock.json index c83b754..8176494 100644 --- a/superpage/package-lock.json +++ b/superpage/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "dependencies": { "@astrojs/react": "^4.2.1", - "@headlessui/react": "^2.2.0", + "@headlessui/react": "^2.2.1", "@heroicons/react": "^2.2.0", "@tailwindcss/vite": "^4.0.13", "@tanstack/react-query": "^5.68.0", @@ -873,15 +873,15 @@ "license": "MIT" }, "node_modules/@headlessui/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", - "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.1.tgz", + "integrity": "sha512-daiUqVLae8CKVjEVT19P/izW0aGK0GNhMSAeMlrDebKmoVZHcRRwbxzgtnEadUVDXyBsWo9/UH4KHeniO+0tMg==", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.17.1", "@react-aria/interactions": "^3.21.3", - "@tanstack/react-virtual": "^3.8.1" + "@tanstack/react-virtual": "^3.11.1" }, "engines": { "node": ">=10" @@ -1316,14 +1316,14 @@ "license": "MIT" }, "node_modules/@react-aria/focus": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.1.tgz", - "integrity": "sha512-lgYs+sQ1TtBrAXnAdRBQrBo0/7o5H6IrfDxec1j+VRpcXL0xyk0xPq+m3lZp8typzIghqDgpnKkJ5Jf4OrzPIw==", + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz", + "integrity": "sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.24.1", - "@react-aria/utils": "^3.28.1", - "@react-types/shared": "^3.28.0", + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, @@ -1333,15 +1333,15 @@ } }, "node_modules/@react-aria/interactions": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.24.1.tgz", - "integrity": "sha512-OWEcIC6UQfWq4Td5Ptuh4PZQ4LHLJr/JL2jGYvuNL6EgL3bWvzPrRYIF/R64YbfVxIC7FeZpPSkS07sZ93/NoA==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz", + "integrity": "sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ==", "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.9.7", - "@react-aria/utils": "^3.28.1", - "@react-stately/flags": "^3.1.0", - "@react-types/shared": "^3.28.0", + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.28.2", + "@react-stately/flags": "^3.1.1", + "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -1350,9 +1350,9 @@ } }, "node_modules/@react-aria/ssr": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", - "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz", + "integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -1365,15 +1365,15 @@ } }, "node_modules/@react-aria/utils": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.1.tgz", - "integrity": "sha512-mnHFF4YOVu9BRFQ1SZSKfPhg3z+lBRYoW5mLcYTQihbKhz48+I1sqRkP7ahMITr8ANH3nb34YaMME4XWmK2Mgg==", + "version": "3.28.2", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz", + "integrity": "sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w==", "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.9.7", - "@react-stately/flags": "^3.1.0", - "@react-stately/utils": "^3.10.5", - "@react-types/shared": "^3.28.0", + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, @@ -1383,18 +1383,18 @@ } }, "node_modules/@react-stately/flags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.0.tgz", - "integrity": "sha512-KSHOCxTFpBtxhIRcKwsD1YDTaNxFtCYuAUb0KEihc16QwqZViq4hasgPBs2gYm7fHRbw7WYzWKf6ZSo/+YsFlg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz", + "integrity": "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, "node_modules/@react-stately/utils": { - "version": "3.10.5", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz", - "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz", + "integrity": "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -1404,9 +1404,9 @@ } }, "node_modules/@react-types/shared": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.28.0.tgz", - "integrity": "sha512-9oMEYIDc3sk0G5rysnYvdNrkSg7B04yTKl50HHSZVbokeHpnU0yRmsDaWb9B/5RprcKj8XszEk5guBO8Sa/Q+Q==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", "license": "Apache-2.0", "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -1757,9 +1757,9 @@ "license": "MIT" }, "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -2016,12 +2016,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.2.tgz", - "integrity": "sha512-LceSUgABBKF6HSsHK2ZqHzQ37IKV/jlaWbHm+NyTa3/WNb/JZVcThDuTainf+PixltOOcFCYXwxbLpOX9sCx+g==", + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", + "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.2" + "@tanstack/virtual-core": "3.13.6" }, "funding": { "type": "github", @@ -2033,9 +2033,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.2.tgz", - "integrity": "sha512-Qzz4EgzMbO5gKrmqUondCjiHcuu4B1ftHb0pjCut661lXZdGoHeze9f/M8iwsK1t5LGR6aNuNGU7mxkowaW6RQ==", + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", + "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", "license": "MIT", "funding": { "type": "github", diff --git a/superpage/package.json b/superpage/package.json index 5e7d6f7..04d5188 100644 --- a/superpage/package.json +++ b/superpage/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@astrojs/react": "^4.2.1", - "@headlessui/react": "^2.2.0", + "@headlessui/react": "^2.2.1", "@heroicons/react": "^2.2.0", "@tailwindcss/vite": "^4.0.13", "@tanstack/react-query": "^5.68.0", diff --git a/superpage/src/components/Card.tsx b/superpage/src/components/Card.tsx index 2343c7c..73d1305 100644 --- a/superpage/src/components/Card.tsx +++ b/superpage/src/components/Card.tsx @@ -1,30 +1,30 @@ -import clsx from "clsx"; +import clsx from 'clsx' interface CardProps { - children: React.ReactNode; - color?: "gradient" | "darker" | "yellow" | "zinc"; - className?: string | undefined; + children: React.ReactNode + color?: 'gradient' | 'darker' | 'yellow' | 'zinc' + className?: string | undefined } -export function Card({ children, color = "gradient", className }: CardProps) { +export function Card({ children, color = 'gradient', className }: CardProps) { const colorVariants = { - gradient: "bg-linear-to-tr from-green-secondary to-yellow-primary", - darker: "bg-green-primary text-white", - yellow: "text-green-primary bg-yellow-tertiary", - zinc: "text-white bg-zinc-900", - }; + gradient: 'bg-linear-to-tr from-[#8CD366] via-[#C7D174] to-[#FFCF82]', + darker: 'bg-green-primary text-white', + yellow: 'text-green-primary bg-yellow-tertiary', + zinc: 'text-white bg-zinc-900', + } return (
{children}
- ); + ) } diff --git a/superpage/src/components/Container.tsx b/superpage/src/components/Container.tsx index 5c0527c..590d3a4 100644 --- a/superpage/src/components/Container.tsx +++ b/superpage/src/components/Container.tsx @@ -1,10 +1,14 @@ -import clsx from "clsx"; +import clsx from 'clsx' interface ContainerProps { - children: React.ReactNode; - className?: string | undefined; + children: React.ReactNode + className?: string | undefined } export function Container({ children, className }: ContainerProps) { - return
{children}
; + return ( +
+ {children} +
+ ) } diff --git a/superpage/src/components/Form.tsx b/superpage/src/components/Form.tsx index b3e48bd..13eb8e3 100644 --- a/superpage/src/components/Form.tsx +++ b/superpage/src/components/Form.tsx @@ -1,33 +1,33 @@ -import { useForm } from "react-hook-form"; -import { useMutation } from "node_modules/@tanstack/react-query/build/legacy"; -import { queryClient } from "../queryClient"; -import axios from "axios"; -import { createElement } from "react"; -import clsx from "clsx"; +import { useForm } from 'react-hook-form' +import { useMutation } from 'node_modules/@tanstack/react-query/build/legacy' +import { queryClient } from '../queryClient' +import axios from 'axios' +import { createElement } from 'react' +import clsx from 'clsx' interface IFormInput { - name: string; - email: string; - message: string; + name: string + email: string + message: string } export function Form() { - const { register, handleSubmit, reset, formState } = useForm(); + const { register, handleSubmit, reset, formState } = useForm() const { mutateAsync } = useMutation( { mutationFn: async (data: IFormInput) => { - return await axios.post("https://n8n.sergio.run/webhook/eduseg", data); + return await axios.post('https://n8n.sergio.run/webhook/eduseg', data) }, onSuccess: () => { - reset(); + reset() }, }, queryClient, - ); + ) const onSubmit = async (data: IFormInput) => { - await mutateAsync(data); - }; + await mutateAsync(data) + } return (
+ + + -
-
- NR-18 PEMT Plataforma Móvel de Trabalho Aéreo -
- -
- +
+ +
-
- -
- - +
+ +
+ +

+ Educação que garante
sua segurança. +

+
- - -
- -
- -

- Educação que garante
sua segurança. -

-
- -
-
Cursos
-
    -
  • Lei Lucas
  • -
  • NR-18 PEMT Plataforma Móvel de Trabalho Aéreo
  • -
  • NR-35 Trabalho em Altura
  • -
  • NR-10 Básico
  • -
-
-
-
- +
+
Conheça outros cursos
+
    +
  • Lei Lucas
  • +
  • NR-18 PEMT Plataforma Móvel de Trabalho Aéreo
  • +
  • NR-35 Trabalho em Altura
  • +
  • NR-10 Básico
  • +
  • + NR-12 Máquinas e Equipamentos Reciclagem +
  • +
  • + NR-18 PEMT Plataforma Elevatória Móvel de Trabalho Reciclagem +
  • +
  • LOTO Lockout Tagout
  • +
  • NR-18 Sinaleiro e Amarrador de Cargas para Içamento
  • +
+
+
+
+ diff --git a/superpage/src/pages/homem-de-negocios.png b/superpage/src/pages/homem-de-negocios.png new file mode 100644 index 0000000..5f77448 Binary files /dev/null and b/superpage/src/pages/homem-de-negocios.png differ diff --git a/superpage/src/pages/index.astro b/superpage/src/pages/index.astro index aa0bbe9..e27079a 100644 --- a/superpage/src/pages/index.astro +++ b/superpage/src/pages/index.astro @@ -1,77 +1,234 @@ --- -import { Bookmark } from "@components/Bookmark"; -import { Card } from "@components/Card"; -import { Container } from "@components/Container"; -import { Form } from "@components/Form"; -import Layout from "@layouts/Layout.astro"; +import { Image } from 'astro:assets' +import { Card } from '@components/Card' +import { Container } from '@components/Container' +import { Form } from '@components/Form' +import Layout from '@layouts/Layout.astro' +import { Example } from '@components/List' +import { + AcademicCapIcon, + GlobeAmericasIcon, + PhoneIcon, + BanknotesIcon, + StarIcon, + ClockIcon, +} from '@heroicons/react/24/outline' +import { CheckBadgeIcon } from '@heroicons/react/24/solid' +import nr18plataforma from './nr18-plataforma.png' +import mulherdenegocios from './mulher-de-negocios.png' +import homemdenegocios from './homem-de-negocios.png' --- - - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam - quis mattis tortor, sit amet mollis lorem. In imperdiet, ante - sit amet maximus dictum, est elit ultrices lacus, in placerat - ante risus vel massa. Maecenas porta purus non feugiat - venenatis. Sed tempus quam id commodo interdum. Aliquam id - ullamcorper diam. Morbi a porttitor tellus. Fusce viverra - euismod laoreet. Cras id sapien quis orci rutrum lacinia. Donec - vitae libero at felis auctor blandit commodo sed libero. Aliquam - tellus risus, sagittis a libero eget, hendrerit feugiat mauris. - Ut vehicula id est non iaculis. Suspendisse potenti. Maecenas in - tellus risus. Proin quis libero et ero s ullamcorper faucibus - non vitae lacus. Vestibulum at ultricies sem, vel euismod dolor. -

- In tempor vel felis ut imperdiet. Pellentesque ac vulputate - lorem, id pellentesque velit. Sed rutrum, nisi vel convallis - rhoncus, ex mi vulputate leo, id hendrerit ipsum sapien in - dolor. Nullam auctor eu nunc sed euismod. Donec molestie velit - nec est bibendum pulvinar. Nullam mattis mollis neque, nec - cursus mi iaculis et. Morbi tempus purus sit amet orci pulvinar - accumsan. Fusce mattis, nisl ac fringilla euismod, orci odio - condimentum sapien, a convallis lacus diam et libero. Nunc non - urna a orci eleifend porttitor in eget nisi. Ut scelerisque - egestas hendrerit. Aenean in tortor cursus, lobortis dolor - iaculis, dignissim velit. Nulla facilisi. -

-
+ + +
+
+ Curso de formação +

+ NR-18 PEMT Plataforma Móvel de Trabalho Aéreo +

+

+ NR 18 PEMT capacita operadores de plataformas elevatórias para + trabalhos em altura com segurança. Com foco na manutenção, inspeção e + uso correto dos EPIs, previne sempre acidentes, garante certificação + MTE e valoriza sua carreira. +

+
    +
  • + + Carga horária de 40 horas +
  • - -

    - In tempor vel felis ut imperdiet. Pellentesque ac vulputate - lorem, id pellentesque velit. Sed rutrum, nisi vel convallis - rhoncus, ex mi vulputate leo, id hendrerit ipsum sapien in - dolor. Nullam auctor eu nunc sed euismod. Donec molestie velit - nec est bibendum pulvinar. Nullam mattis mollis neque, nec - cursus mi iaculis et. Morbi tempus purus sit amet orci pulvinar - accumsan. Fusce mattis, nisl ac fringilla euismod, orci odio - condimentum sapien, a convallis lacus diam et libero. Nunc non - urna a orci eleifend porttitor in eget nisi. Ut scelerisque - egestas hendrerit. Aenean in tortor cursus, lobortis dolor - iaculis, dignissim velit. Nulla facilisi. -

    -
    +
  • + + Certificado com assinatura digital +
  • +
-
-
-
-

Solicite um orçamento

-

- Quer saber como podemos capacitar sua equipe?
- Fale com nossa equipe e receba uma proposta personalizada. -

-
+ +
+
+ + + +
+
+ Homem de negócios + Mulher de negócios +
+
+
+

+ Por que capacitar sua equipe com a EDUSEG® +

+

+ Nós cuidamos da burocracia, oferecemos uma plataforma completa para + simplicar a gestão e capacitação em massa de seus colaboradores. Com + a EDUSEG®, sua empresa se beneficia de uma tecnologia eficiente e + confiável. +

+ +
    +
  • + Conformidade legal garantida +
  • +
  • + Economia de tempo e recursos +
  • +
  • + Relatórios e monitoramento +
  • +
  • + Suporte especializado para gestores +
  • +
+
+
+
+
+ + +
+

Módulos deste curso

+

+ O curso é dividido em módulos para facilitar seu aprendizado e garantir + que você domine todos os aspectos teóricos e práticos. +

+
+
+ +
+
+ + +
    +
  • + + + + Economize até 70% com o curso online +
  • +
  • + + + + Certificado digital reconhecido em até 24 horas +
  • +
  • + + + + Aprenda no seu tempo e de qualquer lugar +
  • +
  • + + + + Suporte de especialistas sempre que precisar +
  • +
+
+ + +

Quem é o instrutor?

+
+ diff --git a/superpage/src/pages/mulher-de-negocios.png b/superpage/src/pages/mulher-de-negocios.png new file mode 100644 index 0000000..e897aad Binary files /dev/null and b/superpage/src/pages/mulher-de-negocios.png differ diff --git a/superpage/src/pages/nr18-plataforma.png b/superpage/src/pages/nr18-plataforma.png new file mode 100644 index 0000000..601b21c Binary files /dev/null and b/superpage/src/pages/nr18-plataforma.png differ