Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
ai_image_gen)
echo "EXTRA_ARGS=--env REPLICATE_API_TOKEN=${{ secrets.REPLICATE_API_TOKEN }}" >> $GITHUB_ENV
;;
weatherstack_app)
echo "EXTRA_ARGS=" >> $GITHUB_ENV
;;
text_annotation_app)
echo "EXTRA_ARGS=" >> $GITHUB_ENV
;;
Expand Down
7 changes: 7 additions & 0 deletions templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@
"demo_url": "https://company-dashboard-navy-book.reflex.run/",
"hidden": false,
"reflex_build": true
},
{
"name": "weatherstack_app",
"description": "A minimal weather app",
"demo_url": "https://company-dashboard-navy-book.reflex.run/",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to make sure this is updated after deploying the app

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup will change this today after re-deploy

"hidden": false,
"reflex_build": true
},
{
"name": "stock_graph_app",
Expand Down
8 changes: 8 additions & 0 deletions weatherstack_app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
__pycache__/
.web
assets/external/
*.py[cod]
*.db
.states
.DS_Store
.idea/
Binary file added weatherstack_app/assets/favicon.ico
Binary file not shown.
Empty file.
2 changes: 2 additions & 0 deletions weatherstack_app/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
reflex>=0.7.10
httpx
3 changes: 3 additions & 0 deletions weatherstack_app/rxconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import reflex as rx

config = rx.Config(app_name="weatherstack_app")
Empty file.
Empty file.
36 changes: 36 additions & 0 deletions weatherstack_app/weatherstack_app/components/preset_cards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import reflex as rx

from weatherstack_app.states.weather_state import WeatherState


def card(flag: str, title: str, city: str) -> rx.Component:
return rx.el.button(
rx.el.div(
rx.el.span(flag, class_name="text-xl"),
rx.el.p(title, class_name="font-medium text-black text-base"),
class_name="flex flex-row gap-2 items-center",
),
type="button",
class_name=(
"flex flex-col gap-1 border bg-white hover:bg-gray-100 "
"shadow-sm px-4 py-3.5 rounded-xl text-start transition-colors flex-1"
),
on_click=WeatherState.get_weather_from_preset(city),
)


def preset_cards() -> rx.Component:
return rx.el.div(
rx.el.div(
card("🇯🇵", "Tokyo, Japan", "Japan"),
card("🇫🇷", "Paris, France", "France"),
card("🇺🇸", "New York, USA", "USA"),
card("🇦🇺", "Sydney, Australia", "Australia"),
card("🇩🇪", "Berlin, Germany", "Germany"),
card("🇧🇷", "São Paulo, Brazil", "Brazil"),
card("🇨🇦", "Toronto, Canada", "Canada"),
card("🇮🇳", "Mumbai, India", "India"),
class_name="gap-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 w-full",
),
class_name="flex flex-col justify-center items-center gap-8 w-full max-w-[55rem] px-6 md:pt-12",
)
111 changes: 111 additions & 0 deletions weatherstack_app/weatherstack_app/components/weather_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import reflex as rx

from weatherstack_app.states.weather_state import WeatherState


def weather_display() -> rx.Component:
return rx.el.div(
rx.cond(
WeatherState.loading,
rx.el.div(
rx.spinner(class_name="text-blue-500"),
rx.el.p(
"Loading weather data...",
class_name="text-lg text-gray-600",
),
class_name="flex flex-col items-center justify-center p-6 bg-white rounded-lg shadow-md",
),
rx.cond(
WeatherState.error_message != "",
rx.el.div(
rx.el.p(
"Error:",
class_name="font-semibold text-red-600",
),
rx.el.p(
WeatherState.error_message,
class_name="text-red-500",
),
class_name="p-6 bg-red-50 rounded-lg shadow-md border border-red-200",
),
rx.cond(
WeatherState.display_weather,
rx.el.div(
rx.el.p(
f"{WeatherState.weather_data['location']['name']}, {WeatherState.weather_data['location']['country']}",
class_name="text-3xl font-bold text-gray-800 mb-4",
),
rx.el.div(
rx.el.img(
src=WeatherState.weather_icon_url,
alt="Weather icon",
class_name="w-20 h-20 mb-2",
),
rx.el.p(
WeatherState.weather_description_text,
class_name="text-xl text-gray-700 capitalize",
),
class_name="flex flex-col items-center mb-4",
),
rx.el.div(
rx.el.div(
rx.el.p(
"Temperature:",
class_name="text-md text-gray-600",
),
rx.el.p(
f"{WeatherState.weather_data['current']['temperature']}°C",
class_name="text-2xl font-semibold text-blue-600",
),
class_name="p-4 bg-blue-50 rounded-lg shadow-sm text-center",
),
rx.el.div(
rx.el.p(
"Humidity:",
class_name="text-md text-gray-600",
),
rx.el.p(
f"{WeatherState.weather_data['current']['humidity']}%",
class_name="text-2xl font-semibold text-green-600",
),
class_name="p-4 bg-green-50 rounded-lg shadow-sm text-center",
),
rx.el.div(
rx.el.p(
"Feels Like:",
class_name="text-md text-gray-600",
),
rx.el.p(
f"{WeatherState.weather_data['current']['feelslike']}°C",
class_name="text-2xl font-semibold text-orange-600",
),
class_name="p-4 bg-orange-50 rounded-lg shadow-sm text-center",
),
rx.el.div(
rx.el.p(
"Wind Speed:",
class_name="text-md text-gray-600",
),
rx.el.p(
f"{WeatherState.weather_data['current']['wind_speed']} km/h",
class_name="text-2xl font-semibold text-purple-600",
),
class_name="p-4 bg-purple-50 rounded-lg shadow-sm text-center",
),
class_name="grid grid-cols-1 md:grid-cols-2 gap-4 w-full",
),
rx.button("Reset", on_click=WeatherState.reset_app),
class_name="p-6 rounded-lg border shadow-sm border w-full max-w-2xl justify-center",
),
rx.el.div(
rx.el.p(
"Enter a city to get the weather forecast.",
class_name="text-lg text-gray-500",
),
class_name="p-6 bg-white rounded-lg shadow-md",
),
),
),
),
class_name="mt-8 w-full flex justify-center",
)
Binary file added weatherstack_app/weatherstack_app/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
172 changes: 172 additions & 0 deletions weatherstack_app/weatherstack_app/states/weather_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from typing import List, TypedDict

import httpx
import reflex as rx

WEATHERSTACK_API_KEY = "YOUR_WEATHERSTACK_API_KEY"
WEATHERSTACK_API_URL = "http://api.weatherstack.com/current"


class Location(TypedDict):
name: str
country: str
region: str
lat: str
lon: str
timezone_id: str
localtime: str
localtime_epoch: int
utc_offset: str


class CurrentWeather(TypedDict):
observation_time: str
temperature: int
weather_code: int
weather_icons: List[str]
weather_descriptions: List[str]
wind_speed: int
wind_degree: int
wind_dir: str
pressure: int
precip: float
humidity: int
cloudcover: int
feelslike: int
uv_index: int
visibility: int
is_day: str


class WeatherRequest(TypedDict):
type: str
query: str
language: str
unit: str


class WeatherData(TypedDict):
request: WeatherRequest | None
location: Location | None
current: CurrentWeather | None


class WeatherState(rx.State):
city: str = ""
weather_data: WeatherData | None = None
loading: bool = False
error_message: str = ""
api_key: str = WEATHERSTACK_API_KEY

@rx.event
def reset_app(self):
self.reset()
return

@rx.event
def handle_form_submit(self, form_data: dict):
self.city = form_data.get("city", "").strip()
if not self.city:
self.error_message = "City name cannot be empty."
self.weather_data = None
return
self.error_message = ""
return WeatherState.get_weather

@rx.event
def get_weather_from_preset(self, city: str):
self.city = city
if not self.city:
self.error_message = "City name cannot be empty."
self.weather_data = None
return
self.error_message = ""
return WeatherState.get_weather

@rx.event(background=True)
async def get_weather(self):
async with self:
if not self.city:
self.error_message = "City name cannot be empty."
self.loading = False
return
if self.api_key == "YOUR_WEATHERSTACK_API_KEY":
self.error_message = "Please replace 'YOUR_WEATHERSTACK_API_KEY' with your actual WeatherStack API key in app/states/weather_state.py."
self.weather_data = None
self.loading = False
return
self.loading = True
self.error_message = ""
self.weather_data = None

try:
params = {
"access_key": self.api_key,
"query": self.city,
}

async with httpx.AsyncClient(timeout=10) as client:
response = await client.get(WEATHERSTACK_API_URL, params=params)
response.raise_for_status()
data = response.json()

async with self:
if "error" in data:
self.error_message = data["error"].get(
"info",
"An error occurred while fetching weather data.",
)
self.weather_data = None
elif "current" in data and "location" in data:
self.weather_data = data
self.error_message = ""
else:
self.error_message = "Unexpected API response format."
self.weather_data = None

except httpx.RequestError as e:
async with self:
self.error_message = f"Network error: {e}"
self.weather_data = None
except Exception as e:
async with self:
self.error_message = f"An unexpected error occurred: {e}"
self.weather_data = None
finally:
async with self:
self.loading = False

def set_city(self, city: str):
self.city = city.strip()
self.error_message = ""

@rx.var
def display_weather(self) -> bool:
return (
self.weather_data is not None
and self.weather_data.get("current") is not None
and (self.weather_data.get("location") is not None)
and (not self.error_message)
)

@rx.var
def weather_icon_url(self) -> str:
if (
self.display_weather
and self.weather_data
and self.weather_data.get("current")
and self.weather_data["current"].get("weather_icons")
):
return self.weather_data["current"]["weather_icons"][0]
return ""

@rx.var
def weather_description_text(self) -> str:
if (
self.display_weather
and self.weather_data
and self.weather_data.get("current")
and self.weather_data["current"].get("weather_descriptions")
):
return ", ".join(self.weather_data["current"]["weather_descriptions"])
return ""
Loading
Loading