From 68c97dfddc3c1397fe10f64b4087f6fb0c28ef79 Mon Sep 17 00:00:00 2001 From: AlitaBernachot Date: Fri, 11 Oct 2024 10:06:55 +0200 Subject: [PATCH 1/2] feat: auth form ui --- .env | 5 + .env.development | 7 +- .env.e2e | 5 + .env.staging | 7 +- cypress/e2e/auth/auth.cy.ts | 39 ++++++ src/App.vue | 4 +- src/assets/main.css | 26 +++- src/bundle/lib.ts | 2 +- src/components/auth/auth-form.vue | 123 ++++++++++++++++++ .../common/dropdown-content.spec.ts | 73 +++++++++++ src/components/common/dropdown-content.vue | 65 +++++++++ src/components/common/dropdown-list.spec.ts | 28 ++-- src/components/common/dropdown-list.vue | 71 +++------- .../{header => header-bar}/header-bar.vue | 20 ++- .../language-selector.spec.ts | 0 .../language-selector.vue | 0 16 files changed, 395 insertions(+), 80 deletions(-) create mode 100644 cypress/e2e/auth/auth.cy.ts create mode 100644 src/components/auth/auth-form.vue create mode 100644 src/components/common/dropdown-content.spec.ts create mode 100644 src/components/common/dropdown-content.vue rename src/components/{header => header-bar}/header-bar.vue (73%) rename src/components/{nav-bars => header-bar}/language-selector.spec.ts (100%) rename src/components/{nav-bars => header-bar}/language-selector.vue (100%) diff --git a/.env b/.env index 24478a6d..60d654f6 100644 --- a/.env +++ b/.env @@ -38,3 +38,8 @@ VITE_MODE_LIB=true VITE_MYMAPS_URL="/mymaps" VITE_ARROW_MODEL_URL="/static-ngeo/models/arrow5.glb" VITE_ELEVATION_URL="/raster" + +# Auth +VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu" +VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password" +VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user" diff --git a/.env.development b/.env.development index 342091b5..7bc9ecb2 100644 --- a/.env.development +++ b/.env.development @@ -37,4 +37,9 @@ VITE_MODE_LIB=false # MyMaps / Draw VITE_MYMAPS_URL="https://migration.geoportail.lu/mymaps" VITE_ARROW_MODEL_URL="https://migration.geoportail.lu/static-ngeo/models/arrow5.glb" -VITE_ELEVATION_URL="https://migration.geoportail.lu/raster" \ No newline at end of file +VITE_ELEVATION_URL="https://migration.geoportail.lu/raster" + +# Auth +VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu" +VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password" +VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user" diff --git a/.env.e2e b/.env.e2e index be795cdb..ae86cd93 100644 --- a/.env.e2e +++ b/.env.e2e @@ -38,3 +38,8 @@ VITE_MODE_LIB=false VITE_MYMAPS_URL="https://migration.geoportail.lu/mymaps" VITE_ARROW_MODEL_URL="https://migration.geoportail.lu/static-ngeo/models/arrow5.glb" VITE_ELEVATION_URL="https://migration.geoportail.lu/raster" + +# Auth +VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu" +VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password" +VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user" diff --git a/.env.staging b/.env.staging index 89190e8f..76fca60e 100644 --- a/.env.staging +++ b/.env.staging @@ -37,4 +37,9 @@ VITE_MODE_LIB=true # MyMaps / Draw VITE_MYMAPS_URL="https://migration.geoportail.lu/mymaps" VITE_ARROW_MODEL_URL="https://migration.geoportail.lu/static-ngeo/models/arrow5.glb" -VITE_ELEVATION_URL="/raster" \ No newline at end of file +VITE_ELEVATION_URL="/raster" + +# Auth +VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu" +VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password" +VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user" diff --git a/cypress/e2e/auth/auth.cy.ts b/cypress/e2e/auth/auth.cy.ts new file mode 100644 index 00000000..73cfa4d2 --- /dev/null +++ b/cypress/e2e/auth/auth.cy.ts @@ -0,0 +1,39 @@ +describe('Authentification', () => { + beforeEach(() => { + cy.visit('/') + }) + + describe('When user arrives on the page', () => { + it('the authentification icon is located in the header', () => { + cy.get('header [data-cy="authFormIcon"]').should('exist') + }) + }) + + describe('When user clicks on the auth icon in the header', () => { + it('displays the authentification form correctly', () => { + cy.get('header [data-cy="authFormIcon"]').click() + cy.get('header [data-cy="authForm"]').should('exist') + cy.get('header [data-cy="authForm"] form input[name="userName"]').should( + 'exist' + ) + cy.get( + 'header [data-cy="authForm"] form input[name="userPassword"]' + ).should('exist') + cy.get( + 'header [data-cy="authForm"] form a[data-cy="authFormLostPwd"]' + ).should('contain.text', "J'ai perdu mon mot de passe") + cy.get( + 'header [data-cy="authForm"] form a[data-cy="authFormNewAccount"]' + ).should('contain.text', 'Créer un nouvel utilisateur') + }) + + describe('When user clicks again', () => { + it('hides the authentification form', () => { + cy.get('header [data-cy="authFormIcon"]').click() + cy.get('header [data-cy="authForm"]').should('exist') + cy.get('header [data-cy="authFormIcon"]').click() + cy.get('header [data-cy="authForm"]').should('not.be.visible') + }) + }) + }) +}) diff --git a/src/App.vue b/src/App.vue index 115147a5..2924d199 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,8 +2,8 @@ import { onMounted, onUnmounted, watch } from 'vue' import { storeToRefs } from 'pinia' -import HeaderBar from './components/header/header-bar.vue' -import FooterBar from './components/footer/footer-bar.vue' +import HeaderBar from '@/components/header-bar/header-bar.vue' +import FooterBar from '@/components/footer/footer-bar.vue' import AlertNotifications from '@/components/alert-notifications/alert-notifications.vue' import RemoteLayers from '@/components/remote-layers/remote-layers.vue' diff --git a/src/assets/main.css b/src/assets/main.css index b5bd0d21..8fa13cd6 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -109,7 +109,7 @@ } .lux-btn { - @apply bg-white border text-primary py-[6px] px-[12px] hover:bg-primary hover:text-white leading-snug focus:bg-[#e6e6e6] focus:border-[#8c8c8c] focus:[color:var(--color-primary)] focus:lux-outlined; + @apply cursor-pointer bg-white border text-primary py-[6px] px-[12px] hover:bg-primary hover:text-white leading-snug focus:bg-[#e6e6e6] focus:border-[#8c8c8c] focus:[color:var(--color-primary)] focus:lux-outlined; border: 1px solid var(--color-gray); } @@ -202,6 +202,10 @@ [&.expanded]:after:content-['\e02c']; } + .lux-navbar-dropdown-auth .lux-dropdown-btn { + @apply after:content-['\E02E']; + } + .lux-navbar-dropdown .lux-dropdown-wrapper { @apply absolute w-full right-0; } @@ -224,6 +228,10 @@ @apply fixed w-56; } + .lux-navbar-dropdown .lux-dropdown-content { + @apply bg-transparent border-none gap-[1px] shadow-none m-0 py-0 float-right relative left-auto; + } + .lux-slider-line { @apply absolute h-full w-[4px] left-[50%] bg-primary ml-[-2px] cursor-ew-resize block top-0; } @@ -395,6 +403,22 @@ @apply text-white bg-primary cursor-default; } + .lux-account-tab { + @apply ml-1 bg-primary text-white after:content-['\E02E'] after:font-icons after:text-3xl after:ml-4 w-20 px-2 pt-1; + } + + .lux-account-content { + @apply bg-primary p-3; + } + + .lux-account input[type='password'], + .lux-account input[type='text'] { + @apply border-none py-2; + background-color: rgba(46, 65, 78, 0.2); + -webkit-box-shadow: inset 0px 2px 4px -1px rgba(0, 57, 79, 0.25); + box-shadow: inset 0px 2px 4px -1px rgba(0, 57, 79, 0.25); + } + .form-control { border: 1px solid var(--color-gray); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); diff --git a/src/bundle/lib.ts b/src/bundle/lib.ts index 15912186..ca843355 100644 --- a/src/bundle/lib.ts +++ b/src/bundle/lib.ts @@ -16,7 +16,7 @@ import MapContainer from '@/components/map/map-container.vue' import BackgroundSelector from '@/components/background-selector/background-selector.vue' import RemoteLayers from '@/components/remote-layers/remote-layers.vue' import LayerMetadata from '@/components/layer-metadata/layer-metadata.vue' -import HeaderBar from '@/components/header/header-bar.vue' +import HeaderBar from '@/components/header-bar/header-bar.vue' import FooterBar from '@/components/footer/footer-bar.vue' import ToolbarDraw from '@/components/footer/toolbar-draw.vue' import LayerPanel from '@/components/layer-panel/layer-panel.vue' diff --git a/src/components/auth/auth-form.vue b/src/components/auth/auth-form.vue new file mode 100644 index 00000000..aa737751 --- /dev/null +++ b/src/components/auth/auth-form.vue @@ -0,0 +1,123 @@ + + + diff --git a/src/components/common/dropdown-content.spec.ts b/src/components/common/dropdown-content.spec.ts new file mode 100644 index 00000000..4d23e2bd --- /dev/null +++ b/src/components/common/dropdown-content.spec.ts @@ -0,0 +1,73 @@ +import { mount } from '@vue/test-utils' +import DropdownContent from './dropdown-content.vue' + +describe('DropdownContent', () => { + it('renders the placeholder prop', () => { + const wrapper = mount(DropdownContent, { + props: { placeholder: 'Test Placeholder' }, + }) + expect(wrapper.find('span').text()).toBe('Test Placeholder') + }) + + it('toggles dropdown on button click', async () => { + const wrapper = mount(DropdownContent) + const button = wrapper.find('button') + + expect(wrapper.find('.lux-dropdown-content').classes()).toContain('hidden') + + await button.trigger('click') + + expect(wrapper.find('.lux-dropdown-content').classes()).not.toContain( + 'hidden' + ) + + await button.trigger('click') + + expect(wrapper.find('.lux-dropdown-content').classes()).toContain('hidden') + }) + + it('opens dropdown when forceOpen is true', async () => { + const wrapper = mount(DropdownContent) + wrapper.vm.toggleDropdown(true) + + await wrapper.vm.$nextTick() + + expect(wrapper.find('.lux-dropdown-content').classes()).not.toContain( + 'hidden' + ) + }) + + it('closes dropdown when clicking outside', async () => { + const wrapper = mount(DropdownContent, { + props: { enableClickOutside: true }, + }) + + wrapper.vm.toggleDropdown(true) + + await wrapper.vm.$nextTick() + + document.dispatchEvent(new MouseEvent('click')) + + await wrapper.vm.$nextTick() + + expect(wrapper.find('.lux-dropdown-content').classes()).toContain('hidden') + }) + + it('does not close dropdown when enableClickOutside is false', async () => { + const wrapper = mount(DropdownContent, { + props: { enableClickOutside: false }, + }) + + wrapper.vm.toggleDropdown(true) + + await wrapper.vm.$nextTick() + + document.dispatchEvent(new MouseEvent('click')) + + await wrapper.vm.$nextTick() + + expect(wrapper.find('.lux-dropdown-content').classes()).not.toContain( + 'hidden' + ) + }) +}) diff --git a/src/components/common/dropdown-content.vue b/src/components/common/dropdown-content.vue new file mode 100644 index 00000000..b27d3745 --- /dev/null +++ b/src/components/common/dropdown-content.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/components/common/dropdown-list.spec.ts b/src/components/common/dropdown-list.spec.ts index c6a7835d..9ab02b0b 100644 --- a/src/components/common/dropdown-list.spec.ts +++ b/src/components/common/dropdown-list.spec.ts @@ -1,4 +1,4 @@ -import { shallowMount } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import DropdownList from './dropdown-list.vue' @@ -10,14 +10,17 @@ const optionsMocks = [ describe('DropdownList', () => { it('renders properly', () => { - const wrapper = shallowMount(DropdownList, { + const wrapper = mount(DropdownList, { props: { placeholder: 'Dropdown placeholder', - options: [], + options: [ + { label: 'option 1 label', value: 'option 1 value' }, + { label: 'option 2 label', value: 'option 2 value' }, + ], }, }) - expect(wrapper.findAll('button').length).toBe(1) + expect(wrapper.findAll('button').length).toBe(3) expect(wrapper.findAll('button')[0].attributes('aria-expanded')).toBe( 'false' ) @@ -25,23 +28,21 @@ describe('DropdownList', () => { 'true' ) expect(wrapper.findAll('ul').length).toBe(1) - expect(wrapper.findAll('ul')[0].attributes('tabindex')).toBe('-1') }) it('renders properly items', () => { - const wrapper = shallowMount(DropdownList, { + const wrapper = mount(DropdownList, { props: { placeholder: 'Dropdown placeholder', options: optionsMocks, }, }) expect(wrapper.findAll('ul').length).toBe(1) - expect(wrapper.findAll('ul')[0].attributes('class')).toContain('hidden') expect(wrapper.findAll('li').length).toBe(3) }) describe('when click on main button', () => { - const wrapper = shallowMount(DropdownList, { + const wrapper = mount(DropdownList, { props: { placeholder: 'Dropdown placeholder', options: optionsMocks, @@ -70,17 +71,6 @@ describe('DropdownList', () => { expect(wrapper.findAll('button')[0].attributes('aria-expanded')).toBe( 'false' ) - expect(wrapper.findAll('ul')[0].attributes('class')).toContain('hidden') - }) - }) - - describe('when click outside', () => { - beforeEach(async () => { - await document.dispatchEvent(new Event('click')) - }) - - it('should hide dropdown', () => { - expect(wrapper.findAll('ul')[0].attributes('class')).toContain('hidden') }) }) }) diff --git a/src/components/common/dropdown-list.vue b/src/components/common/dropdown-list.vue index d5d33e49..d927a089 100644 --- a/src/components/common/dropdown-list.vue +++ b/src/components/common/dropdown-list.vue @@ -1,6 +1,7 @@ diff --git a/src/components/header/header-bar.vue b/src/components/header-bar/header-bar.vue similarity index 73% rename from src/components/header/header-bar.vue rename to src/components/header-bar/header-bar.vue index f96afcb7..3ce4a915 100644 --- a/src/components/header/header-bar.vue +++ b/src/components/header-bar/header-bar.vue @@ -3,10 +3,12 @@ import { watch } from 'vue' import { storeToRefs } from 'pinia' import { useTranslation } from 'i18next-vue' -import LanguageSelector from '@/components/nav-bars/language-selector.vue' import { useAppStore } from '@/stores/app.store' import { useThemeStore } from '@/stores/config.store' -import { themeSelectorService } from '../theme-selector/theme-selector.service' +import { themeSelectorService } from '@/components/theme-selector/theme-selector.service' +import AuthForm from '@/components/auth/auth-form.vue' +import DropdownContent from '@/components/common/dropdown-content.vue' +import LanguageSelector from '@/components/header-bar/language-selector.vue' const { t } = useTranslation() const appStore = useAppStore() @@ -51,6 +53,7 @@ function onClick() {
search
diff --git a/src/components/nav-bars/language-selector.spec.ts b/src/components/header-bar/language-selector.spec.ts similarity index 100% rename from src/components/nav-bars/language-selector.spec.ts rename to src/components/header-bar/language-selector.spec.ts diff --git a/src/components/nav-bars/language-selector.vue b/src/components/header-bar/language-selector.vue similarity index 100% rename from src/components/nav-bars/language-selector.vue rename to src/components/header-bar/language-selector.vue From 084934aa7a3d741861ea6bfcb1eafc241ea1b615 Mon Sep 17 00:00:00 2001 From: AlitaBernachot Date: Wed, 16 Oct 2024 16:49:31 +0200 Subject: [PATCH 2/2] fix: enhance header to open only one dropdown --- src/assets/main.css | 4 ++++ src/components/auth/auth-form.vue | 18 +++++++++----- src/components/common/dropdown-content.vue | 24 ++++++++++++++----- src/components/header-bar/header-bar.vue | 24 ++++++++++++++++++- .../header-bar/language-selector.vue | 16 ++++++------- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/assets/main.css b/src/assets/main.css index 8fa13cd6..3f7de88d 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -419,6 +419,10 @@ box-shadow: inset 0px 2px 4px -1px rgba(0, 57, 79, 0.25); } + .lux-account ::placeholder { + @apply text-gray-400; + } + .form-control { border: 1px solid var(--color-gray); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); diff --git a/src/components/auth/auth-form.vue b/src/components/auth/auth-form.vue index aa737751..1eaf0e42 100644 --- a/src/components/auth/auth-form.vue +++ b/src/components/auth/auth-form.vue @@ -34,7 +34,9 @@ function submit() {