diff --git a/app/components/header.hbs b/app/components/header.hbs index 54224285..f263aacd 100644 --- a/app/components/header.hbs +++ b/app/components/header.hbs @@ -51,7 +51,7 @@ class="nav__element" href={{this.STATUS_URL}} >Status - {{#if @dev}} + {{#if @dev}}
  • {{! TODO - remove query for dev when it goes to production }} {{else}} - {{/if}} {{/if}} diff --git a/app/components/header.js b/app/components/header.js index f268a922..d2961913 100644 --- a/app/components/header.js +++ b/app/components/header.js @@ -43,6 +43,6 @@ export default class HeaderComponent extends Component { this.fastboot.request.host + this.fastboot.request.path : window.location.href; - return `${AUTH.SIGN_IN}?redirectURL=${currentURL}`; + return `${AUTH.GITHUB_SIGN_IN}?redirectURL=${currentURL}`; } } diff --git a/app/components/login.hbs b/app/components/login.hbs new file mode 100644 index 00000000..f8848d8c --- /dev/null +++ b/app/components/login.hbs @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/app/components/login.js b/app/components/login.js new file mode 100644 index 00000000..eca4ee6b --- /dev/null +++ b/app/components/login.js @@ -0,0 +1,30 @@ +import Component from '@glimmer/component'; +import { AUTH } from '../constants/urls'; +import { service } from '@ember/service'; + +export default class LoginComponent extends Component { + @service router; + @service fastboot; + + AUTH_URL = this.generateAuthURL(); + + generateAuthURL() { + let currentURL = this.fastboot.isFastBoot + ? this.fastboot.request.protocol + + '//' + + this.fastboot.request.host + + this.fastboot.request.path + : window.location.href; + + if (currentURL) { + currentURL = currentURL.includes('login') + ? currentURL.replace('login', '') + : currentURL; + } + + return { + GITHUB: `${AUTH.GITHUB_SIGN_IN}?redirectURL=${currentURL}`, + GOOGLE: `${AUTH.GOOGLE_SIGN_IN}&redirectURL=${currentURL}`, + }; + } +} diff --git a/app/components/reusables/login-link.hbs b/app/components/reusables/login-link.hbs new file mode 100644 index 00000000..69e062a8 --- /dev/null +++ b/app/components/reusables/login-link.hbs @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/app/constants/urls.js b/app/constants/urls.js index e4eaf6e0..019846c3 100644 --- a/app/constants/urls.js +++ b/app/constants/urls.js @@ -67,7 +67,8 @@ export const ABOUT = { }; export const AUTH = { - SIGN_IN: `${APPS.API_BACKEND}/auth/github/login`, + GITHUB_SIGN_IN: `${APPS.API_BACKEND}/auth/github/login`, + GOOGLE_SIGN_IN: `${APPS.API_BACKEND}/auth/google/login?dev=true`, SIGN_UP: `${APPS.PROFILE}/new-signup`, }; diff --git a/app/router.js b/app/router.js index a78344ed..47338ac7 100644 --- a/app/router.js +++ b/app/router.js @@ -16,4 +16,5 @@ Router.map(function () { this.route('events'); this.route('debug'); this.route('subscribe'); + this.route('login'); }); diff --git a/app/routes/application.js b/app/routes/application.js index 473ef158..3d7c4032 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -1,2 +1,23 @@ import Route from '@ember/routing/route'; -export default class ApplicationRoute extends Route {} +import { service } from '@ember/service'; +import { TOAST_OPTIONS } from '../constants/toast-options'; + +export default class ApplicationRoute extends Route { + @service router; + @service toast; + + showToast(transition) { + this.toast.error(transition.to.queryParams.error, 'Error', TOAST_OPTIONS); + } + + beforeModel(transition) { + if ( + transition?.to?.queryParams?.dev === 'true' && + transition?.to?.queryParams?.error + ) { + if (transition?.to?.queryParams?.error !== '') { + this.showToast(transition); + } + } + } +} diff --git a/app/routes/login.js b/app/routes/login.js new file mode 100644 index 00000000..43fbe9d5 --- /dev/null +++ b/app/routes/login.js @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default class LoginRoute extends Route { + @service router; + @service login; + + beforeModel(transition) { + if (transition?.to?.queryParams?.dev !== 'true') { + this.router.transitionTo('/page-not-found'); + } + } +} diff --git a/app/styles/app.css b/app/styles/app.css index 48c90b21..2ce11118 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -43,6 +43,7 @@ @import url("unauthenticated.module.css"); @import url("subscribe.module.css"); @import url("phone-input.module.css"); +@import url("login.module.css"); * { margin: 0; diff --git a/app/styles/login.module.css b/app/styles/login.module.css new file mode 100644 index 00000000..4bfeba7a --- /dev/null +++ b/app/styles/login.module.css @@ -0,0 +1,105 @@ +.login__container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 80%; + background: white; + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.login__container .logo__img { + position: absolute; + top: 2.5rem; + left: 3rem; + height: 5rem; + width: 5rem; +} + +.login__container h3 { + text-align: center; + margin: 0.8rem 0; + font-size: 2.25rem; +} + +.link__container { + margin-top: 1rem; +} + +.link__container .join__link { + margin-top: 4px; + transition: + background-color 0.3s ease, + transform 0.3s ease; +} + +.link__container .join__link:hover { + background-color: var(--color-pink-light); +} + +.link__container .join__link:active { + transform: scale(0.95); +} + +.link__container small { + font-size: large; + font-weight: 500; + margin: 12px; +} + +.login__container h3 span { + font-size: 2.5rem; + color: var(--color-pink); +} + +.login__container a { + display: flex; + gap: 8px; +} + +@media (width <= 625px) { + .logo__img { + width: 5rem; + } + + .login__container h3 { + font-size: 1.75rem; + } + + .login__container h3 span { + font-size: 2.25rem; + } + + .link__container small { + font-size: medium; + margin: 0.5rem; + } + + .login__container a img { + height: 20px; + width: 20px; + } +} + +@media (width <= 425px) { + .login__container h3 { + font-size: 1.25rem; + } + + .login__container h3 span { + font-size: 1.8rem; + } + + .link__container { + margin-top: 1rem; + } + + .login__container a img { + height: 12px; + width: 12px; + } +} diff --git a/app/styles/variables.css b/app/styles/variables.css index 2e7c4cfe..e571b478 100644 --- a/app/styles/variables.css +++ b/app/styles/variables.css @@ -16,6 +16,7 @@ --color-blackshadow2: #3c404326; --color-backdrop: #0006; --color-pink: #e30162; + --color-pink-light: #e32476; --color-carmine: #a10829; --color-soft-magenta: #f5c4f1; --color-pink-low-opacity: #e301632e; diff --git a/app/templates/login.hbs b/app/templates/login.hbs new file mode 100644 index 00000000..9208246d --- /dev/null +++ b/app/templates/login.hbs @@ -0,0 +1,2 @@ +{{page-title "Login"}} + \ No newline at end of file diff --git a/public/assets/icons/google-logo.png b/public/assets/icons/google-logo.png new file mode 100644 index 00000000..aa026d5f Binary files /dev/null and b/public/assets/icons/google-logo.png differ diff --git a/tests/constants/urls.js b/tests/constants/urls.js index eaf0a096..399018bc 100644 --- a/tests/constants/urls.js +++ b/tests/constants/urls.js @@ -8,6 +8,7 @@ export const APPS = { TASKS: 'https://my.realdevsquad.com/tasks', IDENTITY: 'https://my.realdevsquad.com/identity', MY_STATUS: 'https://my.realdevsquad.com', + STAGING_API_BACKEND: 'https://staging-api.realdevsquad.com', }; export const ABOUT = { diff --git a/tests/integration/components/header-test.js b/tests/integration/components/header-test.js index 93058f6d..426f7ed3 100644 --- a/tests/integration/components/header-test.js +++ b/tests/integration/components/header-test.js @@ -8,11 +8,12 @@ module('Integration | Component | header', function (hooks) { setupRenderingTest(hooks); test('header elements renders', async function (assert) { - assert.expect(13); + assert.expect(15); this.setProperties({ isLoggedIn: false, isLoading: false, + dev: false, }); this.set('signOut', () => { @@ -24,6 +25,7 @@ module('Integration | Component | header', function (hooks) { @isLoggedIn={{this.isLoggedIn}} @isLoading={{this.isLoading}} @signOut={{this.signOut}} + @dev={{this.dev}} /> `); @@ -52,6 +54,10 @@ module('Integration | Component | header', function (hooks) { */ // assert.dom('[data-test-login]').hasAttribute('href', AUTH.SIGN_IN); assert.dom('[data-test-login-img]').exists(); + + this.set('dev', true); + assert.dom('[data-test-login]').hasAttribute('href', '/login?dev=true'); + assert.dom('[data-test-login]').hasText('Sign In'); }); test('toggle nav menu in mobile view', async function (assert) { diff --git a/tests/integration/components/login-link-test.js b/tests/integration/components/login-link-test.js new file mode 100644 index 00000000..a81aeecc --- /dev/null +++ b/tests/integration/components/login-link-test.js @@ -0,0 +1,49 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'website-www/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | login-link', function (hooks) { + setupRenderingTest(hooks); + + test('it renders content', async function (assert) { + this.set('label', 'For Developers:'); + this.set('auth', 'github'); + this.set('signInText', 'Sign In with GitHub'); + this.set('iconSrc', 'assets/icons/github-logo.png'); + this.set('altText', 'GitHub'); + + await render(hbs` + `); + + assert.dom('small').exists('Label element exists'); + assert.dom('small').hasText('For Developers:', 'Label text is correct'); + + assert + .dom('.join__link span') + .hasText('Sign In with GitHub', 'Sign in text is correct'); + + assert.dom('.login__logo').exists('Logo image exists'); + assert + .dom('.login__logo') + .hasAttribute( + 'src', + 'assets/icons/github-logo.png', + 'Image has correct src', + ); + assert + .dom('.login__logo') + .hasAttribute('alt', 'GitHub', 'Image has correct alt text'); + assert + .dom('.login__logo') + .hasAttribute('height', '24px', 'Image has correct height'); + assert + .dom('.login__logo') + .hasAttribute('width', '24px', 'Image has correct width'); + }); +}); diff --git a/tests/integration/components/login-test.js b/tests/integration/components/login-test.js new file mode 100644 index 00000000..eb861ff9 --- /dev/null +++ b/tests/integration/components/login-test.js @@ -0,0 +1,31 @@ +import { module, test } from 'qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupRenderingTest } from 'website-www/tests/helpers'; +import { APPS } from '../../constants/urls'; + +module('Integration | Component | login', function (hooks) { + setupRenderingTest(hooks); + + test('renders content & generates correct auth URLs', async function (assert) { + await render(hbs``); + + assert.dom('.login__container').exists(); + assert.dom('.login__container h3').hasText('Sign In to Real Dev Squad'); + + assert + .dom('[data-test-login=github]') + .exists() + .hasAttribute( + 'href', + `${APPS.STAGING_API_BACKEND}/auth/github/login?redirectURL=${window.location.href}`, + ); + assert + .dom('[data-test-login=google]') + .exists() + .hasAttribute( + 'href', + `${APPS.STAGING_API_BACKEND}/auth/google/login?dev=true&redirectURL=${window.location.href}`, + ); + }); +}); diff --git a/tests/unit/routes/login-test.js b/tests/unit/routes/login-test.js new file mode 100644 index 00000000..253d0cec --- /dev/null +++ b/tests/unit/routes/login-test.js @@ -0,0 +1,32 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'website-www/tests/helpers'; +import { visit, currentURL } from '@ember/test-helpers'; + +module('Unit | Route | login', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:login'); + assert.ok(route); + }); + + test('it can be only visited with dev flag', async function (assert) { + await visit('/login?dev=true'); + + assert.strictEqual( + currentURL(), + '/login?dev=true', + 'User can access /login with dev flag only', + ); + }); + + test('it cannot be visited without dev flag', async function (assert) { + await visit('/login'); + + assert.strictEqual( + currentURL(), + '/page-not-found', + 'User cannot access /login without dev flag', + ); + }); +});