commit b7deb9cb0ace59228086eae1006ab48efdeaf8d5 Author: Marius Robert Macamete Date: Mon Nov 24 13:17:06 2025 +0200 first commit diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..a86ffda Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000..e18d152 Binary files /dev/null and b/assets/favicon.png differ diff --git a/assets/icons/.DS_Store b/assets/icons/.DS_Store new file mode 100644 index 0000000..2bf8451 Binary files /dev/null and b/assets/icons/.DS_Store differ diff --git a/assets/icons/loading-animation.png b/assets/icons/loading-animation.png new file mode 100644 index 0000000..ba54442 Binary files /dev/null and b/assets/icons/loading-animation.png differ diff --git a/assets/images/.DS_Store b/assets/images/.DS_Store new file mode 100644 index 0000000..85c3b70 Binary files /dev/null and b/assets/images/.DS_Store differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..ba54442 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/logo_aquila_soft_small.png b/assets/images/logo_aquila_soft_small.png new file mode 100644 index 0000000..818e0bd Binary files /dev/null and b/assets/images/logo_aquila_soft_small.png differ diff --git a/home.py b/home.py new file mode 100644 index 0000000..e289ec0 --- /dev/null +++ b/home.py @@ -0,0 +1,469 @@ +import flet as ft +from mail import send_gmail + +class Home: + def __init__(self, page: ft.Page): + self.page = page + + self.web_apps_section = ft.Container( + content=ft.Column( + controls=[ + ft.Icon(ft.icons.WEB, size=34), + ft.Text("Aplicații web", size=18, weight=ft.FontWeight.BOLD), + ft.Text( + "Dashboard‑uri, aplicații interne, panouri de administrare și soluții de prezentare pentru afacerea ta.", + size=14, + color=ft.colors.BLUE_GREY_700, + ), + ], + spacing=10, + ), + padding=20, + border_radius=16, + bgcolor=ft.colors.WHITE, + shadow=ft.BoxShadow( + blur_radius=16, + spread_radius=1, + color=ft.colors.with_opacity(0.08, ft.colors.BLUE_GREY_900), + ), + width=300 + ) + self.mobile_apps_section = ft.Container( + content=ft.Column( + controls=[ + ft.Icon(ft.icons.PHONE_ANDROID, size=34), + ft.Text("Aplicații mobile", size=18, weight=ft.FontWeight.BOLD), + ft.Text( + "Aplicații Android și iOS pentru clienți sau angajați, sincronizate cu serverul tău.", + size=14, + color=ft.colors.BLUE_GREY_700, + ), + ], + spacing=10, + ), + padding=20, + border_radius=16, + bgcolor=ft.colors.WHITE, + shadow=ft.BoxShadow( + blur_radius=16, + spread_radius=1, + color=ft.colors.with_opacity(0.08, ft.colors.BLUE_GREY_900), + ), + width=300 + ) + + self.api_section = ft.Container( + content=ft.Column( + controls=[ + ft.Icon(ft.icons.INTEGRATION_INSTRUCTIONS, size=34), + ft.Text("Integrare & automatizare", size=18, weight=ft.FontWeight.BOLD), + ft.Text( + "Integrare cu API‑uri de plăți, curieri, facturare, notificări și alte servicii esențiale.", + size=14, + color=ft.colors.BLUE_GREY_700, + ), + ], + spacing=10, + ), + padding=20, + border_radius=16, + bgcolor=ft.colors.WHITE, + shadow=ft.BoxShadow( + blur_radius=16, + spread_radius=1, + color=ft.colors.with_opacity(0.08, ft.colors.BLUE_GREY_900), + ), + width=300 + ) + self.page.on_resize = self.on_resize + + self.error_message = ft.Text() + + self.name = ft.TextField( + label="Nume", + col=6, + ) + + self.email = ft.TextField( + label="Adresă de email", + col=6, + ) + + self.message = ft.TextField( + label="Mesaj", + multiline=True, + min_lines=3, + max_lines=5, + col=12, + ) + + def on_resize(self, e): + self.page.update() + + def _hero_section(self): + return ft.Container( + content=ft.Row( + [ + ft.Column( + [ + ft.Text( + "AquilaSoft", + size=42, + weight=ft.FontWeight.BOLD, + color=ft.colors.BLUE_GREY_900, + ), + ft.Row( + controls=[ + ft.ElevatedButton( + "Vezi serviciile", + icon=ft.icons.ROCKET_LAUNCH_OUTLINED, + on_click=lambda e: self.page.scroll_to(key="services_section", duration=500), + ), + ft.OutlinedButton( + "Contactează-ne", + icon=ft.icons.EMAIL_OUTLINED, + on_click=lambda e: self.page.scroll_to(key="contact_section", duration=500), + ), + ], + spacing=10, + ), + ft.Row( + [ + ft.Text( + "Aplicații custom, integrate, construite cu Python, Flutter și tehnologii moderne.", + size=14, + color=ft.colors.BLUE_GREY_500, + ), + ], alignment=ft.MainAxisAlignment.CENTER + ) + ] + ), + ft.Container( + content=ft.Image( + src="images/logo.png", + width=220, + fit=ft.ImageFit.CONTAIN, + ), + padding=20, + border_radius=20, + bgcolor=ft.colors.WHITE, + shadow=ft.BoxShadow( + blur_radius=25, + spread_radius=1, + color=ft.colors.with_opacity(0.15, ft.colors.BLUE_GREY_900), + ), + ) + ], + alignment=ft.MainAxisAlignment.CENTER + ) + if self.page.width > 900 else ft.Column( + [ + ft.Container( + content=ft.Image( + src="images/logo.png", + width=220, + fit=ft.ImageFit.CONTAIN, + ), + padding=20, + border_radius=20, + bgcolor=ft.colors.WHITE, + shadow=ft.BoxShadow( + blur_radius=25, + spread_radius=1, + color=ft.colors.with_opacity(0.15, ft.colors.BLUE_GREY_900), + ), + ), + ft.Column( + [ + ft.Row( + controls=[ + ft.ElevatedButton( + "Vezi serviciile", + icon=ft.icons.ROCKET_LAUNCH_OUTLINED, + on_click=lambda e: self.page.scroll_to(key="services_section", duration=500), + ), + ft.OutlinedButton( + "Contactează-ne", + icon=ft.icons.EMAIL_OUTLINED, + on_click=lambda e: self.page.scroll_to(key="contact_section", duration=500), + ), + ], + spacing=10, + alignment=ft.MainAxisAlignment.CENTER + ), + ft.Row( + [ + ft.Text( + "Aplicații custom, integrate, construite cu Python, Flutter și tehnologii moderne." + if self.page.width > 500 else "Aplicații custom, integrate, \nconstruite cu Python, \nFlutter și tehnologii moderne.", + size=14, + color=ft.colors.BLUE_GREY_500, + text_align=ft.TextAlign.CENTER, + ) + ], + alignment=ft.MainAxisAlignment.CENTER, + expand=True + ) + ] + ), + ], + horizontal_alignment=ft.CrossAxisAlignment.CENTER + ), + bgcolor=ft.Colors.WHITE, + padding=10 + ) + + def _section_title(self, title: str, subtitle: str | None = None) -> ft.Control: + return ft.Column( + controls=[ + ft.Text(title, size=28, weight=ft.FontWeight.BOLD), + ft.Text(subtitle, size=15, color=ft.colors.BLUE_GREY_900, text_align=ft.TextAlign.CENTER) if subtitle else ft.Container(), + ], + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + spacing=5, + ) + + def _about_section(self) -> ft.Control: + return ft.Row( + [ + ft.Container( + content=ft.Column( + controls=[ + self._section_title( + "Cine este AquilaSoft?", + "Suntem un studio de software, cu accent pe calitate, simplitate și soluții gândite pentru oameni ocupați.", + ), + ft.Text( + "AquilaSoft este partenerul tău pentru aplicații personalizate. De la management intern și automatizări, până la prezentări online și module integrate în soluțiile existente, construim software adaptat exact modului în care lucrezi tu.", + size=15, + text_align=ft.TextAlign.CENTER, + ), + ], + spacing=20, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + ), + padding=ft.padding.only(top=20, bottom=20), + width=900 if self.page.width > 900 else self.page.width-50 + ) + ], + alignment=ft.MainAxisAlignment.CENTER + ) + + def _services_section(self) -> ft.Control: + return ft.Column( + controls=[ + self._section_title("Ce putem face pentru tine?"), + ft.Row( + [ + self.web_apps_section, + self.mobile_apps_section, + self.api_section + ], + alignment=ft.MainAxisAlignment.CENTER, + expand=True + ), + ], + spacing=25, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + expand=True, + key="services_section", + ) if self.page.width > 900 else ft.Column( + controls=[ + self._section_title("Ce putem face pentru tine?"), + ft.Column( + [ + self.web_apps_section, + self.mobile_apps_section, + self.api_section + ], + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + expand=True + ), + ], + spacing=25, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + expand=True, + key="services_section", + ) + + def _technologies_section(self) -> ft.Control: + tech_chips = ["Python","Flask","Flet","Flutter"] if self.page.width > 900 else ["Python","Flask","Flet"] + tech_chips2 = ["Docker","PostgreSQL / MariaDB","Linux server"] if self.page.width > 900 else ["Flutter", "Docker"] + tech_chips3 = [] if self.page.width > 900 else ["PostgreSQL / MariaDB","Linux server"] + + return ft.Container( + content=ft.Column( + controls=[ + self._section_title("Tehnologii", "Construim pe un stack modern, stabil și ușor de întreținut."), + ft.Column( + controls=[ + ft.Row( + [ + ft.Chip(label=ft.Text(t)) for t in tech_chips + ], + alignment=ft.MainAxisAlignment.CENTER + ), + ft.Row( + [ + ft.Chip(label=ft.Text(t)) for t in tech_chips2 + ], + alignment=ft.MainAxisAlignment.CENTER + ), + ft.Row( + [ + ft.Chip(label=ft.Text(t)) for t in tech_chips3 + ], + alignment=ft.MainAxisAlignment.CENTER + ), + ], + spacing=10, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + expand=True + ), + ], + spacing=20, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + expand=True + ), + padding=ft.padding.only(top=20, bottom=20), + bgcolor=ft.Colors.WHITE, + expand=True + ) + + def _portfolio_teaser_section(self) -> ft.Control: + return ft.Row( + [ + ft.Container( + content=ft.Column( + controls=[ + self._section_title("Proiecte & experiență"), + ft.Text( + "Lucrăm la aplicații de gestiune, programări medicale, management transport și magazine online. " + "Site‑ul de prezentare va include în curând studii de caz și exemple concrete.", + size=15, + text_align=ft.TextAlign.CENTER, + color=ft.colors.BLUE_GREY_700, + ), + ], + spacing=15, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + ), + padding=ft.padding.only(top=20, bottom=20), + width=900 if self.page.width > 900 else self.page.width-50, + ) + ], + alignment=ft.MainAxisAlignment.CENTER + ) + + def _contact_section(self) -> ft.Control: + return ft.Row( + [ + ft.Container( + content=ft.Column( + controls=[ + self._section_title("Hai să vorbim"), + ft.Text( + "Spune-ne pe scurt ce ai nevoie – o aplicație nouă, un modul pentru un sistem existent sau o idee la început de drum.", + size=15, + text_align=ft.TextAlign.CENTER, + ), + ft.Container(height=10), + ft.ResponsiveRow( + controls=[ + self.name, + self.email, + self.message, + ft.Row( + [ + self.error_message + ] + ), + ft.Container( + content=ft.ElevatedButton( + "Trimite mesajul", + icon=ft.icons.SEND, + on_click=self.on_send_message_btn_click, # de conectat la backend + ), + alignment=ft.alignment.center, + col=12, + padding=ft.padding.only(top=10), + ), + ], + columns=12, + run_spacing=10, + ), + ], + spacing=20, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + ), + padding=ft.padding.only(top=30, bottom=30), + width=900 if self.page.width > 900 else self.page.width-50, + key="contact_section", + ) + ], + alignment=ft.MainAxisAlignment.CENTER + ) + + def _footer_section(self) -> ft.Control: + return ft.Container( + content=ft.Column( + controls=[ + ft.Divider(), + ft.Text( + "© " + "2026" + " AquilaSoft. Toate drepturile rezervate.", + size=12, + color=ft.colors.BLUE_GREY_500, + text_align=ft.TextAlign.CENTER, + ), + ft.Text( + "Creat cu pasiune, Python și un strop de cafea.", + size=12, + color=ft.colors.BLUE_GREY_400, + text_align=ft.TextAlign.CENTER, + ), + ], + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + ), + padding=ft.padding.only(bottom=10, top=-7), + bgcolor=ft.Colors.WHITE + ) + + def on_send_message_btn_click(self, e): + name = self.name.value + email = self.email.value + message = self.message.value + found = False + if name is None or len(name)< 2: + found = True + if email is None or len(email)< 2: + found = True + if message is None or len(message)< 2: + found = True + if not found: + send_gmail('macamete.robert@gmail.com', 'Contact nou pe Aquila Soft', email+'\n'+message) + self.error_message.value = "Mesajul a fost trimis cu succes! \nVeți fi contactat în curând de unul dintre colegii noștri." + self.error_message.color = ft.Colors.GREEN + self.error_message.update() + else: + self.error_message.value = "Toate campurile sunt obligatori!" + self.error_message.color = ft.Colors.RED + self.error_message.update() + + + def build(self): + return ft.Container( + content=ft.Column( + [ + self._hero_section(), + self._about_section(), + self._services_section(), + self._technologies_section(), + self._portfolio_teaser_section(), + self._contact_section(), + self._footer_section() + ], + scroll=ft.ScrollMode.ADAPTIVE, + expand=True, + ), + expand=True + ) \ No newline at end of file diff --git a/mail.py b/mail.py new file mode 100644 index 0000000..86a5c77 --- /dev/null +++ b/mail.py @@ -0,0 +1,139 @@ +import smtplib +from email.message import EmailMessage +import os + +def send_email(to_email, subject, body): + smtp_host = os.environ.get("SMTP_HOST") + smtp_port = int(os.environ.get("SMTP_PORT", 587)) + smtp_user = os.environ.get("SMTP_USER") + smtp_pass = os.environ.get("SMTP_PASS") + sender_email = os.environ.get("SMTP_FROM", smtp_user) + + if not all([smtp_host, smtp_port, smtp_user, smtp_pass]): + raise ValueError("SMTP config incomplete in environment variables.") + + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = sender_email + msg["To"] = to_email + msg.set_content(body) + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.send_message(msg) + + +# Send email with attachment +def send_email_with_attachment(to_email, subject, body, attachment_path): + smtp_host = os.environ.get("SMTP_HOST") + smtp_port = int(os.environ.get("SMTP_PORT", 587)) + smtp_user = os.environ.get("SMTP_USER") + smtp_pass = os.environ.get("SMTP_PASS") + sender_email = os.environ.get("SMTP_FROM", smtp_user) + + if not all([smtp_host, smtp_port, smtp_user, smtp_pass]): + raise ValueError("SMTP config incomplete in environment variables.") + + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = sender_email + msg["To"] = to_email + msg.set_content(body) + + if attachment_path and os.path.isfile(attachment_path): + with open(attachment_path, "rb") as f: + file_data = f.read() + file_name = os.path.basename(attachment_path) + msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name) + else: + raise FileNotFoundError(f"Attachment file not found: {attachment_path}") + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.send_message(msg) + + +# Send email using Gmail directly +def send_gmail(to_email, subject, body): + smtp_host = "smtp.gmail.com" + smtp_port = 587 + smtp_user = 'macamete.robert@gmail.com' + smtp_pass = 'advx yqlv jkaa czvr' + sender_email = 'macamete.robert@gmail.com' + + if not all([smtp_user, smtp_pass]): + raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.") + + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = sender_email + msg["To"] = to_email + msg.set_content(body) + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.send_message(msg) + + +# Send email with attachment using Gmail directly +def send_gmail_with_attachment(to_email, subject, body, attachment_path): + smtp_host = "smtp.gmail.com" + smtp_port = 587 + smtp_user = 'macamete.robert@gmail.com' + smtp_pass = 'advx yqlv jkaa czvr' + sender_email = 'macamete.robert@gmail.com' + + if not all([smtp_user, smtp_pass]): + raise ValueError("GMAIL_USER and GMAIL_PASS must be set in environment variables.") + + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = sender_email + msg["To"] = to_email + msg.set_content(body) + + if attachment_path and os.path.isfile(attachment_path): + with open(attachment_path, "rb") as f: + file_data = f.read() + file_name = os.path.basename(attachment_path) + msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name) + else: + raise FileNotFoundError(f"Attachment file not found: {attachment_path}") + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.send_message(msg) + +# Send email with attachment +def send_custom_email_with_attachment(to_email, subject, body, attachment_path, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS): + smtp_host = SMTP_HOST + smtp_port = int(SMTP_PORT) + smtp_user = SMTP_USER + smtp_pass = SMTP_PASS + sender_email = smtp_user + + if not all([smtp_host, smtp_port, smtp_user, smtp_pass]): + raise ValueError("SMTP config incomplete in environment variables.") + + msg = EmailMessage() + msg["Subject"] = subject + msg["From"] = sender_email + msg["To"] = to_email + msg.set_content(body) + + if attachment_path and os.path.isfile(attachment_path): + with open(attachment_path, "rb") as f: + file_data = f.read() + file_name = os.path.basename(attachment_path) + msg.add_attachment(file_data, maintype="application", subtype="pdf", filename=file_name) + else: + raise FileNotFoundError(f"Attachment file not found: {attachment_path}") + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.send_message(msg) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..d4677fe --- /dev/null +++ b/main.py @@ -0,0 +1,25 @@ +import flet as ft +from home import Home + +def main(page: ft.Page): + page.title = 'AquilaSoft' + page.theme = ft.Theme(color_scheme_seed=ft.Colors.BLUE) + + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.theme_mode = ft.ThemeMode.LIGHT + page.padding = 0 + + def route_change(route): + page.controls.clear() + + if "/" == route: + home = Home(page) + page.add(home.build()) + + page.update() + + page.on_route_change = lambda _: route_change(page.route) + page.go('/') + +ft.app(target=main, assets_dir="assets", view=ft.WEB_BROWSER, port='8550') \ No newline at end of file