From d4127e878b7b8b2a703c08ae94cc91e5e5cd5f08 Mon Sep 17 00:00:00 2001 From: Gourav Kumar Date: Mon, 29 Dec 2025 07:37:08 +0530 Subject: [PATCH] first commit --- app.py | 169 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 46 +++++++++++ secret_survey.py | 182 ++++++++++++++++++++++++++++++++++++++++++++ survey.py | 175 ++++++++++++++++++++++++++++++++++++++++++ survey_results.json | 31 ++++++++ 5 files changed, 603 insertions(+) create mode 100644 app.py create mode 100644 requirements.txt create mode 100644 secret_survey.py create mode 100644 survey.py create mode 100644 survey_results.json diff --git a/app.py b/app.py new file mode 100644 index 0000000..99dc673 --- /dev/null +++ b/app.py @@ -0,0 +1,169 @@ +import json + +from nicegui import ui + +# --- Configuration & Options --- +FREQUENCY_OPTIONS = ["Daily", "Weekly", "Monthly", "Rarely"] +FEATURE_OPTIONS = ["News Feed", "Marketplace", "Groups", "Messenger", "Stories"] +OTHER_APP_OPTIONS = ["Instagram", "TikTok", "X/Twitter", "LinkedIn", "Snapchat", "Reddit"] +GENDER_OPTIONS = ["Male", "Female", "Non-binary", "Prefer not to say"] + + +class SurveyState: + def __init__(self): + self.frequency = None + self.selected_features = [] + self.feature_feedback = {} + self.age = None + self.gender = None + self.location = "" + + def save_to_json(self): + data = { + "usage": {"frequency": self.frequency, "features_used": self.selected_features}, + "feature_feedback": self.feature_feedback, + "demographics": {"age": self.age, "gender": self.gender, "location": self.location}, + } + with open("survey_results.json", "w") as f: + json.dump(data, f, indent=4) + + +state = SurveyState() + + +def create_survey(): + container = ui.column().classes("w-full items-center q-pa-md") + + # --- PAGE 1: USAGE --- + def show_page_1(): + container.clear() + with container: + ui.label("Step 1: Facebook Usage").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-lg p-6"): + ui.label("How frequently do you use Facebook?").classes("text-bold") + ui.radio(options=FREQUENCY_OPTIONS, on_change=lambda: update_p1_btn()).bind_value(state, "frequency") + + ui.label("What features do you use?").classes("text-bold mt-4") + ui.select(options=FEATURE_OPTIONS, multiple=True, on_change=lambda: update_p1_btn()).bind_value( + state, "selected_features" + ).classes("w-full") + + next_btn = ui.button("Next", on_click=show_page_2).classes("mt-6 w-full") + + def update_p1_btn(): + next_btn.set_enabled(state.frequency is not None and len(state.selected_features) > 0) + + update_p1_btn() + + # --- PAGE 2: RATINGS & FEEDBACK --- + def show_page_2(): + container.clear() + # Ensure data structure exists for selected features + for f in state.selected_features: + if f not in state.feature_feedback: + state.feature_feedback[f] = {"rating": 5, "likes": "", "other_apps": [], "improvements": ""} + + with container: + ui.label("Step 2: Feedback").classes("text-h4 mb-4") + with ui.row().classes("w-full max-w-lg justify-between mt-4"): + ui.button("Back", on_click=show_page_1).props("outline") + next_btn = ui.button("Next", on_click=show_page_3) + ui.tooltip("Please answer all the questions (min 20 chars for improvements)").bind_visibility_from( + next_btn, "enabled", backward=lambda v: not v + ) + + def update_p2_btn(): + valid = True + for f in state.selected_features: + f_data = state.feature_feedback[f] + if f_data["rating"] < 4: + if len(f_data.get("improvements", "")) < 20: + valid = False + next_btn.set_enabled(valid) + + update_p2_btn() + + for feature in state.selected_features: + with ui.card().classes("w-full max-w-lg p-6 mb-4"): + ui.label(f"Feature: {feature}").classes("text-h6 text-primary") + + slider = ui.slider(min=1, max=5, step=1, on_change=lambda: update_p2_btn()).bind_value( + state.feature_feedback[feature], "rating" + ) + + # High Rating Logic + with ui.column().classes("w-full").bind_visibility_from(slider, "value", backward=lambda v: v >= 4): + ui.textarea("What do you like about it?").classes("w-full").bind_value( + state.feature_feedback[feature], "likes" + ) + + # Low Rating Logic + with ui.column().classes("w-full").bind_visibility_from(slider, "value", backward=lambda v: v < 4): + ui.label("Other apps you use for this:") + ui.select(options=OTHER_APP_OPTIONS, multiple=True).classes("w-full").bind_value( + state.feature_feedback[feature], "other_apps" + ) + + ui.textarea("Improvements (min 20 chars)", on_change=lambda: update_p2_btn()).classes( + "w-full" + ).bind_value(state.feature_feedback[feature], "improvements") + + # --- PAGE 3: DEMOGRAPHICS --- + def show_page_3(): + container.clear() + with container: + ui.label("Step 3: Demographics").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-lg p-6"): + ui.number("Age", format="%d").classes("w-full").bind_value(state, "age") + ui.select(label="Gender", options=GENDER_OPTIONS).classes("w-full").bind_value(state, "gender") + ui.input("Location").classes("w-full").bind_value(state, "location") + + with ui.row().classes("w-full justify-between mt-6"): + ui.button("Back", on_click=show_page_2).props("outline") + ui.button("Review Responses", on_click=show_confirmation).classes("bg-blue") + + # --- PAGE 4: CONFIRMATION (SUMMARY) --- + def show_confirmation(): + container.clear() + with container: + ui.label("Review Your Responses").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-2xl p-6"): + with ui.column().classes("gap-1"): + ui.label("Usage Info").classes("text-bold text-lg") + ui.label(f"Frequency: {state.frequency}") + ui.label(f"Features: {', '.join(state.selected_features)}") + + ui.separator().classes("my-4") + ui.label("Feedback").classes("text-bold text-lg") + for f in state.selected_features: + data = state.feature_feedback[f] + ui.label(f"• {f}: Rating {data['rating']}/5") + if data["rating"] >= 4: + ui.label(f" Likes: {data['likes'] or 'N/A'}").classes("text-grey-7 ml-4") + else: + ui.label(f" Improvements: {data['improvements']}").classes("text-grey-7 ml-4") + + ui.separator().classes("my-4") + ui.label("Demographics").classes("text-bold text-lg") + ui.label(f"Age: {state.age} | Gender: {state.gender} | Location: {state.location}") + + with ui.row().classes("w-full justify-between mt-8"): + ui.button("Back to Edit", on_click=show_page_3).props("outline") + ui.button("Final Confirm & Submit", on_click=handle_final_submit).classes("bg-green") + + # --- PAGE 5: FINAL ACKNOWLEDGMENT --- + def handle_final_submit(): + state.save_to_json() + container.clear() + with container: + with ui.card().classes("w-full max-w-lg p-12 items-center text-center"): + ui.icon("check_circle", color="green").classes("text-6xl") + ui.label("Thank You!").classes("text-h3 mt-4") + ui.label("Your responses have been recorded successfully.").classes("text-lg text-grey-6") + ui.label("This survey is now closed.").classes("mt-4 italic") + + show_page_1() + + +create_survey() +ui.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4d4783f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,46 @@ +aiofiles==25.1.0 +aiohappyeyeballs==2.6.1 +aiohttp==3.13.2 +aiosignal==1.4.0 +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.12.0 +attrs==25.4.0 +bidict==0.23.1 +certifi==2025.11.12 +click==8.3.1 +docutils==0.22.4 +fastapi==0.128.0 +frozenlist==1.8.0 +h11==0.16.0 +httpcore==1.0.9 +httptools==0.7.1 +httpx==0.28.1 +idna==3.11 +ifaddr==0.2.0 +itsdangerous==2.2.0 +Jinja2==3.1.6 +markdown2==2.5.4 +MarkupSafe==3.0.3 +multidict==6.7.0 +nicegui==3.4.1 +orjson==3.11.5 +propcache==0.4.1 +pydantic==2.12.5 +pydantic_core==2.41.5 +Pygments==2.19.2 +python-dotenv==1.2.1 +python-engineio==4.13.0 +python-multipart==0.0.21 +python-socketio==5.16.0 +PyYAML==6.0.3 +simple-websocket==1.1.0 +starlette==0.50.0 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +uvicorn==0.40.0 +uvloop==0.22.1 +watchfiles==1.1.1 +websockets==15.0.1 +wsproto==1.3.2 +yarl==1.22.0 diff --git a/secret_survey.py b/secret_survey.py new file mode 100644 index 0000000..ee823e7 --- /dev/null +++ b/secret_survey.py @@ -0,0 +1,182 @@ +import json +import secrets + +from nicegui import app, ui + +# --- 1. User Database & Token Generation --- +# In a real app, you'd save these to a file or DB. +# Generating unique tokens for your three users: +USER_MAP = {"k9a2_xJv1": "Ken", "Lz78_pQn9": "Liz", "Rb55_mTk2": "Ruben"} + +# --- Survey Options & State --- +FREQUENCY_OPTIONS = ["Daily", "Weekly", "Monthly", "Rarely"] +FEATURE_OPTIONS = ["News Feed", "Marketplace", "Groups", "Messenger", "Stories"] +OTHER_APP_OPTIONS = ["Instagram", "TikTok", "X/Twitter", "LinkedIn", "Snapchat", "Reddit"] +GENDER_OPTIONS = ["Male", "Female", "Non-binary", "Prefer not to say"] + + +class SurveyState: + def __init__(self, name): + self.user_name = name + self.frequency = None + self.selected_features = [] + self.feature_feedback = {} + self.age = None + self.gender = None + self.location = "" + + def save_to_json(self): + data = { + "user": self.user_name, + "usage": {"frequency": self.frequency, "features_used": self.selected_features}, + "feature_feedback": self.feature_feedback, + "demographics": {"age": self.age, "gender": self.gender, "location": self.location}, + } + filename = f"survey_{self.user_name.lower()}.json" + with open(filename, "w") as f: + json.dump(data, f, indent=4) + + +# --- 2. The Main Page Function --- +@ui.page("/") +def main_page(token: str): + # Extract 'id' from the URL query parameters + # user_token = app.native.main_window.get_url() if hasattr(app, "native") else "" + # Standard way in web mode: + # token = ui.query_params.get("id") + if token is None: + with ui.column().classes("w-full items-center mt-20"): + ui.icon("error", color="red").classes("text-6xl") + ui.label("User not found").classes("text-h4 text-negative") + ui.label("Please check your unique link and try again.").classes("text-grey") + return + + if token not in USER_MAP: + with ui.column().classes("w-full items-center mt-20"): + ui.icon("error", color="red").classes("text-6xl") + ui.label("User not found").classes("text-h4 text-negative") + ui.label("Please check your unique link and try again.").classes("text-grey") + return + + # Valid User Found + user_name = USER_MAP[token] + state = SurveyState(user_name) + container = ui.column().classes("w-full items-center q-pa-md") + + # --- PAGE 1: GREETING & USAGE --- + def show_page_1(): + container.clear() + with container: + ui.label(f"Welcome, {state.user_name}!").classes("text-h3 text-primary mb-2") + ui.label("Facebook Usage Survey").classes("text-h5 mb-6 text-grey-7") + + with ui.card().classes("w-full max-w-lg p-6"): + ui.label("How frequently do you use Facebook?").classes("text-bold") + ui.radio(options=FREQUENCY_OPTIONS).bind_value(state, "frequency") + + ui.label("What features do you use?").classes("text-bold mt-4") + ui.select(options=FEATURE_OPTIONS, multiple=True).classes("w-full").bind_value( + state, "selected_features" + ) + + next_btn = ui.button("Next", on_click=show_page_2).classes("mt-6 w-full") + + ui.timer( + 0.5, lambda: next_btn.set_enabled(state.frequency is not None and len(state.selected_features) > 0) + ) + + # --- PAGE 2: STAR RATINGS (With fix for terminal errors) --- + def show_page_2(): + container.clear() + for f in state.selected_features: + if f not in state.feature_feedback: + state.feature_feedback[f] = {"rating": 5, "likes": "", "other_apps": [], "improvements": ""} + + with container: + ui.label("Feature Feedback").classes("text-h4 mb-4") + + def is_page_2_valid(): + for f in state.selected_features: + d = state.feature_feedback[f] + if d["rating"] < 4 and len(d.get("improvements", "")) < 20: + return False + return True + + for feature in state.selected_features: + with ui.card().classes("w-full max-w-lg p-6 mb-4"): + ui.label(f"Rate the {feature}").classes("text-h6 text-primary") + stars = ( + ui.rating(value=5, icon="star") + .classes("text-3xl") + .bind_value(state.feature_feedback[feature], "rating") + ) + + with ui.column().classes("w-full").bind_visibility_from(stars, "value", backward=lambda v: v >= 4): + ui.textarea("What do you like about it?").classes("w-full").bind_value( + state.feature_feedback[feature], "likes" + ) + + with ui.column().classes("w-full").bind_visibility_from(stars, "value", backward=lambda v: v < 4): + ui.label("Other apps you use:").classes("text-caption") + ui.select(options=OTHER_APP_OPTIONS, multiple=True).classes("w-full").bind_value( + state.feature_feedback[feature], "other_apps" + ) + ui.textarea("Improvements (min 20 characters)").classes("w-full").bind_value( + state.feature_feedback[feature], "improvements" + ) + + with ui.row().classes("w-full max-w-lg justify-between mt-4"): + ui.button("Back", on_click=show_page_1).props("outline") + next_btn = ui.button("Next", on_click=show_page_3) + ui.tooltip("Please answer all questions fully").bind_visibility_from( + next_btn, "enabled", backward=lambda v: not v + ) + ui.timer(0.5, lambda: next_btn.set_enabled(is_page_2_valid())) + + # --- PAGE 3: DEMOGRAPHICS --- + def show_page_3(): + container.clear() + with container: + ui.label("Demographics").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-lg p-6"): + ui.number("Age", format="%d").classes("w-full").bind_value(state, "age") + ui.select(label="Gender", options=GENDER_OPTIONS).classes("w-full").bind_value(state, "gender") + ui.input("Location").classes("w-full").bind_value(state, "location") + + with ui.row().classes("w-full justify-between mt-6"): + ui.button("Back", on_click=show_page_2).props("outline") + ui.button("Review Survey", on_click=show_confirmation).classes("bg-blue") + + # --- PAGE 4: CONFIRMATION --- + def show_confirmation(): + container.clear() + with container: + ui.label("Review Your Answers").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-2xl p-6"): + with ui.column().classes("w-full gap-2"): + ui.label(f"**Name:** {state.user_name}") + ui.label(f"**Frequency:** {state.frequency}") + ui.separator() + for f in state.selected_features: + d = state.feature_feedback[f] + ui.label(f"**{f}**: {d['rating']} Stars") + ui.separator() + ui.label(f"**Location:** {state.location}") + + with ui.row().classes("w-full justify-between mt-8"): + ui.button("Back to Edit", on_click=show_page_3).props("outline") + ui.button("Confirm & Submit", on_click=handle_final_submit).classes("bg-green") + + def handle_final_submit(): + state.save_to_json() + container.clear() + with container: + with ui.card().classes("w-full max-w-lg p-12 items-center text-center"): + ui.icon("check_circle", color="green").classes("text-6xl") + ui.label("Thank You!").classes("text-h4 mt-2") + ui.label(f"Your response has been saved as survey_{state.user_name.lower()}.json") + + show_page_1() + + +ui.run() diff --git a/survey.py b/survey.py new file mode 100644 index 0000000..b5c950a --- /dev/null +++ b/survey.py @@ -0,0 +1,175 @@ +import json + +from nicegui import ui + +# --- Survey Options --- +FREQUENCY_OPTIONS = ["Daily", "Weekly", "Monthly", "Rarely"] +FEATURE_OPTIONS = ["News Feed", "Marketplace", "Groups", "Messenger", "Stories"] +OTHER_APP_OPTIONS = ["Instagram", "TikTok", "X/Twitter", "LinkedIn", "Snapchat", "Reddit"] +GENDER_OPTIONS = ["Male", "Female", "Non-binary", "Prefer not to say"] + + +class SurveyState: + def __init__(self): + self.frequency = None + self.selected_features = [] + self.feature_feedback = {} + self.age = None + self.gender = None + self.location = "" + + def save_to_json(self): + data = { + "usage": {"frequency": self.frequency, "features_used": self.selected_features}, + "feature_feedback": self.feature_feedback, + "demographics": {"age": self.age, "gender": self.gender, "location": self.location}, + } + with open("survey_results.json", "w") as f: + json.dump(data, f, indent=4) + + +state = SurveyState() + + +def create_survey(): + container = ui.column().classes("w-full items-center q-pa-md") + + # --- PAGE 1: USAGE --- + def show_page_1(): + container.clear() + with container: + ui.label("Facebook Usage").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-lg p-6"): + ui.label("How frequently do you use Facebook?").classes("text-bold") + p1_radio = ui.radio(options=FREQUENCY_OPTIONS).bind_value(state, "frequency") + + ui.label("What features do you use?").classes("text-bold mt-4") + p1_select = ( + ui.select(options=FEATURE_OPTIONS, multiple=True) + .classes("w-full") + .bind_value(state, "selected_features") + ) + + next_btn = ui.button("Next", on_click=show_page_2).classes("mt-6 w-full") + + # Validation + ui.timer( + 0.1, lambda: next_btn.set_enabled(state.frequency is not None and len(state.selected_features) > 0) + ) + + # --- PAGE 2: STAR RATINGS & CONDITIONAL FEEDBACK --- + def show_page_2(): + container.clear() + # Initialize dictionary for selected features + for f in state.selected_features: + if f not in state.feature_feedback: + state.feature_feedback[f] = {"rating": 5, "likes": "", "other_apps": [], "improvements": ""} + + with container: + ui.label("Feature Feedback").classes("text-h4 mb-4") + + # We define the button and validation logic first to ensure they are in scope + def is_page_2_valid(): + for f in state.selected_features: + data = state.feature_feedback[f] + if data["rating"] < 4: + if len(data.get("improvements", "")) < 20: + return False + return True + + for feature in state.selected_features: + with ui.card().classes("w-full max-w-lg p-6 mb-4"): + ui.label(f"Rate the {feature}").classes("text-h6 text-primary") + + # Star Rating (Quasar based) + stars = ( + ui.rating(value=5, icon="star") + .classes("text-3xl") + .bind_value(state.feature_feedback[feature], "rating") + ) + + # CASE: Rating 4 or 5 + with ui.column().classes("w-full").bind_visibility_from(stars, "value", backward=lambda v: v >= 4): + ui.textarea("What do you like about this feature?").classes("w-full").bind_value( + state.feature_feedback[feature], "likes" + ) + + # CASE: Rating below 4 + with ui.column().classes("w-full").bind_visibility_from(stars, "value", backward=lambda v: v < 4): + ui.label("What other apps do you use for this?") + ui.select(options=OTHER_APP_OPTIONS, multiple=True).classes("w-full").bind_value( + state.feature_feedback[feature], "other_apps" + ) + + ui.textarea("How can this be improved? (min 20 characters)").classes("w-full").bind_value( + state.feature_feedback[feature], "improvements" + ) + + with ui.row().classes("w-full max-w-lg justify-between mt-4"): + ui.button("Back", on_click=show_page_1).props("outline") + next_btn = ui.button("Next", on_click=show_page_3) + ui.tooltip("Please answer all questions (min 20 chars for improvements)").bind_visibility_from( + next_btn, "enabled", backward=lambda v: not v + ) + + # Reactive validation timer + ui.timer(0.5, lambda: next_btn.set_enabled(is_page_2_valid())) + + # --- PAGE 3: DEMOGRAPHICS --- + def show_page_3(): + container.clear() + with container: + ui.label("Demographics").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-lg p-6"): + ui.number("Age", format="%d").classes("w-full").bind_value(state, "age") + ui.select(label="Gender", options=GENDER_OPTIONS).classes("w-full").bind_value(state, "gender") + ui.input("Location").classes("w-full").bind_value(state, "location") + + with ui.row().classes("w-full justify-between mt-6"): + ui.button("Back", on_click=show_page_2).props("outline") + ui.button("Review Survey", on_click=show_confirmation).classes("bg-blue") + + # --- PAGE 4: CONFIRMATION (SUMMARY) --- + def show_confirmation(): + container.clear() + with container: + ui.label("Review Your Answers").classes("text-h4 mb-4") + with ui.card().classes("w-full max-w-2xl p-6"): + with ui.column().classes("w-full gap-2"): + ui.label(f"**Frequency:** {state.frequency}").classes("text-md") + ui.label(f"**Features:** {', '.join(state.selected_features)}") + + ui.separator().classes("my-2") + for f in state.selected_features: + d = state.feature_feedback[f] + ui.label(f"**{f}**: {d['rating']} Stars").classes("text-primary") + if d["rating"] >= 4: + ui.label(f"Likes: {d['likes'] or 'No comment'}").classes("ml-4 text-grey-8") + else: + ui.label(f"Improvements: {d['improvements']}").classes("ml-4 text-grey-8") + + ui.separator().classes("my-2") + ui.label( + f"**Age:** {state.age or 'N/A'} | **Gender:** {state.gender} | **Location:** {state.location}" + ) + + with ui.row().classes("w-full justify-between mt-8"): + ui.button("Back to Edit", on_click=show_page_3).props("outline") + ui.button("Confirm & Submit", on_click=handle_final_submit).classes("bg-green") + + # --- FINAL PAGE: ACKNOWLEDGMENT --- + def handle_final_submit(): + state.save_to_json() + container.clear() + with container: + with ui.card().classes("w-full max-w-lg p-12 items-center text-center"): + ui.icon("verified", color="green").classes("text-6xl") + ui.label("Submission Successful").classes("text-h4 mt-2") + ui.label("Thank you for your time. Your responses have been saved.").classes("text-grey-7") + # No buttons here prevents the user from going back + + show_page_1() + + +create_survey() +ui.run() diff --git a/survey_results.json b/survey_results.json new file mode 100644 index 0000000..1409969 --- /dev/null +++ b/survey_results.json @@ -0,0 +1,31 @@ +{ + "usage": { + "frequency": "Weekly", + "features_used": [ + "News Feed", + "Marketplace" + ] + }, + "feature_feedback": { + "News Feed": { + "rating": 2, + "likes": "", + "other_apps": [ + "TikTok", + "LinkedIn" + ], + "improvements": "I do not like this features" + }, + "Marketplace": { + "rating": 5, + "likes": "very useful", + "other_apps": [], + "improvements": "" + } + }, + "demographics": { + "age": 32.0, + "gender": "Female", + "location": "kolkata" + } +} \ No newline at end of file