Files
moreminimore-chat/app/javascript/v3/views/login/Saml.vue
Kunthawat Greethong f17ce89d01 [brand] rebrand to Moreminimore Chat (configurable, phase 1)
Set the default brand to Moreminimore Chat while keeping branding
configurable via the existing InstallationConfig/ENV mechanism.

- installation_config.yml: INSTALLATION_NAME/BRAND_NAME -> Moreminimore Chat,
  BRAND_URL/WIDGET_BRAND_URL -> https://www.moreminimore.com,
  TERMS/PRIVACY -> moreminimore.com, MAILER_SUPPORT_EMAIL -> contact@moreminimore.com;
  drop LOGO_DARK (single PNG logo for light+dark).
- Mailer sender defaults -> 'Moreminimore Chat Team <contact@moreminimore.com>'
  across devise.rb, application_mailer, conversation_reply_mailer,
  mail_presenter, email_address_parseable.
- Replace SVG brand assets with logo.png/logo_thumbnail.png (1000x1000);
  update onboarding, super_admin login/nav, Vue login/signup/saml, survey,
  helpcenter, widget, year-in-review to use the single PNG and Moreminimore Chat
  name; remove dark dual-logo pattern.
- manifest.json name/short_name -> Moreminimore Chat; en.yml integration copy.

Approved by independent five-key pre-commit review deleg_a733cdf4
(passed=true, security_concerns=[], logic_errors=[]). Privacy audit findings=0.
2026-08-16 08:20:13 +07:00

127 lines
3.1 KiB
Vue

<script setup>
import { ref, nextTick, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { required, email } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
// components
import FormInput from '../../components/Form/Input.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
const props = defineProps({
authError: {
type: String,
default: '',
},
target: {
type: String,
default: 'web',
},
});
const store = useStore();
const { t } = useI18n();
const credentials = ref({
email: '',
});
const loginApi = ref({
showLoading: false,
hasErrored: false,
});
const handleAuthError = () => {
if (!props.authError) {
return;
}
const translatedMessage = t('LOGIN.SAML.API.ERROR_MESSAGE');
useAlert(translatedMessage);
loginApi.value.hasErrored = true;
};
const validations = {
credentials: {
email: {
required,
email,
},
},
};
const v$ = useVuelidate(validations, { credentials });
const globalConfig = computed(() => store.getters['globalConfig/get']);
const csrfToken = ref('');
onMounted(async () => {
csrfToken.value =
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute('content') || '';
await nextTick(handleAuthError);
});
</script>
<template>
<main
class="flex flex-col w-full min-h-screen py-20 bg-n-brand/5 dark:bg-n-background sm:px-6 lg:px-8"
>
<section class="max-w-5xl mx-auto">
<img
:src="globalConfig.logo"
:alt="globalConfig.installationName"
class="block w-auto h-8 mx-auto"
/>
<h2 class="mt-6 text-3xl font-medium text-center text-n-slate-12">
{{ t('LOGIN.SAML.TITLE') }}
</h2>
</section>
<section
class="bg-white shadow sm:mx-auto mt-11 sm:w-full sm:max-w-lg dark:bg-n-solid-2 p-11 sm:shadow-lg sm:rounded-lg"
:class="{
'animate-wiggle': loginApi.hasErrored,
}"
>
<form class="space-y-5" method="POST" action="/api/v1/auth/saml_login">
<FormInput
v-model="credentials.email"
name="email"
type="text"
:tabindex="1"
required
:label="t('LOGIN.SAML.WORK_EMAIL.LABEL')"
:placeholder="t('LOGIN.SAML.WORK_EMAIL.PLACEHOLDER')"
:has-error="v$.credentials.email.$error"
@input="v$.credentials.email.$touch"
/>
<input
type="hidden"
class="h-0"
name="authenticity_token"
:value="csrfToken"
/>
<input type="hidden" class="h-0" name="target" :value="target" />
<NextButton
lg
type="submit"
class="w-full"
:tabindex="2"
:label="t('LOGIN.SAML.SUBMIT')"
:disabled="loginApi.showLoading"
:is-loading="loginApi.showLoading"
/>
</form>
</section>
<p class="mt-6 text-sm text-center text-n-slate-11">
<router-link to="/app/login" class="text-link text-n-brand">
{{ t('LOGIN.SAML.BACK_TO_LOGIN') }}
</router-link>
</p>
</main>
</template>