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:
setop 2024-07-08 21:44:23 +00:00
commit 2078dbcf55
16 changed files with 367 additions and 173 deletions

View file

@ -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

View file

@ -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)

View file

@ -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
View file

@ -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"
}

View file

@ -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: {

View file

@ -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>

View file

@ -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: [] }

View file

@ -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> {

View file

@ -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 {

View file

@ -218,6 +218,7 @@ export const SEARCH_CALENDAR_EVENTS = gql`
endsOn: $endsOn
page: $eventPage
limit: $limit
longevents: false
) {
total
elements {

View file

@ -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 {

View file

@ -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}",

View file

@ -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");
}
};

View file

@ -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 &&

View file

@ -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(

View file

@ -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>(() => {