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 { Top } from "./top";
import { Nav } from "./nav"; import { Nav } from "./nav";
import css from "./shell.module.css"; 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 ( return (
<main class={css.container}> <main class={css.container}>
<Top /> <Top user={props.user} />
<Nav /> <Nav />
<div class={css.body}> <div class={css.body}>

View file

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

View file

@ -1,10 +1,36 @@
import { Meta } from "@solidjs/meta"; 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 { createEffect, on, ParentProps } from "solid-js";
import { getRequestEvent } from "solid-js/web";
import { auth } from "~/auth";
import { Shell } from "~/features/shell"; import { Shell } from "~/features/shell";
import { useTheme } from "~/features/theme"; 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) { export default function ShellPage(props: ParentProps) {
const user = createAsync(() => load());
const themeContext = useTheme(); const themeContext = useTheme();
createEffect( createEffect(
@ -17,7 +43,7 @@ export default function ShellPage(props: ParentProps) {
); );
return ( return (
<Shell> <Shell user={user()}>
<Meta name="color-scheme" content={themeContext.theme.colorScheme} /> <Meta name="color-scheme" content={themeContext.theme.colorScheme} />
{props.children} {props.children}