Merge branch 'fixes' into 'main'
Little features Closes #1082, #1102, #1154 et #540 See merge request framasoft/mobilizon!1305
This commit is contained in:
commit
720c11c43f
|
@ -169,7 +169,8 @@ e2e:
|
|||
artifacts:
|
||||
expire_in: 2 days
|
||||
paths:
|
||||
- js/playwright-report
|
||||
- js/playwright-report/
|
||||
- js/test-results/
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
|
|
|
@ -19,6 +19,7 @@ config :mobilizon, :instance,
|
|||
registrations_open: false,
|
||||
registration_email_allowlist: [],
|
||||
registration_email_denylist: [],
|
||||
disable_database_login: false,
|
||||
languages: [],
|
||||
default_language: "en",
|
||||
demo: false,
|
||||
|
|
|
@ -54,6 +54,7 @@ import {
|
|||
defineAsyncComponent,
|
||||
computed,
|
||||
watch,
|
||||
onBeforeUnmount,
|
||||
} from "vue";
|
||||
import { LocationType } from "@/types/user-location.model";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
|
@ -123,7 +124,7 @@ const interval = ref<number>(0);
|
|||
|
||||
const notifier = inject<Notifier>("notifier");
|
||||
|
||||
interval.value = setInterval(async () => {
|
||||
interval.value = window.setInterval(async () => {
|
||||
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
if (accessToken) {
|
||||
const token = jwt_decode<JwtPayload>(accessToken);
|
||||
|
@ -155,6 +156,7 @@ onBeforeMount(async () => {
|
|||
});
|
||||
|
||||
const snackbar = inject<Snackbar>("snackbar");
|
||||
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
onMounted(() => {
|
||||
online.value = window.navigator.onLine;
|
||||
|
@ -187,6 +189,7 @@ onMounted(() => {
|
|||
},
|
||||
});
|
||||
});
|
||||
darkModePreference.addEventListener("change", changeTheme);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -289,6 +292,23 @@ watch(config, async (configWatched: IConfig | undefined) => {
|
|||
});
|
||||
|
||||
const isDemoMode = computed(() => config.value?.demoMode);
|
||||
|
||||
const changeTheme = () => {
|
||||
console.debug("changing theme");
|
||||
if (
|
||||
localStorage.getItem("theme") === "dark" ||
|
||||
(!("theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
darkModePreference.removeEventListener("change", changeTheme);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -2,6 +2,10 @@ body {
|
|||
@apply bg-body-background-color dark:bg-zinc-800 dark:text-white;
|
||||
}
|
||||
|
||||
.out {
|
||||
@apply underline hover:decoration-2 hover:decoration-mbz-yellow-alt-600;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.btn {
|
||||
@apply font-bold py-2 px-4 bg-mbz-bluegreen hover:bg-mbz-bluegreen-600 text-white rounded h-10 outline-none focus:ring ring-offset-1 ring-offset-slate-50 ring-blue-300;
|
||||
|
@ -194,6 +198,10 @@ body {
|
|||
@apply pl-2;
|
||||
}
|
||||
|
||||
.o-field--addons .o-radio:not(:only-child) input {
|
||||
@apply rounded-full;
|
||||
}
|
||||
|
||||
/* Editor */
|
||||
button.menubar__button {
|
||||
@apply dark:text-white;
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
<div class="">
|
||||
<o-field
|
||||
:label-for="id"
|
||||
expanded
|
||||
:message="fieldErrors"
|
||||
:type="{ 'is-danger': fieldErrors }"
|
||||
:variant="{ danger: fieldErrors }"
|
||||
class="!-mt-2"
|
||||
:labelClass="labelClass"
|
||||
>
|
||||
|
@ -32,7 +31,6 @@
|
|||
v-model="queryText"
|
||||
:placeholder="placeholderWithDefault"
|
||||
:customFormatter="(elem: IAddress) => addressFullName(elem)"
|
||||
:loading="isFetching"
|
||||
:debounceTyping="debounceDelay"
|
||||
@typing="asyncData"
|
||||
:icon="canShowLocateMeButton ? null : 'map-marker'"
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
/>
|
||||
<full-address-auto-complete
|
||||
:resultType="AddressSearchType.ADMINISTRATIVE"
|
||||
:doGeoLocation="false"
|
||||
v-model="location"
|
||||
:hide-map="true"
|
||||
:hide-selected="true"
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
@click="scrollLeft"
|
||||
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -left-5"
|
||||
>
|
||||
<div class=""><</div>
|
||||
<span class=""><</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
|
@ -41,7 +41,7 @@
|
|||
@click="scrollRight"
|
||||
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -right-5"
|
||||
>
|
||||
<div class="">></div>
|
||||
<span class="">></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,9 +29,7 @@
|
|||
v-for="group in selectedGroups"
|
||||
:key="group.id"
|
||||
:group="group"
|
||||
:view-mode="'column'"
|
||||
:minimal="true"
|
||||
:has-border="true"
|
||||
:mode="'column'"
|
||||
:showSummary="false"
|
||||
/>
|
||||
|
||||
|
|
|
@ -19,9 +19,7 @@
|
|||
v-for="event in events?.elements"
|
||||
:key="event.id"
|
||||
:event="event"
|
||||
view-mode="column"
|
||||
:has-border="true"
|
||||
:minimal="true"
|
||||
mode="column"
|
||||
/>
|
||||
<more-content
|
||||
:to="{
|
||||
|
|
|
@ -185,7 +185,7 @@
|
|||
>{{ t("Login") }}</router-link
|
||||
>
|
||||
</li>
|
||||
<li v-if="!currentActor?.id">
|
||||
<li v-if="!currentActor?.id && canRegister">
|
||||
<router-link
|
||||
:to="{ name: RouteName.REGISTER }"
|
||||
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
|
@ -374,7 +374,7 @@ import { ICurrentUserRole } from "@/types/enums";
|
|||
import { logout } from "../utils/auth";
|
||||
import { displayName } from "../types/actor";
|
||||
import RouteName from "../router/name";
|
||||
import { ref, watch } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
|
@ -387,6 +387,7 @@ import {
|
|||
import { useMutation } from "@vue/apollo-composable";
|
||||
import { UPDATE_DEFAULT_ACTOR } from "@/graphql/actor";
|
||||
import { changeIdentity } from "@/utils/identity";
|
||||
import { useRegistrationConfig } from "@/composition/apollo/config";
|
||||
// import { useRestrictions } from "@/composition/apollo/config";
|
||||
|
||||
const { currentUser } = useCurrentUserClient();
|
||||
|
@ -399,6 +400,15 @@ const router = useRouter();
|
|||
// const route = useRoute();
|
||||
|
||||
const { identities } = useCurrentUserIdentities();
|
||||
const { registrationsOpen, registrationsAllowlist, databaseLogin } =
|
||||
useRegistrationConfig();
|
||||
|
||||
const canRegister = computed(() => {
|
||||
return (
|
||||
(registrationsOpen.value || registrationsAllowlist.value) &&
|
||||
databaseLogin.value
|
||||
);
|
||||
});
|
||||
|
||||
// const mobileNavbarActive = ref(false);
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
<event-card
|
||||
v-if="instanceOfIEvent(activeElement)"
|
||||
:event="(activeElement as IEvent)"
|
||||
:has-border="false"
|
||||
view-mode="column"
|
||||
mode="column"
|
||||
:options="{
|
||||
isRemoteEvent: activeElement.__typename === 'EventResult',
|
||||
isLoggedIn,
|
||||
|
@ -21,8 +20,7 @@
|
|||
<group-card
|
||||
v-else
|
||||
:group="(activeElement as IGroup)"
|
||||
:has-border="false"
|
||||
view-mode="column"
|
||||
mode="column"
|
||||
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
|
||||
:isLoggedIn="isLoggedIn"
|
||||
/>
|
||||
|
@ -34,8 +32,7 @@
|
|||
<event-card
|
||||
v-if="instanceOfIEvent(activeElement)"
|
||||
:event="(activeElement as IEvent)"
|
||||
view-mode="column"
|
||||
:has-border="false"
|
||||
mode="column"
|
||||
:options="{
|
||||
isRemoteEvent: activeElement.__typename === 'EventResult',
|
||||
isLoggedIn,
|
||||
|
@ -44,8 +41,7 @@
|
|||
<group-card
|
||||
v-else
|
||||
:group="(activeElement as IGroup)"
|
||||
:has-border="false"
|
||||
view-mode="column"
|
||||
mode="column"
|
||||
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
|
||||
:isLoggedIn="isLoggedIn"
|
||||
/>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
GEOCODING_AUTOCOMPLETE,
|
||||
LOCATION,
|
||||
MAPS_TILES,
|
||||
REGISTRATIONS,
|
||||
RESOURCE_PROVIDERS,
|
||||
RESTRICTIONS,
|
||||
ROUTING_TYPE,
|
||||
|
@ -204,3 +205,28 @@ export function useSearchConfig() {
|
|||
const searchConfig = computed(() => result.value?.config.search);
|
||||
return { searchConfig, error, loading, onResult };
|
||||
}
|
||||
|
||||
export function useRegistrationConfig() {
|
||||
const { result, error, loading, onResult } = useQuery<{
|
||||
config: Pick<
|
||||
IConfig,
|
||||
"registrationsOpen" | "registrationsAllowlist" | "auth"
|
||||
>;
|
||||
}>(REGISTRATIONS, undefined, { fetchPolicy: "cache-only" });
|
||||
|
||||
const registrationsOpen = computed(
|
||||
() => result.value?.config.registrationsOpen
|
||||
);
|
||||
const registrationsAllowlist = computed(
|
||||
() => result.value?.config.registrationsAllowlist
|
||||
);
|
||||
const databaseLogin = computed(() => result.value?.config.auth.databaseLogin);
|
||||
return {
|
||||
registrationsOpen,
|
||||
registrationsAllowlist,
|
||||
databaseLogin,
|
||||
error,
|
||||
loading,
|
||||
onResult,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ export const CONFIG = gql`
|
|||
}
|
||||
auth {
|
||||
ldap
|
||||
databaseLogin
|
||||
oauthProviders {
|
||||
id
|
||||
label
|
||||
|
@ -386,6 +387,7 @@ export const LOGIN_CONFIG = gql`
|
|||
query LoginConfig {
|
||||
config {
|
||||
auth {
|
||||
databaseLogin
|
||||
oauthProviders {
|
||||
id
|
||||
label
|
||||
|
@ -444,3 +446,15 @@ export const SEARCH_CONFIG = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REGISTRATIONS = gql`
|
||||
query Registrations {
|
||||
config {
|
||||
registrationsOpen
|
||||
registrationsAllowlist
|
||||
auth {
|
||||
databaseLogin
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1408,5 +1408,14 @@
|
|||
"Most recently published": "Most recently published",
|
||||
"Least recently published": "Least recently published",
|
||||
"With the most participants": "With the most participants",
|
||||
"Number of members": "Number of members"
|
||||
}
|
||||
"Number of members": "Number of members",
|
||||
"More options": "More options",
|
||||
"Reported by someone anonymously": "Reported by someone anonymously",
|
||||
"Back to homepage": "Back to homepage",
|
||||
"Category list": "Category list",
|
||||
"No categories with public upcoming events on this instance were found.": "No categories with public upcoming events on this instance were found.",
|
||||
"Theme": "Theme",
|
||||
"Adapt to system theme": "Adapt to system theme",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark"
|
||||
}
|
|
@ -1406,5 +1406,14 @@
|
|||
"{timezoneLongName} ({timezoneShortName})": "{timezoneLongName} ({timezoneShortName})",
|
||||
"{title} ({count} todos)": "{title} ({count} todos)",
|
||||
"{username} was invited to {group}": "{username} a été invité à {group}",
|
||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
|
||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
|
||||
"More options": "Plus d'options",
|
||||
"Reported by someone anonymously": "Signalé par quelqu'un anonymement",
|
||||
"Back to homepage": "Retour à la page d'accueil",
|
||||
"Category list": "Liste des catégories",
|
||||
"No categories with public upcoming events on this instance were found.": "Aucune catégorie avec des événements publics à venir n'a été trouvée.",
|
||||
"Theme": "Thème",
|
||||
"Adapt to system theme": "S’adapter au thème du système",
|
||||
"Light": "Clair",
|
||||
"Dark": "Sombre"
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ export interface IConfig {
|
|||
version: string;
|
||||
auth: {
|
||||
ldap: boolean;
|
||||
databaseLogin: boolean;
|
||||
oauthProviders: IOAuthProvider[];
|
||||
};
|
||||
uploadLimits: {
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<o-field
|
||||
v-if="eventCategories"
|
||||
v-if="orderedCategories"
|
||||
:label="t('Category')"
|
||||
label-for="categoryField"
|
||||
class="w-full md:max-w-fit"
|
||||
|
@ -45,7 +45,7 @@
|
|||
expanded
|
||||
>
|
||||
<option
|
||||
v-for="category in eventCategories"
|
||||
v-for="category in orderedCategories"
|
||||
:value="category.id"
|
||||
:key="category.id"
|
||||
>
|
||||
|
@ -595,6 +595,7 @@ import { Notifier } from "@/plugins/notifier";
|
|||
import { useHead } from "@vueuse/head";
|
||||
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
||||
import type { Locale } from "date-fns";
|
||||
import sortBy from "lodash/sortBy";
|
||||
|
||||
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
||||
|
||||
|
@ -1331,6 +1332,11 @@ watch(group, () => {
|
|||
event.value.visibility = EventVisibility.PUBLIC;
|
||||
}
|
||||
});
|
||||
|
||||
const orderedCategories = computed(() => {
|
||||
if (!eventCategories.value) return undefined;
|
||||
return sortBy(eventCategories.value, ["label"]);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -193,7 +193,7 @@
|
|||
<template #options>
|
||||
<fieldset class="flex flex-col">
|
||||
<legend class="sr-only">{{ t("Categories") }}</legend>
|
||||
<div v-for="category in eventCategories" :key="category.id">
|
||||
<div v-for="category in orderedCategories" :key="category.id">
|
||||
<input
|
||||
:id="category.id"
|
||||
v-model="categoryOneOf"
|
||||
|
@ -692,6 +692,7 @@ import { IAddress } from "@/types/address.model";
|
|||
import { IConfig } from "@/types/config.model";
|
||||
import { TypeNamed } from "@/types/apollo";
|
||||
import { LatLngBounds } from "leaflet";
|
||||
import lodashSortBy from "lodash/sortBy";
|
||||
|
||||
const EventMarkerMap = defineAsyncComponent(
|
||||
() => import("@/components/Search/EventMarkerMap.vue")
|
||||
|
@ -825,6 +826,11 @@ const props = defineProps<{
|
|||
const { features } = useFeatures();
|
||||
const { eventCategories } = useEventCategories();
|
||||
|
||||
const orderedCategories = computed(() => {
|
||||
if (!eventCategories.value) return [];
|
||||
return lodashSortBy(eventCategories.value, ["label"]);
|
||||
});
|
||||
|
||||
const searchEvents = computed(() => searchElementsResult.value?.searchEvents);
|
||||
const searchGroups = computed(() => searchElementsResult.value?.searchGroups);
|
||||
|
||||
|
|
|
@ -13,6 +13,36 @@
|
|||
]"
|
||||
/>
|
||||
<div>
|
||||
<o-field :label="t('Theme')" addonsClass="flex flex-col">
|
||||
<o-field>
|
||||
<o-checkbox v-model="systemTheme">{{
|
||||
t("Adapt to system theme")
|
||||
}}</o-checkbox>
|
||||
</o-field>
|
||||
<o-field>
|
||||
<fieldset>
|
||||
<legend class="sr-only">{{ t("Theme") }}</legend>
|
||||
<o-radio
|
||||
:class="{ 'border-mbz-bluegreen border-2': theme === 'light' }"
|
||||
class="p-4 bg-white text-zinc-800 rounded-md mt-2 mr-2"
|
||||
:disabled="systemTheme"
|
||||
v-model="theme"
|
||||
name="theme"
|
||||
native-value="light"
|
||||
>{{ t("Light") }}</o-radio
|
||||
>
|
||||
<o-radio
|
||||
:class="{ 'border-mbz-bluegreen border-2': theme === 'dark' }"
|
||||
class="p-4 bg-zinc-800 rounded-md text-white mt-2 ml-2"
|
||||
:disabled="systemTheme"
|
||||
v-model="theme"
|
||||
name="theme"
|
||||
native-value="dark"
|
||||
>{{ t("Dark") }}</o-radio
|
||||
>
|
||||
</fieldset>
|
||||
</o-field>
|
||||
</o-field>
|
||||
<o-field :label="t('Language')" label-for="setting-language">
|
||||
<o-select
|
||||
:loading="loadingTimezones || loadingUserSettings"
|
||||
|
@ -65,7 +95,6 @@
|
|||
<full-address-auto-complete
|
||||
v-if="loggedUser?.settings"
|
||||
:resultType="AddressSearchType.ADMINISTRATIVE"
|
||||
:doGeoLocation="false"
|
||||
v-model="address"
|
||||
:default-text="address?.description"
|
||||
id="setting-city"
|
||||
|
@ -120,7 +149,7 @@ import { Address, IAddress } from "@/types/address.model";
|
|||
import { useTimezones } from "@/composition/apollo/config";
|
||||
import { useUserSettings } from "@/composition/apollo/user";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { computed, defineAsyncComponent } from "vue";
|
||||
import { computed, defineAsyncComponent, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useMutation } from "@vue/apollo-composable";
|
||||
|
||||
|
@ -140,6 +169,44 @@ useHead({
|
|||
|
||||
// langs: Record<string, string> = langs;
|
||||
|
||||
const theme = ref(localStorage.getItem("theme"));
|
||||
const systemTheme = ref(!("theme" in localStorage));
|
||||
|
||||
watch(systemTheme, (newSystemTheme) => {
|
||||
console.debug("changing system theme", newSystemTheme);
|
||||
if (newSystemTheme) {
|
||||
theme.value = null;
|
||||
localStorage.removeItem("theme");
|
||||
} else {
|
||||
theme.value = "light";
|
||||
localStorage.setItem("theme", theme.value);
|
||||
}
|
||||
changeTheme();
|
||||
});
|
||||
|
||||
watch(theme, (newTheme) => {
|
||||
console.debug("changing theme value", newTheme);
|
||||
if (newTheme) {
|
||||
localStorage.setItem("theme", newTheme);
|
||||
}
|
||||
changeTheme();
|
||||
});
|
||||
|
||||
const changeTheme = () => {
|
||||
console.debug("changing theme to apply");
|
||||
if (
|
||||
localStorage.getItem("theme") === "dark" ||
|
||||
(!("theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
console.debug("applying dark theme");
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
console.debug("removing dark theme");
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
|
||||
const selectedTimezone = computed({
|
||||
get() {
|
||||
if (loggedUser.value?.settings?.timezone) {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
>
|
||||
{{ error }}
|
||||
</o-notification>
|
||||
<form @submit="loginAction">
|
||||
<form @submit="loginAction" v-if="config?.auth.databaseLogin">
|
||||
<o-field
|
||||
:label="t('Email')"
|
||||
label-for="email"
|
||||
|
@ -81,13 +81,6 @@
|
|||
</p>
|
||||
<!-- <o-loading :is-full-page="false" v-model="submitted" /> -->
|
||||
|
||||
<div
|
||||
class="control"
|
||||
v-if="config && config?.auth.oauthProviders.length > 0"
|
||||
>
|
||||
<auth-providers :oauthProviders="config.auth.oauthProviders" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mt-3">
|
||||
<o-button
|
||||
tag="router-link"
|
||||
|
@ -107,7 +100,7 @@
|
|||
}"
|
||||
>{{ t("Didn't receive the instructions?") }}</o-button
|
||||
>
|
||||
<p class="control" v-if="config && config.registrationsOpen">
|
||||
<p class="control" v-if="canRegister">
|
||||
<o-button
|
||||
tag="router-link"
|
||||
variant="text"
|
||||
|
@ -123,6 +116,9 @@
|
|||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="config && config?.auth.oauthProviders.length > 0">
|
||||
<auth-providers :oauthProviders="config.auth.oauthProviders" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
@ -162,11 +158,21 @@ const route = useRoute();
|
|||
const { currentUser } = useCurrentUserClient();
|
||||
|
||||
const { result: configResult } = useQuery<{
|
||||
config: Pick<IConfig, "auth" | "registrationsOpen">;
|
||||
config: Pick<
|
||||
IConfig,
|
||||
"auth" | "registrationsOpen" | "registrationsAllowlist"
|
||||
>;
|
||||
}>(LOGIN_CONFIG);
|
||||
|
||||
const config = computed(() => configResult.value?.config);
|
||||
|
||||
const canRegister = computed(() => {
|
||||
return (
|
||||
(config.value?.registrationsOpen || config.value?.registrationsAllowlist) &&
|
||||
config.value?.auth.databaseLogin
|
||||
);
|
||||
});
|
||||
|
||||
const errors = ref<string[]>([]);
|
||||
const submitted = ref(false);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="container mx-auto pt-6">
|
||||
<div class="container mx-auto py-6">
|
||||
<section class="">
|
||||
<h1>
|
||||
{{
|
||||
|
@ -123,7 +123,7 @@
|
|||
/>
|
||||
</o-field>
|
||||
|
||||
<div class="flex items-start mb-6">
|
||||
<div class="flex items-start mb-6 mt-2">
|
||||
<div class="flex items-center h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
@ -155,7 +155,7 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<p class="create-account control has-text-centered">
|
||||
<p>
|
||||
<o-button
|
||||
variant="primary"
|
||||
size="large"
|
||||
|
@ -166,19 +166,19 @@
|
|||
</o-button>
|
||||
</p>
|
||||
|
||||
<p class="control has-text-centered">
|
||||
<router-link
|
||||
class="button is-text"
|
||||
<p class="my-6">
|
||||
<o-button
|
||||
tag="router-link"
|
||||
variant="text"
|
||||
:to="{
|
||||
name: RouteName.RESEND_CONFIRMATION,
|
||||
params: { email: credentials.email },
|
||||
}"
|
||||
>{{ t("Didn't receive the instructions?") }}</router-link
|
||||
>{{ t("Didn't receive the instructions?") }}</o-button
|
||||
>
|
||||
</p>
|
||||
<p class="control has-text-centered">
|
||||
<router-link
|
||||
class="button is-text"
|
||||
<o-button
|
||||
tag="router-link"
|
||||
variant="text"
|
||||
:to="{
|
||||
name: RouteName.LOGIN,
|
||||
params: {
|
||||
|
@ -186,7 +186,7 @@
|
|||
password: credentials.password,
|
||||
},
|
||||
}"
|
||||
>{{ t("Login") }}</router-link
|
||||
>{{ t("Login") }}</o-button
|
||||
>
|
||||
</p>
|
||||
|
||||
|
@ -252,13 +252,13 @@ const title = computed((): string => {
|
|||
if (config.value) {
|
||||
return t("Register an account on {instanceName}!", {
|
||||
instanceName: config.value?.name,
|
||||
}) as string;
|
||||
});
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: title.value,
|
||||
title: () => title.value,
|
||||
});
|
||||
|
||||
const { onDone, onError, mutate } = useMutation(CREATE_USER);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
|
|
@ -14,7 +14,9 @@ test("Login has everything we need", async ({ page }) => {
|
|||
hasText: "Didn't receive the instructions?",
|
||||
});
|
||||
|
||||
const registerLink = page.locator("a", { hasText: "Create an account" });
|
||||
const registerLink = page.locator("a > span > span", {
|
||||
hasText: "Create an account",
|
||||
});
|
||||
|
||||
await expect(forgotPasswordLink).toBeVisible();
|
||||
await expect(reAskInstructionsLink).toBeVisible();
|
||||
|
|
|
@ -24,7 +24,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Address do
|
|||
}
|
||||
|
||||
res =
|
||||
if is_nil(object["address"]) do
|
||||
if is_nil(object["address"]) or not is_map(object["address"]) do
|
||||
res
|
||||
else
|
||||
Map.merge(res, %{
|
||||
|
|
|
@ -156,6 +156,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
|||
federating: Config.instance_federating(),
|
||||
auth: %{
|
||||
ldap: Config.ldap_enabled?(),
|
||||
database_login:
|
||||
Application.get_env(:mobilizon, :instance) |> get_in([:disable_database_login]) == false,
|
||||
oauth_providers: Config.oauth_consumer_strategies()
|
||||
},
|
||||
upload_limits: %{
|
||||
|
|
|
@ -305,6 +305,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
|||
"""
|
||||
object :auth do
|
||||
field(:ldap, :boolean, description: "Whether or not LDAP auth is enabled")
|
||||
field(:database_login, :boolean, description: "Whether or not database login is enabled")
|
||||
field(:oauth_providers, list_of(:oauth_provider), description: "List of oauth providers")
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
||||
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
||||
<meta name="theme-color" content={theme_color()} />
|
||||
<script>
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
<%= if is_root(assigns) do %>
|
||||
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
||||
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
||||
|
|
Loading…
Reference in a new issue