diff --git a/src/shared/Jordnaer.Shared/FeatureFlags.cs b/src/shared/Jordnaer.Shared/FeatureFlags.cs
index 320ddf02..0c9fe25f 100644
--- a/src/shared/Jordnaer.Shared/FeatureFlags.cs
+++ b/src/shared/Jordnaer.Shared/FeatureFlags.cs
@@ -7,4 +7,5 @@ public static class FeatureFlags
public const string Posts = "Posts";
public const string AccountSettings = "AccountSettings";
public const string NotificationSettings = "NotificationSettings";
+ public const string MapSearch = "MapSearch";
}
diff --git a/src/shared/Jordnaer.Shared/Groups/GroupSearchFilter.cs b/src/shared/Jordnaer.Shared/Groups/GroupSearchFilter.cs
index f2e3adb9..151968ed 100644
--- a/src/shared/Jordnaer.Shared/Groups/GroupSearchFilter.cs
+++ b/src/shared/Jordnaer.Shared/Groups/GroupSearchFilter.cs
@@ -18,6 +18,18 @@ public record GroupSearchFilter
[RadiusRequired]
public string? Location { get; set; }
+ ///
+ /// Latitude coordinate for map-based location search.
+ /// When set (along with Longitude), takes precedence over Location string.
+ ///
+ public double? Latitude { get; set; }
+
+ ///
+ /// Longitude coordinate for map-based location search.
+ /// When set (along with Latitude), takes precedence over Location string.
+ ///
+ public double? Longitude { get; set; }
+
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
@@ -32,19 +44,23 @@ public override int GetHashCode()
hash = hash * 23 + (Categories != null ? Categories.Aggregate(0, (current, category) => current + category.GetHashCode()) : 0);
hash = hash * 23 + WithinRadiusKilometers.GetHashCode();
hash = hash * 23 + (Location?.GetHashCode() ?? 0);
+ hash = hash * 23 + Latitude.GetHashCode();
+ hash = hash * 23 + Longitude.GetHashCode();
return hash;
}
}
- public virtual bool Equals(UserSearchFilter? other)
+ public virtual bool Equals(GroupSearchFilter? other)
{
return other is not null &&
Name == other.Name &&
((Categories == null && other.Categories == null) ||
(Categories != null && other.Categories != null && Categories.SequenceEqual(other.Categories))) &&
WithinRadiusKilometers == other.WithinRadiusKilometers &&
- Location == other.Location;
+ Location == other.Location &&
+ Latitude == other.Latitude &&
+ Longitude == other.Longitude;
}
}
@@ -52,14 +68,17 @@ public virtual bool Equals(UserSearchFilter? other)
{
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
- var userSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;
+ var groupSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;
- if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
+ if (groupSearchFilter.WithinRadiusKilometers is null &&
+ string.IsNullOrEmpty(groupSearchFilter.Location) &&
+ !groupSearchFilter.Latitude.HasValue &&
+ !groupSearchFilter.Longitude.HasValue)
{
return ValidationResult.Success!;
}
- return userSearchFilter.WithinRadiusKilometers is null
+ return groupSearchFilter.WithinRadiusKilometers is null
? new ValidationResult("Radius skal vælges når et område er valgt.")
: ValidationResult.Success!;
}
@@ -68,14 +87,21 @@ protected override ValidationResult IsValid(object? value, ValidationContext val
{
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
- var userSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;
+ var groupSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;
- if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
+ if (groupSearchFilter.WithinRadiusKilometers is null &&
+ string.IsNullOrEmpty(groupSearchFilter.Location) &&
+ !groupSearchFilter.Latitude.HasValue &&
+ !groupSearchFilter.Longitude.HasValue)
{
return ValidationResult.Success!;
}
- return string.IsNullOrEmpty(userSearchFilter.Location)
+ // Valid if either Location string is set OR lat/long coordinates are set
+ var hasLocation = !string.IsNullOrEmpty(groupSearchFilter.Location) ||
+ (groupSearchFilter.Latitude.HasValue && groupSearchFilter.Longitude.HasValue);
+
+ return !hasLocation
? new ValidationResult("Område skal vælges når en radius er valgt.")
: ValidationResult.Success!;
}
diff --git a/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs b/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs
index 736f85ed..601bbc7d 100644
--- a/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs
+++ b/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs
@@ -17,6 +17,18 @@ public class PostSearchFilter
[RadiusRequired]
public string? Location { get; set; }
+ ///
+ /// Latitude coordinate for map-based location search.
+ /// When set (along with Longitude), takes precedence over Location string.
+ ///
+ public double? Latitude { get; set; }
+
+ ///
+ /// Longitude coordinate for map-based location search.
+ /// When set (along with Latitude), takes precedence over Location string.
+ ///
+ public double? Longitude { get; set; }
+
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
}
@@ -27,7 +39,10 @@ protected override ValidationResult IsValid(object? value, ValidationContext val
{
var postSearchFilter = (PostSearchFilter)validationContext.ObjectInstance;
- if (postSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(postSearchFilter.Location))
+ if (postSearchFilter.WithinRadiusKilometers is null &&
+ string.IsNullOrEmpty(postSearchFilter.Location) &&
+ !postSearchFilter.Latitude.HasValue &&
+ !postSearchFilter.Longitude.HasValue)
{
return ValidationResult.Success!;
}
@@ -44,13 +59,19 @@ protected override ValidationResult IsValid(object? value, ValidationContext val
{
var postSearchFilter = (PostSearchFilter)validationContext.ObjectInstance;
- if (postSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(postSearchFilter.Location))
+ if (postSearchFilter.WithinRadiusKilometers is null &&
+ string.IsNullOrEmpty(postSearchFilter.Location) &&
+ !postSearchFilter.Latitude.HasValue &&
+ !postSearchFilter.Longitude.HasValue)
{
return ValidationResult.Success!;
-
}
- return string.IsNullOrEmpty(postSearchFilter.Location)
+ // Valid if either Location string is set OR lat/long coordinates are set
+ var hasLocation = !string.IsNullOrEmpty(postSearchFilter.Location) ||
+ (postSearchFilter.Latitude.HasValue && postSearchFilter.Longitude.HasValue);
+
+ return !hasLocation
? new ValidationResult("Område skal vælges når en radius er valgt.")
: ValidationResult.Success!;
}
diff --git a/src/shared/Jordnaer.Shared/UserSearch/UserSearchFilter.cs b/src/shared/Jordnaer.Shared/UserSearch/UserSearchFilter.cs
index 79f14ae4..78b087f0 100644
--- a/src/shared/Jordnaer.Shared/UserSearch/UserSearchFilter.cs
+++ b/src/shared/Jordnaer.Shared/UserSearch/UserSearchFilter.cs
@@ -17,6 +17,18 @@ public record UserSearchFilter
[RadiusRequired]
public string? Location { get; set; }
+ ///
+ /// Latitude coordinate for map-based location search.
+ /// When set (along with Longitude), takes precedence over Location string.
+ ///
+ public double? Latitude { get; set; }
+
+ ///
+ /// Longitude coordinate for map-based location search.
+ /// When set (along with Latitude), takes precedence over Location string.
+ ///
+ public double? Longitude { get; set; }
+
[Range(0, 18, ErrorMessage = "Skal være mellem 0 og 18 år")]
public int? MinimumChildAge { get; set; }
[Range(0, 18, ErrorMessage = "Skal være mellem 0 og 18 år")]
@@ -37,6 +49,8 @@ public override int GetHashCode()
hash = hash * 23 + (Categories != null ? Categories.Aggregate(0, (current, category) => current + category.GetHashCode()) : 0);
hash = hash * 23 + WithinRadiusKilometers.GetHashCode();
hash = hash * 23 + (Location?.GetHashCode() ?? 0);
+ hash = hash * 23 + Latitude.GetHashCode();
+ hash = hash * 23 + Longitude.GetHashCode();
hash = hash * 23 + MinimumChildAge.GetHashCode();
hash = hash * 23 + MaximumChildAge.GetHashCode();
hash = hash * 23 + ChildGender.GetHashCode();
@@ -53,6 +67,8 @@ public virtual bool Equals(UserSearchFilter? other)
(Categories != null && other.Categories != null && Categories.SequenceEqual(other.Categories))) &&
WithinRadiusKilometers == other.WithinRadiusKilometers &&
Location == other.Location &&
+ Latitude == other.Latitude &&
+ Longitude == other.Longitude &&
MinimumChildAge == other.MinimumChildAge &&
MaximumChildAge == other.MaximumChildAge &&
ChildGender == other.ChildGender;
@@ -65,7 +81,10 @@ protected override ValidationResult IsValid(object? value, ValidationContext val
{
var userSearchFilter = (UserSearchFilter)validationContext.ObjectInstance;
- if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
+ if (userSearchFilter.WithinRadiusKilometers is null &&
+ string.IsNullOrEmpty(userSearchFilter.Location) &&
+ !userSearchFilter.Latitude.HasValue &&
+ !userSearchFilter.Longitude.HasValue)
{
return ValidationResult.Success!;
}
@@ -81,13 +100,16 @@ protected override ValidationResult IsValid(object? value, ValidationContext val
{
var userSearchFilter = (UserSearchFilter)validationContext.ObjectInstance;
- if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
+ if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location) && !userSearchFilter.Latitude.HasValue && !userSearchFilter.Longitude.HasValue)
{
return ValidationResult.Success!;
-
}
- return string.IsNullOrEmpty(userSearchFilter.Location)
+ // Valid if either Location string is set OR lat/long coordinates are set
+ var hasLocation = !string.IsNullOrEmpty(userSearchFilter.Location) ||
+ (userSearchFilter.Latitude.HasValue && userSearchFilter.Longitude.HasValue);
+
+ return !hasLocation
? new ValidationResult("Område skal vælges når en radius er valgt.")
: ValidationResult.Success!;
}
diff --git a/src/web/Jordnaer/Components/App.razor b/src/web/Jordnaer/Components/App.razor
index 28c28837..c3519d82 100644
--- a/src/web/Jordnaer/Components/App.razor
+++ b/src/web/Jordnaer/Components/App.razor
@@ -47,6 +47,11 @@
+ @* Leaflet.js for map search functionality *@
+
+
@@ -71,6 +76,12 @@
+
+ @* Leaflet.js for map search functionality *@
+
+