tangled
alpha
login
or
join now
finxol.io
/
bookmarker
0
fork
atom
A very simple bookmarking webapp
bookmarker.finxol.deno.net/
0
fork
atom
overview
issues
pulls
pipelines
feat: improve new bookmark page
finxol.io
1 month ago
8a870c65
e50e5c63
verified
This commit was signed with the committer's
known signature
.
finxol.io
SSH Key Fingerprint:
SHA256:olFE3asYdoBMScuJOt60UxXdJ0RFdGv5kVKrdOtIcPI=
1/1
deploy.yaml
success
8s
+209
-25
4 changed files
expand all
collapse all
unified
split
src
routes
index.tsx
new.css
new.tsx
root.css
+1
-1
src/routes/index.tsx
···
147
147
title="Delete bookmark"
148
148
onClick={() =>
149
149
deleteBookmark.mutate(
150
150
-
bookmark.id,
150
150
+
String(bookmark.id),
151
151
)}
152
152
disabled={deleteBookmark.isPending}
153
153
>
+148
src/routes/new.css
···
1
1
+
.new-page {
2
2
+
padding: calc(var(--spacing) * 2);
3
3
+
max-width: 520px;
4
4
+
margin: 0 auto;
5
5
+
width: 100%;
6
6
+
}
7
7
+
8
8
+
.back-link {
9
9
+
display: inline-flex;
10
10
+
align-items: center;
11
11
+
gap: calc(var(--spacing) * 0.5);
12
12
+
font-size: 0.85rem;
13
13
+
font-weight: 500;
14
14
+
color: oklch(from var(--primary-text) l c h / 0.5);
15
15
+
text-decoration: none;
16
16
+
margin-bottom: calc(var(--spacing) * 3);
17
17
+
transition: color 0.15s ease;
18
18
+
19
19
+
.back-link:hover {
20
20
+
color: var(--primary-text);
21
21
+
}
22
22
+
}
23
23
+
24
24
+
.new-card {
25
25
+
background: oklch(from var(--primary-text) l c h / 0.1);
26
26
+
border: 1px solid oklch(from var(--primary-text) l c h / 0.1);
27
27
+
border-radius: var(--radius);
28
28
+
padding: calc(var(--spacing) * 3);
29
29
+
30
30
+
h2 {
31
31
+
font-size: 1.25rem;
32
32
+
font-weight: 700;
33
33
+
margin-bottom: calc(var(--spacing) * 0.5);
34
34
+
}
35
35
+
36
36
+
.new-subtitle {
37
37
+
font-size: 0.875rem;
38
38
+
color: oklch(from var(--primary-text) l c h / 0.7);
39
39
+
margin-bottom: calc(var(--spacing) * 2.5);
40
40
+
text-wrap: balance;
41
41
+
text-wrap: pretty;
42
42
+
}
43
43
+
}
44
44
+
45
45
+
.new-form {
46
46
+
display: flex;
47
47
+
flex-direction: column;
48
48
+
gap: calc(var(--spacing) * 2);
49
49
+
}
50
50
+
51
51
+
.input-group {
52
52
+
display: flex;
53
53
+
flex-direction: column;
54
54
+
gap: calc(var(--spacing) * 0.5);
55
55
+
56
56
+
label {
57
57
+
font-size: 0.8rem;
58
58
+
font-weight: 600;
59
59
+
color: oklch(from var(--primary-text) l c h / 0.7);
60
60
+
}
61
61
+
}
62
62
+
63
63
+
.input-wrapper {
64
64
+
position: relative;
65
65
+
display: flex;
66
66
+
align-items: center;
67
67
+
68
68
+
.input-icon {
69
69
+
position: absolute;
70
70
+
left: calc(var(--spacing) * 1);
71
71
+
color: oklch(from var(--primary-text) l c h / 0.3);
72
72
+
pointer-events: none;
73
73
+
}
74
74
+
75
75
+
input {
76
76
+
width: 100%;
77
77
+
padding: calc(var(--spacing) * 1) calc(var(--spacing) * 1);
78
78
+
padding-left: calc(var(--spacing) * 3);
79
79
+
border: 1px solid oklch(from var(--primary-text) l c h / 0.15);
80
80
+
border-radius: var(--radius);
81
81
+
font-size: 0.9rem;
82
82
+
color: var(--primary-text);
83
83
+
background: transparent;
84
84
+
outline: none;
85
85
+
transition:
86
86
+
border-color 0.15s ease,
87
87
+
box-shadow 0.15s ease;
88
88
+
89
89
+
&::placeholder {
90
90
+
color: oklch(from var(--primary-text) l c h / 0.3);
91
91
+
}
92
92
+
93
93
+
&:focus {
94
94
+
border-color: var(--primary);
95
95
+
box-shadow: 0 0 0 3px oklch(from var(--primary) l c h / 0.12);
96
96
+
}
97
97
+
98
98
+
&:disabled {
99
99
+
opacity: 0.6;
100
100
+
cursor: not-allowed;
101
101
+
}
102
102
+
}
103
103
+
}
104
104
+
105
105
+
.submit-button {
106
106
+
display: inline-flex;
107
107
+
align-items: center;
108
108
+
justify-content: center;
109
109
+
gap: calc(var(--spacing) * 0.5);
110
110
+
padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2);
111
111
+
background-color: var(--primary);
112
112
+
color: white;
113
113
+
border: none;
114
114
+
border-radius: var(--radius);
115
115
+
font-weight: 600;
116
116
+
font-size: 0.875rem;
117
117
+
cursor: pointer;
118
118
+
transition: opacity 0.15s ease;
119
119
+
}
120
120
+
121
121
+
.submit-button:hover:not(:disabled) {
122
122
+
opacity: 0.85;
123
123
+
}
124
124
+
125
125
+
.submit-button:disabled {
126
126
+
opacity: 0.5;
127
127
+
cursor: not-allowed;
128
128
+
}
129
129
+
130
130
+
.error-message {
131
131
+
margin-top: calc(var(--spacing) * 1.5);
132
132
+
padding: calc(var(--spacing) * 1);
133
133
+
background: oklch(0.65 0.25 25 / 0.08);
134
134
+
border: 1px solid oklch(0.65 0.25 25 / 0.2);
135
135
+
border-radius: var(--radius);
136
136
+
color: oklch(0.5 0.2 25);
137
137
+
font-size: 0.85rem;
138
138
+
}
139
139
+
140
140
+
.spinner {
141
141
+
animation: spin 1s linear infinite;
142
142
+
}
143
143
+
144
144
+
@keyframes spin {
145
145
+
to {
146
146
+
transform: rotate(360deg);
147
147
+
}
148
148
+
}
+58
-22
src/routes/new.tsx
···
1
1
import { createFileRoute, useNavigate } from "@tanstack/solid-router"
2
2
import { useMutation, useQueryClient } from "@tanstack/solid-query"
3
3
-
import { createSignal } from "solid-js"
3
3
+
import { createSignal, Show } from "solid-js"
4
4
import { client } from "../apiclient.ts"
5
5
+
import { ArrowLeftIcon, LinkIcon, LoaderIcon } from "lucide-solid"
6
6
+
import { Link } from "@tanstack/solid-router"
7
7
+
import "./new.css"
5
8
6
9
export const Route = createFileRoute("/new")({
7
10
component: RouteComponent,
···
42
45
}
43
46
44
47
return (
45
45
-
<div>
46
46
-
<h2>Add a new bookmark</h2>
47
47
-
<form onSubmit={handleSubmit}>
48
48
-
<label for="url">URL</label>
49
49
-
<input
50
50
-
id="url"
51
51
-
type="url"
52
52
-
required
53
53
-
placeholder="https://example.com"
54
54
-
value={url()}
55
55
-
onInput={(e) => setUrl(e.currentTarget.value)}
56
56
-
disabled={addBookmark.isPending}
57
57
-
/>
58
58
-
<button type="submit" disabled={addBookmark.isPending}>
59
59
-
{addBookmark.isPending ? "Adding..." : "Add bookmark"}
60
60
-
</button>
61
61
-
</form>
62
62
-
{addBookmark.isError && (
63
63
-
<p style="color: red;">
64
64
-
{addBookmark.error?.message ?? "Failed to add bookmark"}
48
48
+
<div class="new-page">
49
49
+
<Link to="/" class="back-link">
50
50
+
<ArrowLeftIcon size={16} />
51
51
+
Back to bookmarks
52
52
+
</Link>
53
53
+
54
54
+
<div class="new-card">
55
55
+
<h2>Add a new bookmark</h2>
56
56
+
<p class="new-subtitle">
57
57
+
Paste a URL and we'll grab the title, description, and image
58
58
+
for you.
65
59
</p>
66
66
-
)}
60
60
+
61
61
+
<form onSubmit={handleSubmit} class="new-form">
62
62
+
<div class="input-group">
63
63
+
<label for="url">URL</label>
64
64
+
<div class="input-wrapper">
65
65
+
<LinkIcon size={16} class="input-icon" />
66
66
+
<input
67
67
+
id="url"
68
68
+
type="url"
69
69
+
required
70
70
+
placeholder="https://example.com"
71
71
+
value={url()}
72
72
+
onInput={(e) => setUrl(e.currentTarget.value)}
73
73
+
disabled={addBookmark.isPending}
74
74
+
/>
75
75
+
</div>
76
76
+
</div>
77
77
+
78
78
+
<button
79
79
+
type="submit"
80
80
+
class="submit-button"
81
81
+
disabled={addBookmark.isPending || !url().trim()}
82
82
+
>
83
83
+
<Show
84
84
+
when={!addBookmark.isPending}
85
85
+
fallback={
86
86
+
<>
87
87
+
<LoaderIcon size={16} class="spinner" />
88
88
+
Saving...
89
89
+
</>
90
90
+
}
91
91
+
>
92
92
+
Save bookmark
93
93
+
</Show>
94
94
+
</button>
95
95
+
</form>
96
96
+
97
97
+
<Show when={addBookmark.isError}>
98
98
+
<div class="error-message">
99
99
+
{addBookmark.error?.message ?? "Failed to add bookmark"}
100
100
+
</div>
101
101
+
</Show>
102
102
+
</div>
67
103
</div>
68
104
)
69
105
}
+2
-2
src/routes/root.css
···
20
20
--spacing: 0.75rem;
21
21
--radius: 0.5rem;
22
22
23
23
-
@media screen and (prefers-color-scheme: dark) {
23
23
+
@media screen and (prefers-color-scheme: light) {
24
24
--primary: var(--fuchsia-900);
25
25
-
--primary-text: var(--fuchsia-300);
25
25
+
--primary-text: var(--fuchsia-200);
26
26
--secondary: var(--fuchsia-900);
27
27
--secondary-text: var(--teal-300);
28
28
--accent: var(--orange-900);