summaryrefslogtreecommitdiff
path: root/Biz/PodcastItLater/UI.py
diff options
context:
space:
mode:
Diffstat (limited to 'Biz/PodcastItLater/UI.py')
-rw-r--r--Biz/PodcastItLater/UI.py247
1 files changed, 247 insertions, 0 deletions
diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py
index e9ef27d..b243ae7 100644
--- a/Biz/PodcastItLater/UI.py
+++ b/Biz/PodcastItLater/UI.py
@@ -207,6 +207,14 @@ class PageLayout(Component[AnyChildren, PageLayoutAttrs]):
classes=["dropdown-item"],
),
),
+ html.li(
+ html.a(
+ html.i(classes=["bi", "bi-chat-heart-fill", "me-2"]),
+ "Feedback",
+ href="/admin/feedback",
+ classes=["dropdown-item"],
+ ),
+ ),
classes=["dropdown-menu"],
aria_labelledby="adminDropdown",
),
@@ -621,6 +629,245 @@ class PricingPageAttrs(Attrs):
user: dict[str, typing.Any] | None
+class FeedbackFormAttrs(Attrs):
+ """Attributes for FeedbackForm component."""
+
+ campaign_id: str | None
+ success: bool
+
+
+class FeedbackForm(Component[AnyChildren, FeedbackFormAttrs]):
+ """Feedback collection form component."""
+
+ @override
+ def render(self) -> html.div:
+ campaign_id = self.attrs.get("campaign_id")
+ success = self.attrs.get("success", False)
+
+ if success:
+ return html.div(
+ html.div(
+ html.div(
+ html.h3(
+ html.i(
+ classes=["bi", "bi-check-circle-fill", "me-2"]
+ ),
+ "Thank You!",
+ classes=["text-success", "mb-3"],
+ ),
+ html.p(
+ "Your feedback has been submitted. "
+ "We really appreciate you taking the time to help us "
+ "improve PodcastItLater.",
+ classes=["lead"],
+ ),
+ html.a(
+ html.i(classes=["bi", "bi-arrow-left", "me-2"]),
+ "Back to home",
+ href="/",
+ classes=["btn", "btn-primary", "mt-3"],
+ ),
+ classes=["card-body", "text-center", "py-5"],
+ ),
+ classes=["card", "shadow-sm"],
+ ),
+ )
+
+ source_options = [
+ ("", "Select one..."),
+ ("outreach", "Email from Ben/Ava"),
+ ("search", "Search engine"),
+ ("social", "Social media"),
+ ("friend", "Friend/colleague"),
+ ("other", "Other"),
+ ]
+
+ return html.div(
+ html.div(
+ html.div(
+ html.h3(
+ html.i(classes=["bi", "bi-chat-heart-fill", "me-2"]),
+ "Share Your Feedback",
+ classes=["card-title", "mb-4"],
+ ),
+ html.p(
+ "Help us make PodcastItLater better! "
+ "All fields are optional.",
+ classes=["text-muted", "mb-4"],
+ ),
+ html.form(
+ html.input(
+ type="hidden",
+ name="campaign_id",
+ value=campaign_id or "",
+ )
+ if campaign_id
+ else html.div(),
+ # Email
+ html.div(
+ html.label(
+ "Email (optional)",
+ for_="email",
+ classes=["form-label"],
+ ),
+ html.input(
+ type="email",
+ name="email",
+ id="email",
+ placeholder="you@example.com",
+ classes=["form-control"],
+ ),
+ html.div(
+ "If you'd like us to follow up with you",
+ classes=["form-text"],
+ ),
+ classes=["mb-3"],
+ ),
+ # Source dropdown
+ html.div(
+ html.label(
+ "How did you hear about us?",
+ for_="source",
+ classes=["form-label"],
+ ),
+ html.select(
+ *[
+ html.option(label, value=value)
+ for value, label in source_options
+ ],
+ name="source",
+ id="source",
+ classes=["form-select"],
+ ),
+ classes=["mb-3"],
+ ),
+ # Use case
+ html.div(
+ html.label(
+ "What would you use PodcastItLater for?",
+ for_="use_case",
+ classes=["form-label"],
+ ),
+ html.textarea(
+ name="use_case",
+ id="use_case",
+ rows="3", # type: ignore[call-arg]
+ placeholder=(
+ "e.g., catching up on articles during commute, "
+ "listening to research papers while exercising..."
+ ),
+ classes=["form-control"],
+ ),
+ classes=["mb-3"],
+ ),
+ # General feedback
+ html.div(
+ html.label(
+ "Any other feedback?",
+ for_="feedback_text",
+ classes=["form-label"],
+ ),
+ html.textarea(
+ name="feedback_text",
+ id="feedback_text",
+ rows="3", # type: ignore[call-arg]
+ placeholder="Suggestions, issues, feature requests...",
+ classes=["form-control"],
+ ),
+ classes=["mb-3"],
+ ),
+ # Rating
+ html.div(
+ html.label(
+ "How likely are you to recommend PIL? (1-5)",
+ for_="rating",
+ classes=["form-label"],
+ ),
+ html.div(
+ *[
+ html.div(
+ html.input(
+ type="radio",
+ name="rating",
+ id=f"rating{i}",
+ value=str(i),
+ classes=["btn-check"],
+ ),
+ html.label(
+ str(i),
+ for_=f"rating{i}",
+ classes=[
+ "btn",
+ "btn-outline-primary",
+ ],
+ ),
+ classes=["me-2"],
+ )
+ for i in range(1, 6)
+ ],
+ classes=["d-flex"],
+ ),
+ html.div(
+ "1 = Not likely, 5 = Very likely",
+ classes=["form-text"],
+ ),
+ classes=["mb-4"],
+ ),
+ # Submit
+ html.button(
+ html.i(classes=["bi", "bi-send-fill", "me-2"]),
+ "Submit Feedback",
+ type="submit",
+ classes=["btn", "btn-primary", "btn-lg"],
+ ),
+ action="/feedback",
+ method="post",
+ ),
+ classes=["card-body", "p-4"],
+ ),
+ classes=["card", "shadow-sm"],
+ ),
+ )
+
+
+class FeedbackPageAttrs(Attrs):
+ """Attributes for FeedbackPage component."""
+
+ user: dict[str, typing.Any] | None
+ campaign_id: str | None
+ success: bool
+
+
+class FeedbackPage(Component[AnyChildren, FeedbackPageAttrs]):
+ """Feedback page with layout."""
+
+ @override
+ def render(self) -> PageLayout:
+ user = self.attrs.get("user")
+ campaign_id = self.attrs.get("campaign_id")
+ success = self.attrs.get("success", False)
+
+ return PageLayout(
+ html.div(
+ html.div(
+ html.div(
+ FeedbackForm(
+ campaign_id=campaign_id,
+ success=success,
+ ),
+ classes=["col-lg-8", "mx-auto"],
+ ),
+ classes=["row"],
+ ),
+ ),
+ user=user,
+ current_page="feedback",
+ page_title="Feedback - PodcastItLater",
+ error=None,
+ meta_tags=[],
+ )
+
+
class PricingPage(Component[AnyChildren, PricingPageAttrs]):
"""Pricing page component."""