Skip to content

Commit 6c56272

Browse files
authored
Add Dashboard 2.0 (#3141)
* basic structure and header * basic sidebar * desktop layout and home display done * minor adjustments, lunchtime * make sidebar menu snazzy * beautify menu * fix page transition jumping * minor changes * add nice mobile nav and formatting * spacing * spacing * fix and elegantize highlight element on load * improve spacing * improve spacing * upgrade directionality * minor changes * minor temporary changes * changes * make menu physical * make row clickable, minor adjustments * minor cosmetic adjustments * further cosmetic adjustments * prepare for merge * minor adjustments and move into authed * refactor list items, design appears done and ready to wire * adjustments to motion params, make mobile menu physical, start populating live data * improve formatting * cleanup layout, start adding link google functionality * google cred adding and removing added * update last used display, start implementing identity switcher * switching works now * minor changes * polishing switching UX * further polish * implement using v2 endpoint * remove try-ii from pr * fix linting errors * remove constructor * cleanup according to feedback * revert * linter * UX updates as per erika * further code organization * grid layout * fix grid placeholder
1 parent 345e2cf commit 6c56272

21 files changed

+1365
-1
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<script lang="ts">
2+
import { onMount } from "svelte";
3+
import { isDesktopViewport } from "$lib/utils/UI/deviceDetection";
4+
import SideBar from "$lib/components/ui/SideBar.svelte";
5+
import Tabs from "$lib/components/ui/Tabs.svelte";
6+
7+
const { sidebarElements, tabElements, content, header, footer } = $props();
8+
9+
const handleResize = () => {
10+
displayVersion = isDesktopViewport() ? "sidebar" : "tabs";
11+
};
12+
13+
let displayVersion: "sidebar" | "tabs" = $state("sidebar");
14+
15+
onMount(handleResize);
16+
</script>
17+
18+
<svelte:window onresize={handleResize} />
19+
20+
{#if displayVersion === "sidebar"}
21+
<div class="sidebar-layout">
22+
<SideBar class="col-start-1 col-end-2 row-start-1 row-end-6">
23+
{@render sidebarElements?.()}
24+
</SideBar>
25+
<div
26+
class="bg-bg-primary_alt col-start-2 col-end-5 row-start-1 row-end-6"
27+
></div>
28+
<div class="col-start-3 col-end-4 row-start-3 row-end-4 p-4">
29+
{@render content?.()}
30+
</div>
31+
<header class="col-start-2 col-end-5 row-start-1 row-end-2 pt-2 pr-6">
32+
{@render header?.()}
33+
</header>
34+
<footer class="col-start-2 col-end-5 row-start-5 row-end-6">
35+
{@render footer?.()}
36+
</footer>
37+
</div>
38+
{:else}
39+
<div class="bg-bg-primary_alt flex min-h-screen flex-col">
40+
<header class="bg-bg-primary_alt absolute top-0 right-0 mt-1">
41+
{@render header?.()}
42+
</header>
43+
<Tabs>
44+
{@render tabElements?.()}
45+
</Tabs>
46+
<div class="bg-bg-primary_alt flex-1 px-4">
47+
{@render content?.()}
48+
</div>
49+
<footer class="bg-bg-primary_alt">
50+
{@render footer?.()}
51+
</footer>
52+
</div>
53+
{/if}
54+
55+
<style>
56+
.sidebar-layout {
57+
display: grid;
58+
grid-template-columns:
59+
296px
60+
minmax(0, 1fr)
61+
minmax(min-content, 4fr)
62+
minmax(0, 1fr);
63+
64+
grid-template-rows: min-content 1fr max-content 1fr min-content;
65+
min-height: 100dvh;
66+
min-width: 100dvw;
67+
}
68+
69+
@media (max-width: 1155px) {
70+
.sidebar-layout {
71+
display: grid;
72+
grid-template-columns:
73+
296px
74+
1fr
75+
minmax(0, max-content)
76+
1fr;
77+
78+
grid-template-rows: min-content 1fr max-content 1fr min-content;
79+
min-height: 100dvh;
80+
min-width: 100dvw;
81+
}
82+
}
83+
</style>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<script lang="ts">
2+
import {
3+
type OpenIdCredential,
4+
type AuthnMethodData,
5+
} from "$lib/generated/internet_identity_types";
6+
import { formatLastUsage } from "$lib/utils/time";
7+
import { nonNullish } from "@dfinity/utils";
8+
import { fade } from "svelte/transition";
9+
import PlaceHolder from "./PlaceHolder.svelte";
10+
import Ellipsis from "../utils/Ellipsis.svelte";
11+
12+
let {
13+
accessMethod,
14+
class: classes,
15+
}: {
16+
accessMethod: AuthnMethodData | OpenIdCredential | null;
17+
class?: string;
18+
} = $props();
19+
20+
const getAuthnMethodAlias = (authnMethod: AuthnMethodData) => {
21+
const metadataAlias = authnMethod.metadata.find(
22+
([key, _val]) => key === "alias",
23+
)?.[1]!;
24+
if (metadataAlias && "String" in metadataAlias) {
25+
return metadataAlias.String;
26+
}
27+
};
28+
29+
const getOpenIdCredentialName = (credential: OpenIdCredential | null) => {
30+
if (!credential) return null;
31+
const metadataName = credential.metadata.find(
32+
([key, _val]) => key === "name",
33+
)?.[1]!;
34+
if (metadataName && "String" in metadataName) {
35+
return metadataName.String;
36+
}
37+
return undefined;
38+
};
39+
40+
const getOpenIdCredentialEmail = (credential: OpenIdCredential | null) => {
41+
if (!credential) return null;
42+
const metadataEmail = credential.metadata.find(
43+
([key, _val]) => key === "email",
44+
)?.[1]!;
45+
if (metadataEmail && "String" in metadataEmail) {
46+
return metadataEmail.String;
47+
}
48+
return undefined;
49+
};
50+
51+
let openIdHasName = $derived(
52+
accessMethod &&
53+
!("authn_method" in accessMethod) &&
54+
!!getOpenIdCredentialName(accessMethod),
55+
);
56+
let openIdHasEmail = $derived(
57+
accessMethod &&
58+
!("authn_method" in accessMethod) &&
59+
!!getOpenIdCredentialEmail(accessMethod),
60+
);
61+
</script>
62+
63+
{#if accessMethod}
64+
{#if "authn_method" in accessMethod}
65+
<!-- Passkey -->
66+
<div
67+
class={[
68+
"text-text-primary foldable-subgrid text-sm font-semibold nth-[2]:hidden",
69+
classes,
70+
]}
71+
in:fade={{ delay: 30, duration: 30 }}
72+
out:fade={{ duration: 30 }}
73+
>
74+
<div class="flex min-w-32 items-center pr-3">
75+
{getAuthnMethodAlias(accessMethod)}
76+
</div>
77+
{#if nonNullish(accessMethod.last_authentication[0])}
78+
<div class="text-text-tertiary flex items-center font-normal">
79+
Last used {formatLastUsage(
80+
new Date(
81+
Number(accessMethod.last_authentication[0] / BigInt(1000000)),
82+
),
83+
)}
84+
</div>
85+
{/if}
86+
</div>
87+
{:else}
88+
<!-- OpenID -->
89+
<div
90+
class={[
91+
"text-text-primary foldable-subgrid text-sm font-semibold nth-[2]:hidden",
92+
classes,
93+
]}
94+
in:fade={{ delay: 30, duration: 30 }}
95+
out:fade={{ duration: 30 }}
96+
>
97+
{#if openIdHasName && openIdHasEmail}
98+
<div class="flex min-w-32 flex-col justify-center pr-3">
99+
<div>{getOpenIdCredentialName(accessMethod)}</div>
100+
<div class="text-text-tertiary font-extralight">
101+
<Ellipsis text={getOpenIdCredentialEmail(accessMethod)!}></Ellipsis>
102+
</div>
103+
</div>
104+
{:else if !openIdHasName && openIdHasEmail}
105+
<div class="flex min-w-32 items-center pr-3">
106+
<Ellipsis text={getOpenIdCredentialEmail(accessMethod)!}></Ellipsis>
107+
</div>
108+
{:else if openIdHasName && !openIdHasEmail}
109+
<div class="min-w-32 pr-3">
110+
{getOpenIdCredentialName(accessMethod)}
111+
</div>
112+
{/if}
113+
114+
{#if nonNullish(accessMethod.last_usage_timestamp[0])}
115+
<div class="text-text-tertiary flex items-center font-normal">
116+
Last used {formatLastUsage(
117+
new Date(
118+
Number(accessMethod.last_usage_timestamp[0] / BigInt(1000000)),
119+
),
120+
)}
121+
</div>
122+
{:else}
123+
<div></div>
124+
{/if}
125+
</div>
126+
{/if}
127+
{:else}
128+
<!-- <div
129+
class="flex items-center"
130+
in:fade={{ duration: 30 }}
131+
out:fade={{ duration: 30, delay: 30 }}
132+
> -->
133+
<PlaceHolder
134+
class="mt-1 mr-8 hidden h-4 !rounded-sm nth-last-[2]:inline-block"
135+
hiddenIndex={7}
136+
/>
137+
<!-- </div> -->
138+
{/if}
139+
140+
<style>
141+
.foldable-subgrid {
142+
display: grid;
143+
/* We may need to update the minimum width for really long emails, but it seems fine for now. */
144+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
145+
}
146+
</style>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script lang="ts">
2+
import { type Snippet } from "svelte";
3+
let {
4+
children,
5+
href,
6+
hrefAriaLabel = "Link",
7+
class: classes,
8+
}: {
9+
children: Snippet;
10+
href?: string;
11+
hrefAriaLabel?: string;
12+
class?: string;
13+
} = $props();
14+
</script>
15+
16+
{#if href}
17+
<a {href} class="contents h-full w-full" aria-label={hrefAriaLabel}>
18+
<li
19+
class={`border-border-tertiary text-fg-primary not-disabled:hover:bg-bg-primary_hover disabled:border-border-disabled disabled:text-fg-disabled focus-visible:ring-offset-bg-primary focus-visible:ring-focus-ring flex cursor-pointer items-center gap-2 border-t p-4 text-sm outline-none not-last:border-b last:rounded-b-2xl focus-visible:ring-2 focus-visible:ring-offset-2 ${classes}`}
20+
>
21+
{@render children?.()}
22+
</li>
23+
</a>
24+
{:else}
25+
<li
26+
class={`border-border-tertiary flex items-center gap-2 border-t p-4 not-last:border-b ${classes}`}
27+
>
28+
{@render children?.()}
29+
</li>
30+
{/if}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script lang="ts">
2+
import { type Snippet } from "svelte";
3+
4+
const { children, class: classes }: { children: Snippet; class?: string } =
5+
$props();
6+
</script>
7+
8+
<section
9+
class={[
10+
"bg-bg-primary border-border-secondary rounded-2xl border not-dark:shadow-sm",
11+
classes,
12+
]}
13+
>
14+
{@render children?.()}
15+
</section>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts">
2+
import { fade } from "svelte/transition";
3+
4+
const {
5+
class: classes,
6+
hiddenIndex = 2,
7+
}: { class?: string; hiddenIndex?: number } = $props();
8+
</script>
9+
10+
<div
11+
class={`min-h-1 min-w-1 ${classes} bg-fg-brand-primary animate-pulse rounded-lg nth-[${hiddenIndex}]:hidden`}
12+
out:fade={{ duration: 30, delay: 30 }}
13+
></div>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script lang="ts">
2+
import { type Snippet } from "svelte";
3+
import Logo from "$lib/components/ui/Logo.svelte";
4+
5+
const { children, class: classes }: { children: Snippet; class?: string } =
6+
$props();
7+
</script>
8+
9+
<nav
10+
class={["bg-bg-primary border-border-secondary border-r px-4 py-5", classes]}
11+
>
12+
<div class="mb-6 flex flex-1 items-center gap-4 px-3">
13+
<Logo class="text-fg-primary h-5.5" />
14+
<h1 class="text-md text-text-primary font-semibold sm:block">
15+
Internet Identity <span class="font-medium">Hub</span>
16+
</h1>
17+
</div>
18+
<div class="flex flex-col justify-start">
19+
{@render children?.()}
20+
</div>
21+
</nav>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script lang="ts">
2+
import ButtonOrAnchor from "$lib/components/utils/ButtonOrAnchor.svelte";
3+
import { type Snippet } from "svelte";
4+
5+
let {
6+
children,
7+
href,
8+
class: classes,
9+
}: { children: Snippet; href?: string; class?: string } = $props();
10+
</script>
11+
12+
<ButtonOrAnchor
13+
class={`${classes} text-text-primary flex items-center gap-2 rounded-sm px-3 py-2`}
14+
{href}
15+
>
16+
{@render children?.()}
17+
</ButtonOrAnchor>

0 commit comments

Comments
 (0)