+104
frontend/src/routes/+error.svelte
+104
frontend/src/routes/+error.svelte
···
1
+
<script lang="ts">
2
+
import { page } from '$app/stores';
3
+
import { APP_NAME } from '$lib/branding';
4
+
5
+
const status = $page.status;
6
+
const message = $page.error?.message || 'something went wrong';
7
+
</script>
8
+
9
+
<svelte:head>
10
+
<title>{status} - {APP_NAME}</title>
11
+
</svelte:head>
12
+
13
+
<div class="error-container">
14
+
{#if status === 404}
15
+
<div class="bufo-container">
16
+
<img
17
+
src="https://all-the.bufo.zone/bufo-shrug.png"
18
+
alt="bufo shrug"
19
+
class="bufo-img"
20
+
/>
21
+
</div>
22
+
<h1>404</h1>
23
+
<p class="error-message">we couldn't find what you're looking for</p>
24
+
{:else if status === 500}
25
+
<h1>500</h1>
26
+
<p class="error-message">something went wrong on our end</p>
27
+
<p class="error-detail">we've been notified and will look into it</p>
28
+
{:else}
29
+
<h1>{status}</h1>
30
+
<p class="error-message">{message}</p>
31
+
{/if}
32
+
33
+
<a href="/" class="home-link">go home</a>
34
+
</div>
35
+
36
+
<style>
37
+
.error-container {
38
+
display: flex;
39
+
flex-direction: column;
40
+
align-items: center;
41
+
justify-content: center;
42
+
min-height: 100vh;
43
+
padding: 2rem;
44
+
text-align: center;
45
+
}
46
+
47
+
.bufo-container {
48
+
margin-bottom: 2rem;
49
+
}
50
+
51
+
.bufo-img {
52
+
width: 200px;
53
+
height: auto;
54
+
opacity: 0.9;
55
+
}
56
+
57
+
h1 {
58
+
font-size: 4rem;
59
+
color: #e8e8e8;
60
+
margin: 0 0 1rem 0;
61
+
font-weight: 700;
62
+
}
63
+
64
+
.error-message {
65
+
font-size: 1.25rem;
66
+
color: #b0b0b0;
67
+
margin: 0 0 0.5rem 0;
68
+
}
69
+
70
+
.error-detail {
71
+
font-size: 1rem;
72
+
color: #808080;
73
+
margin: 0 0 2rem 0;
74
+
}
75
+
76
+
.home-link {
77
+
color: var(--accent);
78
+
text-decoration: none;
79
+
font-size: 1.1rem;
80
+
padding: 0.75rem 1.5rem;
81
+
border: 1px solid var(--accent);
82
+
border-radius: 6px;
83
+
transition: all 0.2s;
84
+
}
85
+
86
+
.home-link:hover {
87
+
background: var(--accent);
88
+
color: #000;
89
+
}
90
+
91
+
@media (max-width: 768px) {
92
+
.bufo-img {
93
+
width: 150px;
94
+
}
95
+
96
+
h1 {
97
+
font-size: 3rem;
98
+
}
99
+
100
+
.error-message {
101
+
font-size: 1.1rem;
102
+
}
103
+
}
104
+
</style>
+165
frontend/src/routes/u/[handle]/+error.svelte
+165
frontend/src/routes/u/[handle]/+error.svelte
···
1
+
<script lang="ts">
2
+
import { onMount } from 'svelte';
3
+
import { page } from '$app/stores';
4
+
import { APP_NAME } from '$lib/branding';
5
+
6
+
const status = $page.status;
7
+
const handle = $page.params.handle;
8
+
9
+
let checkingBluesky = $state(false);
10
+
let blueskyProfileExists = $state(false);
11
+
let blueskyUrl = $state('');
12
+
13
+
onMount(async () => {
14
+
// if this is a 404, check if the handle exists on Bluesky
15
+
if (status === 404 && handle) {
16
+
checkingBluesky = true;
17
+
try {
18
+
// try to resolve the handle via ATProto
19
+
const response = await fetch(
20
+
`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`
21
+
);
22
+
23
+
if (response.ok) {
24
+
const data = await response.json();
25
+
if (data.did) {
26
+
blueskyProfileExists = true;
27
+
blueskyUrl = `https://bsky.app/profile/${handle}`;
28
+
}
29
+
}
30
+
} catch (e) {
31
+
console.error('failed to check Bluesky:', e);
32
+
} finally {
33
+
checkingBluesky = false;
34
+
}
35
+
}
36
+
});
37
+
</script>
38
+
39
+
<svelte:head>
40
+
<title>404 - artist not found - {APP_NAME}</title>
41
+
</svelte:head>
42
+
43
+
<div class="error-container">
44
+
<div class="bufo-container">
45
+
<img
46
+
src="https://all-the.bufo.zone/bufo-shrug.png"
47
+
alt="bufo shrug"
48
+
class="bufo-img"
49
+
/>
50
+
</div>
51
+
52
+
<h1>404</h1>
53
+
54
+
{#if checkingBluesky}
55
+
<p class="error-message">checking if @{handle} exists...</p>
56
+
{:else if blueskyProfileExists}
57
+
<p class="error-message">@{handle} hasn't joined {APP_NAME} yet</p>
58
+
<p class="error-detail">but they're on Bluesky!</p>
59
+
<div class="actions">
60
+
<a href={blueskyUrl} target="_blank" rel="noopener" class="bsky-link">
61
+
view their Bluesky profile
62
+
</a>
63
+
<a href="/" class="home-link">go home</a>
64
+
</div>
65
+
{:else}
66
+
<p class="error-message">we couldn't find anyone by @{handle}</p>
67
+
<p class="error-detail">the handle might not exist or could be misspelled</p>
68
+
<a href="/" class="home-link">go home</a>
69
+
{/if}
70
+
</div>
71
+
72
+
<style>
73
+
.error-container {
74
+
display: flex;
75
+
flex-direction: column;
76
+
align-items: center;
77
+
justify-content: center;
78
+
min-height: 100vh;
79
+
padding: 2rem;
80
+
text-align: center;
81
+
}
82
+
83
+
.bufo-container {
84
+
margin-bottom: 2rem;
85
+
}
86
+
87
+
.bufo-img {
88
+
width: 200px;
89
+
height: auto;
90
+
opacity: 0.9;
91
+
}
92
+
93
+
h1 {
94
+
font-size: 4rem;
95
+
color: #e8e8e8;
96
+
margin: 0 0 1rem 0;
97
+
font-weight: 700;
98
+
}
99
+
100
+
.error-message {
101
+
font-size: 1.25rem;
102
+
color: #b0b0b0;
103
+
margin: 0 0 0.5rem 0;
104
+
}
105
+
106
+
.error-detail {
107
+
font-size: 1rem;
108
+
color: #808080;
109
+
margin: 0 0 2rem 0;
110
+
}
111
+
112
+
.actions {
113
+
display: flex;
114
+
gap: 1rem;
115
+
flex-wrap: wrap;
116
+
justify-content: center;
117
+
}
118
+
119
+
.home-link,
120
+
.bsky-link {
121
+
color: var(--accent);
122
+
text-decoration: none;
123
+
font-size: 1.1rem;
124
+
padding: 0.75rem 1.5rem;
125
+
border: 1px solid var(--accent);
126
+
border-radius: 6px;
127
+
transition: all 0.2s;
128
+
display: inline-block;
129
+
}
130
+
131
+
.bsky-link {
132
+
background: var(--accent);
133
+
color: #000;
134
+
}
135
+
136
+
.bsky-link:hover {
137
+
background: #6a9fff;
138
+
border-color: #6a9fff;
139
+
}
140
+
141
+
.home-link:hover {
142
+
background: var(--accent);
143
+
color: #000;
144
+
}
145
+
146
+
@media (max-width: 768px) {
147
+
.bufo-img {
148
+
width: 150px;
149
+
}
150
+
151
+
h1 {
152
+
font-size: 3rem;
153
+
}
154
+
155
+
.error-message {
156
+
font-size: 1.1rem;
157
+
}
158
+
159
+
.actions {
160
+
flex-direction: column;
161
+
width: 100%;
162
+
max-width: 300px;
163
+
}
164
+
}
165
+
</style>
+44
-3
frontend/src/routes/u/[handle]/+page.svelte
+44
-3
frontend/src/routes/u/[handle]/+page.svelte
···
269
269
{/if}
270
270
</h2>
271
271
{#if tracks.length === 0}
272
-
<p class="empty">no tracks yet</p>
272
+
<div class="empty-state">
273
+
<p class="empty-message">no tracks yet</p>
274
+
<p class="empty-detail">
275
+
{artist.display_name} hasn't uploaded any music to {APP_NAME}.
276
+
</p>
277
+
<a
278
+
href="https://bsky.app/profile/{artist.handle}"
279
+
target="_blank"
280
+
rel="noopener"
281
+
class="bsky-link"
282
+
>
283
+
view their Bluesky profile
284
+
</a>
285
+
</div>
273
286
{:else}
274
287
<div class="track-list">
275
288
{#each tracks as track, i}
···
657
670
gap: 0.75rem;
658
671
}
659
672
660
-
.empty {
673
+
.empty-state {
661
674
text-align: center;
662
675
padding: 3rem;
676
+
background: #141414;
677
+
border: 1px solid #282828;
678
+
border-radius: 8px;
679
+
}
680
+
681
+
.empty-message {
682
+
color: #b0b0b0;
683
+
font-size: 1.25rem;
684
+
margin: 0 0 0.5rem 0;
685
+
}
686
+
687
+
.empty-detail {
663
688
color: #808080;
664
-
font-style: italic;
689
+
margin: 0 0 1.5rem 0;
690
+
}
691
+
692
+
.bsky-link {
693
+
color: var(--accent);
694
+
text-decoration: none;
695
+
font-size: 1rem;
696
+
padding: 0.75rem 1.5rem;
697
+
border: 1px solid var(--accent);
698
+
border-radius: 6px;
699
+
transition: all 0.2s;
700
+
display: inline-block;
701
+
}
702
+
703
+
.bsky-link:hover {
704
+
background: var(--accent);
705
+
color: #000;
665
706
}
666
707
667
708
/* respect reduced motion preference */