diff --git a/backend/internal/handlers/profiles/profiles.go b/backend/internal/handlers/profiles/profiles.go
index d0aa4bc..d6eca92 100644
--- a/backend/internal/handlers/profiles/profiles.go
+++ b/backend/internal/handlers/profiles/profiles.go
@@ -287,3 +287,26 @@ func (s *Service) GetUserVisitedVenues(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(venues)
}
+
+func (s *Service) GetUserLocation(c *fiber.Ctx) error {
+ userID := c.Params("userId")
+ if userID == "" {
+ return fiber.NewError(fiber.StatusBadRequest, "User ID is required")
+ }
+
+ userUUID, err := uuid.Parse(userID)
+ if err != nil {
+ return fiber.NewError(fiber.StatusBadRequest, "Invalid User ID format")
+ }
+
+ // Fetch latitude and longitude from the store
+ location, err := s.store.GetUserLocation(c.Context(), userUUID)
+ if err != nil {
+ return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch user location")
+ }
+
+ // Return the latitude and longitude
+ return c.Status(fiber.StatusOK).JSON(location)
+}
+
+
diff --git a/backend/internal/handlers/profiles/routes.go b/backend/internal/handlers/profiles/routes.go
index e462180..addb6b2 100644
--- a/backend/internal/handlers/profiles/routes.go
+++ b/backend/internal/handlers/profiles/routes.go
@@ -27,5 +27,6 @@ func Routes(app *fiber.App, params types.Params) {
protected.Get("/reviewed-venues/:userId", service.GetUserReviewsWithVenueData)
protected.Get("/saved-venues/:userId", service.GetUserSavedVenues)
protected.Get("/visited-venues/:userId", service.GetUserVisitedVenues)
+ protected.Get("/:userId/location", service.GetUserLocation)
}
diff --git a/backend/internal/handlers/venues/routes.go b/backend/internal/handlers/venues/routes.go
index 2a88c8c..f929994 100644
--- a/backend/internal/handlers/venues/routes.go
+++ b/backend/internal/handlers/venues/routes.go
@@ -18,6 +18,7 @@ func Routes(app *fiber.App, params types.Params) {
//Endpoints
protected.Get("/", service.GetAllVenuesWithFilter)
protected.Get("/batch", service.GetVenuesByIDs)
+ protected.Get("/location", service.GetVenuesByLocation)
protected.Get("/persona/:venueId", service.GetVenuePersona)
protected.Get("/search", service.GetVenuesFromName)
diff --git a/backend/internal/handlers/venues/venues.go b/backend/internal/handlers/venues/venues.go
index a5cf1cc..8c22b96 100644
--- a/backend/internal/handlers/venues/venues.go
+++ b/backend/internal/handlers/venues/venues.go
@@ -251,3 +251,37 @@ func (s *Service) GetVenuesByIDs(c *fiber.Ctx) error {
// Return the list of venues
return c.Status(fiber.StatusOK).JSON(venues)
}
+
+func (s *Service) GetVenuesByLocation(c *fiber.Ctx) error {
+ // Parse latitude
+ latitude := c.QueryFloat("latitude")
+ fmt.Print(latitude)
+ if latitude == 0 {
+ log.Printf("Invalid or missing latitude parameter: %v", latitude)
+ return fiber.NewError(fiber.StatusBadRequest, "Invalid or missing latitude parameter")
+ }
+
+ // Parse longitude
+ longitude := c.QueryFloat("longitude")
+ if longitude == 0 {
+ log.Printf("Invalid or missing longitude parameter: %v", longitude)
+ return fiber.NewError(fiber.StatusBadRequest, "Invalid or missing longitude parameter")
+ }
+
+ // Parse radius with default value of 1000
+ radius := c.QueryInt("radius", 1000)
+ if radius <= 0 {
+ log.Printf("Invalid radius parameter: %d", radius)
+ return fiber.NewError(fiber.StatusBadRequest, "Invalid radius parameter")
+ }
+
+ // Fetch venues by location
+ venues, err := s.store.GetVenuesByLocation(c.Context(), latitude, longitude, radius)
+ if err != nil {
+ log.Printf("Error fetching venues by location: %v | Latitude: %f | Longitude: %f | Radius: %d", err, latitude, longitude, radius)
+ return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch venues by location")
+ }
+
+ // Return the list of venues as JSON
+ return c.Status(fiber.StatusOK).JSON(venues)
+}
diff --git a/backend/internal/models/location.go b/backend/internal/models/location.go
new file mode 100644
index 0000000..b709f50
--- /dev/null
+++ b/backend/internal/models/location.go
@@ -0,0 +1,6 @@
+package models
+
+type Location struct {
+ Latitude float64 `json:"latitude"`
+ Longitude float64 `json:"longitude"`
+}
diff --git a/backend/internal/storage/postgres/profiles.go b/backend/internal/storage/postgres/profiles.go
index cc35f96..16ea5ca 100644
--- a/backend/internal/storage/postgres/profiles.go
+++ b/backend/internal/storage/postgres/profiles.go
@@ -440,3 +440,24 @@ func (db *DB) GetUserVisitedVenues(ctx context.Context, userID uuid.UUID) ([]mod
return venues, rows.Err()
}
+
+func (db *DB) GetUserLocation(ctx context.Context, userID uuid.UUID) (models.Location, error) {
+ query := `
+ SELECT
+ ST_Y(location::geometry) AS latitude,
+ ST_X(location::geometry) AS longitude
+ FROM users
+ WHERE user_id = $1
+ `
+
+ var location models.Location
+ row := db.conn.QueryRow(ctx, query, userID)
+ if err := row.Scan(&location.Latitude, &location.Longitude); err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return models.Location{}, fmt.Errorf("no location found for user_id: %s", userID)
+ }
+ return models.Location{}, err
+ }
+
+ return location, nil
+}
diff --git a/backend/internal/storage/postgres/venues.go b/backend/internal/storage/postgres/venues.go
index ace2fba..8bb5ad4 100644
--- a/backend/internal/storage/postgres/venues.go
+++ b/backend/internal/storage/postgres/venues.go
@@ -110,6 +110,7 @@ func (db *DB) GetAllVenues(ctx context.Context) ([]models.Venue, error) {
saturday_hours, sunday_hours, ST_Y(location::geometry) AS latitude, ST_X(location::geometry) AS longitude FROM venue`
rows, err := db.conn.Query(ctx, query)
if err != nil {
+ fmt.Print("hello")
return []models.Venue{}, err
}
defer rows.Close()
@@ -183,6 +184,49 @@ func (db *DB) GetVenuesByIDs(ctx context.Context, venueIDs []uuid.UUID) ([]model
return venues, rows.Err()
}
+func (db *DB) GetVenuesByLocation(ctx context.Context, latitude float64, longitude float64, radiusInMeters int) ([]models.Venue, error) {
+ query := `
+ SELECT
+ venue_id,
+ name,
+ address,
+ city,
+ state,
+ zip_code,
+ ST_Y(location::geometry) AS latitude,
+ ST_X(location::geometry) AS longitude,
+ venue_type,
+ total_rating,
+ price,
+ avg_mainstream,
+ avg_price,
+ avg_exclusive,
+ avg_energy,
+ monday_hours,
+ tuesday_hours,
+ wednesday_hours,
+ thursday_hours,
+ friday_hours,
+ saturday_hours,
+ sunday_hours,
+ created_at,
+ COALESCE(updated_at, '9999-12-31 23:59:59') AS updated_at
+ FROM venue
+ WHERE ST_DWithin(
+ location::geography,
+ ST_MakePoint($1, $2)::geography, $3) limit 20
+ `
+
+ rows, err := db.conn.Query(ctx, query, latitude, longitude, radiusInMeters)
+ if err != nil {
+ log.Printf("Database query failed: %v | Query: %s | Params: longitude=%f, latitude=%f, radius=%d", err, query, longitude, latitude, radiusInMeters)
+ return nil, fmt.Errorf("database query error: %w", err)
+ }
+ defer rows.Close()
+ arr, err := pgx.CollectRows(rows, pgx.RowToStructByName[models.Venue])
+ return arr, err
+}
+
func (db *DB) GetAllVenuesWithFilter(ctx context.Context, where string, sort string) ([]models.Venue, error) {
query := `SELECT venue_id, name, address, city, state, zip_code, created_at, venue_type, updated_at, price, total_rating,
avg_energy, avg_mainstream, avg_exclusive, avg_price, monday_hours, tuesday_hours, wednesday_hours, thursday_hours, friday_hours,
diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go
index 89ccf8d..975b680 100644
--- a/backend/internal/storage/storage.go
+++ b/backend/internal/storage/storage.go
@@ -37,6 +37,7 @@ type Profile interface {
GetUserReviewsWithVenueData(ctx context.Context, userID uuid.UUID) ([]models.ReviewWithVenue, error)
GetUserSavedVenues(context.Context, uuid.UUID) ([]models.Venue, error)
GetUserVisitedVenues(context.Context, uuid.UUID) ([]models.Venue, error)
+ GetUserLocation(ctx context.Context, userID uuid.UUID) (models.Location, error)
}
type UserRating interface {
@@ -52,6 +53,7 @@ type Venues interface {
GetVenuesFromName(context.Context, string) ([]models.Venue, error)
GetAllVenues(ctx context.Context) ([]models.Venue, error)
GetVenuesByIDs(ctx context.Context, ids []uuid.UUID) ([]models.Venue, error)
+ GetVenuesByLocation(ctx context.Context, latitude float64, longitude float64, radiusInMeters int) ([]models.Venue, error)
}
type VenueRatings interface {
diff --git a/frontend/assets/custom-marker.png b/frontend/assets/custom-marker.png
new file mode 100644
index 0000000..f668193
Binary files /dev/null and b/frontend/assets/custom-marker.png differ
diff --git a/frontend/assets/location.svg b/frontend/assets/location.svg
new file mode 100644
index 0000000..1ce6c94
--- /dev/null
+++ b/frontend/assets/location.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/assets/rating-star.png b/frontend/assets/rating-star.png
new file mode 100644
index 0000000..44bea00
Binary files /dev/null and b/frontend/assets/rating-star.png differ
diff --git a/frontend/components/Map/BottomModal.tsx b/frontend/components/Map/BottomModal.tsx
index ab167f3..3776b22 100644
--- a/frontend/components/Map/BottomModal.tsx
+++ b/frontend/components/Map/BottomModal.tsx
@@ -1,31 +1,15 @@
import React from "react";
-import {
- View,
- Text,
- StyleSheet,
- Modal,
- TouchableWithoutFeedback,
- TouchableOpacity,
- Dimensions,
- FlatList,
-} from "react-native";
-import { useNavigation } from "@react-navigation/native";
-import { BottomTabNavProps } from "@/types/NavigationTypes";
-
-const { height: screenHeight } = Dimensions.get("window");
+import { View, Text, StyleSheet, Modal, TouchableWithoutFeedback } from "react-native";
+import { Venue } from "@/types/Venue";
interface BottomModalProps {
visible: boolean;
onClose: () => void;
- venues: Array<{ venue_id: string; name: string; address: string }>;
+ venue: Venue | null;
}
-const BottomModal: React.FC = ({
- visible,
- onClose,
- venues,
-}) => {
- const navigation = useNavigation();
+const BottomModal: React.FC = ({ visible, onClose, venue }) => {
+ if (!venue) return null;
return (
= ({
- Happening Today
-
- item.venue_id}
- renderItem={({ item }) => (
- {
- onClose();
- navigation.navigate("Venue", { venue: item });
- }}
- >
-
- {item.name}
- {item.address}
-
-
- )}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.listContent}
- />
+ {venue.name}
+
+ {venue.address}
+
+ {/* ⭐ {venue.rating} | 💲{venue.price} */}
+ {/*
+ {venue.isOpen ? "Open Now" : "Closed"}
+ */}
@@ -69,15 +40,14 @@ const BottomModal: React.FC = ({
const styles = StyleSheet.create({
overlay: {
flex: 1,
- backgroundColor: "rgba(0, 0, 0, 0.4)",
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "flex-end",
},
modalContainer: {
backgroundColor: "#1c1c1e",
- padding: 16,
+ padding: 20,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
- height: screenHeight * 0.5,
},
tabIndicator: {
width: 40,
@@ -87,32 +57,24 @@ const styles = StyleSheet.create({
marginBottom: 8,
borderRadius: 2,
},
- sectionTitle: {
- color: "#fff",
+ venueName: {
fontSize: 18,
fontWeight: "bold",
- marginBottom: 8,
- fontFamily: "Archivo_700Bold"
- },
- listContent: {
- paddingBottom: 20,
+ color: "#fff",
},
- venueItem: {
- backgroundColor: "#333",
- padding: 10,
- borderRadius: 8,
- marginBottom: 10,
+ venueDetails: {
+ fontSize: 14,
+ color: "#bbb",
+ marginVertical: 4,
},
- venueName: {
+ rating: {
+ fontSize: 14,
color: "#fff",
- fontSize: 16,
- fontWeight: "bold",
- fontFamily: "Archivo_700Bold"
+ marginVertical: 4,
},
- venueAddress: {
- color: "#bbb",
+ status: {
fontSize: 14,
- fontFamily: "Archivo_500Medium"
+ // color: venue.isOpen ? "#4caf50" : "#f44336",
},
});
diff --git a/frontend/components/Map/SearchBar.tsx b/frontend/components/Map/SearchBar.tsx
index 072b864..0baaaff 100644
--- a/frontend/components/Map/SearchBar.tsx
+++ b/frontend/components/Map/SearchBar.tsx
@@ -35,28 +35,30 @@ const styles = StyleSheet.create({
searchContainer: {
flexDirection: "row",
alignItems: "center",
- padding: 10,
- backgroundColor: "#1c1c1c",
- width: "100%",
position: "absolute",
- top: 0,
+ top: 20,
+ left: 20,
+ right: 20,
zIndex: 1,
- // borderBottomWidth: 1,
- // borderBottomColor: "#555",
+ backgroundColor: "#333",
+ borderRadius: 20,
+ padding: 10,
+ shadowColor: "#000",
+ shadowOpacity: 0.2,
+ shadowOffset: { width: 0, height: 3 },
+ shadowRadius: 5,
+ elevation: 5,
},
searchIcon: {
- paddingRight: 10,
+ marginRight: 10,
},
searchInput: {
flex: 1,
height: 40,
- borderColor: "#555",
- borderWidth: 1,
- borderRadius: 10,
- paddingLeft: 10,
color: "#fff",
- backgroundColor: "#1e1e1e",
- fontFamily: "Archivo_500Medium"
+ backgroundColor: "#444",
+ borderRadius: 10,
+ paddingHorizontal: 10,
},
});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1ee5fa5..e2c94d6 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -2720,126 +2720,6 @@
"darwin"
]
},
- "node_modules/@expo/ngrok-bin-darwin-x64": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-darwin-x64/-/ngrok-bin-darwin-x64-2.3.41.tgz",
- "integrity": "sha512-29QZHfX4Ec0p0pQF5UrqiP2/Qe7t2rI96o+5b8045VCEl9AEAKHceGuyo+jfUDR4FSQBGFLSDb06xy8ghL3ZYA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@expo/ngrok-bin-freebsd-ia32": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-freebsd-ia32/-/ngrok-bin-freebsd-ia32-2.3.41.tgz",
- "integrity": "sha512-YYXgwNZ+p0aIrwgb+1/RxJbsWhGEzBDBhZulKg1VB7tKDAd2C8uGnbK1rOCuZy013iOUsJDXaj9U5QKc13iIXw==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@expo/ngrok-bin-freebsd-x64": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-freebsd-x64/-/ngrok-bin-freebsd-x64-2.3.41.tgz",
- "integrity": "sha512-1Ei6K8BB+3etmmBT0tXYC4dyVkJMigT4ELbRTF5jKfw1pblqeXM9Qpf3p8851PTlH142S3bockCeO39rSkOnkg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@expo/ngrok-bin-linux-arm": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-arm/-/ngrok-bin-linux-arm-2.3.41.tgz",
- "integrity": "sha512-B6+rW/+tEi7ZrKWQGkRzlwmKo7c1WJhNODFBSgkF/Sj9PmmNhBz67mer91S2+6nNt5pfcwLLd61CjtWfR1LUHQ==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@expo/ngrok-bin-linux-arm64": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-arm64/-/ngrok-bin-linux-arm64-2.3.41.tgz",
- "integrity": "sha512-eC8GA/xPcmQJy4h+g2FlkuQB3lf5DjITy8Y6GyydmPYMByjUYAGEXe0brOcP893aalAzRqbNOAjSuAw1lcCLSQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@expo/ngrok-bin-linux-ia32": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-ia32/-/ngrok-bin-linux-ia32-2.3.41.tgz",
- "integrity": "sha512-w5Cy31wSz4jYnygEHS7eRizR1yt8s9TX6kHlkjzayIiRTFRb2E1qD2l0/4T2w0LJpBjM5ZFPaaKqsNWgCUIEow==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@expo/ngrok-bin-linux-x64": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-linux-x64/-/ngrok-bin-linux-x64-2.3.41.tgz",
- "integrity": "sha512-LcU3MbYHv7Sn2eFz8Yzo2rXduufOvX1/hILSirwCkH+9G8PYzpwp2TeGqVWuO+EmvtBe6NEYwgdQjJjN6I4L1A==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@expo/ngrok-bin-sunos-x64": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-sunos-x64/-/ngrok-bin-sunos-x64-2.3.41.tgz",
- "integrity": "sha512-bcOj45BLhiV2PayNmLmEVZlFMhEiiGpOr36BXC0XSL+cHUZHd6uNaS28AaZdz95lrRzGpeb0hAF8cuJjo6nq4g==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "sunos"
- ]
- },
- "node_modules/@expo/ngrok-bin-win32-ia32": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-win32-ia32/-/ngrok-bin-win32-ia32-2.3.41.tgz",
- "integrity": "sha512-0+vPbKvUA+a9ERgiAknmZCiWA3AnM5c6beI+51LqmjKEM4iAAlDmfXNJ89aAbvZMUtBNwEPHzJHnaM4s2SeBhA==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@expo/ngrok-bin-win32-x64": {
- "version": "2.3.41",
- "resolved": "https://registry.npmjs.org/@expo/ngrok-bin-win32-x64/-/ngrok-bin-win32-x64-2.3.41.tgz",
- "integrity": "sha512-mncsPRaG462LiYrM8mQT8OYe3/i44m3N/NzUeieYpGi8+pCOo8TIC23kR9P93CVkbM9mmXsy3X6hq91a8FWBdA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
"node_modules/@expo/osascript": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.4.tgz",
@@ -2857,16 +2737,16 @@
"resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.6.1.tgz",
"integrity": "sha512-4rT46wP/94Ll+CWXtFKok1Lbo9XncSUtErFOo/9/3FVughGbIfdG4SKZOAWIpr9wxwEfkyhHfAP9q71ONlWODw==",
"dependencies": {
- "@expo/json-file": "^9.0.0",
+ "@expo/json-file": "^8.3.0",
"@expo/spawn-async": "^1.7.2",
"ansi-regex": "^5.0.0",
"chalk": "^4.0.0",
"find-up": "^5.0.0",
+ "find-yarn-workspace-root": "~2.0.0",
"js-yaml": "^3.13.1",
- "micromatch": "^4.0.8",
- "npm-package-arg": "^11.0.0",
+ "micromatch": "^4.0.2",
+ "npm-package-arg": "^7.0.0",
"ora": "^3.4.0",
- "resolve-workspace-root": "^2.0.0",
"split": "^1.0.1",
"sudo-prompt": "9.1.1"
}
@@ -4570,9 +4450,6 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
"peerDependenciesMeta": {
"typescript": {
"optional": true
@@ -4670,11 +4547,6 @@
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
}
},
"node_modules/@typescript-eslint/visitor-keys": {
@@ -4693,6 +4565,17 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@urql/core": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-5.0.8.tgz",
@@ -5120,7 +5003,7 @@
"integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
"dependencies": {
"@babel/compat-data": "^7.22.6",
- "@babel/helper-define-polyfill-provider": "^0.6.3",
+ "@babel/helper-define-polyfill-provider": "^0.6.2",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -5144,7 +5027,7 @@
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
"integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.3"
+ "@babel/helper-define-polyfill-provider": "^0.6.2"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -6504,7 +6387,7 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
"integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
"dependencies": {
- "dotenv": "^16.4.5"
+ "dotenv": "^16.4.4"
},
"engines": {
"node": ">=12"
@@ -6609,7 +6492,7 @@
"function.prototype.name": "^1.1.6",
"get-intrinsic": "^1.2.4",
"get-symbol-description": "^1.0.2",
- "globalthis": "^1.0.4",
+ "globalthis": "^1.0.3",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2",
"has-proto": "^1.0.3",
@@ -6625,10 +6508,10 @@
"is-string": "^1.0.7",
"is-typed-array": "^1.1.13",
"is-weakref": "^1.0.2",
- "object-inspect": "^1.13.3",
+ "object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
"object.assign": "^4.1.5",
- "regexp.prototype.flags": "^1.5.3",
+ "regexp.prototype.flags": "^1.5.2",
"safe-array-concat": "^1.1.2",
"safe-regex-test": "^1.0.3",
"string.prototype.trim": "^1.2.9",
@@ -6779,12 +6662,12 @@
"@eslint/plugin-kit": "^0.2.3",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.1",
+ "@humanwhocodes/retry": "^0.4.0",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.5",
+ "cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.2.0",
@@ -6803,7 +6686,8 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
+ "optionator": "^0.9.3",
+ "text-table": "^0.2.0"
},
"bin": {
"eslint": "bin/eslint.js"
@@ -10817,9 +10701,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.49",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
- "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "version": "8.4.48",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.48.tgz",
+ "integrity": "sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==",
"funding": [
{
"type": "opencollective",
@@ -11207,6 +11091,14 @@
}
}
},
+ "node_modules/react-native-animatable": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.3.tgz",
+ "integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==",
+ "dependencies": {
+ "prop-types": "^15.7.2"
+ }
+ },
"node_modules/react-native-dotenv": {
"version": "3.4.11",
"resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.11.tgz",
@@ -11283,6 +11175,29 @@
}
}
},
+ "node_modules/react-native-modal": {
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.1.tgz",
+ "integrity": "sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==",
+ "dependencies": {
+ "prop-types": "^15.6.2",
+ "react-native-animatable": "1.3.3"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": ">=0.65.0"
+ }
+ },
+ "node_modules/react-native-modalize": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/react-native-modalize/-/react-native-modalize-2.1.1.tgz",
+ "integrity": "sha512-4/7EZWsrUqAAkkAVEnOsSdpAPQaEBewX7TvwFuzgvGDzxKpq3O58I9SnSeU8QtG/r91XYHJNaU5dAuDrcLjUaQ==",
+ "peerDependencies": {
+ "react": "> 15.0.0",
+ "react-native": "> 0.50.0",
+ "react-native-gesture-handler": "> 1.0.0"
+ }
+ },
"node_modules/react-native-reanimated": {
"version": "3.16.3",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.3.tgz",
@@ -11692,7 +11607,7 @@
"regenerate": "^1.4.2",
"regenerate-unicode-properties": "^10.2.0",
"regjsgen": "^0.8.0",
- "regjsparser": "^0.12.0",
+ "regjsparser": "^0.11.0",
"unicode-match-property-ecmascript": "^2.0.0",
"unicode-match-property-value-ecmascript": "^2.1.0"
},
@@ -13203,8 +13118,7 @@
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-proto": "^1.0.3",
- "is-typed-array": "^1.1.13",
- "reflect.getprototypeof": "^1.0.6"
+ "is-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
@@ -13260,9 +13174,6 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
"peerDependenciesMeta": {
"typescript": {
"optional": true
diff --git a/frontend/screens/HomeScreen.tsx b/frontend/screens/HomeScreen.tsx
index c45b78a..0fdb17a 100644
--- a/frontend/screens/HomeScreen.tsx
+++ b/frontend/screens/HomeScreen.tsx
@@ -6,41 +6,44 @@ import EventsScrollable from "./explore/EventsScrollable";
import { API_DOMAIN } from "@env";
import { useNavigation } from "@react-navigation/native";
-const HomeScreen: React.FC = () => {
+interface HomeScreenProps {
+ showSearchBar?: boolean;
+}
- const navigation = useNavigation();
+const HomeScreen: React.FC = ({ showSearchBar = true }) => {
+ const navigation = useNavigation();
+ const handleSearch = async (text: string) => {
+ const req = await fetch(`${API_DOMAIN}/venues/search?q=${encodeURIComponent(text)}`);
+
+ if (!req.ok) {
+ console.error("Failed to search for venues");
+ return;
+ }
+
+ const res = await req.json();
+ navigation.navigate("VenueCards", { venues: res });
+ }
- const handleSearch = async (text: string) => {
-
-
- const req = await fetch(`${API_DOMAIN}/venues/search?q=${encodeURIComponent(text)}`);
-
- if (!req.ok) {
- console.error("Failed to search for venues");
- return;
- }
-
- const res = await req.json();
-
- navigation.navigate("VenueCards", { venues: res });
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+ return (
+
+ {showSearchBar && (
+
+
- );
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
const styles = StyleSheet.create({
diff --git a/frontend/screens/MapScreen.tsx b/frontend/screens/MapScreen.tsx
index 07ca513..200b571 100644
--- a/frontend/screens/MapScreen.tsx
+++ b/frontend/screens/MapScreen.tsx
@@ -1,99 +1,244 @@
import { useState, useEffect } from "react";
import { View, StyleSheet, TouchableOpacity, Text } from "react-native";
import MapView, { Marker } from "react-native-maps";
+import { Modalize } from "react-native-modalize";
+import { GestureHandlerRootView } from "react-native-gesture-handler";
import SearchBar from "@/components/Map/SearchBar";
-import BottomModal from "@/components/Map/BottomModal";
+import { Image } from "react-native";
import { API_DOMAIN } from "@env";
import { Venue } from "@/types/Venue";
import { useAuth } from "@/context/AuthContext";
import React from "react";
+import HomeScreen from "./HomeScreen";
+import EventCard from "./explore/EventCard";
+import CustomMarkerImage from "@/assets/custom-marker.png";
+import RatingStarImage from "@/assets/rating-star.png";
const MapScreen: React.FC = () => {
const [allVenues, setAllVenues] = useState([]);
- const [isModalVisible, setModalVisible] = useState(false);
- const { accessToken } = useAuth();
+ const [selectedVenue, setSelectedVenue] = useState(null);
+ const [mapKey, setMapKey] = useState(0);
+ const { user, accessToken } = useAuth();
+ const modalRef = React.useRef(null);
- const mapCustomStyle = [ { "elementType": "geometry", "stylers": [ { "color": "#242f3e" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#746855" } ] }, { "elementType": "labels.text.stroke", "stylers": [ { "color": "#242f3e" } ] }, { "featureType": "administrative.locality", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi.park", "elementType": "geometry", "stylers": [ { "color": "#263c3f" } ] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [ { "color": "#6b9a76" } ] }, { "featureType": "road", "elementType": "geometry", "stylers": [ { "color": "#38414e" } ] }, { "featureType": "road", "elementType": "geometry.stroke", "stylers": [ { "color": "#212a37" } ] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [ { "color": "#9ca5b3" } ] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [ { "color": "#746855" } ] }, { "featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [ { "color": "#1f2835" } ] }, { "featureType": "road.highway", "elementType": "labels.text.fill", "stylers": [ { "color": "#f3d19c" } ] }, { "featureType": "transit", "elementType": "geometry", "stylers": [ { "color": "#2f3948" } ] }, { "featureType": "transit.station", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "water", "elementType": "geometry", "stylers": [ { "color": "#17263c" } ] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [ { "color": "#515c6d" } ] }, { "featureType": "water", "elementType": "labels.text.stroke", "stylers": [ { "color": "#17263c" } ] } ]
-
- const getAllVenues = async (): Promise => {
- if (!accessToken) {
- console.log("No access token available");
- return null;
+ const fetchVenues = async (): Promise => {
+ if (!accessToken || !user?.location) {
+ console.log("No access token available or user location available");
+ return;
}
try {
- const res = await fetch(`${API_DOMAIN}/venues`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${accessToken}`,
- },
- });
-
- if (!res.ok) {
- throw new Error(`Error fetching data: ${res.statusText}`);
+ const locationRes = await fetch(
+ `${API_DOMAIN}/profiles/${user.user_id}/location`,
+ {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+
+ if (!locationRes.ok)
+ throw new Error(
+ `Error fetching user location: ${locationRes.statusText}`
+ );
+ const { latitude, longitude } = await locationRes.json();
+ // Fetch venues by location
+ const radius = 80000; // 80,000 meters (80 km)
+ const venuesRes = await fetch(
+ `${API_DOMAIN}/venues/location?latitude=${latitude}&longitude=${longitude}&radius=${radius}`,
+ // `${API_DOMAIN}/venues`,
+ {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+
+ if (!venuesRes.ok) {
+ throw new Error(`Error fetching venues: ${venuesRes.statusText}`);
}
+ const venues: Venue[] = await venuesRes.json();
+ console.log(venues[1]);
- const data: Venue[] = await res.json();
- return data;
+ setAllVenues(venues);
+ setMapKey((prevKey) => prevKey + 1);
} catch (err) {
- console.log("Could not connect to db or something", err);
- return null;
+ console.error("Failed to fetch venues by location:", err);
+ }
+ };
+
+ const getPriceRepresentation = (price: number | undefined): string => {
+ // Fallback to 1 star if price is undefined or 0
+ if (!price || price === 0) {
+ return "$";
}
+ // Return the correct number of dollar signs
+ return "$".repeat(price);
};
useEffect(() => {
- getAllVenues().then((venues) => {
- if (venues) {
- setAllVenues(venues);
- } else {
- console.log("Unable to get all venues");
- }
- });
- }, [accessToken]);
+ fetchVenues();
+ }, [accessToken, user?.location]);
- const toggleModal = () => {
- setModalVisible(!isModalVisible);
+ const handleMarkerPress = (venue: Venue) => {
+ setSelectedVenue(venue);
+ modalRef.current?.open();
+ };
+
+ const handleToggleModal = () => {
+ setSelectedVenue(null);
+ modalRef.current?.open();
+ };
+
+ // Get the current day of the week for displaying venue hours
+ const getCurrentDayHours = (venue: Venue): string | number => {
+ const days = [
+ "monday_hours",
+ "tuesday_hours",
+ "wednesday_hours",
+ "thursday_hours",
+ "friday_hours",
+ "saturday_hours",
+ "sunday_hours",
+ ];
+ const today = new Date().getDay();
+ const dayKey = days[today - 1] as keyof Venue;
+
+ if (venue[dayKey] == "NULL") {
+ return "Hours not available";
+ }
+
+ return venue[dayKey] || "Hours not available";
};
return (
-
- setModalVisible(false)}
- venues={allVenues}
- />
-
-
- {allVenues.length > 0 &&
- allVenues.map((v) => (
+
+
+ {/* Floating Search Bar */}
+
+
+ {/* Map */}
+
+ {allVenues.map((venue) => (
+ title={venue.name}
+ description={venue.address}
+ onPress={() => handleMarkerPress(venue)}
+ >
+
+
+ {/* */}
+
+
))}
-
-
- {/* Circular button to toggle modal visibility */}
-
- +
-
-
+
+
+ {/** TODO: Fix button loading time */}
+ {/* Toggle Button */}
+
+ +
+
+
+ {/* Bottom Modal */}
+
+ {selectedVenue ? (
+ // Selected Venue View
+ // TODO: Match fonts to figma
+
+ {/* Venue Title with Glow */}
+
+ {selectedVenue.name || "Unknown Venue"}
+
+
+ {/* Venue Details */}
+
+ Venue type | {selectedVenue.city || "N/A"},{" "}
+ {selectedVenue.state || "N/A"}
+
+
+ {/* Rating Container */}
+ {/** TODO: Map venue data to star rating, money sign, open status. */}
+
+ {/* Stand-in Rating */}
+
+ {selectedVenue.total_rating}
+
+
+ {/* Star Icon */}
+
+
+ {/* Divider */}
+ |
+
+ {/* Money Sign */}
+
+ {getPriceRepresentation(selectedVenue?.price)}
+
+
+ {/* Divider */}
+ |
+
+ {/* Hours for the Day */}
+
+ {getCurrentDayHours(selectedVenue)}
+
+
+
+ {/* Centered EventCard */}
+
+
+
+
+ ) : (
+
+ )}
+
+
+
);
};
@@ -102,10 +247,9 @@ const styles = StyleSheet.create({
flex: 1,
},
map: {
- width: "100%",
- height: "100%",
+ flex: 1,
},
- circularButton: {
+ toggleButton: {
position: "absolute",
bottom: 30,
left: "50%",
@@ -127,6 +271,114 @@ const styles = StyleSheet.create({
fontSize: 24,
fontWeight: "bold",
},
+ modalBackground: {
+ backgroundColor: "#1E1E2C",
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ },
+ handleStyle: {
+ backgroundColor: "#FFFFFF",
+ width: 50,
+ height: 5,
+ borderRadius: 2.5,
+ alignSelf: "center",
+ marginVertical: 10,
+ },
+ modalContent: {
+ padding: 20,
+ paddingTop: 35,
+ },
+ venueName: {
+ fontSize: 30,
+ fontWeight: "bold",
+ color: "#FFFFFF",
+ textShadowColor: "rgba(255, 255, 255, 0.8)",
+ textShadowOffset: { width: 0, height: 0 },
+ textShadowRadius: 10,
+ marginBottom: 5,
+ },
+ venueDetails: {
+ fontSize: 16,
+ color: "#CCCCCC",
+ marginBottom: 10,
+ },
+ ratingGlow: {
+ fontSize: 24,
+ color: "#FFD700",
+ textShadowColor: "rgba(255, 215, 0, 0.8)",
+ textShadowOffset: { width: 0, height: 0 },
+ textShadowRadius: 10,
+ marginRight: 10,
+ },
+ priceText: {
+ fontSize: 18,
+ color: "#FFFFFF",
+ marginLeft: 10,
+ },
+ listTitle: {
+ fontSize: 18,
+ fontWeight: "bold",
+ color: "#FFFFFF",
+ marginBottom: 10,
+ },
+ venueItem: {
+ paddingVertical: 10,
+ borderBottomWidth: 1,
+ borderBottomColor: "#444",
+ },
+ venueAddress: {
+ fontSize: 14,
+ color: "#BBBBBB",
+ },
+ customMarker: {
+ width: 40,
+ height: 40,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ markerImage: {
+ width: "100%",
+ height: "100%",
+ },
+ ratingContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "flex-start",
+ marginTop: 10,
+ },
+ standInRating: {
+ fontSize: 18,
+ color: "#FFFFFF",
+ marginRight: 5,
+ },
+ ratingStar: {
+ width: 20,
+ height: 20,
+ marginHorizontal: 5,
+ },
+ divider: {
+ fontSize: 18,
+ color: "#FFFFFF",
+ marginHorizontal: 5,
+ },
+ moneySign: {
+ fontSize: 18,
+ fontWeight: "bold",
+ color: "#FFFFFF",
+ marginHorizontal: 5,
+ },
+ statusText: {
+ fontSize: 18,
+ color: "#FFFFFF",
+ marginLeft: 5,
+ },
+ eventCardContainer: {
+ width: "100%",
+ justifyContent: "center",
+ alignItems: "center",
+ marginTop: 20,
+ paddingHorizontal: 10,
+ },
});
export default MapScreen;
diff --git a/frontend/types/Venue.ts b/frontend/types/Venue.ts
index 19f97b1..7ea4768 100644
--- a/frontend/types/Venue.ts
+++ b/frontend/types/Venue.ts
@@ -1,11 +1,21 @@
-export interface Venue {
- venue_id: string;
- name: string;
- address: string;
- city: string;
- state: string;
- zipcode: string;
- longitude: number;
- latitude: number;
- created_at: string;
+export interface Venue {
+ venue_id: string;
+ name: string;
+ address: string;
+ city: string;
+ state: string;
+ zipcode: string;
+ longitude: number;
+ latitude: number;
+ created_at: string;
+ total_rating: number;
+ price: number;
+ monday_hours: string;
+ tuesday_hours: string;
+ wednesday_hours: string;
+ thursday_horus: string;
+ friday_hours: string;
+ saturday_hours: string;
+ sunday_hours: string;
+
}
\ No newline at end of file