diff --git a/.gitignore b/.gitignore index d0aec68..412fd51 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,6 @@ logs/ log/ -.vscode/ .idea/ *.swp *.swo diff --git a/.vscode/dictionaries/project-words.txt b/.vscode/dictionaries/project-words.txt index 5d55556..65bc8a5 100755 --- a/.vscode/dictionaries/project-words.txt +++ b/.vscode/dictionaries/project-words.txt @@ -1,3 +1,4 @@ +APIHTTP appleboy projectx RZRO diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d39149..9529875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,12 @@ All notable changes to this repository will be documented in this file. - Added Database connection retry logic -## [1.1.1] THu, Mar 12, 2026 +## [1.1.1] Thu, Mar 12, 2026 - Added Memory Cache - Redesign Page + +## [1.1.2] Sat, Mar 14, 2026 + +- Mobile responsiveness +- Added Contact us Page diff --git a/app/routes.py b/app/routes.py index 034d54c..6b3a41f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,51 +1,52 @@ import os from datetime import datetime, timezone from typing import Optional -from app.utils.cache import list_cache_clean, clear_cache + from fastapi import ( APIRouter, - Form, - Request, - status, - HTTPException, BackgroundTasks, + Form, Header, + HTTPException, Query, + Request, + status, ) from fastapi.responses import ( HTMLResponse, + JSONResponse, PlainTextResponse, RedirectResponse, - JSONResponse, ) from fastapi.templating import Jinja2Templates from pydantic import BaseModel, Field - from app import __version__ from app.utils import db from app.utils.cache import ( + clear_cache, get_from_cache, get_recent_from_cache, get_short_from_cache, - set_cache_pair, increment_visit_cache, - url_cache, + list_cache_clean, remove_cache_key, rev_cache, + set_cache_pair, + url_cache, ) from app.utils.config import ( + CACHE_PURGE_TOKEN, DOMAIN, MAX_RECENT_URLS, - CACHE_PURGE_TOKEN, QR_DIR, ) from app.utils.helper import ( - generate_code, - sanitize_url, - is_valid_url, authorize_url, format_date, + generate_code, + is_valid_url, + sanitize_url, ) from app.utils.qr import generate_qr_with_logo @@ -112,12 +113,14 @@ async def create_short_url( if not original_url or not is_valid_url(original_url): # validate the URL session["error"] = "Please enter a valid URL." + session["original_url"] = original_url # preserve user input return RedirectResponse("/", status_code=status.HTTP_303_SEE_OTHER) if not authorize_url( original_url ): # authorize the URL based on whitelist/blacklist session["error"] = "This domain is not allowed." + session["original_url"] = original_url # preserve user input return RedirectResponse("/", status_code=status.HTTP_303_SEE_OTHER) short_code: Optional[str] = get_short_from_cache(original_url) @@ -150,6 +153,11 @@ async def create_short_url( return RedirectResponse("/", status_code=status.HTTP_303_SEE_OTHER) +@ui_router.get("/contact", response_class=HTMLResponse) +async def contact(request: Request): + return templates.TemplateResponse("contact.html", {"request": request}) + + @ui_router.get("/history", response_class=HTMLResponse) async def recent_urls(request: Request): recent_urls_list = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache( diff --git a/app/static/css/tiny.css b/app/static/css/tiny.css index 9f50fb2..d940fba 100644 --- a/app/static/css/tiny.css +++ b/app/static/css/tiny.css @@ -1,15 +1,7 @@ -html, -body { - height: 100%; - margin: 0; - font-family: Arial; - padding: 0; - font-family: "Poppins", system-ui, Arial, sans-serif; - background: var(--bg); - background-size: cover; - background-position: center; - background-size: cover; - background-position: center; +*, +*::before, +*::after { + box-sizing: border-box; } :root { @@ -27,6 +19,11 @@ body { font-family: "Inter", system-ui, sans-serif; margin: 0; overflow-x: hidden; + background-image: radial-gradient(circle at 50% -20%, #1e1e2e 0%, transparent 50%); +} + +a { + color: var(--accent); } body.light-theme { @@ -47,6 +44,16 @@ body.light-theme { min-height: 100vh; } +.main-layout { + max-width: 870px; + margin: 0 auto; + padding: 6rem 1rem 4rem; + display: flex; + flex-direction: column; + gap: 2rem; + flex: 1; +} + .main-layout { width: 90%; margin: 2rem auto; @@ -63,7 +70,6 @@ body.light-theme { justify-content: space-between; padding: 0 10px; box-sizing: border-box; - background: var(--glass); border-bottom: 1px solid var(--glass-border); z-index: 1000; @@ -103,7 +109,9 @@ body.light-theme .app-header { .header-nav { display: flex; gap: 26px; - margin: 0 auto; + flex-wrap: wrap; + white-space: nowrap; + flex-shrink: 1; } .nav-link, @@ -175,9 +183,49 @@ body.dark-theme .app-header { color: var(--accent); } +/* hamburger hidden on desktop */ + +.hamburger { + display: none; + font-size: 22px; + background: transparent; + border: none; + color: var(--text-primary); + cursor: pointer; +} + @media (max-width: 600px) { - .logo { - font-size: 1.2rem; + .app-logo { + transform: scale(0.9); + } + + .app-name { + font-size: 1.1rem; + } +} + +@media (max-width: 700px) { + .hamburger { + display: block; + } + + .header-nav { + display: none; + position: fixed; + top: 55px; + left: 0; + right: 0; + width: 100%; + flex-direction: column; + background: var(--bg); + padding: 20px; + display: none; + gap: 16px; + border-bottom: 1px solid var(--glass-border); + } + + .header-nav.open { + display: flex; } } @@ -275,6 +323,10 @@ body.dark-theme .app-header { gap: 1.5rem; } +.short-url { + max-width: 100%; +} + .qr-image { width: 80px; height: 80px; @@ -295,6 +347,8 @@ body.dark-theme .app-header { font-weight: 700; color: var(--text-primary); text-decoration: none; + word-break: break-all; + overflow-wrap: anywhere; } .result-actions { @@ -388,6 +442,21 @@ body.dark-theme .app-header { font-weight: 700; } +@media (max-width: 768px) { + .input-wrapper { + flex-direction: column; + } + + .btn-primary { + width: 100%; + } + + .scroll-container { + padding-left: 12px; + padding-right: 12px; + } +} + /* =============================== MODERN GLASS RECENT TABLE ================================= */ @@ -402,6 +471,7 @@ body.dark-theme .app-header { .recent-table-wrapper { width: 100%; overflow-x: auto; + -webkit-overflow-scrolling: touch; } /* =============================== @@ -414,7 +484,7 @@ body.dark-theme .app-header { border-radius: 12px; overflow: hidden; table-layout: fixed; - min-width: 800px; + min-width: 720px; } .recent-table thead { @@ -590,10 +660,7 @@ footer.big-footer { background: var(--bg); border-top: 1px solid var(--glass-border); padding: 4rem 1rem 2rem; - margin-top: 4rem; - position: fixed; - bottom: 0; - width: 100%; + margin-top: auto; } .footer-grid { @@ -777,3 +844,18 @@ body.light-theme .history-link { .history-link:hover { opacity: 0.7; } + +.contact-info { + max-width: 600px; + margin: 0 auto; + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: 1rem; + padding: 2rem; + list-style-type: none; + & li { + margin-bottom: 1rem; + font-size: 1.1rem; + color: var(--text-primary); + } +} diff --git a/app/templates/404.html b/app/templates/404.html index 0825534..df38cde 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -3,7 +3,7 @@
-