update superpage
This commit is contained in:
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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): ...
|
||||
|
||||
@@ -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): ...
|
||||
|
||||
155
http-api/user.py
155
http-api/user.py
@@ -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)
|
||||
6
superpage/.prettierrc
Normal file
6
superpage/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -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()]
|
||||
}
|
||||
});
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
allowedHosts: ['7aaa-187-57-7-239.ngrok-free.app'],
|
||||
},
|
||||
})
|
||||
|
||||
94
superpage/package-lock.json
generated
94
superpage/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className={clsx(
|
||||
"lg:rounded-2xl",
|
||||
"lg:drop-shadow-sm",
|
||||
"p-3 lg:p-12",
|
||||
'lg:rounded-2xl',
|
||||
'lg:drop-shadow-sm',
|
||||
'p-3 lg:p-12',
|
||||
colorVariants[color],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 <div className={clsx("max-w-7xl mx-auto", className)}>{children}</div>;
|
||||
return (
|
||||
<div className={clsx('max-w-7xl mx-auto max-lg:px-3', className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<IFormInput>();
|
||||
const { register, handleSubmit, reset, formState } = useForm<IFormInput>()
|
||||
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 (
|
||||
<form
|
||||
@@ -41,17 +41,17 @@ export function Form() {
|
||||
<div className="grid lg:grid-cols-2 gap-3 lg:gap-6">
|
||||
<label>
|
||||
Nome
|
||||
<Input {...register("name")} />
|
||||
<Input {...register('name')} />
|
||||
</label>
|
||||
<label>
|
||||
Email
|
||||
<Input {...register("email")} />
|
||||
<Input {...register('email')} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
Mensagem
|
||||
<Input as="textarea" className="h-26" {...register("message")} />
|
||||
<Input as="textarea" className="h-26" {...register('message')} />
|
||||
</label>
|
||||
|
||||
<button
|
||||
@@ -61,20 +61,20 @@ export function Form() {
|
||||
Quero um orçamento
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
interface IInput extends React.HTMLAttributes<HTMLElement> {
|
||||
as?: string;
|
||||
className?: string | undefined;
|
||||
as?: string
|
||||
className?: string | undefined
|
||||
}
|
||||
|
||||
export function Input({ as = "input", className, ...props }: IInput) {
|
||||
export function Input({ as = 'input', className, ...props }: IInput) {
|
||||
return createElement(as, {
|
||||
className: clsx(
|
||||
"border border-transparent focus:border-green-secondary focus:ring ring-green-secondary text-white bg-black p-3 rounded-lg w-full outline-none",
|
||||
'border border-transparent focus:border-green-secondary focus:ring ring-green-secondary text-white bg-black p-3 rounded-lg w-full outline-none',
|
||||
className,
|
||||
),
|
||||
...props,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
102
superpage/src/components/List.jsx
Normal file
102
superpage/src/components/List.jsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel,
|
||||
} from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/solid'
|
||||
|
||||
export function Example() {
|
||||
return (
|
||||
<>
|
||||
<ListItem defaultOpen={false}>
|
||||
<ListButton>1. Introdução</ListButton>
|
||||
<ListPanel>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit
|
||||
amet neque id libero semper vulputate a ut ex. Pellentesque semper
|
||||
ultrices mi in efficitur.
|
||||
</p>
|
||||
<p>
|
||||
Nulla sit amet quam eu neque convallis volutpat. Pellentesque eu
|
||||
commodo sem. Suspendisse ac lobortis massa, ac mattis mauris.
|
||||
Integer malesuada bibendum ante, sed consequat augue convallis et.
|
||||
</p>
|
||||
</ListPanel>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListButton>2. Aspectos gerais dos primeiros socorros</ListButton>
|
||||
<ListPanel>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit
|
||||
amet neque id libero semper vulputate a ut ex. Pellentesque semper
|
||||
ultrices mi in efficitur.
|
||||
</p>
|
||||
<p>
|
||||
Nulla sit amet quam eu neque convallis volutpat. Pellentesque eu
|
||||
commodo sem. Suspendisse ac lobortis massa, ac mattis mauris.
|
||||
Integer malesuada bibendum ante, sed consequat augue convallis et.
|
||||
</p>
|
||||
</ListPanel>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListButton>3. Sinais vitais e avaliação primária</ListButton>
|
||||
<ListPanel>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit
|
||||
amet neque id libero semper vulputate a ut ex. Pellentesque semper
|
||||
ultrices mi in efficitur.
|
||||
</p>
|
||||
<p>
|
||||
Nulla sit amet quam eu neque convallis volutpat. Pellentesque eu
|
||||
commodo sem. Suspendisse ac lobortis massa, ac mattis mauris.
|
||||
Integer malesuada bibendum ante, sed consequat augue convallis et.
|
||||
</p>
|
||||
</ListPanel>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListButton>4. Parada cardiorrespiratória</ListButton>
|
||||
<ListPanel>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed sit
|
||||
amet neque id libero semper vulputate a ut ex. Pellentesque semper
|
||||
ultrices mi in efficitur.
|
||||
</p>
|
||||
<p>
|
||||
Nulla sit amet quam eu neque convallis volutpat. Pellentesque eu
|
||||
commodo sem. Suspendisse ac lobortis massa, ac mattis mauris.
|
||||
Integer malesuada bibendum ante, sed consequat augue convallis et.
|
||||
</p>
|
||||
</ListPanel>
|
||||
</ListItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListItem({ children, ...props }) {
|
||||
return (
|
||||
<Disclosure
|
||||
as="div"
|
||||
className="bg-white/10 rounded-lg w-full data-open:bg-white/15"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Disclosure>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListButton({ children }) {
|
||||
return (
|
||||
<DisclosureButton className="group flex items-center justify-between w-full px-5 py-3 cursor-pointer">
|
||||
<span className="text-left">{children}</span>
|
||||
<ChevronDownIcon className="size-5 fill-white/60 group-data-[hover]:fill-white/50 group-data-[open]:rotate-180" />
|
||||
</DisclosureButton>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListPanel({ children }) {
|
||||
return (
|
||||
<DisclosurePanel className="text-sm/6 text-white/70 space-y-2 px-5 pb-3">
|
||||
{children}
|
||||
</DisclosurePanel>
|
||||
)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export function Regular(props) {
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function Smallest(props) {
|
||||
@@ -95,5 +95,5 @@ export function Smallest(props) {
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,32 +4,32 @@ import {
|
||||
ComboboxInput,
|
||||
ComboboxOption,
|
||||
ComboboxOptions,
|
||||
} from "@headlessui/react";
|
||||
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
} from '@headlessui/react'
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import clsx from 'clsx'
|
||||
import { useState } from 'react'
|
||||
|
||||
const people = [
|
||||
{ id: 1, name: "8 horas" },
|
||||
{ id: 2, name: "40 horas" },
|
||||
];
|
||||
{ id: 1, name: '8 horas' },
|
||||
{ id: 2, name: '40 horas' },
|
||||
]
|
||||
|
||||
export function Select() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [selected, setSelected] = useState(people[1]);
|
||||
const [query, setQuery] = useState('')
|
||||
const [selected, setSelected] = useState(people[1])
|
||||
|
||||
const filteredPeople =
|
||||
query === ""
|
||||
query === ''
|
||||
? people
|
||||
: people.filter((person) => {
|
||||
return person.name.toLowerCase().includes(query.toLowerCase());
|
||||
});
|
||||
return person.name.toLowerCase().includes(query.toLowerCase())
|
||||
})
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
value={selected}
|
||||
onChange={(value) => setSelected(value)}
|
||||
onClose={() => setQuery("")}
|
||||
onClose={() => setQuery('')}
|
||||
>
|
||||
<div className="relative">
|
||||
<ComboboxInput
|
||||
@@ -50,5 +50,5 @@ export function Select() {
|
||||
))}
|
||||
</ComboboxOptions>
|
||||
</Combobox>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import "../styles/app.css";
|
||||
import { Regular as Logo } from "@components/Logo";
|
||||
import { Container } from "@components/Container";
|
||||
import { Select } from "@components/Select";
|
||||
import '../styles/app.css'
|
||||
import { Regular as Logo } from '@components/Logo'
|
||||
import { Container } from '@components/Container'
|
||||
import {
|
||||
ArrowLeftStartOnRectangleIcon,
|
||||
AcademicCapIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
ChevronDownIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Container className="flex items-center p-3">
|
||||
<Container className="flex items-center py-3">
|
||||
<Logo className="h-8" />
|
||||
|
||||
<div class="ml-auto">
|
||||
@@ -31,49 +31,63 @@ import {
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<section class="bg-lime-400 sticky top-0 z-10">
|
||||
<Container className="flex items-center p-3">
|
||||
<div class="flex gap-1.5 lg:gap-3 items-center">
|
||||
<div class="bg-black p-1.5 lg:p-3 rounded-lg lg:rounded-xl">
|
||||
<AcademicCapIcon className="w-5 fill-lime-400" />
|
||||
</div>
|
||||
|
||||
<section class="bg-lime-400 sticky top-0 z-10 py-3">
|
||||
<Container className="flex items-center">
|
||||
<div
|
||||
class="flex gap-1.5 lg:gap-3 items-center rounded hover:ring-2 ring-black"
|
||||
>
|
||||
<div class="flex gap-1.5">
|
||||
<div class="text-black truncate max-lg:max-w-36">
|
||||
<div class="text-black truncate max-lg:max-w-36 font-semibold">
|
||||
NR-18 PEMT Plataforma Móvel de Trabalho Aéreo
|
||||
</div>
|
||||
<!-- <Select client:load /> -->
|
||||
<ChevronDownIcon className="w-5 fill-black" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
class="bg-black p-2.5 rounded-md cursor-pointer text-sm"
|
||||
class="bg-black font-semibold py-2.5 px-3 rounded-md cursor-pointer"
|
||||
>
|
||||
Comprar R$149,00
|
||||
Contratar agora
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<div class="mb-12">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<footer class="bg-stone-900 p-3 lg:py-10">
|
||||
<Container className="space-y-5">
|
||||
<div class="space-y-1">
|
||||
<footer class="bg-stone-900 py-3 lg:py-10 hidden">
|
||||
<Container className="space-y-5 lg:grid grid-cols-6">
|
||||
<div class="space-y-2.5">
|
||||
<Logo className="h-10" />
|
||||
<p class="text-sm text-green-tertiary leading-4">
|
||||
<p class="text-sm/4 tracking-tighter text-balance">
|
||||
Educação que garante<br />sua segurança.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<h6 class="text-xl">Cursos</h6>
|
||||
<ul>
|
||||
<div class="space-y-1 lg:col-span-5">
|
||||
<h6 class="text-2xl/4">Conheça outros cursos</h6>
|
||||
<ul class="mt-2.5">
|
||||
<li>Lei Lucas</li>
|
||||
<li>NR-18 PEMT Plataforma Móvel de Trabalho Aéreo</li>
|
||||
<li>NR-35 Trabalho em Altura</li>
|
||||
<li>NR-10 Básico</li>
|
||||
<li>
|
||||
NR-12 Máquinas e Equipamentos <span
|
||||
class="bg-lime-300 py-0.5 px-1 text-black rounded text-sm/2"
|
||||
>Reciclagem</span
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
NR-18 PEMT Plataforma Elevatória Móvel de Trabalho <span
|
||||
class="bg-lime-300 py-0.5 px-1 text-black rounded text-sm/2"
|
||||
>Reciclagem</span
|
||||
>
|
||||
</li>
|
||||
<li>LOTO Lockout Tagout</li>
|
||||
<li>NR-18 Sinaleiro e Amarrador de Cargas para Içamento</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
BIN
superpage/src/pages/homem-de-negocios.png
Normal file
BIN
superpage/src/pages/homem-de-negocios.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 300 KiB |
@@ -1,69 +1,226 @@
|
||||
---
|
||||
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'
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Container className="lg:space-y-5 lg:p-3">
|
||||
<!-- <div class="max-lg:px-5">
|
||||
<Bookmark className="h-96" />
|
||||
</div> -->
|
||||
<Card>
|
||||
<p>
|
||||
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.
|
||||
</p><p>
|
||||
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.
|
||||
<Container className="py-3 lg:py-12 lg:flex items-center gap-5">
|
||||
<Image
|
||||
src={nr18plataforma}
|
||||
alt="NR-18"
|
||||
class="size-1/3 object-bottom hidden lg:block"
|
||||
/>
|
||||
<section>
|
||||
<div class="space-y-5">
|
||||
<span class="font-medium">Curso de formação</span>
|
||||
<h1 class="font-semibold text-5xl lg:text-7xl">
|
||||
NR-18 PEMT Plataforma Móvel de Trabalho Aéreo
|
||||
</h1>
|
||||
<p class="text-base/6">
|
||||
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.
|
||||
</p>
|
||||
<ul class="lg:flex gap-3">
|
||||
<li class="flex gap-1">
|
||||
<ClockIcon className="w-5" />
|
||||
<span>Carga horária de 40 horas</span>
|
||||
</li>
|
||||
|
||||
<li class="flex gap-1">
|
||||
<CheckBadgeIcon className="w-5 fill-blue-400" />
|
||||
<span>Certificado com assinatura digital</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
class="flex max-lg:flex-col justify-center gap-2.5 lg:gap-8 lg:mt-16"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="text-black font-semibold bg-lime-400 rounded p-3.5 hover:bg-white max-lg:text-center"
|
||||
>
|
||||
Contratar agora
|
||||
</a>
|
||||
<a href="http://bit.ly/3RlROu6" class="flex flex-col">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-bold">4.7</span>
|
||||
<ul class="flex">
|
||||
<li>
|
||||
<StarIcon className="w-4 text-yellow-500 fill-yellow-500" />
|
||||
</li>
|
||||
<li>
|
||||
<StarIcon className="w-4 text-yellow-500 fill-yellow-500" />
|
||||
</li>
|
||||
<li>
|
||||
<StarIcon className="w-4 text-yellow-500 fill-yellow-500" />
|
||||
</li>
|
||||
<li>
|
||||
<StarIcon className="w-4 text-yellow-500 fill-yellow-500" />
|
||||
</li>
|
||||
<li><StarIcon className="w-4" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<span>66 avaliações no Google</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Container>
|
||||
|
||||
<Container className="h-full">
|
||||
<div class="border border-lime-400 rounded-2xl grid grid-cols-3">
|
||||
<div class="bg-lime-300 rounded-2xl p-6 relative h-[36rem]">
|
||||
<Image
|
||||
alt="Homem de negócios"
|
||||
src={homemdenegocios}
|
||||
class="absolute bottom-0 -left-32"
|
||||
/>
|
||||
<Image
|
||||
alt="Mulher de negócios"
|
||||
src={mulherdenegocios}
|
||||
class="absolute bottom-0 -right-26"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-2 flex items-center">
|
||||
<div class="w-7/12 mx-auto space-y-5 p-6">
|
||||
<h2 class="text-4xl font-semibold">
|
||||
Por que capacitar sua equipe com a EDUSEG®
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<ul class="grid grid-cols-2 gap-2.5">
|
||||
<li class="bg-white/10 p-5 rounded-lg">
|
||||
Conformidade legal garantida
|
||||
</li>
|
||||
<li class="bg-white/10 p-5 rounded-lg">
|
||||
Economia de tempo e recursos
|
||||
</li>
|
||||
<li class="bg-white/10 p-5 rounded-lg">
|
||||
Relatórios e monitoramento
|
||||
</li>
|
||||
<li class="bg-white/10 p-5 rounded-lg">
|
||||
Suporte especializado para gestores
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<Container
|
||||
className="py-3 lg:py-12 grid gap-2.5 lg:grid-cols-3 lg:gap-5 lg:w-3/6"
|
||||
>
|
||||
<div class="space-y-2.5">
|
||||
<h1 class="text-4xl lg:text-5xl text-pretty">Módulos deste curso</h1>
|
||||
<p class="text-base/6">
|
||||
O curso é dividido em módulos para facilitar seu aprendizado e garantir
|
||||
que você domine todos os aspectos teóricos e práticos.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lg:col-span-2 flex flex-col gap-1.5">
|
||||
<Example client:load />
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<Container className="py-6 hidden">
|
||||
<ul class="flex gap-2.5">
|
||||
<li class="flex items-center gap-2.5">
|
||||
<span class="bg-white/10 p-2 rounded">
|
||||
<BanknotesIcon className="w-6" />
|
||||
</span>
|
||||
<span class="text-base/5">Economize até 70% com o curso online</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2.5">
|
||||
<span class="bg-white/10 p-2 rounded">
|
||||
<AcademicCapIcon className="w-6" />
|
||||
</span>
|
||||
<span class="text-base/6"
|
||||
>Certificado digital reconhecido em até 24 horas</span
|
||||
>
|
||||
</li>
|
||||
<li class="flex items-center gap-2.5">
|
||||
<span class="bg-white/10 p-2 rounded">
|
||||
<GlobeAmericasIcon className="w-6" />
|
||||
</span>
|
||||
<span>Aprenda no seu tempo e de qualquer lugar</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2.5">
|
||||
<span class="bg-white/10 p-2 rounded">
|
||||
<PhoneIcon className="w-6" />
|
||||
</span>
|
||||
<span>Suporte de especialistas sempre que precisar</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Container>
|
||||
|
||||
<Container className="hidden">
|
||||
<h2>Quem é o instrutor?</h2>
|
||||
</Container>
|
||||
<!--
|
||||
<Container className="lg:space-y-5 lg:p-3">
|
||||
<Card className="space-y-2.5">
|
||||
<h1 class="text-2xl font-medium">
|
||||
Garanta a capacitação para sua empresa
|
||||
</h1>
|
||||
|
||||
<ul>
|
||||
<li>Pagamento flexível</li>
|
||||
<li>Acesso imediato</li>
|
||||
<li>Carga horária</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li>Economize até 70% com o curso online</li>
|
||||
<li>Certificado Digital reconhecido em até 24 Horas</li>
|
||||
<li>Aprenda no seu tempo e de qualquer lugar</li>
|
||||
<li>Suporte de especialistas sempre que precisar</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card color="darker">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</Card>
|
||||
<section class="lg:grid grid-cols-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-medium">Módulos deste treinamento</h1>
|
||||
<div>
|
||||
O curso é dividido em módulos para facilitar seu aprendizado
|
||||
e garantir que você domine todos os aspectos teóricos e
|
||||
práticos.
|
||||
</div>
|
||||
</div>
|
||||
<div><Example client:load /></div>
|
||||
</section>
|
||||
</Container>
|
||||
|
||||
<section class="grid lg:grid-cols-2 gap-6 p-3">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-medium text-4xl">Solicite um orçamento</h4>
|
||||
<p>
|
||||
Quer saber como podemos capacitar sua equipe?<br
|
||||
class="max-lg:hidden"
|
||||
/>
|
||||
Quer saber como podemos capacitar sua equipe?
|
||||
<br class="max-lg:hidden" />
|
||||
Fale com nossa equipe e receba uma proposta personalizada.
|
||||
</p>
|
||||
</div>
|
||||
@@ -73,5 +230,5 @@ import Layout from "@layouts/Layout.astro";
|
||||
<Form client:load />
|
||||
</Card>
|
||||
</section>
|
||||
</Container>
|
||||
-->
|
||||
</Layout>
|
||||
|
||||
BIN
superpage/src/pages/mulher-de-negocios.png
Normal file
BIN
superpage/src/pages/mulher-de-negocios.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 256 KiB |
BIN
superpage/src/pages/nr18-plataforma.png
Normal file
BIN
superpage/src/pages/nr18-plataforma.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 948 KiB |
Reference in New Issue
Block a user