feat(ui): Youreka favicon, logout confirmation, nav active states, brand alignment
- favicon is the Youreka logo (64px PNG generated from the source mark), on the tools pages and server error pages - signing out asks first: a Carbon cds-modal (Cancel / danger Sign out) shared by the header and the mobile side nav; Cancel closes, Sign out confirms and logs out - the current tool is now visibly selected: Carbon marks active nav via a[aria-current=page] INSIDE the shadow, but aria-current sits on the host, so the rule never matched — the header item now gets a blue underline and the side-nav link a blue left border (via ::part(link) for colour, plus the host for the marker) - logo and wordmark are middle-aligned inside a flex brand wrapper - tests: favicon/modal/signout-trigger markup and the active-tool marking on both navs (45 total)
This commit is contained in:
@@ -7,12 +7,13 @@
|
|||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@carbon/styles@1.69.0/css/styles.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@carbon/styles@1.69.0/css/styles.min.css">
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<link rel="stylesheet" href="/assets/style.css">
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect width='24' height='24' rx='4' fill='%230f62fe'/><path d='M12 5a6 6 0 0 0-6 6c0 2.6.8 4.1 1.5 5.2' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/><path d='M15.5 19a7.5 7.5 0 0 0 3-9.7A6 6 0 0 0 7.1 7.9' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/><path d='M9.5 18.5a5.5 5.5 0 0 1-.8-7.6 3.8 3.8 0 0 1 7.2 1.5 4.3 4.3 0 0 1-3.4 4.6' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/></svg>">
|
<link rel="icon" type="image/png" href="/assets/youreka-favicon.png">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue-croppa@1.3.8/dist/vue-croppa.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue-croppa@1.3.8/dist/vue-croppa.min.js"></script>
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-croppa@1.3.8/dist/vue-croppa.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-croppa@1.3.8/dist/vue-croppa.min.css">
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
||||||
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/modal.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/button.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/button.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/notification.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/notification.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tag.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tag.min.js"></script>
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@carbon/styles@1.69.0/css/styles.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@carbon/styles@1.69.0/css/styles.min.css">
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<link rel="stylesheet" href="/assets/style.css">
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect width='24' height='24' rx='4' fill='%230f62fe'/><path d='M12 5a6 6 0 0 0-6 6c0 2.6.8 4.1 1.5 5.2' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/><path d='M15.5 19a7.5 7.5 0 0 0 3-9.7A6 6 0 0 0 7.1 7.9' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/><path d='M9.5 18.5a5.5 5.5 0 0 1-.8-7.6 3.8 3.8 0 0 1 7.2 1.5 4.3 4.3 0 0 1-3.4 4.6' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/></svg>">
|
<link rel="icon" type="image/png" href="/assets/youreka-favicon.png">
|
||||||
<script src="https://cdn.socket.io/4.4.0/socket.io.min.js" integrity="sha384-1fOn6VtTq3PWwfsOrk45LnYcGosJwzMHv+Xh/Jx5303FVOXzEnw0EpLv30mtjmlj" crossorigin="anonymous"></script>
|
<script src="https://cdn.socket.io/4.4.0/socket.io.min.js" integrity="sha384-1fOn6VtTq3PWwfsOrk45LnYcGosJwzMHv+Xh/Jx5303FVOXzEnw0EpLv30mtjmlj" crossorigin="anonymous"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
||||||
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/modal.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/button.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/button.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/notification.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/notification.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/progress-bar.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/progress-bar.min.js"></script>
|
||||||
|
|||||||
@@ -29,9 +29,23 @@ cds-tile, cds-clickable-tile { display: block; }
|
|||||||
|
|
||||||
/* ── header + side nav (Carbon ui-shell) ────────────────────────────────── */
|
/* ── header + side nav (Carbon ui-shell) ────────────────────────────────── */
|
||||||
cds-header:not(:defined), cds-side-nav:not(:defined) { visibility: hidden; }
|
cds-header:not(:defined), cds-side-nav:not(:defined) { visibility: hidden; }
|
||||||
.brand-logo { display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--cds-border-subtle, #e0e0e0); border-radius: 4px; padding: 2px; margin-right: 0.5rem; }
|
.brand-inner { display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.brand-logo { display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--cds-border-subtle, #e0e0e0); border-radius: 4px; padding: 2px; }
|
||||||
.brand-logo img { display: block; }
|
.brand-logo img { display: block; }
|
||||||
.brand-suffix { color: var(--cds-text-secondary, #525252); font-weight: 400; }
|
.brand-suffix { color: var(--cds-text-secondary, #525252); font-weight: 400; }
|
||||||
|
|
||||||
|
/* the current tool is marked on the hosts (aria-current sits outside the shadow,
|
||||||
|
so Carbon's own [aria-current=page] rule never matches) */
|
||||||
|
cds-header-nav-item[aria-current="page"] {
|
||||||
|
border-bottom: 2px solid var(--cds-border-interactive, #0f62fe);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
cds-header-nav-item[aria-current="page"]::part(link) { color: var(--cds-text-primary, #161616); }
|
||||||
|
cds-side-nav-link[aria-current="page"] {
|
||||||
|
border-left: 3px solid var(--cds-border-interactive, #0f62fe);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
cds-side-nav-link[aria-current="page"]::part(link) { color: var(--cds-text-primary, #161616); }
|
||||||
.header-right { margin-left: auto; display: flex; align-items: center; gap: 1rem; padding-right: 1rem; }
|
.header-right { margin-left: auto; display: flex; align-items: center; gap: 1rem; padding-right: 1rem; }
|
||||||
.header-agent { margin-left: 1rem; }
|
.header-agent { margin-left: 1rem; }
|
||||||
/* mobile: account actions live in the side nav, not the header */
|
/* mobile: account actions live in the side nav, not the header */
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
@@ -7,8 +7,9 @@
|
|||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@carbon/styles@1.69.0/css/styles.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@carbon/styles@1.69.0/css/styles.min.css">
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<link rel="stylesheet" href="/assets/style.css">
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect width='24' height='24' rx='4' fill='%230f62fe'/><path d='M12 5a6 6 0 0 0-6 6c0 2.6.8 4.1 1.5 5.2' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/><path d='M15.5 19a7.5 7.5 0 0 0 3-9.7A6 6 0 0 0 7.1 7.9' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/><path d='M9.5 18.5a5.5 5.5 0 0 1-.8-7.6 3.8 3.8 0 0 1 7.2 1.5 4.3 4.3 0 0 1-3.4 4.6' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'/></svg>">
|
<link rel="icon" type="image/png" href="/assets/youreka-favicon.png">
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
||||||
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/modal.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tile.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tile.min.js"></script>
|
||||||
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tag.min.js"></script>
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tag.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
+40
-3
@@ -73,6 +73,10 @@ function errorPage(res, status, title, message, actions = {}) {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>${esc(title)} · YOUreka Tools</title>
|
<title>${esc(title)} · YOUreka Tools</title>
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<link rel="stylesheet" href="/assets/style.css">
|
||||||
|
<link rel="icon" type="image/png" href="/assets/youreka-favicon.png">
|
||||||
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/modal.min.js"></script>
|
||||||
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/ui-shell.min.js"></script>
|
||||||
|
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/version/v2.50.0/tag.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="app cds--g100">
|
<body class="app cds--g100">
|
||||||
${appHeader(null, null)}
|
${appHeader(null, null)}
|
||||||
@@ -120,13 +124,13 @@ const appHeader = (session, active) => {
|
|||||||
const name = session ? (session.firstName || session.phone || '') : '';
|
const name = session ? (session.firstName || session.phone || '') : '';
|
||||||
const right = session
|
const right = session
|
||||||
? `<span class="user-chip"><span class="avatar">${esc(name.trim().slice(0, 2).toUpperCase())}</span>${esc(name)}</span>
|
? `<span class="user-chip"><span class="avatar">${esc(name.trim().slice(0, 2).toUpperCase())}</span>${esc(name)}</span>
|
||||||
<a class="linklike" href="/auth/logout">Sign out</a>`
|
<a class="linklike" href="/auth/logout" data-signout>Sign out</a>`
|
||||||
: '<a class="linklike" href="/auth/login">Sign in</a>';
|
: '<a class="linklike" href="/auth/login">Sign in</a>';
|
||||||
return `
|
return `
|
||||||
<cds-header aria-label="YOUreka Tools">
|
<cds-header aria-label="YOUreka Tools">
|
||||||
<cds-header-menu-button button-label-active="Close menu" button-label-inactive="Open menu" id="header-menu-button"></cds-header-menu-button>
|
<cds-header-menu-button button-label-active="Close menu" button-label-inactive="Open menu" id="header-menu-button"></cds-header-menu-button>
|
||||||
<cds-header-name href="/">
|
<cds-header-name href="/">
|
||||||
<span class="brand-logo"><img src="/assets/youreka-logo.png" alt="" width="20" height="20"></span>YOUreka<span class="brand-suffix"> Tools</span>
|
<span class="brand-inner"><span class="brand-logo"><img src="/assets/youreka-logo.png" alt="" width="20" height="20"></span><span>YOUreka<span class="brand-suffix"> Tools</span></span></span>
|
||||||
</cds-header-name>
|
</cds-header-name>
|
||||||
<cds-header-nav>
|
<cds-header-nav>
|
||||||
${toolLink('/cropper', 'Image Cropper', 'cropper')}
|
${toolLink('/cropper', 'Image Cropper', 'cropper')}
|
||||||
@@ -144,9 +148,42 @@ const appHeader = (session, active) => {
|
|||||||
${sideLink('/cropper', 'Image Cropper', 'cropper')}
|
${sideLink('/cropper', 'Image Cropper', 'cropper')}
|
||||||
${sideLink('/fingerprint', 'Fingerprint Agent', 'fingerprint')}
|
${sideLink('/fingerprint', 'Fingerprint Agent', 'fingerprint')}
|
||||||
<cds-side-nav-divider></cds-side-nav-divider>
|
<cds-side-nav-divider></cds-side-nav-divider>
|
||||||
<cds-side-nav-link href="/auth/logout">Sign out</cds-side-nav-link>
|
<cds-side-nav-link href="/auth/logout" data-signout>Sign out</cds-side-nav-link>
|
||||||
</cds-side-nav-items>
|
</cds-side-nav-items>
|
||||||
</cds-side-nav>
|
</cds-side-nav>
|
||||||
|
<cds-modal id="signout-modal" size="xs" aria-label="Sign out of YOUreka Tools">
|
||||||
|
<cds-modal-header>
|
||||||
|
<cds-modal-close-button></cds-modal-close-button>
|
||||||
|
<cds-modal-label>Account</cds-modal-label>
|
||||||
|
<cds-modal-heading>Sign out of YOUreka Tools?</cds-modal-heading>
|
||||||
|
</cds-modal-header>
|
||||||
|
<cds-modal-body>You will need to authenticate again to use the tools.</cds-modal-body>
|
||||||
|
<cds-modal-footer>
|
||||||
|
<cds-modal-footer-button kind="secondary" data-modal-close>Cancel</cds-modal-footer-button>
|
||||||
|
<cds-modal-footer-button kind="danger" id="signout-confirm">Sign out</cds-modal-footer-button>
|
||||||
|
</cds-modal-footer>
|
||||||
|
</cds-modal>
|
||||||
|
<script>
|
||||||
|
// Confirm before signing out (Carbon modal)
|
||||||
|
(function () {
|
||||||
|
function wire() {
|
||||||
|
var modal = document.getElementById('signout-modal');
|
||||||
|
var confirm = document.getElementById('signout-confirm');
|
||||||
|
if (!modal || !confirm) return;
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
var trigger = event.target && event.target.closest ? event.target.closest('[data-signout]') : null;
|
||||||
|
if (!trigger) return;
|
||||||
|
event.preventDefault();
|
||||||
|
modal.open = true;
|
||||||
|
});
|
||||||
|
confirm.addEventListener('click', function () {
|
||||||
|
window.location.href = '/auth/logout';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', wire);
|
||||||
|
else wire();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -312,6 +312,22 @@ test('injected session values cannot break out of the script tag', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the active tool is marked on both navigation surfaces', async () => {
|
||||||
|
const { base, stop } = await bootApp();
|
||||||
|
try {
|
||||||
|
const cookies = await login(base);
|
||||||
|
const cropper = await (await fetch(`${base}/cropper`, { headers: { cookie: cookies } })).text();
|
||||||
|
assert.match(cropper, /cds-header-nav-item href="\/cropper" aria-current="page"/);
|
||||||
|
assert.match(cropper, /cds-side-nav-link href="\/cropper" aria-current="page"/);
|
||||||
|
assert.ok(
|
||||||
|
!/cds-header-nav-item href="\/fingerprint" aria-current/.test(cropper),
|
||||||
|
'the other tool is not marked current'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('all pages render on the g100 dark theme', async () => {
|
test('all pages render on the g100 dark theme', async () => {
|
||||||
const { base, stop } = await bootApp();
|
const { base, stop } = await bootApp();
|
||||||
try {
|
try {
|
||||||
@@ -342,11 +358,16 @@ test('landing header is server-rendered for the session', async () => {
|
|||||||
assert.match(anon, /cds-side-nav-link href="\/auth\/logout"/);
|
assert.match(anon, /cds-side-nav-link href="\/auth\/logout"/);
|
||||||
assert.match(anon, /youreka-logo\.png/);
|
assert.match(anon, /youreka-logo\.png/);
|
||||||
assert.match(anon, /header-menu-button/);
|
assert.match(anon, /header-menu-button/);
|
||||||
|
// favicon is the Youreka logo; sign-out is confirmed via a Carbon modal
|
||||||
|
assert.match(anon, /rel="icon"[^>]*youreka-favicon\.png/);
|
||||||
|
assert.match(anon, /data-signout/);
|
||||||
|
assert.match(anon, /cds-modal id="signout-modal"/);
|
||||||
|
assert.match(anon, /id="signout-confirm"/);
|
||||||
|
|
||||||
const cookies = await login(base);
|
const cookies = await login(base);
|
||||||
const authed = await (await fetch(`${base}/`, { headers: { cookie: cookies } })).text();
|
const authed = await (await fetch(`${base}/`, { headers: { cookie: cookies } })).text();
|
||||||
assert.match(authed, /Ada/);
|
assert.match(authed, /Ada/);
|
||||||
assert.match(authed, /href="\/auth\/logout">Sign out</);
|
assert.match(authed, /href="\/auth\/logout"[^>]*>Sign out</);
|
||||||
} finally {
|
} finally {
|
||||||
await stop();
|
await stop();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user