did some work on the user session and components

This commit is contained in:
Chris Kruining 2025-04-14 23:28:03 +02:00
parent 33dc08bb82
commit 350c767a13
Signed by: chris
SSH key fingerprint: SHA256:nG82MUfuVdRVyCKKWqhY+pCrbz9nbX6uzUns4RKa1Pg
11 changed files with 172 additions and 60 deletions

View file

@ -2,11 +2,16 @@ import { ParentComponent } from "solid-js";
import { Top } from "./top";
import { Nav } from "./nav";
import css from "./shell.module.css";
import { User } from "../user";
export const Shell: ParentComponent = (props) => {
interface ShellProps {
user: User | undefined;
}
export const Shell: ParentComponent<ShellProps> = (props) => {
return (
<main class={css.container}>
<Top />
<Top user={props.user} />
<Nav />
<div class={css.body}>

View file

@ -7,3 +7,31 @@
background-color: inherit;
padding: 0.5em;
}
.accountTrigger {
anchor-name: --account-trigger;
background: transparent;
padding: 0;
margin: 0;
border-radius: var(--radius-round);
}
.accountMenu {
position-anchor: --account-trigger;
position: absolute;
inset: auto;
inset-inline-end: anchor(end);
inset-block-start: anchor(start);
display: block grid;
grid-auto-flow: row;
gap: var(--size-3);
padding: var(--size-3);
background-color: light-dark(var(--gray-1), var(--gray-9));
border-radius: var(--radius-2);
box-shadow: var(--shadow-2);
&:not(:popover-open) {
display: none;
}
}

View file

@ -1,44 +1,33 @@
import { Component, createEffect, createMemo, Show } from "solid-js";
import { ColorSchemePicker } from "../theme";
import { signIn, signOut, useSession } from "~/auth";
import { Component, createEffect, Show } from "solid-js";
import { signIn, signOut } from "~/auth";
import { hash } from "~/utilities";
import { Avatar, Profile, User } from "../user";
import css from "./top.module.css";
import { Avatar } from "../user";
export const Top: Component = (props) => {
const session = useSession();
const hashedEmail = hash("SHA-256", () => session().data?.user.email);
interface TopProps {
user: User | undefined;
}
const login = async () => {
const response = await signIn.oauth2({
export const Top: Component<TopProps> = (props) => {
const login = async (e: SubmitEvent) => {
e.preventDefault();
await signIn.oauth2({
providerId: "authelia",
callbackURL: "/",
});
console.log("signin response", response);
};
const logout = async () => {
const response = await signOut();
const logout = async (e: SubmitEvent) => {
e.preventDefault();
console.log("signout response", response);
await signOut();
};
createEffect(() => {
console.log(hashedEmail());
});
createEffect(() => {
console.log(session().data?.user);
});
return (
<aside class={css.top}>
<Show
when={session().isPending === false && session().isRefetching === false}
>
<Show
when={session().data?.user}
when={props.user}
fallback={
<form method="post" onSubmit={login}>
<button type="submit">Sign in</button>
@ -47,25 +36,23 @@ export const Top: Component = (props) => {
>
{(user) => (
<>
<div>
<Avatar />
<img
src={
user().image ??
`https://www.gravatar.com/avatar/${hashedEmail()}`
}
/>
<span>{user().name}</span>
<span>{user().email}</span>
</div>
<button
class={css.accountTrigger}
id="account-menu-trigger"
popovertarget="account-menu-popover"
>
<Avatar user={user()} />
</button>
<div class={css.accountMenu} id="account-menu-popover" popover>
<Profile user={user()} />
<form method="post" onSubmit={logout}>
<button type="submit">Log out</button>
</form>
</div>
</>
)}
</Show>
</Show>
<ColorSchemePicker />
{/* <ColorSchemePicker /> */}
</aside>
);
};

View file

@ -0,0 +1,7 @@
.avatar {
inline-size: var(--size-8);
border-radius: var(--radius-round);
aspect-ratio: 1;
object-fit: cover;
object-position: center;
}

View file

@ -1,11 +1,27 @@
import { Component, createMemo, Show } from "solid-js";
import { User } from "./user";
import { hash } from "~/utilities";
import css from "./avatar.module.css";
export const Avatar: Component = (props) => {
const src = createMemo(() => "");
interface AvatarProps {
user: User | undefined;
}
return (
<Show when={src()}>
<img src={src()} />
</Show>
);
export const Avatar: Component<AvatarProps> = (props) => {
const hashedEmail = hash("SHA-256", () => props.user?.email);
const src = createMemo(() => {
const user = props.user;
if (user === undefined) {
return "";
}
if (user.image === null) {
return `https://www.gravatar.com/avatar/${hashedEmail()}`;
}
return user.image;
});
return <img src={src()} class={css.avatar} />;
};

View file

@ -1 +1,4 @@
export type { User } from "./user";
export { Avatar } from "./avatar";
export { Profile } from "./profile";

View file

@ -0,0 +1,22 @@
.profile {
display: block grid;
grid: auto 1fr / auto 1fr;
gap: var(--size-2);
place-content: start;
background-color: light-dark(var(--gray-1), var(--gray-9));
& > img {
grid-area: span 2 / 1;
}
& > strong {
font-size: var(--size-4);
line-height: 1;
color: light-dark(var(--gray-7), var(--gray-3));
}
& > span {
line-height: 1;
color: light-dark(var(--gray-4), var(--gray-6));
}
}

View file

@ -0,0 +1,18 @@
import { Component } from "solid-js";
import { User } from "./user";
import { Avatar } from "./avatar";
import css from "./profile.module.css";
interface ProfileProps {
user: User | undefined;
}
export const Profile: Component<ProfileProps> = (props) => {
return (
<div class={css.profile}>
<Avatar user={props.user} />
<strong>{props.user?.name ?? ""}</strong>
<span>{props.user?.email ?? ""}</span>
</div>
);
};

View file

@ -1,5 +1,5 @@
export interface User {
name: string;
email: string;
image: string;
image: string | null;
}

View file

@ -1,10 +1,36 @@
import { Meta } from "@solidjs/meta";
import { query, createAsync, action } from "@solidjs/router";
import { query, createAsync } from "@solidjs/router";
import { createEffect, on, ParentProps } from "solid-js";
import { getRequestEvent } from "solid-js/web";
import { auth } from "~/auth";
import { Shell } from "~/features/shell";
import { useTheme } from "~/features/theme";
import { User } from "~/features/user";
const load = query(async (): Promise<User | undefined> => {
"use server";
const session = await auth.api.getSession({
headers: getRequestEvent()!.request.headers,
});
if (session === null) {
return undefined;
}
const { name, email, image = null } = session.user;
return { name, email, image };
}, "session");
export const route = {
async preload() {
return load();
},
};
export default function ShellPage(props: ParentProps) {
const user = createAsync(() => load());
const themeContext = useTheme();
createEffect(
@ -17,7 +43,7 @@ export default function ShellPage(props: ParentProps) {
);
return (
<Shell>
<Shell user={user()}>
<Meta name="color-scheme" content={themeContext.theme.colorScheme} />
{props.children}