From b268c3fff8110895b0888e4dffe8168fc3ef8b0a Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Sat, 13 Dec 2025 00:58:30 +0700 Subject: [PATCH] Update Responsive and Contact Us page and function --- __pycache__/server.cpython-312.pyc | Bin 149643 -> 154812 bytes server.py | 104 +++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index 04465dce429fe7cdbf5d4fea2d4171e2a2a26034..fde64855486414ba7b26d9bdbd214ed00c2d1e8a 100644 GIT binary patch delta 8265 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 Rab&;9vHcG5zRSf8{{!zr+xP$g delta 3625 zcmb_e3s6+o8RkFiy)4f~a9!6`cGqMQ)~cvf5Cz33#s@Jvt=1S((0j2cf*wSTrWzFE z1E`W)b95pa>#I|ZU`#xkMj)oB@exC6Vx$!kX#|6cidEDZ+w~-&tJlg4csh9SOwTo8jAE3qBk{wlQu9B?wQqV_Mj-ikDZ}I`HQnG0_TL}6)lGJ?$zvBkK zkF;^JO?%xI>BL+{KeKYe;P&2$YiGMghMSddgx9xXoEPIeF)nD8-E6@%hMa%F+VFSnEr+9hw9TyiLqt3)gVhqI zTr}kP6qiQi5WE~1^O%=XYw$Xvv#-+Rg))e@roV2CK!xm{`((W67X zh$vFu)tNntv<3gu5Lcn|5pjYKdOzXwqrt0E=W`jeYekL_os$C^w7n7iU)NM+-*j2xFyBel68FGH6GrrnBr?%1=lWEP@fkb?=W{a2-oV|9W8g1yL*V&M;JC}Ff-iUx(B=(MUm7|q9|z$Ye1=JIs7YJyjJOFG;( z!Fb*{8%9Zi6GTaRMO5*aHP;y{8$SVy)2y+qU zAtWGBgdBu$bY6hcB80^VZzANQVKKr8gjCcalwzaV;KR?ag#`eqY-}O4%1hC_lqDBI z94uv`J3HT61b+oN4-I)NV;u~EJpO_P@gS`fx+XsJ5Ac?6px(tSbehd>G05v0n`hEQ7cpeH+$e_0>chdY~hFPzJ%0CKO9}@O~(gpn$)3 z5KJICd7neCz;}h|q`x?J7GQ-XFp9em!4@eDPmL#-ZpMxZ5E5DS5l9wM5#^91>xXJL zdnJ~*+1hfLE^QM2*ZIwIP~^b(giJRhl(5t)h?cjYyp?ULg6{2%$Ew7^X)`ueLATfTx7WD8*xqr_$n zuZE~lJ=7Srq8fThyG7J={&6+DCQAo|oX^9*gJuc-%zSHMn2hfr-OG||VG!)wkVkq# z>4tqIhBehf0F<#iwcs>;Bz)Ms^APJtKScNaEU{4(T~r7CBrX&cytED?eBmJb@)D$4 z(cQrQ7ud{)Y*;F5sS+cD8pVc`W6CztE1Drz`dlc+?XGAB zUpUGtZ$UciT@M!M$p+U$XEwG4>{69*DPox|@CJOsFSo#prbkN^-U?&j12(M{tl|HK zY=Xb+y#}<5ooR*Fy|mqI^gW1{z7Uyr@#Xhmi44ct!+(qO<=uo0qe!f=4eJTyU5-of z0GFZ+r`62a3N}BFm7Ww+*3=3K@HL-IpN7l1Uj)j}cyzr`x&5iYP37BUQySWqaLArD2>ODs2(L_U(Y zhmzkwJ$n#Jj>0Zh-hm8q>hFmDHRy(cU98bXUkPb5}SC@#|ELdNF*)(mE7> zK=>OQ-h~XYeuMHwgfxU2gzwqLF61@qk0|Tq8c=FPxWZ%y=_xk}d9jDNJCIIntb^F$ zD*LU2q(tisuA^f!LJ0274V3Cp8jg^Ja1(VZHh+t?I*3!gjf!imTUU}K-52s%KC>&? z1l-b_tTj99(Ru*`>k#y%5h}6XXt=}<#Sqn+gmNDNHRc()IrFpUs8nR^7fTxaYy5I@ zXUtG@a#+LwGS#Xtcwcmn#3cGhD1j9ZAi2-qLG4qOyhYH^_Vo3LBRxnGVR_BqVhiF( zs4V&V_&f;Wg>mF3UpU1VCXih)xF%Sgla`S=El2E5#tfClV6j+)0SIvj0}*sSgHVb` zNI=-f#t$J;P|x#*kUzn)5mp z6hFtb7y-Wn6kmLb?={_qfR8A}H<99FNb&iihlzN8D84BaUj~YIpW-#8c#$aH8H(qP o;?7Zt)ea?N0wTqd2r@o7F+BPuV(CC`gz<5|B0J11dJ0+d9}|}GS^xk5 diff --git a/server.py b/server.py index eb0fcc9..e51023a 100644 --- a/server.py +++ b/server.py @@ -2280,6 +2280,22 @@ class DonationCheckoutRequest(BaseModel): raise ValueError('Donation must be at least $1.00 (100 cents)') return v +# Pydantic model for contact form +class ContactFormRequest(BaseModel): + first_name: str = Field(..., min_length=1, max_length=100) + last_name: str = Field(..., min_length=1, max_length=100) + email: str = Field(..., min_length=1, max_length=255) + subject: str = Field(..., min_length=1, max_length=200) + message: str = Field(..., min_length=1, max_length=2000) + + @validator('email') + def validate_email(cls, v): + import re + email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + if not re.match(email_regex, v): + raise ValueError('Invalid email address') + return v + @api_router.get("/subscriptions/plans") async def get_subscription_plans(db: Session = Depends(get_db)): """Get all active subscription plans.""" @@ -3320,6 +3336,94 @@ async def create_donation_checkout( logger.error(f"Error creating donation checkout: {str(e)}") raise HTTPException(status_code=500, detail="Failed to create donation checkout") +@api_router.post("/contact") +async def submit_contact_form( + request: ContactFormRequest, + db: Session = Depends(get_db) +): + """Handle contact form submission and send email to admin.""" + + try: + # Get admin email from environment or use default + admin_email = os.getenv("ADMIN_EMAIL", "info@loaftx.org") + + # Create email content + subject = f"New Contact Form Submission: {request.subject}" + + html_content = f""" + + + + + + +
+
+

New Contact Form Submission

+
+
+
+
From:
+
{request.first_name} {request.last_name}
+
+ +
+
Email:
+
{request.email}
+
+ +
+
Subject:
+
{request.subject}
+
+ +
+
Message:
+
{request.message}
+
+ +

+ Reply directly to this email to respond to {request.first_name}. +

+
+
+ + + """ + + # Import send_email from email_service + from email_service import send_email + + # Send email to admin + email_sent = await send_email(admin_email, subject, html_content) + + if not email_sent: + logger.error(f"Failed to send contact form email from {request.email}") + raise HTTPException(status_code=500, detail="Failed to send contact form. Please try again later.") + + logger.info(f"Contact form submitted by {request.first_name} {request.last_name} ({request.email})") + + return { + "message": "Contact form submitted successfully. We'll get back to you soon!", + "success": True + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error processing contact form: {str(e)}") + raise HTTPException(status_code=500, detail="Failed to process contact form") + @app.post("/api/webhooks/stripe") async def stripe_webhook(request: Request, db: Session = Depends(get_db)): """Handle Stripe webhook events. Note: This endpoint is NOT on the api_router to avoid /api/api prefix."""