tangled
alpha
login
or
join now
l4.pm
/
bluroma
forked from
hexmani.ac/bluroma
1
fork
atom
pleroma-like client for Bluesky
1
fork
atom
overview
issues
pulls
pipelines
Create proper dashboard layout with mini profile
hexmani.ac
5 months ago
7eb5c43c
d3765b7d
verified
This commit was signed with the committer's
known signature
.
hexmani.ac
SSH Key Fingerprint:
SHA256:tV3v2UX4P3x12jjh+mHVzpRQ4ZhNBCHoFwqRiYzzTcM=
+209
-48
13 changed files
expand all
collapse all
unified
split
bun.lock
package.json
src
components
container.tsx
miniProfile.tsx
navbar.tsx
postForm.tsx
routes
dashboard.tsx
splash.tsx
styles
container.scss
main.scss
profile.scss
routes
dashboard.scss
tsconfig.json
+6
bun.lock
···
4
4
"": {
5
5
"name": "vite-template-solid",
6
6
"dependencies": {
7
7
+
"@atcute/bluesky": "^3.2.8",
8
8
+
"@atcute/client": "^4.0.5",
7
9
"@atcute/lexicons": "^1.2.2",
8
10
"@atcute/oauth-browser-client": "^1.0.27",
9
11
"@solidjs/router": "^0.15.3",
···
20
22
},
21
23
},
22
24
"packages": {
25
25
+
"@atcute/atproto": ["@atcute/atproto@3.1.8", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-Miu+S7RSgAYbmQWtHJKfSFUN5Kliqoo4YH0rILPmBtfmlZieORJgXNj9oO/Uive0/ulWkiRse07ATIcK8JxMnw=="],
26
26
+
27
27
+
"@atcute/bluesky": ["@atcute/bluesky@3.2.8", "", { "dependencies": { "@atcute/atproto": "^3.1.8", "@atcute/lexicons": "^1.2.2" } }, "sha512-wxEnSOvX7nLH4sVzX9YFCkaNEWIDrTv3pTs6/x4NgJ3AJ3XJio0OYPM8tR7wAgsklY6BHvlAgt3yoCDK0cl1CA=="],
28
28
+
23
29
"@atcute/client": ["@atcute/client@4.0.5", "", { "dependencies": { "@atcute/identity": "^1.1.1", "@atcute/lexicons": "^1.2.2" } }, "sha512-R8Qen8goGmEkynYGg2m6XFlVmz0GTDvQ+9w+4QqOob+XMk8/WDpF4aImev7WKEde/rV2gjcqW7zM8E6W9NShDA=="],
24
30
25
31
"@atcute/identity": ["@atcute/identity@1.1.1", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@badrap/valita": "^0.4.6" } }, "sha512-zax42n693VEhnC+5tndvO2KLDTMkHOz8UExwmklvJv7R9VujfEwiSWhcv6Jgwb3ellaG8wjiQ1lMOIjLLvwh0Q=="],
+2
package.json
···
19
19
"vite-plugin-solid": "^2.11.8"
20
20
},
21
21
"dependencies": {
22
22
+
"@atcute/bluesky": "^3.2.8",
23
23
+
"@atcute/client": "^4.0.5",
22
24
"@atcute/lexicons": "^1.2.2",
23
25
"@atcute/oauth-browser-client": "^1.0.27",
24
26
"@solidjs/router": "^0.15.3",
+7
-3
src/components/container.tsx
···
8
8
const Container = (props: ContainerProps) => {
9
9
return (
10
10
<div class="container">
11
11
-
<div class="container-header">
12
12
-
<span>{props.title}</span>
13
13
-
</div>
11
11
+
{props.title ? (
12
12
+
<div class="container-header">
13
13
+
<span>{props.title}</span>
14
14
+
</div>
15
15
+
) : (
16
16
+
<></>
17
17
+
)}
14
18
{props.children}
15
19
</div>
16
20
);
+58
src/components/miniProfile.tsx
···
1
1
+
import { Component, Match, Show, Switch, createResource } from "solid-js";
2
2
+
import { Client } from "@atcute/client";
3
3
+
import { agent } from "./login";
4
4
+
5
5
+
type MiniProfileProps = {
6
6
+
did: `did:${string}:${string}`;
7
7
+
};
8
8
+
9
9
+
async function getProfileDetails(did: `did:${string}:${string}`) {
10
10
+
const rpc = new Client({ handler: agent });
11
11
+
12
12
+
const res = await rpc.get("app.bsky.actor.getProfile", {
13
13
+
params: {
14
14
+
actor: did,
15
15
+
},
16
16
+
});
17
17
+
18
18
+
if (!res.ok) {
19
19
+
throw new Error(`Failed to fetch profile details: ${res.status}`);
20
20
+
}
21
21
+
22
22
+
return res.data;
23
23
+
}
24
24
+
25
25
+
const MiniProfile = (props: MiniProfileProps) => {
26
26
+
const [profileInfo] = createResource(agent.sub, getProfileDetails);
27
27
+
28
28
+
return (
29
29
+
<>
30
30
+
<Show when={profileInfo.loading}>
31
31
+
<p>loading...</p>
32
32
+
</Show>
33
33
+
<Switch>
34
34
+
<Match when={profileInfo.error}>
35
35
+
<p>Error: {profileInfo.error.message}</p>
36
36
+
</Match>
37
37
+
<Match when={profileInfo()}>
38
38
+
<div
39
39
+
class="mini-profile"
40
40
+
// todo: add banner fade
41
41
+
style={`background-image: linear-gradient(to bottom, rgba(15, 22, 30, 0.85)), url(${profileInfo()?.banner}); background-size: cover; background-repeat: no-repeat;`}
42
42
+
>
43
43
+
<img
44
44
+
src={profileInfo()?.avatar}
45
45
+
alt={`Profile picture for ${profileInfo()?.handle}`}
46
46
+
/>
47
47
+
<div class="mini-profile-info">
48
48
+
<p>{profileInfo()?.displayName}</p>
49
49
+
<p>@{profileInfo()?.handle}</p>
50
50
+
</div>
51
51
+
</div>
52
52
+
</Match>
53
53
+
</Switch>
54
54
+
</>
55
55
+
);
56
56
+
};
57
57
+
58
58
+
export default MiniProfile;
+2
-1
src/components/navbar.tsx
···
1
1
import { A } from "@solidjs/router";
2
2
import { Component } from "solid-js/types/server/rendering.js";
3
3
+
import { loginState } from "./login";
3
4
4
5
const Navbar: Component = () => {
5
6
return (
6
7
<>
7
8
<nav id="nav">
8
9
<div class="center-nav">
9
9
-
<A href="/">
10
10
+
<A href={loginState() ? "/dash" : "/"}>
10
11
<img src="favicon.png" />
11
12
</A>
12
13
</div>
+24
src/components/postForm.tsx
···
1
1
+
import { Component } from "solid-js";
2
2
+
3
3
+
const PostForm: Component = () => {
4
4
+
return (
5
5
+
<>
6
6
+
<form
7
7
+
autocomplete="off"
8
8
+
onclick={(e) => e.preventDefault()}
9
9
+
class="post-form"
10
10
+
>
11
11
+
<textarea
12
12
+
id="post-textbox"
13
13
+
name="post-textbox"
14
14
+
rows="1"
15
15
+
cols="1"
16
16
+
placeholder="The car's on fire, and there's no driver at the wheel..."
17
17
+
></textarea>
18
18
+
<button type="submit">Post</button>
19
19
+
</form>
20
20
+
</>
21
21
+
);
22
22
+
};
23
23
+
24
24
+
export default PostForm;
+30
-5
src/routes/dashboard.tsx
···
1
1
-
import { killSession, loginState } from "../components/login";
1
1
+
import Container from "../components/container";
2
2
+
import { agent, killSession, loginState } from "../components/login";
3
3
+
import MiniProfile from "../components/miniProfile";
4
4
+
import PostForm from "../components/postForm";
2
5
3
6
const Dashboard = () => {
4
7
if (!loginState()) {
···
6
9
}
7
10
8
11
return (
9
9
-
<div>
10
10
-
<h1>Dashboard</h1>
11
11
-
<button onclick={killSession}>Log out</button>
12
12
-
</div>
12
12
+
<>
13
13
+
<div id="sidebar">
14
14
+
<Container
15
15
+
title=""
16
16
+
children={
17
17
+
<>
18
18
+
<MiniProfile did={agent.sub} />
19
19
+
<PostForm />
20
20
+
<button onClick={killSession}>Log out</button>
21
21
+
</>
22
22
+
}
23
23
+
/>
24
24
+
</div>
25
25
+
<div id="content">
26
26
+
<Container
27
27
+
title="Following"
28
28
+
children={
29
29
+
<div class="container-content">
30
30
+
<div class="dashboard-feed">
31
31
+
<p>No more posts</p>
32
32
+
</div>
33
33
+
</div>
34
34
+
}
35
35
+
/>
36
36
+
</div>
37
37
+
</>
13
38
);
14
39
};
15
40
+5
-1
src/routes/splash.tsx
···
5
5
import blueskyLogo from "/bluesky.svg?url";
6
6
import tangledLogo from "/tangled.svg?url";
7
7
import Container from "../components/container";
8
8
-
import { Login } from "../components/login";
8
8
+
import { Login, loginState } from "../components/login";
9
9
10
10
const Splash: Component = () => {
11
11
+
if (loginState()) {
12
12
+
location.href = "/dash";
13
13
+
}
14
14
+
11
15
return (
12
16
<>
13
17
<div id="sidebar">
+1
-2
src/styles/container.scss
···
1
1
@use "./vars";
2
2
3
3
.container {
4
4
-
background-color: rgba(15, 22, 30, 1);
4
4
+
background-color: #24262d;
5
5
border-radius: vars.$containerBorderRadius;
6
6
margin: 1em;
7
7
padding: 0 0 1em 0;
···
16
16
}
17
17
18
18
.container-header {
19
19
-
font-weight: 500;
20
19
background-color: vars.$foregroundColor;
21
20
text-align: left;
22
21
padding: 1em;
+4
-35
src/styles/main.scss
···
1
1
+
@use "./button";
1
2
@use "./container";
3
3
+
@use "./nav";
4
4
+
@use "./profile";
5
5
+
@use "./routes/dashboard";
2
6
@use "./routes/login";
3
7
@use "./vars";
4
4
-
@use "./button";
5
5
-
@use "./nav";
6
8
7
7
-
/* Core page format */
8
9
body {
9
10
text-align: center;
10
11
color: vars.$textColor;
···
39
40
font-weight: bold;
40
41
font-style: italic;
41
42
}
42
42
-
43
43
-
/* Dashboard */
44
44
-
45
45
-
.post-form {
46
46
-
display: flex;
47
47
-
flex-direction: column;
48
48
-
place-content: center;
49
49
-
50
50
-
button {
51
51
-
}
52
52
-
}
53
53
-
54
54
-
#post-textbox {
55
55
-
background-color: vars.$foregroundColor;
56
56
-
border: 0;
57
57
-
border-radius: containerBorderRadius;
58
58
-
box-shadow:
59
59
-
0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset,
60
60
-
0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset,
61
61
-
0px 0px 2px 0px rgba(0, 0, 0, 1) inset;
62
62
-
color: vars.$textColor;
63
63
-
font-family: inherit;
64
64
-
font-size: 14px;
65
65
-
resize: none;
66
66
-
max-width: 90%;
67
67
-
}
68
68
-
69
69
-
.dashboard-feed {
70
70
-
p {
71
71
-
color: #8d8d8d;
72
72
-
}
73
73
-
}
+30
src/styles/profile.scss
···
1
1
+
@use "vars";
2
2
+
3
3
+
.mini-profile {
4
4
+
display: flex;
5
5
+
flex-direction: row;
6
6
+
align-items: center;
7
7
+
gap: 1rem;
8
8
+
padding: 1rem;
9
9
+
margin-bottom: 1rem;
10
10
+
border-radius: vars.$containerBorderRadius;
11
11
+
12
12
+
img {
13
13
+
max-height: 64px;
14
14
+
box-shadow: 10px 5px 5px rgba(0, 0, 0, 0.2);
15
15
+
border-radius: 3px;
16
16
+
}
17
17
+
}
18
18
+
19
19
+
.mini-profile-info {
20
20
+
text-align: left;
21
21
+
display: flex;
22
22
+
flex-direction: column;
23
23
+
align-items: flex-start;
24
24
+
justify-content: center;
25
25
+
gap: 0.5rem;
26
26
+
27
27
+
p {
28
28
+
margin: 0;
29
29
+
}
30
30
+
}
+39
src/styles/routes/dashboard.scss
···
1
1
+
@use "../vars";
2
2
+
3
3
+
// todo: fix small width
4
4
+
.post-form {
5
5
+
display: grid;
6
6
+
grid-template-columns: auto;
7
7
+
grid-template-rows: 5rem auto;
8
8
+
margin: 0 1rem;
9
9
+
10
10
+
button {
11
11
+
width: 35%;
12
12
+
padding: 0.5rem 0.5rem;
13
13
+
justify-self: end;
14
14
+
}
15
15
+
16
16
+
textarea {
17
17
+
background-color: vars.$foregroundColor;
18
18
+
border: 0;
19
19
+
border-radius: 3px;
20
20
+
box-shadow:
21
21
+
0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset,
22
22
+
0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset,
23
23
+
0px 0px 2px 0px rgba(0, 0, 0, 1) inset;
24
24
+
box-sizing: border-box;
25
25
+
color: vars.$textColor;
26
26
+
font-family: inherit;
27
27
+
font-size: 14px;
28
28
+
resize: none;
29
29
+
padding: 0.5rem 0.5rem;
30
30
+
hyphens: none;
31
31
+
width: 100%;
32
32
+
}
33
33
+
}
34
34
+
35
35
+
.dashboard-feed {
36
36
+
p {
37
37
+
color: #8d8d8d;
38
38
+
}
39
39
+
}
+1
-1
tsconfig.json
···
15
15
16
16
// Type Checking & Safety
17
17
"strict": true,
18
18
-
"types": ["vite/client"]
18
18
+
"types": ["vite/client", "@atcute/bluesky"]
19
19
}
20
20
}