forked from potsda.mn/mobilizon
Merge branch '1475-1469-homepage-location-enhancement-no-activities' into 'main'
fix #1469 and # 1475 Closes #1469 and #1475 See merge request framasoft/mobilizon!1581
This commit is contained in:
commit
2078dbcf55
|
@ -69,13 +69,31 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
|
||||
@spec list_events(any(), map(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Page.t(Event.t())} | {:error, :events_max_limit_reached}
|
||||
def list_events(
|
||||
_parent,
|
||||
%{
|
||||
page: page,
|
||||
limit: limit,
|
||||
order_by: order_by,
|
||||
direction: direction,
|
||||
longevents: longevents,
|
||||
location: location,
|
||||
radius: radius
|
||||
},
|
||||
_resolution
|
||||
)
|
||||
when limit < @event_max_limit do
|
||||
{:ok,
|
||||
Events.list_events(page, limit, order_by, direction, true, longevents, location, radius)}
|
||||
end
|
||||
|
||||
def list_events(
|
||||
_parent,
|
||||
%{page: page, limit: limit, order_by: order_by, direction: direction},
|
||||
_resolution
|
||||
)
|
||||
when limit < @event_max_limit do
|
||||
{:ok, Events.list_events(page, limit, order_by, direction)}
|
||||
{:ok, Events.list_events(page, limit, order_by, direction, true)}
|
||||
end
|
||||
|
||||
def list_events(_parent, %{page: _page, limit: _limit}, _resolution) do
|
||||
|
|
|
@ -375,6 +375,13 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||
object :event_queries do
|
||||
@desc "Get all events"
|
||||
field :events, :paginated_event_list do
|
||||
arg(:location, :string, default_value: nil, description: "A geohash for coordinates")
|
||||
|
||||
arg(:radius, :float,
|
||||
default_value: nil,
|
||||
description: "Radius around the location to search in"
|
||||
)
|
||||
|
||||
arg(:page, :integer, default_value: 1, description: "The page in the paginated event list")
|
||||
arg(:limit, :integer, default_value: 10, description: "The limit of events per page")
|
||||
|
||||
|
@ -388,6 +395,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||
description: "Direction for the sort"
|
||||
)
|
||||
|
||||
arg(:longevents, :boolean,
|
||||
default_value: nil,
|
||||
description: "if mention filter in or out long events"
|
||||
)
|
||||
|
||||
middleware(Rajska.QueryAuthorization, permit: :all)
|
||||
|
||||
resolve(&Event.list_events/3)
|
||||
|
|
|
@ -359,19 +359,34 @@ defmodule Mobilizon.Events do
|
|||
@doc """
|
||||
Returns the list of events.
|
||||
"""
|
||||
@spec list_events(integer | nil, integer | nil, atom, atom, boolean) :: Page.t(Event.t())
|
||||
@spec list_events(
|
||||
integer | nil,
|
||||
integer | nil,
|
||||
atom,
|
||||
atom,
|
||||
boolean,
|
||||
boolean | nil,
|
||||
string | nil,
|
||||
float | nil
|
||||
) :: Page.t(Event.t())
|
||||
def list_events(
|
||||
page \\ nil,
|
||||
limit \\ nil,
|
||||
sort \\ :begins_on,
|
||||
direction \\ :asc,
|
||||
is_future \\ true
|
||||
is_future \\ true,
|
||||
longevents \\ nil,
|
||||
location \\ nil,
|
||||
radius \\ nil
|
||||
) do
|
||||
Event
|
||||
|> distinct([e], [{^direction, ^sort}, asc: e.id])
|
||||
|> preload([:organizer_actor, :participants])
|
||||
|> sort(sort, direction)
|
||||
|> maybe_join_address(%{location: location, radius: radius})
|
||||
|> events_for_location(%{location: location, radius: radius})
|
||||
|> filter_future_events(is_future)
|
||||
|> events_for_longevents(longevents)
|
||||
|> filter_public_visibility()
|
||||
|> filter_draft()
|
||||
|> filter_cancelled_events()
|
||||
|
@ -572,7 +587,7 @@ defmodule Mobilizon.Events do
|
|||
|> events_for_search_query()
|
||||
|> events_for_begins_on(Map.get(args, :begins_on, DateTime.utc_now()))
|
||||
|> events_for_ends_on(Map.get(args, :ends_on))
|
||||
|> events_for_longevents(args)
|
||||
|> events_for_longevents(Map.get(args, :longevents))
|
||||
|> events_for_category(args)
|
||||
|> events_for_categories(args)
|
||||
|> events_for_languages(args)
|
||||
|
@ -1379,15 +1394,13 @@ defmodule Mobilizon.Events do
|
|||
end
|
||||
end
|
||||
|
||||
@spec events_for_longevents(Ecto.Queryable.t(), map()) :: Ecto.Query.t()
|
||||
defp events_for_longevents(query, args) do
|
||||
@spec events_for_longevents(Ecto.Queryable.t(), Boolean.t() | nil) :: Ecto.Query.t()
|
||||
defp events_for_longevents(query, longevents) do
|
||||
duration = Config.get([:instance, :duration_of_long_event], 0)
|
||||
|
||||
if duration <= 0 do
|
||||
query
|
||||
else
|
||||
longevents = Map.get(args, :longevents)
|
||||
|
||||
case longevents do
|
||||
nil ->
|
||||
query
|
||||
|
|
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -6,7 +6,7 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"name": "mobilizon",
|
||||
"version": "4.1.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.16",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"@fullcalendar/daygrid": "^6.1.10",
|
||||
"@fullcalendar/interaction": "^6.1.10",
|
||||
"@fullcalendar/vue3": "^6.1.10",
|
||||
"@oruga-ui/oruga-next": "^0.8.10",
|
||||
"@oruga-ui/oruga-next": "0.8.12",
|
||||
"@oruga-ui/theme-oruga": "^0.2.0",
|
||||
"@sentry/tracing": "^7.1",
|
||||
"@sentry/vue": "^7.1",
|
||||
|
@ -3138,9 +3138,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@oruga-ui/oruga-next": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@oruga-ui/oruga-next/-/oruga-next-0.8.10.tgz",
|
||||
"integrity": "sha512-ETPSoGZu1parbj8C3V2ZojQnN4ptQMiJEwS9Hx44NcaDzu4q/FDsYkKYiz6G9kx8cDceXXxvydfOUpZePVVdzw==",
|
||||
"version": "0.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@oruga-ui/oruga-next/-/oruga-next-0.8.12.tgz",
|
||||
"integrity": "sha512-I1jcsTA4J6HQdNpSWgK4cNSqv1cHsghQGtJ12p0yXDSJseek0Y8f4vf9+tDRtfONzWHuRyWUGcHIfePsRKVbiQ==",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<form
|
||||
id="search-anchor"
|
||||
class="container mx-auto my-3 px-2 flex flex-wrap flex-col sm:flex-row items-stretch gap-2 text-center items-center justify-center dark:text-slate-100"
|
||||
class="container mx-auto my-3 px-2 flex flex-wrap flex-col sm:flex-row items-stretch gap-2 text-center justify-center dark:text-slate-100"
|
||||
role="search"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
|
@ -38,6 +38,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { IAddress } from "@/types/address.model";
|
||||
import { AddressSearchType } from "@/types/enums";
|
||||
import {
|
||||
addressToLocation,
|
||||
getLocationFromLocal,
|
||||
storeLocationInLocal,
|
||||
} from "@/utils/location";
|
||||
import { computed, defineAsyncComponent } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
@ -51,6 +56,7 @@ const props = defineProps<{
|
|||
location: IAddress | null;
|
||||
locationDefaultText?: string | null;
|
||||
search: string;
|
||||
fromLocalStorage?: boolean | false;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -64,10 +70,19 @@ const emit = defineEmits<{
|
|||
|
||||
const location = computed({
|
||||
get(): IAddress | null {
|
||||
return props.location;
|
||||
if (props.location) {
|
||||
return props.location;
|
||||
}
|
||||
if (props.fromLocalStorage) {
|
||||
return getLocationFromLocal();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
set(newLocation: IAddress | null) {
|
||||
emit("update:location", newLocation);
|
||||
if (props.fromLocalStorage) {
|
||||
storeLocationInLocal(newLocation);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -82,12 +97,7 @@ const search = computed({
|
|||
|
||||
const submit = () => {
|
||||
emit("submit");
|
||||
const lat = location.value?.geom
|
||||
? parseFloat(location.value?.geom?.split(";")?.[1])
|
||||
: undefined;
|
||||
const lon = location.value?.geom
|
||||
? parseFloat(location.value?.geom?.split(";")?.[0])
|
||||
: undefined;
|
||||
const { lat, lon } = addressToLocation(location.value);
|
||||
router.push({
|
||||
name: RouteName.SEARCH,
|
||||
query: {
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
<template>
|
||||
<close-content
|
||||
class="container mx-auto px-2"
|
||||
v-show="loading || (events && events.total > 0)"
|
||||
:suggestGeoloc="suggestGeoloc"
|
||||
:suggestGeoloc="false"
|
||||
v-on="attrs"
|
||||
@doGeoLoc="emit('doGeoLoc')"
|
||||
:doingGeoloc="doingGeoloc"
|
||||
>
|
||||
<template #title>
|
||||
<template v-if="userLocationName">
|
||||
{{ t("Events nearby {position}", { position: userLocationName }) }}
|
||||
<template v-if="userLocation?.name">
|
||||
{{
|
||||
t("Incoming events and activities nearby {position}", {
|
||||
position: userLocation?.name,
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t("Events close to you") }}
|
||||
{{ t("Incoming events and activities") }}
|
||||
</template>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
<div v-if="!loading && events.total == 0">
|
||||
<template v-if="userLocation?.name">
|
||||
{{
|
||||
t(
|
||||
"No events found nearby {position}. Try removing your position to see all events!",
|
||||
{ position: userLocation?.name }
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t("No events found") }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<skeleton-event-result
|
||||
v-for="i in 6"
|
||||
|
@ -28,25 +44,36 @@
|
|||
:key="event.uuid"
|
||||
/>
|
||||
<more-content
|
||||
v-if="userLocationName && userLocation?.lat && userLocation?.lon"
|
||||
v-if="userLocation?.name && userLocation?.lat && userLocation?.lon"
|
||||
:to="{
|
||||
name: RouteName.SEARCH,
|
||||
query: {
|
||||
locationName: userLocationName,
|
||||
locationName: userLocation?.name,
|
||||
lat: userLocation.lat?.toString(),
|
||||
lon: userLocation.lon?.toString(),
|
||||
contentType: 'EVENTS',
|
||||
contentType: 'ALL',
|
||||
distance: `${distance}_km`,
|
||||
},
|
||||
}"
|
||||
:picture="userLocation?.picture"
|
||||
>
|
||||
{{
|
||||
t("View more events around {position}", {
|
||||
position: userLocationName,
|
||||
t("View more events and activities around {position}", {
|
||||
position: userLocation?.name,
|
||||
})
|
||||
}}
|
||||
</more-content>
|
||||
<more-content
|
||||
v-else
|
||||
:to="{
|
||||
name: RouteName.SEARCH,
|
||||
query: {
|
||||
contentType: 'ALL',
|
||||
},
|
||||
}"
|
||||
>
|
||||
{{ t("View more events and activities") }}
|
||||
</more-content>
|
||||
</template>
|
||||
</close-content>
|
||||
</template>
|
||||
|
@ -55,76 +82,55 @@
|
|||
import { LocationType } from "../../types/user-location.model";
|
||||
import MoreContent from "./MoreContent.vue";
|
||||
import CloseContent from "./CloseContent.vue";
|
||||
import { computed, onMounted, useAttrs } from "vue";
|
||||
import { SEARCH_EVENTS } from "@/graphql/search";
|
||||
import { watch, computed, useAttrs } from "vue";
|
||||
import { FETCH_EVENTS } from "@/graphql/event";
|
||||
import { IEvent } from "@/types/event.model";
|
||||
import { useLazyQuery } from "@vue/apollo-composable";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import EventCard from "../Event/EventCard.vue";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import SkeletonEventResult from "../Event/SkeletonEventResult.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { coordsToGeoHash } from "@/utils/location";
|
||||
import { roundToNearestMinute } from "@/utils/datetime";
|
||||
import RouteName from "@/router/name";
|
||||
import { EventSortField, SortDirection } from "@/types/enums";
|
||||
|
||||
const props = defineProps<{
|
||||
userLocation: LocationType;
|
||||
doingGeoloc?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits(["doGeoLoc"]);
|
||||
|
||||
const EVENT_PAGE_LIMIT = 12;
|
||||
defineEmits(["doGeoLoc"]);
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
const attrs = useAttrs();
|
||||
|
||||
const userLocation = computed(() => props.userLocation);
|
||||
|
||||
const userLocationName = computed(() => {
|
||||
return userLocation.value?.name;
|
||||
const geoHash = computed(() => {
|
||||
console.debug("userLocation updated", userLocation.value);
|
||||
const geo = coordsToGeoHash(userLocation.value.lat, userLocation.value.lon);
|
||||
console.debug("geohash:", geo);
|
||||
return geo;
|
||||
});
|
||||
const suggestGeoloc = computed(() => userLocation.value?.isIPLocation);
|
||||
|
||||
const geoHash = computed(() =>
|
||||
coordsToGeoHash(props.userLocation.lat, props.userLocation.lon)
|
||||
const distance = computed<number>(() =>
|
||||
userLocation.value?.isIPLocation ? 150 : 25
|
||||
);
|
||||
|
||||
const distance = computed<number>(() => (suggestGeoloc.value ? 150 : 25));
|
||||
|
||||
const now = computed(() => roundToNearestMinute(new Date()));
|
||||
|
||||
const searchEnabled = computed(() => geoHash.value != undefined);
|
||||
|
||||
const {
|
||||
result: eventsResult,
|
||||
loading: loadingEvents,
|
||||
load: load,
|
||||
} = useLazyQuery<{
|
||||
const eventsQuery = useQuery<{
|
||||
searchEvents: Paginate<IEvent>;
|
||||
}>(
|
||||
SEARCH_EVENTS,
|
||||
() => ({
|
||||
location: geoHash.value,
|
||||
beginsOn: now.value,
|
||||
endsOn: undefined,
|
||||
radius: distance.value,
|
||||
eventPage: 1,
|
||||
limit: EVENT_PAGE_LIMIT,
|
||||
type: "IN_PERSON",
|
||||
}),
|
||||
() => ({
|
||||
enabled: searchEnabled.value,
|
||||
fetchPolicy: "cache-first",
|
||||
})
|
||||
);
|
||||
}>(FETCH_EVENTS, () => ({
|
||||
orderBy: EventSortField.BEGINS_ON,
|
||||
direction: SortDirection.ASC,
|
||||
longevents: false,
|
||||
location: geoHash.value,
|
||||
radius: distance.value,
|
||||
}));
|
||||
|
||||
const events = computed(
|
||||
() => eventsResult.value?.searchEvents ?? { elements: [], total: 0 }
|
||||
() => eventsQuery.result.value?.events ?? { elements: [], total: 0 }
|
||||
);
|
||||
watch(events, (e) => console.debug("events: ", e));
|
||||
|
||||
onMounted(async () => {
|
||||
await load();
|
||||
});
|
||||
|
||||
const loading = computed(() => props.doingGeoloc || loadingEvents.value);
|
||||
const loading = computed(() => props.doingGeoloc || eventsQuery.loading.value);
|
||||
watch(loading, (l) => console.debug("loading: ", l));
|
||||
</script>
|
||||
|
|
|
@ -60,6 +60,7 @@ const { result: resultEvents, loading: loadingEvents } = useQuery<{
|
|||
}>(FETCH_EVENTS, {
|
||||
orderBy: EventSortField.BEGINS_ON,
|
||||
direction: SortDirection.ASC,
|
||||
longevents: false,
|
||||
});
|
||||
const events = computed(
|
||||
() => resultEvents.value?.events ?? { total: 0, elements: [] }
|
||||
|
|
|
@ -2,6 +2,7 @@ import { IDENTITIES, REGISTER_PERSON } from "@/graphql/actor";
|
|||
import {
|
||||
CURRENT_USER_CLIENT,
|
||||
LOGGED_USER,
|
||||
LOGGED_USER_LOCATION,
|
||||
SET_USER_SETTINGS,
|
||||
UPDATE_USER_LOCALE,
|
||||
USER_SETTINGS,
|
||||
|
@ -51,6 +52,20 @@ export function useUserSettings() {
|
|||
return { loggedUser, error, loading };
|
||||
}
|
||||
|
||||
export function useUserLocation() {
|
||||
const {
|
||||
result: userSettingsResult,
|
||||
error,
|
||||
loading,
|
||||
onResult,
|
||||
} = useQuery<{ loggedUser: IUser }>(LOGGED_USER_LOCATION);
|
||||
|
||||
const location = computed(
|
||||
() => userSettingsResult.value?.loggedUser.settings.location
|
||||
);
|
||||
return { location, error, loading, onResult };
|
||||
}
|
||||
|
||||
export async function doUpdateSetting(
|
||||
variables: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
|
|
|
@ -103,16 +103,22 @@ export const FETCH_EVENT_BASIC = gql`
|
|||
|
||||
export const FETCH_EVENTS = gql`
|
||||
query FetchEvents(
|
||||
$location: String
|
||||
$radius: Float
|
||||
$orderBy: EventOrderBy
|
||||
$direction: SortDirection
|
||||
$page: Int
|
||||
$limit: Int
|
||||
$longevents: Boolean
|
||||
) {
|
||||
events(
|
||||
location: $location
|
||||
radius: $radius
|
||||
orderBy: $orderBy
|
||||
direction: $direction
|
||||
page: $page
|
||||
limit: $limit
|
||||
longevents: $longevents
|
||||
) {
|
||||
total
|
||||
elements {
|
||||
|
|
|
@ -218,6 +218,7 @@ export const SEARCH_CALENDAR_EVENTS = gql`
|
|||
endsOn: $endsOn
|
||||
page: $eventPage
|
||||
limit: $limit
|
||||
longevents: false
|
||||
) {
|
||||
total
|
||||
elements {
|
||||
|
|
|
@ -138,6 +138,20 @@ export const USER_SETTINGS = gql`
|
|||
${USER_SETTINGS_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const LOGGED_USER_LOCATION = gql`
|
||||
query LoggedUserLocation {
|
||||
loggedUser {
|
||||
settings {
|
||||
location {
|
||||
range
|
||||
geohash
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const LOGGED_USER_TIMEZONE = gql`
|
||||
query LoggedUserTimezone {
|
||||
loggedUser {
|
||||
|
|
|
@ -1626,7 +1626,7 @@
|
|||
"With {participants}": "With {participants}",
|
||||
"Conversations": "Conversations",
|
||||
"New private message": "New private message",
|
||||
"There's no conversations yet": "There's no conversations yet",
|
||||
"There's no conversations yet": "There are no conversations yet",
|
||||
"Open conversations": "Open conversations",
|
||||
"List of conversations": "List of conversations",
|
||||
"Conversation with {participants}": "Conversation with {participants}",
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
import ngeohash from "ngeohash";
|
||||
|
||||
import { IAddress, Address } from "@/types/address.model";
|
||||
import { LocationType } from "@/types/user-location.model";
|
||||
import { IUserPreferredLocation } from "@/types/current-user.model";
|
||||
|
||||
const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
|
||||
|
||||
export const addressToLocation = (
|
||||
address: IAddress
|
||||
): LocationType | undefined => {
|
||||
if (!address.geom) return undefined;
|
||||
const arr = address.geom.split(";");
|
||||
if (arr.length < 2) return undefined;
|
||||
return {
|
||||
lon: parseFloat(arr[0]),
|
||||
lat: parseFloat(arr[1]),
|
||||
name: address.description,
|
||||
};
|
||||
};
|
||||
|
||||
export const coordsToGeoHash = (
|
||||
lat: number | undefined,
|
||||
lon: number | undefined,
|
||||
|
@ -14,9 +31,72 @@ export const coordsToGeoHash = (
|
|||
};
|
||||
|
||||
export const geoHashToCoords = (
|
||||
geohash: string | undefined
|
||||
geohash: string | undefined | null
|
||||
): { latitude: number; longitude: number } | undefined => {
|
||||
if (!geohash) return undefined;
|
||||
const { latitude, longitude } = ngeohash.decode(geohash);
|
||||
return latitude && longitude ? { latitude, longitude } : undefined;
|
||||
};
|
||||
|
||||
export const storeLocationInLocal = (location: IAddress | null): undefined => {
|
||||
if (location) {
|
||||
window.localStorage.setItem("location", JSON.stringify(location));
|
||||
} else {
|
||||
window.localStorage.removeItem("location");
|
||||
}
|
||||
};
|
||||
|
||||
export const getLocationFromLocal = (): IAddress | null => {
|
||||
const locationString = window.localStorage.getItem("location");
|
||||
if (!locationString) {
|
||||
return null;
|
||||
}
|
||||
const location = JSON.parse(locationString) as IAddress;
|
||||
if (!location.description || !location.geom) {
|
||||
return null;
|
||||
}
|
||||
return location;
|
||||
};
|
||||
|
||||
export const storeRadiusInLocal = (radius: number | null): undefined => {
|
||||
if (radius) {
|
||||
window.localStorage.setItem("radius", radius.toString());
|
||||
} else {
|
||||
window.localStorage.removeItem("radius");
|
||||
}
|
||||
};
|
||||
|
||||
export const getRadiusFromLocal = (): IAddress | null => {
|
||||
const locationString = window.localStorage.getItem("location");
|
||||
if (!locationString) {
|
||||
return null;
|
||||
}
|
||||
const location = JSON.parse(locationString) as IAddress;
|
||||
if (!location.description || !location.geom) {
|
||||
return null;
|
||||
}
|
||||
return location;
|
||||
};
|
||||
|
||||
export const storeUserLocationAndRadiusFromUserSettings = (
|
||||
location: IUserPreferredLocation | null
|
||||
): undefined => {
|
||||
if (location) {
|
||||
const latlon = geoHashToCoords(location.geohash);
|
||||
if (latlon) {
|
||||
storeLocationInLocal({
|
||||
...new Address(),
|
||||
geom: `${latlon.longitude};${latlon.latitude}`,
|
||||
description: location.name || "",
|
||||
type: "administrative",
|
||||
});
|
||||
}
|
||||
if (location.range) {
|
||||
storeRadiusInLocal(location.range);
|
||||
} else {
|
||||
console.debug("user has not set a radius");
|
||||
}
|
||||
} else {
|
||||
console.debug("user has not set a location");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -26,7 +26,12 @@
|
|||
<!-- Unlogged introduction -->
|
||||
<unlogged-introduction :config="config" v-if="config && !isLoggedIn" />
|
||||
<!-- Search fields -->
|
||||
<search-fields v-model:search="search" v-model:location="location" />
|
||||
<search-fields
|
||||
v-model:search="search"
|
||||
v-model:location="location"
|
||||
:locationDefaultText="location?.description"
|
||||
:fromLocalStorage="true"
|
||||
/>
|
||||
<!-- Categories preview -->
|
||||
<categories-preview />
|
||||
<!-- Welcome back -->
|
||||
|
@ -143,7 +148,6 @@
|
|||
/>
|
||||
<CloseGroups :userLocation="userLocation" @doGeoLoc="performGeoLocation()" />
|
||||
<OnlineEvents />
|
||||
<LastEvents v-if="instanceName" :instanceName="instanceName" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -160,7 +164,7 @@ import { IEvent } from "../types/event.model";
|
|||
// import { IFollowedGroupEvent } from "../types/followedGroupEvent.model";
|
||||
import CloseEvents from "@/components/Local/CloseEvents.vue";
|
||||
import CloseGroups from "@/components/Local/CloseGroups.vue";
|
||||
import LastEvents from "@/components/Local/LastEvents.vue";
|
||||
// import LastEvents from "@/components/Local/LastEvents.vue";
|
||||
import OnlineEvents from "@/components/Local/OnlineEvents.vue";
|
||||
import {
|
||||
computed,
|
||||
|
@ -183,7 +187,7 @@ import CategoriesPreview from "@/components/Home/CategoriesPreview.vue";
|
|||
import UnloggedIntroduction from "@/components/Home/UnloggedIntroduction.vue";
|
||||
import SearchFields from "@/components/Home/SearchFields.vue";
|
||||
import { useHead } from "@unhead/vue";
|
||||
import { geoHashToCoords } from "@/utils/location";
|
||||
import { addressToLocation, geoHashToCoords } from "@/utils/location";
|
||||
import { useServerProvidedLocation } from "@/composition/apollo/config";
|
||||
import { ABOUT } from "@/graphql/config";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
|
@ -239,6 +243,10 @@ const currentUserParticipations = computed(
|
|||
const location = ref(null);
|
||||
const search = ref("");
|
||||
|
||||
watch(location, (newLoc, oldLoc) =>
|
||||
console.debug("LOCATION UPDATED from", { ...oldLoc }, " to ", { ...newLoc })
|
||||
);
|
||||
|
||||
const isToday = (date: string): boolean => {
|
||||
return new Date(date).toDateString() === new Date().toDateString();
|
||||
};
|
||||
|
@ -383,11 +391,7 @@ const coords = computed(() => {
|
|||
userSettingsLocationGeoHash.value ?? undefined
|
||||
);
|
||||
|
||||
if (userSettingsCoords) {
|
||||
return { ...userSettingsCoords, isIPLocation: false };
|
||||
}
|
||||
|
||||
return { ...serverLocation.value, isIPLocation: true };
|
||||
return { ...serverLocation.value, isIPLocation: !userSettingsCoords };
|
||||
});
|
||||
|
||||
const { result: reverseGeocodeResult } = useQuery<{
|
||||
|
@ -428,6 +432,11 @@ const currentUserLocation = computed(() => {
|
|||
});
|
||||
|
||||
const userLocation = computed(() => {
|
||||
console.debug("new userLocation");
|
||||
if (location.value) {
|
||||
console.debug("userLocation is typed location");
|
||||
return addressToLocation(location.value);
|
||||
}
|
||||
if (
|
||||
!userSettingsLocation.value ||
|
||||
(userSettingsLocation.value?.isIPLocation &&
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<template>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<SearchFields
|
||||
<search-fields
|
||||
class="md:ml-10 mr-2"
|
||||
v-model:search="search"
|
||||
v-model:location="location"
|
||||
:locationDefaultText="locationName"
|
||||
:fromLocalStorage="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -917,6 +918,9 @@ const groupPage = useRouteQuery("groupPage", 1, integerTransformer);
|
|||
const latitude = useRouteQuery("lat", undefined, floatTransformer);
|
||||
const longitude = useRouteQuery("lon", undefined, floatTransformer);
|
||||
|
||||
// TODO
|
||||
// This should be updated with getRadiusFromLocal if we want to use user's
|
||||
// preferences
|
||||
const distance = useRouteQuery("distance", "10_km");
|
||||
const when = useRouteQuery("when", "any");
|
||||
const contentType = useRouteQuery(
|
||||
|
|
|
@ -127,15 +127,17 @@
|
|||
<script setup lang="ts">
|
||||
import { LOGIN } from "@/graphql/auth";
|
||||
import { LOGIN_CONFIG } from "@/graphql/config";
|
||||
import { LOGGED_USER_LOCATION } from "@/graphql/user";
|
||||
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import { ILogin } from "@/types/login.model";
|
||||
import { IUser } from "@/types/current-user.model";
|
||||
import { saveUserData, SELECTED_PROVIDERS } from "@/utils/auth";
|
||||
import { storeUserLocationAndRadiusFromUserSettings } from "@/utils/location";
|
||||
import {
|
||||
initializeCurrentActor,
|
||||
NoIdentitiesException,
|
||||
} from "@/utils/identity";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
import { useMutation, useLazyQuery, useQuery } from "@vue/apollo-composable";
|
||||
import { computed, reactive, ref, onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
@ -153,14 +155,14 @@ const route = useRoute();
|
|||
|
||||
const { currentUser } = useCurrentUserClient();
|
||||
|
||||
const { result: configResult } = useQuery<{
|
||||
const configQuery = useQuery<{
|
||||
config: Pick<
|
||||
IConfig,
|
||||
"auth" | "registrationsOpen" | "registrationsAllowlist"
|
||||
>;
|
||||
}>(LOGIN_CONFIG);
|
||||
|
||||
const config = computed(() => configResult.value?.config);
|
||||
const config = computed(() => configQuery.result.value?.config);
|
||||
|
||||
const canRegister = computed(() => {
|
||||
return (
|
||||
|
@ -181,85 +183,90 @@ const credentials = reactive({
|
|||
const redirect = useRouteQuery("redirect", "");
|
||||
const errorCode = useRouteQuery("code", null, enumTransformer(LoginErrorCode));
|
||||
|
||||
const {
|
||||
onDone: onLoginMutationDone,
|
||||
onError: onLoginMutationError,
|
||||
mutate: loginMutation,
|
||||
} = useMutation(LOGIN);
|
||||
// Login
|
||||
const loginMutation = useMutation(LOGIN);
|
||||
// Load user identities
|
||||
const currentUserIdentitiesQuery = useLazyCurrentUserIdentities();
|
||||
// Update user in cache
|
||||
const currentUserMutation = useMutation(UPDATE_CURRENT_USER_CLIENT);
|
||||
// Retrieve preferred location
|
||||
const loggedUserLocationQuery = useLazyQuery<{
|
||||
loggedUser: IUser;
|
||||
}>(LOGGED_USER_LOCATION);
|
||||
|
||||
onLoginMutationDone(async (result) => {
|
||||
const data = result.data;
|
||||
submitted.value = false;
|
||||
if (data == null) {
|
||||
throw new Error("Data is undefined");
|
||||
}
|
||||
|
||||
saveUserData(data.login);
|
||||
await setupClientUserAndActors(data.login);
|
||||
|
||||
if (redirect.value) {
|
||||
console.debug("We have a redirect", redirect.value);
|
||||
router.push(redirect.value);
|
||||
return;
|
||||
}
|
||||
console.debug("No redirect, going to homepage");
|
||||
if (window.localStorage) {
|
||||
console.debug("Has localstorage, setting welcome back");
|
||||
window.localStorage.setItem("welcome-back", "yes");
|
||||
}
|
||||
router.replace({ name: RouteName.HOME });
|
||||
return;
|
||||
});
|
||||
|
||||
onLoginMutationError((err) => {
|
||||
console.error(err);
|
||||
submitted.value = false;
|
||||
if (err.graphQLErrors) {
|
||||
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
||||
errors.value.push(message);
|
||||
});
|
||||
} else if (err.networkError) {
|
||||
errors.value.push(err.networkError.message);
|
||||
}
|
||||
});
|
||||
|
||||
const loginAction = (e: Event) => {
|
||||
// form submit action
|
||||
const loginAction = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (submitted.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitted.value = true;
|
||||
errors.value = [];
|
||||
|
||||
loginMutation({
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
});
|
||||
};
|
||||
|
||||
const { load: loadIdentities } = useLazyCurrentUserIdentities();
|
||||
|
||||
const { onDone: onCurrentUserMutationDone, mutate: updateCurrentUserMutation } =
|
||||
useMutation(UPDATE_CURRENT_USER_CLIENT);
|
||||
|
||||
onCurrentUserMutationDone(async () => {
|
||||
console.debug("Current user mutation done, now setuping actors…");
|
||||
// since we fail to refresh the navbar properly, we force a page reload.
|
||||
// see the explanation of the bug bellow
|
||||
window.location = redirect.value || "/";
|
||||
try {
|
||||
/* FIXME this promise never resolved the first time
|
||||
no idea why !
|
||||
this appends even with the last version of apollo-composable (4.0.2)
|
||||
may be related to that : https://github.com/vuejs/apollo/issues/1543
|
||||
*/
|
||||
const result = await loadIdentities();
|
||||
console.debug("login, loadIdentities resolved");
|
||||
if (!result) return;
|
||||
await initializeCurrentActor(result.loggedUser.actors);
|
||||
// Step 1: login the user
|
||||
const { data: loginData } = await loginMutation.mutate({
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
});
|
||||
submitted.value = false;
|
||||
if (loginData == null) {
|
||||
throw new Error("Login: user's data is undefined");
|
||||
}
|
||||
|
||||
// Login saved to local storage
|
||||
saveUserData(loginData.login);
|
||||
|
||||
// Step 2: save login in apollo cache
|
||||
await currentUserMutation.mutate({
|
||||
id: loginData.login.user.id,
|
||||
email: credentials.email,
|
||||
isLoggedIn: true,
|
||||
role: loginData.login.user.role,
|
||||
});
|
||||
|
||||
// Step 3a: Retrieving user location
|
||||
const loggedUserLocationPromise = loggedUserLocationQuery.load();
|
||||
|
||||
// Step 3b: Setuping user's identities
|
||||
// FIXME this promise never resolved the first time
|
||||
// no idea why !
|
||||
// this appends even with the last version of apollo-composable (4.0.2)
|
||||
// may be related to that : https://github.com/vuejs/apollo/issues/1543
|
||||
// EDIT: now it works :shrug:
|
||||
const currentUserIdentitiesResult = await currentUserIdentitiesQuery.load();
|
||||
if (!currentUserIdentitiesResult) {
|
||||
throw new Error("Loading user's identities failed");
|
||||
}
|
||||
|
||||
await initializeCurrentActor(currentUserIdentitiesResult.loggedUser.actors);
|
||||
|
||||
// Step 3a following
|
||||
const loggedUserLocationResult = await loggedUserLocationPromise;
|
||||
storeUserLocationAndRadiusFromUserSettings(
|
||||
loggedUserLocationResult?.loggedUser?.settings?.location
|
||||
);
|
||||
|
||||
// Soft redirect
|
||||
if (redirect.value) {
|
||||
console.debug("We have a redirect", redirect.value);
|
||||
router.push(redirect.value);
|
||||
return;
|
||||
}
|
||||
console.debug("No redirect, going to homepage");
|
||||
if (window.localStorage) {
|
||||
console.debug("Has localstorage, setting welcome back");
|
||||
window.localStorage.setItem("welcome-back", "yes");
|
||||
}
|
||||
router.replace({ name: RouteName.HOME });
|
||||
|
||||
// Hard redirect
|
||||
// since we fail to refresh the navbar properly, we force a page reload.
|
||||
// see the explanation of the bug bellow
|
||||
// window.location = redirect.value || "/";
|
||||
} catch (err: any) {
|
||||
if (err instanceof NoIdentitiesException && currentUser.value) {
|
||||
console.debug("No identities, redirecting to profile registration");
|
||||
await router.push({
|
||||
name: RouteName.REGISTER_PROFILE,
|
||||
params: {
|
||||
|
@ -268,19 +275,17 @@ onCurrentUserMutationDone(async () => {
|
|||
},
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
console.error(err);
|
||||
submitted.value = false;
|
||||
if (err.graphQLErrors) {
|
||||
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
||||
errors.value.push(message);
|
||||
});
|
||||
} else if (err.networkError) {
|
||||
errors.value.push(err.networkError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const setupClientUserAndActors = async (login: ILogin): Promise<void> => {
|
||||
console.debug("Setuping client user and actors");
|
||||
updateCurrentUserMutation({
|
||||
id: login.user.id,
|
||||
email: credentials.email,
|
||||
isLoggedIn: true,
|
||||
role: login.user.role,
|
||||
});
|
||||
};
|
||||
|
||||
const hasCaseWarning = computed<boolean>(() => {
|
||||
|
|
Loading…
Reference in a new issue