Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebaa29028e | ||
|
|
f044d4a237 | ||
|
|
dc5f3d401b | ||
|
|
aa420c19d5 | ||
|
|
5078471b33 | ||
|
|
bf34e4af40 | ||
|
|
f83ab96325 | ||
|
|
14da622e5e | ||
|
|
399ea281c2 | ||
|
|
ca31498cec | ||
|
|
5d231645a7 | ||
|
|
ef5c451c8d |
@@ -1,3 +1,3 @@
|
|||||||
# nyipyatki
|
# juhannus
|
||||||
|
|
||||||
Repository for inviting our friends to new year
|
Repository for inviting our friends to new year
|
||||||
@@ -15,5 +15,7 @@ RUN pip3 install --no-cache-dir -r requirements.txt
|
|||||||
# Copy the rest of the application code
|
# Copy the rest of the application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
# Set the command to run your application
|
# Set the command to run your application
|
||||||
CMD ["/bin/sh", "entrypoint.sh"]
|
CMD ["/bin/sh", "entrypoint.sh"]
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ STARTUP_TABLE_CREATION_QUERIES = {
|
|||||||
Name varchar(255)
|
Name varchar(255)
|
||||||
);""",
|
);""",
|
||||||
"hosting": """CREATE TABLE IF NOT EXISTS hosting (
|
"hosting": """CREATE TABLE IF NOT EXISTS hosting (
|
||||||
id SERIAL,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
Name varchar(255),
|
Name VARCHAR(255) UNIQUE,
|
||||||
Capacity INT,
|
Capacity INT,
|
||||||
reservedBy varchar(255)
|
reservedBy VARCHAR(255)
|
||||||
);""",
|
);""",
|
||||||
"santa": """CREATE TABLE IF NOT EXISTS santa (
|
"santa": """CREATE TABLE IF NOT EXISTS santa (
|
||||||
Name varchar(255),
|
Name varchar(255),
|
||||||
@@ -28,19 +28,13 @@ STARTUP_TABLE_CREATION_QUERIES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
INJECT_TABLE_CREATION_QUERIES = {
|
INJECT_TABLE_CREATION_QUERIES = {
|
||||||
"hosting": """
|
"hosting": """
|
||||||
INSERT INTO hosting (Name, Capacity, reservedBy)
|
INSERT IGNORE INTO hosting (Name, Capacity, reservedBy) VALUES
|
||||||
SELECT name, capacity, reservedBy FROM (
|
('Матрац 160cм', 2, ''),
|
||||||
SELECT 'Матрац 160см' AS name, 2 AS capacity, '' AS reservedBy
|
('Кровать 120cм', 2, ''),
|
||||||
UNION ALL
|
('Матрац 90cм', 1, ''),
|
||||||
SELECT 'Кровать 120см', 2, ''
|
('Диван', 1, '');
|
||||||
UNION ALL
|
"""
|
||||||
SELECT 'Матрац 90см', 1, ''
|
|
||||||
UNION ALL
|
|
||||||
SELECT 'Диван', 1, ''
|
|
||||||
) AS temp
|
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM hosting WHERE Name = temp.name);
|
|
||||||
""",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Severity(Enum):
|
class Severity(Enum):
|
||||||
@@ -61,7 +55,6 @@ class DBClient:
|
|||||||
self.pool = self.create_pool() # Create a connection pool
|
self.pool = self.create_pool() # Create a connection pool
|
||||||
|
|
||||||
self.initialize_database()
|
self.initialize_database()
|
||||||
self.initialize_secret_santa() # Initialize Secret Santa
|
|
||||||
|
|
||||||
def validate_env_variables(self):
|
def validate_env_variables(self):
|
||||||
if not self.db_server or not self.db_port or not self.password or not self.database:
|
if not self.db_server or not self.db_port or not self.password or not self.database:
|
||||||
@@ -84,56 +77,6 @@ class DBClient:
|
|||||||
self.query(STARTUP_TABLE_CREATION_QUERIES['sessions'])
|
self.query(STARTUP_TABLE_CREATION_QUERIES['sessions'])
|
||||||
self.query(STARTUP_TABLE_CREATION_QUERIES['hosting'])
|
self.query(STARTUP_TABLE_CREATION_QUERIES['hosting'])
|
||||||
self.query(INJECT_TABLE_CREATION_QUERIES['hosting'])
|
self.query(INJECT_TABLE_CREATION_QUERIES['hosting'])
|
||||||
|
|
||||||
def initialize_secret_santa(self):
|
|
||||||
table_exists = self.query("SHOW TABLES LIKE 'santa';")
|
|
||||||
|
|
||||||
if not table_exists:
|
|
||||||
self.query(STARTUP_TABLE_CREATION_QUERIES['santa'])
|
|
||||||
|
|
||||||
count_query = self.query('SELECT COUNT(*) FROM santa;')
|
|
||||||
if count_query[0][0] > 0:
|
|
||||||
self.app.logger.warning('The santa table is not empty. No assignments will be made.')
|
|
||||||
return
|
|
||||||
|
|
||||||
attendees = self.query('SELECT Name FROM users WHERE Attendance = 1;')
|
|
||||||
|
|
||||||
if not attendees:
|
|
||||||
return
|
|
||||||
|
|
||||||
attendees = [user[0] for user in attendees]
|
|
||||||
couples = [("Тюлень", "Тюлениха"), ("Медведь", "Ксения")]
|
|
||||||
|
|
||||||
max_attempts = 1000
|
|
||||||
|
|
||||||
for attempt in range(max_attempts):
|
|
||||||
shuffled_attendees = attendees.copy()
|
|
||||||
random.shuffle(shuffled_attendees)
|
|
||||||
|
|
||||||
santa_assignments = {}
|
|
||||||
valid = True
|
|
||||||
|
|
||||||
for index, user in enumerate(shuffled_attendees):
|
|
||||||
prev_user = shuffled_attendees[index - 1]
|
|
||||||
next_user = shuffled_attendees[(index + 1) % len(shuffled_attendees)]
|
|
||||||
|
|
||||||
if any(user in couple and (prev_user in couple or next_user in couple) for couple in couples):
|
|
||||||
valid = False
|
|
||||||
break
|
|
||||||
|
|
||||||
santa_assignments[user] = prev_user
|
|
||||||
|
|
||||||
if valid:
|
|
||||||
break
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.app.logger.warning('Could not find valid Santa assignments after multiple attempts.')
|
|
||||||
return
|
|
||||||
|
|
||||||
self.app.logger.info(f'Santa assignments: {santa_assignments}')
|
|
||||||
|
|
||||||
for user, santa in santa_assignments.items():
|
|
||||||
self.query('INSERT INTO santa (Name, Santa) VALUES (%s, %s);', (user, santa))
|
|
||||||
|
|
||||||
|
|
||||||
def query(self, query_str, params=None):
|
def query(self, query_str, params=None):
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ app = Flask(__name__)
|
|||||||
app.config['JSON_AS_ASCII'] = False # Ensures non-ASCII characters are preserved
|
app.config['JSON_AS_ASCII'] = False # Ensures non-ASCII characters are preserved
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
allowed_origins = [
|
allowed_origins = [
|
||||||
"https://nyipyatki.davydovcloud.com",
|
"https://juhannus.davylis.com",
|
||||||
"http://192.168.100.*",
|
"http://192.168.100.*",
|
||||||
|
"http://localhost:*",
|
||||||
]
|
]
|
||||||
CORS(app, resources={r"*": {"origins": allowed_origins}}) # Only allow example.com
|
CORS(app, resources={r"*": {"origins": allowed_origins}}) # Only allow example.com
|
||||||
database = DBClient(app)
|
database = DBClient(app)
|
||||||
@@ -27,4 +28,4 @@ registerUserEndpoints(app=app, database=database)
|
|||||||
registerHostingEndpoints(app=app, database=database)
|
registerHostingEndpoints(app=app, database=database)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|||||||
@@ -164,65 +164,3 @@ def registerUserEndpoints(app, database):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify(success=False, message=str(e)), 500
|
return jsonify(success=False, message=str(e)), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/users/wishlist', methods=['PUT'])
|
|
||||||
def update_wishlist():
|
|
||||||
data = request.json
|
|
||||||
token = data.get('token')
|
|
||||||
wishlist_url = data.get('wishlist')
|
|
||||||
|
|
||||||
if wishlist_url is None:
|
|
||||||
return jsonify(success=False, message="Wishlist URL is required"), 400
|
|
||||||
|
|
||||||
query_session = "SELECT * FROM sessions WHERE Token=%s"
|
|
||||||
try:
|
|
||||||
result = database.query(query_session, params=(token,))
|
|
||||||
if not result:
|
|
||||||
return jsonify(success=False, message="Token is invalid or expired"), 401
|
|
||||||
|
|
||||||
user_name = result[0][1]
|
|
||||||
wishlist_query = "UPDATE users SET WishListUrl = %s WHERE Name = %s"
|
|
||||||
update_result = database.query(wishlist_query, params=(wishlist_url, user_name))
|
|
||||||
|
|
||||||
return jsonify(success=True, message="WishListUrl updated successfully"), 200
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify(success=False, message=str(e)), 500
|
|
||||||
|
|
||||||
@app.route('/users/santa', methods=['GET'])
|
|
||||||
def get_santainfo():
|
|
||||||
token = request.args.get('token')
|
|
||||||
|
|
||||||
if not token:
|
|
||||||
return jsonify(success=False, message="Token is required"), 400
|
|
||||||
|
|
||||||
query_session = "SELECT * FROM sessions WHERE Token=%s"
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = database.query(query_session, params=(token,))
|
|
||||||
if not result:
|
|
||||||
return jsonify(success=False, message="Token is invalid or expired"), 401
|
|
||||||
|
|
||||||
user_name = result[0][1]
|
|
||||||
|
|
||||||
santa_query = "SELECT Name FROM santa WHERE Santa = %s"
|
|
||||||
santa_result = database.query(santa_query, params=(user_name,))
|
|
||||||
|
|
||||||
if not santa_result:
|
|
||||||
return jsonify(success=False, message=f"User's {user_name} Santa info not found"), 404
|
|
||||||
|
|
||||||
santa_to = santa_result[0][0]
|
|
||||||
|
|
||||||
wishlist_query = "SELECT WishListUrl FROM users WHERE Name = %s"
|
|
||||||
wishlist_result = database.query(wishlist_query, params=(santa_to,))
|
|
||||||
|
|
||||||
santa_info = {
|
|
||||||
"santa_to": santa_to,
|
|
||||||
"wishlist": wishlist_result[0][0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonify(success=True, santa_info=santa_info), 200
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify(success=False, message=str(e)), 500
|
|
||||||
|
|||||||
@@ -4,29 +4,40 @@ services:
|
|||||||
context: backend
|
context: backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
environment:
|
||||||
- "2027:5000"
|
- ROOT_PWD=${ROOT_PWD}
|
||||||
container_name: "${BACKEND_CT_NAME:-nyi-backend}"
|
- DB_NAME=${DB_NAME}
|
||||||
|
- DB_SERVER=${DB_SERVER}
|
||||||
|
- DB_PORT=3306
|
||||||
|
container_name: "${BACKEND_CT_NAME:-juhannus-invitation-backend}"
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
- npm_network
|
||||||
depends_on:
|
depends_on:
|
||||||
- db # Ensure backend waits for db to start
|
- db # Ensure backend waits for db to start
|
||||||
db:
|
db:
|
||||||
image: mysql:9.4.0
|
image: mysql:9.4.0
|
||||||
container_name: nyi-db
|
container_name: juhannus-invitation-db
|
||||||
ports:
|
|
||||||
- "${DB_SERVER}:${DB_PORT}:3306"
|
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_ROOT_PASSWORD=${ROOT_PWD}
|
- MYSQL_ROOT_PASSWORD=${ROOT_PWD}
|
||||||
- MYSQL_DATABASE=${DB_NAME}
|
- MYSQL_DATABASE=${DB_NAME}
|
||||||
volumes:
|
volumes:
|
||||||
- nyi_db_volume:/var/lib/mysql
|
- invitation_db_volume:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: frontend
|
context: frontend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
container_name: "${FRONTEND_CT_NAME:-juhannus-invitation-frontend}"
|
||||||
- "2028:80"
|
networks:
|
||||||
container_name: "${FRONTEND_CT_NAME:-nyi-frontend}"
|
- npm_network
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend # Ensure fronetnd waits for backend to start
|
- backend # Ensure fronetnd waits for backend to start
|
||||||
volumes:
|
volumes:
|
||||||
nyi_db_volume:
|
invitation_db_volume:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
npm_network:
|
||||||
|
external: true
|
||||||
|
backend:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
body {
|
body {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
/* Light background for contrast */
|
/* Light background for contrast */
|
||||||
background-image: url('./assets/snowflakes.png');
|
background-image: url('./assets/leafs.png');
|
||||||
/* Background image */
|
/* Background image */
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
/* Cover the entire screen */
|
/* Cover the entire screen */
|
||||||
@@ -114,7 +114,7 @@ section {
|
|||||||
|
|
||||||
/* Festive Buttons */
|
/* Festive Buttons */
|
||||||
button {
|
button {
|
||||||
background-color: #b77de5;
|
background-color: #b5bb08;
|
||||||
/* Purple with a slight festive flair */
|
/* Purple with a slight festive flair */
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -126,7 +126,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #9b6bb5;
|
background-color: #48844e;
|
||||||
/* Darker purple on hover */
|
/* Darker purple on hover */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ table {
|
|||||||
|
|
||||||
/* Table header styles */
|
/* Table header styles */
|
||||||
th {
|
th {
|
||||||
background-color: #060698;
|
background-color: #06984a;
|
||||||
/* Christmas red */
|
/* Christmas red */
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
@@ -194,6 +194,9 @@ td {
|
|||||||
/* Light green background for cells */
|
/* Light green background for cells */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td a {
|
||||||
|
color: #ff7f2a;
|
||||||
|
}
|
||||||
/* Zebra striping for table rows */
|
/* Zebra striping for table rows */
|
||||||
tr:nth-child(even) {
|
tr:nth-child(even) {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
@@ -218,7 +221,7 @@ table {
|
|||||||
|
|
||||||
/* Add some festive decorations */
|
/* Add some festive decorations */
|
||||||
th::after {
|
th::after {
|
||||||
content: '🎄';
|
content: '🌳';
|
||||||
/* Small Christmas tree icon in the header */
|
/* Small Christmas tree icon in the header */
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -234,8 +237,8 @@ td::before {
|
|||||||
content: '';
|
content: '';
|
||||||
/* Placeholder for decoration */
|
/* Placeholder for decoration */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-image: url('path/to/snowflake-icon.png');
|
background-image: url('path/to/leaf-icon.png');
|
||||||
/* Snowflake icon */
|
/* leaf icon */
|
||||||
width: 16px;
|
width: 16px;
|
||||||
/* Adjust as necessary */
|
/* Adjust as necessary */
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -250,7 +253,7 @@ td::before {
|
|||||||
|
|
||||||
nav {
|
nav {
|
||||||
top: 0; /* Align it to the top */
|
top: 0; /* Align it to the top */
|
||||||
background-color: rgba(0, 42, 255, 0.6);; /* Background color */
|
background-color: #06984aef; /* Background color */
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
z-index: 1000; /* Make sure it stays above other content */
|
z-index: 1000; /* Make sure it stays above other content */
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@@ -293,7 +296,7 @@ nav a:hover {
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.nav-toggle {
|
.nav-toggle {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: rgba(0, 42, 255, 0.6); /* Button color */
|
background-color: rgba(255, 255, 0, 0.6); /* Button color */
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import Greeting from './components/Greeting';
|
|||||||
import Hosting from './components/Hosting';
|
import Hosting from './components/Hosting';
|
||||||
import InitialSetup from './components/InitialSetup';
|
import InitialSetup from './components/InitialSetup';
|
||||||
import Program from './components/Program';
|
import Program from './components/Program';
|
||||||
import Snowflakes from './components/Snowflakes';
|
import Leafs from './components/Leafs';
|
||||||
import { FullScreenLoading } from './components/Loading';
|
import { FullScreenLoading } from './components/Loading';
|
||||||
import SecretSanta from './components/SecretSanta';
|
|
||||||
import Suggestions from './components/Suggestions';
|
|
||||||
import AttendanceTable from './components/AttendnaceTable';
|
import AttendanceTable from './components/AttendnaceTable';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -31,7 +29,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FullScreenLoading />
|
<FullScreenLoading />
|
||||||
<Snowflakes />
|
<Leafs />
|
||||||
<InitialSetup />
|
<InitialSetup />
|
||||||
|
|
||||||
<button onClick={toggleNav} className="nav-toggle">
|
<button onClick={toggleNav} className="nav-toggle">
|
||||||
@@ -49,12 +47,6 @@ function App() {
|
|||||||
<li>
|
<li>
|
||||||
<a href="#program">Программа</a>
|
<a href="#program">Программа</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="#suggestions">Пожелания</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#santa">Secret Santa</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="#attendance-table">Кто празднует?</a>
|
<a href="#attendance-table">Кто празднует?</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -73,12 +65,6 @@ function App() {
|
|||||||
<div id="program">
|
<div id="program">
|
||||||
<Program />
|
<Program />
|
||||||
</div>
|
</div>
|
||||||
<div id="suggestions">
|
|
||||||
<Suggestions />
|
|
||||||
</div>
|
|
||||||
<div id="santa">
|
|
||||||
<SecretSanta />
|
|
||||||
</div>
|
|
||||||
<div id="attendance-table">
|
<div id="attendance-table">
|
||||||
<AttendanceTable />
|
<AttendanceTable />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
width: 100%; /* Full width of the viewport */
|
width: 100%; /* Full width of the viewport */
|
||||||
overflow-x: auto; /* Enables horizontal scrolling if necessary */
|
overflow-x: auto; /* Enables horizontal scrolling if necessary */
|
||||||
margin: 20px 0; /* Spacing above and below the container */
|
margin: 20px 0; /* Spacing above and below the container */
|
||||||
background-color: rgba(0, 42, 255, 0.6);
|
background-color: #06984aef;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,15 +49,15 @@ function Greeting() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
<h1>Приглашение на Новый год 2025-2026 🎄</h1>
|
<h1>Приглашение на Иванов день (juhannus) 🤪</h1>
|
||||||
<p className="mainText">
|
<p className="mainText">
|
||||||
<h3>
|
<h3>
|
||||||
{userName ? <>{userName}</> : <>Дорогая пятка!</>}
|
{userName ? <>{userName}</> : <>Дорогая пятка!</>}
|
||||||
! 🦶
|
! 🦶
|
||||||
</h3>
|
</h3>
|
||||||
Приглашаем тебя отпраздновать предстоящий Новый Год <b>2025-2026</b> с нами в сосновой избе, в которой, ко всему прочему, будет праздноваться годовщина нашей жизни в ней!
|
Приглашаем тебя отпраздновать предстоящий Иванов день <b>19.6.2026 - 21.6.2026</b> с нами в сосновой избе, в которой, ко всему прочему, несколько выпускников будут праздновать выпускной!
|
||||||
|
|
||||||
Наши двери открыты с <b>30.12.2025</b>. Праздник обычно длится до <b>01.01.2025</b>, но если тебе или твоим спутникам будет безумно плохо, то можно остаться и до второго числа.
|
Наши двери открыты с <b>19.6.2026</b>.
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
<Attendance/>
|
<Attendance/>
|
||||||
|
|||||||
38
frontend/src/components/Leafs.css
Normal file
38
frontend/src/components/Leafs.css
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
.leafs {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none; /* Make leafs non-interactive */
|
||||||
|
overflow: hidden; /* Hide overflow */
|
||||||
|
z-index: 998; /* Ensure leafs are above other content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaf {
|
||||||
|
position: absolute;
|
||||||
|
top: -10%; /* Start above the top of the screen */
|
||||||
|
color: white; /* leaf color */
|
||||||
|
font-size: 1em; /* Size of the leaf; adjust as needed */
|
||||||
|
opacity: 0.6; /* Transparency */
|
||||||
|
animation: fall linear infinite; /* Apply the fall animation */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Falling animation */
|
||||||
|
@keyframes fall {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0) translateY(0) rotate(0deg);/* Start position */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-5vw) translateY(120vh) rotate(360deg); /* End position */
|
||||||
|
opacity: 0.1; /* Optional: fade out */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Randomize leaf size and animation */
|
||||||
|
.leaf:nth-child(1) { animation-duration: 6s; left: 10%; font-size: 0.8em;}
|
||||||
|
.leaf:nth-child(2) { animation-duration: 8s; left: 20%; font-size: 3em;}
|
||||||
|
.leaf:nth-child(3) { animation-duration: 5s; left: 30%; font-size: 4em;}
|
||||||
|
.leaf:nth-child(4) { animation-duration: 7s; left: 40%; font-size: 0.9em;}
|
||||||
|
.leaf:nth-child(5) { animation-duration: 10s; left: 50%; font-size: 2em;}
|
||||||
|
|
||||||
|
/* Add more child selectors for additional leafs */
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './Snowflakes.css';
|
import './Leafs.css';
|
||||||
|
|
||||||
const Snowflakes: React.FC = () => {
|
const Leafs: React.FC = () => {
|
||||||
// Adjust the number of snowflakes as needed
|
// Adjust the number of leafs as needed
|
||||||
const snowflakeCount = Array.from({ length: 50 });
|
const leafCount = Array.from({ length: 50 });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="snowflakes">
|
<div className="leafs">
|
||||||
{snowflakeCount.map((_, index) => (
|
{leafCount.map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="snowflake"
|
className="leaf"
|
||||||
style={{
|
style={{
|
||||||
left: `${Math.random() * 100}vw`, // Random position across the full width
|
left: `${Math.random() * 100}vw`, // Random position across the full width
|
||||||
animationDuration: `${Math.random() * 20 + 10}s`, // Random fall duration between 2s and 5s
|
animationDuration: `${Math.random() * 20 + 10}s`, // Random fall duration between 2s and 5s
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
❄️
|
🍃
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Snowflakes;
|
export default Leafs;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
.spinner {
|
.spinner {
|
||||||
font-size: 100px; /* Adjust size as needed */
|
font-size: 100px; /* Adjust size as needed */
|
||||||
animation: spin 2s linear infinite; /* Spin animation */
|
animation: spin 2s linear infinite; /* Spin animation */
|
||||||
position: fixed; /* Keep the snowflake in a fixed position */
|
position: fixed; /* Keep the leaf in a fixed position */
|
||||||
top: 50%; /* Adjust vertical position */
|
top: 50%; /* Adjust vertical position */
|
||||||
left: 50%; /* Center horizontally */
|
left: 50%; /* Center horizontally */
|
||||||
transform: translateX(-50%); /* Center the snowflake */
|
transform: translateX(-50%); /* Center the leaf */
|
||||||
z-index: 10000; /* Ensure it's above the full-screen loading */
|
z-index: 10000; /* Ensure it's above the full-screen loading */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './Loading.css'; // Import CSS for styling
|
|||||||
export const Loading: React.FC = () => {
|
export const Loading: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="spinner">
|
<div className="spinner">
|
||||||
🎅🏻
|
🍺
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const Program = () => {
|
|||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
<h2>Программа</h2>
|
<h2>Программа</h2>
|
||||||
|
|
||||||
<h3>30 декабря</h3>
|
<h3>19 июня</h3>
|
||||||
<div className="table-wrapper">
|
<div className="table-wrapper">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -16,22 +16,22 @@ const Program = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>15-18</td>
|
<td>15-20</td>
|
||||||
<td>Гости приезжают и селятся</td>
|
<td>Гости приезжают и селятся</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>18-19</td>
|
<td>18-21</td>
|
||||||
<td>Ужин</td>
|
<td>Ужин</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>19-N/A</td>
|
<td>19-N/A</td>
|
||||||
<td>Отдых и заготовки к кануну Нового Года</td>
|
<td>Отдых и заготовки к juhannus, программа мероприятий в Сюсмя (её выложат в июне)</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>31 декабря</h3>
|
<h3>20 июня</h3>
|
||||||
<div className="table-wrapper">
|
<div className="table-wrapper">
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
@@ -43,27 +43,29 @@ const Program = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>07-10</td>
|
<td>07-11</td>
|
||||||
<td>Утро, завтрак</td>
|
<td>Утро, шикарный завтрак</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>11-14</td>
|
<td>11-14</td>
|
||||||
<td>Сюсьма, прогулки, дополнительные закупки, подготовка к вечеру, обед</td>
|
<td>дополнительные закупки, подготовка к вечеру, обед</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>14-19</td>
|
<td>14-19</td>
|
||||||
<td>Готовим ужин, чиллим</td>
|
<td>Есть возможность
|
||||||
|
|
||||||
|
забронировать баню с бассейном <a href="https://ilolainn.fi/fi_FI/saunamme/maisemasauna-2" target="_blank">(ссылка)</a> или, если будет хорошая погода, водные скутеры <a href="https://campingsysma.fi/aktiviteetit/" target="_blank">(ссылка)</a> и поплавать по озеру, либо устроить поход <a href="https://sysma.fi/luontokohteet-ja-reitit/" target="_blank">(ссылка)</a> (варианты можно обсудить в беседе)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>19-23:59</td>
|
<td>20-N/A</td>
|
||||||
<td>Ужин, отдых, разговоры, игры</td>
|
<td>Отдых и juhannus, программа мероприятий в Сюсмя (её выложат в июне)</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>1 января</h3>
|
<h3>21 июня</h3>
|
||||||
<div className="table-wrapper">
|
<div className="table-wrapper">
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
@@ -76,19 +78,15 @@ const Program = () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>00-N/A</td>
|
<td>00-N/A</td>
|
||||||
<td>🎄 ☃️ 🍾 🥂 🎇 🎆</td>
|
<td>Приключения</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>07-12</td>
|
<td>09-13</td>
|
||||||
<td>Утро, завтрак</td>
|
<td>Утро, шикарный завтрак/обед</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>12-15</td>
|
<td>13-N/A</td>
|
||||||
<td>Сауна/Отдых</td>
|
<td>Отдых</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>15-N/A</td>
|
|
||||||
<td>Кто-то остается, кто-то собирается домой</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
.snowflakes {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none; /* Make snowflakes non-interactive */
|
|
||||||
overflow: hidden; /* Hide overflow */
|
|
||||||
z-index: 998; /* Ensure snowflakes are above other content */
|
|
||||||
}
|
|
||||||
|
|
||||||
.snowflake {
|
|
||||||
position: absolute;
|
|
||||||
top: -10%; /* Start above the top of the screen */
|
|
||||||
color: white; /* Snowflake color */
|
|
||||||
font-size: 1em; /* Size of the snowflake; adjust as needed */
|
|
||||||
opacity: 0.5; /* Transparency */
|
|
||||||
animation: fall linear infinite; /* Apply the fall animation */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Falling animation */
|
|
||||||
@keyframes fall {
|
|
||||||
0% {
|
|
||||||
transform: translateX(0) translateY(0); /* Start position */
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(-5vw) translateY(120vh); /* End position */
|
|
||||||
opacity: 0.1; /* Optional: fade out */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Randomize snowflake size and animation */
|
|
||||||
.snowflake:nth-child(1) { animation-duration: 6s; left: 10%; font-size: 0.8em;}
|
|
||||||
.snowflake:nth-child(2) { animation-duration: 8s; left: 20%; font-size: 3em;}
|
|
||||||
.snowflake:nth-child(3) { animation-duration: 5s; left: 30%; font-size: 4em;}
|
|
||||||
.snowflake:nth-child(4) { animation-duration: 7s; left: 40%; font-size: 0.9em;}
|
|
||||||
.snowflake:nth-child(5) { animation-duration: 10s; left: 50%; font-size: 2em;}
|
|
||||||
|
|
||||||
/* Add more child selectors for additional snowflakes */
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
export const API_URL = 'https://nyipyatki-backend.davydovcloud.com';
|
export const API_URL = 'https://juhannus-backend.davylis.com';
|
||||||
export const GUESTS = [
|
export const GUESTS = [
|
||||||
"Медведь",
|
"Медведь",
|
||||||
"Ксения",
|
"Ксения",
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ import react from '@vitejs/plugin-react'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
allowedHosts: ['nyipyatki-dev.davydovcloud.com'],
|
allowedHosts: ['juhannus-dev.davylis.com'],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "invitation",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user