api js
6
makefile
@@ -1,6 +1,6 @@
|
|||||||
VENV=source ./.venv/bin/activate;
|
VENV=source ./.venv/bin/activate;
|
||||||
ALEMBIC=alembic -c ./server/backend/database/alembic/alembic.ini
|
ALEMBIC=alembic -c ./server/backend/database/alembic/alembic.ini
|
||||||
.PHONY: run run_debug migrate_head migrate_down migrate_history migrate_current migrate
|
.PHONY: run run_debug migrate_head migrate_down migrate_history migrate_current migrate_heads migrate_stamp migrate
|
||||||
run:
|
run:
|
||||||
$(VENV) python run.py --user_name admin
|
$(VENV) python run.py --user_name admin
|
||||||
run_debug:
|
run_debug:
|
||||||
@@ -13,5 +13,9 @@ migrate_history:
|
|||||||
$(VENV) $(ALEMBIC) history
|
$(VENV) $(ALEMBIC) history
|
||||||
migrate_current:
|
migrate_current:
|
||||||
$(VENV) $(ALEMBIC) current
|
$(VENV) $(ALEMBIC) current
|
||||||
|
migrate_heads:
|
||||||
|
$(VENV) $(ALEMBIC) heads
|
||||||
|
migrate_stamp:
|
||||||
|
$(VENV) $(ALEMBIC) stamp head
|
||||||
migrate:
|
migrate:
|
||||||
$(VENV) $(ALEMBIC) revision --autogenerate
|
$(VENV) $(ALEMBIC) revision --autogenerate
|
||||||
1
run.py
@@ -8,6 +8,7 @@ async def init_admin(user_name: str):
|
|||||||
admin_user = {
|
admin_user = {
|
||||||
"code": "123456",
|
"code": "123456",
|
||||||
"name": user_name,
|
"name": user_name,
|
||||||
|
"middlename": user_name,
|
||||||
"surname": user_name,
|
"surname": user_name,
|
||||||
"admin": True
|
"admin": True
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 15b87c1584bf
|
||||||
|
Revises: 2e39b25a3b28
|
||||||
|
Create Date: 2026-03-13 22:53:40.498042
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '15b87c1584bf'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = 'dd476c0dcf61'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'food')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('food', sa.BOOLEAN(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 1fa13c2c4df4
|
||||||
|
Revises: 15b87c1584bf
|
||||||
|
Create Date: 2026-03-13 22:56:36.115487
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '1fa13c2c4df4'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = '15b87c1584bf'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('type_of_food', sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'type_of_food')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 55f24e794643
|
||||||
|
Revises: 1fa13c2c4df4
|
||||||
|
Create Date: 2026-03-13 23:03:47.236864
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '55f24e794643'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = '1fa13c2c4df4'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('middlename', sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'middlename')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: dd476c0dcf61
|
||||||
|
Revises: 1e2bd98e74a5
|
||||||
|
Create Date: 2026-03-13 22:38:32.065614
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'dd476c0dcf61'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = '1e2bd98e74a5'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'alco')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('alco', sa.BOOLEAN(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -23,11 +23,11 @@ class User(Base):
|
|||||||
code = Column(String, unique=True, nullable=True)
|
code = Column(String, unique=True, nullable=True)
|
||||||
|
|
||||||
name = Column(String, nullable=True)
|
name = Column(String, nullable=True)
|
||||||
|
middlename=Column(String, nullable=True)
|
||||||
surname = Column(String, nullable=True)
|
surname = Column(String, nullable=True)
|
||||||
text_field = Column(String, nullable=True)
|
text_field = Column(String, nullable=True)
|
||||||
food = Column(Boolean)
|
type_of_food = Column(String, nullable=True)
|
||||||
alco = Column(Boolean)
|
types_of_alco = Column(String, default="Nothing", nullable=True)
|
||||||
types_of_alco = Column(String, default="Nothing")
|
|
||||||
|
|
||||||
activated = Column(Boolean)
|
activated = Column(Boolean)
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field, field_validator
|
|||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from pydantic.types import StringConstraints
|
from pydantic.types import StringConstraints
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
from typing import Optional
|
||||||
import re
|
import re
|
||||||
|
|
||||||
NameStr = Annotated[
|
NameStr = Annotated[
|
||||||
@@ -23,16 +24,18 @@ class UserOut(BaseModel):
|
|||||||
name: NameStr = Field(..., description="Name of the guest")
|
name: NameStr = Field(..., description="Name of the guest")
|
||||||
surname: NameStr = Field(..., description="Surname of the guest")
|
surname: NameStr = Field(..., description="Surname of the guest")
|
||||||
|
|
||||||
class UserUpdate(UserAccess):
|
class UserUpdate(BaseModel):
|
||||||
name: NameStr = Field(..., description="Name of the guest")
|
code: Optional[str] = Field(None, min_length=6, max_length=6, description="Code of the guest")
|
||||||
surname: NameStr = Field(..., description="Surname of the guest")
|
name: Optional[NameStr] = Field(None, description="Name of the guest")
|
||||||
text_field: str = Field("", max_length=500, description="what the guest wants")
|
middlename: Optional[NameStr] = Field(None, description="Middlename of the guest")
|
||||||
activated: bool = Field(False, description="activation of the guest")
|
surname: Optional[NameStr] = Field(None, description="Surname of the guest")
|
||||||
food: bool = Field(False, description="Options meat or fish")
|
text_field: Optional[str] = Field(None, max_length=500, description="what the guest wants")
|
||||||
alco: bool = Field(False, description="if the guest will drink alco or not")
|
activated: Optional[bool] = Field(None, description="activation of the guest")
|
||||||
types_of_alco: str = Field("", description="types of alco")
|
type_of_food: Optional[str] = Field(None, description="meat or fish")
|
||||||
|
types_of_alco: Optional[str] = Field(None, description="types of alco")
|
||||||
|
|
||||||
class UserCreate(UserUpdate):
|
class UserCreate(UserUpdate):
|
||||||
|
code: str = Field(..., min_length=6, max_length=6, description="Code of the guest")
|
||||||
admin:bool = Field(False, description="Admin privilegies")
|
admin:bool = Field(False, description="Admin privilegies")
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
DIR:str
|
DIR:str
|
||||||
|
|||||||
0
server/frontend/auth/login.css
Normal file
21
server/frontend/auth/login.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login</title>
|
||||||
|
<link rel="stylesheet" href="login.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="glass-container">
|
||||||
|
<div class="login-box">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form id="loginForm">
|
||||||
|
<input type="password" id="password" name="password" required placeholder="Code">
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="login.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
server/frontend/auth/login.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
function getToken() {
|
||||||
|
return localStorage.getItem("token") || sessionStorage.getItem("token");
|
||||||
|
}
|
||||||
|
function tokenCheck(){
|
||||||
|
const token = getToken();
|
||||||
|
if (token) {
|
||||||
|
window.location.href = "http://localhost:5500/server/frontend/main/index.html";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const userData = {
|
||||||
|
password
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8000/api/auth", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
password: userData.password
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json(); // читаем только один раз
|
||||||
|
if (response.ok) { // сохраняем только при успехе
|
||||||
|
// в sessionstorage до перезахода в браузер(
|
||||||
|
sessionStorage.setItem("token", data.access_token);
|
||||||
|
|
||||||
|
window.location.href = 'http://localhost:5500/server/frontend/main/index.html';
|
||||||
|
} else { //парсинг и вывод ошибок, если есть
|
||||||
|
if (Array.isArray(data.detail)) {
|
||||||
|
const messages = data.detail.map(e => {
|
||||||
|
const field = e.loc.filter(locPart => locPart !== 'body').join(' -> ');
|
||||||
|
return `${field}: ${e.msg}`;
|
||||||
|
});
|
||||||
|
showError(messages);
|
||||||
|
} else if (typeof data.detail === 'string') {
|
||||||
|
showError([data.detail]);
|
||||||
|
} else {
|
||||||
|
showError(['Unknown error']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showError(['Connection error: ' + err.message]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function showError(messages){ //Добавление их на form со стилями
|
||||||
|
let errorElem = document.getElementById('formError');
|
||||||
|
let container = document.getElementById('glass-container');
|
||||||
|
if (!errorElem){
|
||||||
|
errorElem = document.createElement('div');
|
||||||
|
errorElem.style.transition="3s";
|
||||||
|
errorElem.id = 'formError';
|
||||||
|
errorElem.style.color = 'red';
|
||||||
|
errorElem.style.marginTop = '20px';
|
||||||
|
errorElem.style.fontSize = '14px';
|
||||||
|
errorElem.style.fontWeight = '100';
|
||||||
|
errorElem.style.marginBottom = '20px';
|
||||||
|
errorElem.style.lineHeight="120%";
|
||||||
|
errorElem.style.height = 'auto';
|
||||||
|
const form = document.getElementById('loginForm');
|
||||||
|
form.insertAdjacentElement('afterend', errorElem);
|
||||||
|
};
|
||||||
|
errorElem.innerHTML = '';
|
||||||
|
messages.forEach(msg => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.style.listStyleType="none";
|
||||||
|
li.textContent = msg;
|
||||||
|
errorElem.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
69
server/frontend/main/api.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
function getToken() {
|
||||||
|
return localStorage.getItem("token") || sessionStorage.getItem("token");
|
||||||
|
}
|
||||||
|
function tokenCheck(){
|
||||||
|
const token = getToken();
|
||||||
|
if (!token) {
|
||||||
|
window.location.href = "http://localhost:5500/server/frontend/auth/login.html";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenCheck()
|
||||||
|
document.getElementById('logoutForm').addEventListener('submit', async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
sessionStorage.removeItem("token");
|
||||||
|
tokenCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const form = document.querySelector(".form-info");
|
||||||
|
|
||||||
|
form.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// try {
|
||||||
|
// собираем данные из формы
|
||||||
|
const name = document.getElementById('ffname').value || "";
|
||||||
|
const middlename = document.getElementById('fmname').value || "";
|
||||||
|
const surname = document.getElementById('flname').value || "";
|
||||||
|
const text_field = document.getElementById('text_field')?.value || "";
|
||||||
|
const food = document.querySelector('input[name="food"]:checked')?.value || "";
|
||||||
|
const types_of_alco = Array.from(document.querySelectorAll('input[name="drink"]:checked'))
|
||||||
|
.map(el => el.value)
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
const guestData = {
|
||||||
|
name: name,
|
||||||
|
middlename: middlename,
|
||||||
|
surname: surname,
|
||||||
|
text_field: text_field,
|
||||||
|
activated: true,
|
||||||
|
types_of_food: food,
|
||||||
|
types_of_alco: types_of_alco
|
||||||
|
};
|
||||||
|
console.log(guestData)
|
||||||
|
|
||||||
|
// отправка на /api/update с Authorization
|
||||||
|
// const updateResponse = await fetch('/api/update', {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// 'Authorization': `Bearer ${token}`
|
||||||
|
// },
|
||||||
|
// body: JSON.stringify(guestData)
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!updateResponse.ok) {
|
||||||
|
// throw new Error('Ошибка при отправке формы');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const updateData = await updateResponse.json();
|
||||||
|
// console.log('Форма успешно отправлена:', updateData);
|
||||||
|
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error(err);
|
||||||
|
// alert('Ошибка: ' + err.message);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 362 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 525 KiB After Width: | Height: | Size: 525 KiB |
|
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 920 B |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
@@ -64,7 +64,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<content class="content">
|
<main class="content">
|
||||||
<section class="heading" id="heading">
|
<section class="heading" id="heading">
|
||||||
<article class="heading_content">
|
<article class="heading_content">
|
||||||
<div class="heading_cont">
|
<div class="heading_cont">
|
||||||
@@ -300,38 +300,27 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<div class="transfer_inner">
|
<div class="transfer_inner">
|
||||||
<div class="transfer_map">
|
<div class="transfer_map">
|
||||||
<map class="map">
|
<div class="map">
|
||||||
<div style="position:relative;overflow:hidden; border-radius: 33px;">
|
<div style="position:relative;overflow:hidden; border-radius: 33px;">
|
||||||
<a href="https://yandex.com/maps/org/glavnoye_upravleniye_zapisi_aktov_grazhdanskogo_sostoyaniya/104116805635/?utm_medium=mapframe&utm_source=maps"
|
<a href="https://www.google.com/maps/place/Glavnoye+Upravleniye+Zapisi+Aktov+Grazhdanskogo+Sostoyaniya+Tverskoy+Oblasti/@56.8571039,35.901453,17z/data=!3m1!4b1!4m6!3m5!1s0x46b687a4d4edacad:0x15df9a38c874eb5d!8m2!3d56.857101!4d35.9040279!16s%2Fg%2F11ls6j2lvj?entry=ttu&g_ep=EgoyMDI2MDMxMS4wIKXMDSoASAFQAw%3D%3D"
|
||||||
style="color:#eee;font-size:12px;position:absolute;top:0px;">
|
style="color:#eee;font-size:12px;position:absolute;top:0px;">
|
||||||
Главное управление записи актов гражданского состояния</a>
|
Главное управление записи актов гражданского состояния</a>
|
||||||
<iframe
|
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2181.349338718406!2d35.901452977352925!3d56.85710390639812!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x46b687a4d4edacad%3A0x15df9a38c874eb5d!2sGlavnoye%20Upravleniye%20Zapisi%20Aktov%20Grazhdanskogo%20Sostoyaniya%20Tverskoy%20Oblasti!5e0!3m2!1sen!2s!4v1773428540512!5m2!1sen!2s" width="760" height="500" style="border:1px;" allowfullscreen="true" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||||
src="https://yandex.com/map-widget/v1/?ll=35.903981%2C56.857281&mode=search&oid=104116805635&ol=biz&sctx=ZAAAAAgBEAAaKAoSCY3PZP88XSFAEVrW%2FWMhDklAEhIJzjgNUYU%2Fsz8RXD6Skh6Gtj8iBgABAgMEBSgKOABAgqANSAFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9FbmFibGVkPTFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9NYXhhZHY9MTViRHJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9SZWdpb25JZHM9WzEsMTAxNzRdYkByZWFycj1zY2hlbWVfTG9jYWwvR2VvdXBwZXIvQWR2ZXJ0cy9NYXhhZHZUb3BNaXgvTWF4YWR2Rm9yTWl4PTEwagJkZZ0BzczMPaABAKgBAL0BByG7v8IBBoPA4e6DA4ICE9C30LDQs9GBINGC0LLQtdGA0YyKAgCSAgIxNJoCDGRlc2t0b3AtbWFwcw%3D%3D&sll=35.903981%2C56.857281&sspn=0.001002%2C0.000999&text=%D0%B7%D0%B0%D0%B3%D1%81%20%D1%82%D0%B2%D0%B5%D1%80%D1%8C&z=19.23"
|
|
||||||
width="760" height="500" frameborder="1" allowfullscreen="true"
|
|
||||||
style="position:relative;"></iframe>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="map_adress">Тверь, Свободный переулок, 5</p>
|
<p class="map_adress">Тверь, Свободный переулок, 5</p>
|
||||||
<h6 class="map_info">Начало в</h6>
|
<h6 class="map_info">Начало в</h6>
|
||||||
<h6 class="map_time">10:30</h6>
|
<h6 class="map_time">10:30</h6>
|
||||||
</map>
|
</div>
|
||||||
<map class="map">
|
<div class="map">
|
||||||
<div style="position:relative;overflow:hidden; border-radius: 33px;">
|
<div style="position:relative;overflow:hidden; border-radius: 33px;">
|
||||||
<a href="https://yandex.com/maps/org/loft_1870/90344521327/?utm_medium=mapframe&utm_source=maps"
|
<a href="https://www.google.com/maps/place/Loft+1870+%7C+%D0%9F%D0%BB%D0%BE%D1%89%D0%B0%D0%B4%D0%BA%D0%B0+%D0%B4%D0%BB%D1%8F+%D0%BC%D0%B5%D1%80%D0%BE%D0%BF%D1%80%D0%B8%D1%8F%D1%82%D0%B8%D0%B9+%D0%A2%D0%B2%D0%B5%D1%80%D1%8C+%7C+%D0%BA%D0%B5%D0%B9%D1%82%D0%B5%D1%80%D0%B8%D0%BD%D0%B3+%D0%BD%D0%B0+%D1%81%D0%B2%D0%B0%D0%B4%D1%8C%D0%B1%D1%83,+%D0%B1%D0%B0%D0%BD%D0%BA%D0%B5%D1%82%D0%BD%D1%8B%D0%B9+%D0%B7%D0%B0%D0%BB/@56.850656,35.8603082,17.32z/data=!3m1!5s0x46b687a53bac0dcf:0x5c270d07d710d5a!4m6!3m5!1s0x46b68764eb0c1387:0x7cb135f5414bc1e0!8m2!3d56.8508612!4d35.8650531!16s%2Fg%2F11p0wc5v7q?entry=ttu&g_ep=EgoyMDI2MDMxMS4wIKXMDSoASAFQAw%3D%3D"
|
||||||
style="color:#eee;font-size:12px;position:absolute;top:0px;">Лофт 1870</a><a
|
style="color:#eee;font-size:12px;position:absolute;top:0px;">Лофт 1870</a>
|
||||||
href="https://yandex.com/maps/14/tver/category/banquet_hall/184108315/?utm_medium=mapframe&utm_source=maps"
|
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1749.675516895515!2d35.86030818806184!3d56.850656040536194!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x46b68764eb0c1387%3A0x7cb135f5414bc1e0!2zTG9mdCAxODcwIHwg0J_Qu9C-0YnQsNC00LrQsCDQtNC70Y8g0LzQtdGA0L7Qv9GA0LjRj9GC0LjQuSDQotCy0LXRgNGMIHwg0LrQtdC50YLQtdGA0LjQvdCzINC90LAg0YHQstCw0LTRjNCx0YMsINCx0LDQvdC60LXRgtC90YvQuSDQt9Cw0Ls!5e0!3m2!1sen!2s!4v1773428705434!5m2!1sen!2s" width="760" height="500" style="border:1px;" allowfullscreen="true" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||||
style="color:#eee;font-size:12px;position:absolute;top:14px;">Банкетный зал в
|
|
||||||
Твери</a><a
|
|
||||||
href="https://yandex.com/maps/14/tver/category/organization_of_events/184108329/?utm_medium=mapframe&utm_source=maps"
|
|
||||||
style="color:#eee;font-size:12px;position:absolute;top:28px;">Организация мероприятий в
|
|
||||||
Твери</a><iframe
|
|
||||||
src="https://yandex.com/map-widget/v1/?filter=alternate_vertical%3AWhatWhere&ll=35.862462%2C56.851043&mode=search&oid=90344521327&ol=biz&sctx=ZAAAAAgCEAAaKAoSCUqaP6a180FAEbg9QWK7bUxAEhIJ8wTCTrFqUD8RgXwJFRxeUD8iBgABAgMEBSgKOABAgaANSAFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9FbmFibGVkPTFiOnJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9NYXhhZHY9MTViRHJlYXJyPXNjaGVtZV9Mb2NhbC9HZW91cHBlci9BZHZlcnRzL0N1c3RvbU1heGFkdi9SZWdpb25JZHM9WzEsMTAxNzRdYkByZWFycj1zY2hlbWVfTG9jYWwvR2VvdXBwZXIvQWR2ZXJ0cy9NYXhhZHZUb3BNaXgvTWF4YWR2Rm9yTWl4PTEwagJydZ0BzczMPaABAKgBAL0BvACOKsIBKO%2F8z8fQAtiGgZHvBbCG7O6vBo79quPrAY6e17vSBOOAnfSN%2BvnKggGCAhPQu9C%2B0YTRgiDRgtCy0LXRgNGMigIAkgICMTSaAgxkZXNrdG9wLW1hcHM%3D&sll=35.862462%2C56.851043&sspn=0.007794%2C0.007771&text=%D0%BB%D0%BE%D1%84%D1%82%20%D1%82%D0%B2%D0%B5%D1%80%D1%8C&z=16.27"
|
|
||||||
width="760" height="500" frameborder="1" allowfullscreen="true"
|
|
||||||
style="position:relative;"></iframe>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="map_adress">Тверь, улица Двор Пролетарки, 16</p>
|
<p class="map_adress">Тверь, улица Двор Пролетарки, 16</p>
|
||||||
<h6 class="map_info">Начало в</h6>
|
<h6 class="map_info">Начало в</h6>
|
||||||
<h6 class="map_time">14:00</h6>
|
<h6 class="map_time">14:00</h6>
|
||||||
</map>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -434,20 +423,20 @@
|
|||||||
Просим Вас заполнить форму и подтвердить своё участие
|
Просим Вас заполнить форму и подтвердить своё участие
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<form class="form-example">
|
<form class="form-info">
|
||||||
|
|
||||||
<!-- Левая колонка -->
|
<!-- Левая колонка -->
|
||||||
<div class="form_name">
|
<div class="form_name">
|
||||||
<label>Имя
|
<label>Имя
|
||||||
<input type="text" name="firstName" required>
|
<input type="text" name="firstName" id="ffname" required>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>Отчество
|
<label>Отчество
|
||||||
<input type="text" name="middleName" required>
|
<input type="text" name="middleName" id="fmname" required>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>Фамилия
|
<label>Фамилия
|
||||||
<input type="text" name="lastName" required>
|
<input type="text" name="lastName" id="flname" required>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -456,12 +445,12 @@
|
|||||||
<p class="block_title">Горячее блюдо</p>
|
<p class="block_title">Горячее блюдо</p>
|
||||||
|
|
||||||
<label class="option">
|
<label class="option">
|
||||||
<input type="radio" name="food" value="meat">
|
<input type="radio" name="food" id="rmeat" value="meat" required>
|
||||||
Мясо
|
Мясо
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="option">
|
<label class="option">
|
||||||
<input type="radio" name="food" value="fish">
|
<input type="radio" name="food" id="rfish" value="fish">
|
||||||
Рыба
|
Рыба
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -470,21 +459,21 @@
|
|||||||
<div class="form_drink">
|
<div class="form_drink">
|
||||||
<p class="block_title">Напитки</p>
|
<p class="block_title">Напитки</p>
|
||||||
|
|
||||||
<label class="option"><input type="checkbox" name="drink" value="champagne"> Шампанское</label>
|
<label class="option"><input type="checkbox" name="drink" value="champagne" id="cchampagne"> Шампанское</label>
|
||||||
<label class="option"><input type="checkbox" name="drink" value="wine"> Вино</label>
|
<label class="option"><input type="checkbox" name="drink" value="wine" id="cwine"> Вино</label>
|
||||||
<label class="option"><input type="checkbox" name="drink" value="vodka"> Водка</label>
|
<label class="option"><input type="checkbox" name="drink" value="vodka" id="cvodka"> Водка</label>
|
||||||
<label class="option"><input type="checkbox" name="drink" value="whiskey"> Виски</label>
|
<label class="option"><input type="checkbox" name="drink" value="whiskey" id="cwhiskey"> Виски</label>
|
||||||
<label class="option"><input type="checkbox" name="drink" value="tequila"> Текила</label>
|
<label class="option"><input type="checkbox" name="drink" value="tequila" id="ctequila"> Текила</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="answer_btn">
|
<button type="submit" class="answer_btn" id="bsubmit">
|
||||||
Подтвердить участие
|
Подтвердить участие
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</content>
|
</main>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="footer_inner">
|
<div class="footer_inner">
|
||||||
@@ -534,6 +523,7 @@
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script>
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
|
<script src="api.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -455,7 +455,7 @@ h4 {
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-example {
|
.form-info {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
gap: 50px;
|
gap: 50px;
|
||||||