Yōten: A social tracker for your language learning journey built on the atproto.
1package views
2
3import (
4 "fmt"
5
6 "yoten.app/internal/db"
7 "yoten.app/internal/server/views/layouts"
8 "yoten.app/internal/server/views/partials"
9)
10
11templ EditResourcePage(params EditResourcePageParams) {
12 @layouts.Base(layouts.BaseParams{Title: "edit resource"}) {
13 @partials.Header(partials.HeaderProps{User: params.User})
14 <div class="container mx-auto px-4 py-8 max-w-2xl">
15 <form
16 class="card mb-8 group"
17 hx-post={ templ.SafeURL(fmt.Sprintf("/resource/edit/%s", params.Resource.Rkey)) }
18 hx-swap="none"
19 hx-disabled-elt="#save-button,#cancel-button"
20 >
21 <div>
22 <h1 class="text-3xl font-bold">Edit Custom Resource</h1>
23 </div>
24 <div class="flex flex-col sm:flex-row gap-2 justify-between">
25 <div class="flex flex-col gap-2 w-full">
26 <label for="title" class="font-medium text-sm">Title</label>
27 <div x-data={ templ.JSONString(JsonText{Text: params.Resource.Title}) }>
28 <input
29 x-model="text"
30 id="title"
31 name="title"
32 placeholder="e.g., Korean grammar Guide"
33 class="input w-full"
34 maxLength="100"
35 />
36 <div class="text-right text-sm text-text-muted mt-1">
37 <span x-text="text.length"></span> / 100
38 </div>
39 </div>
40 </div>
41 <div x-data={ templ.JSONString(JsonText{Text: params.Resource.Type.String()}) }>
42 <div class="flex flex-col gap-2 w-full">
43 <label for="type" class="font-medium text-sm">Type</label>
44 <select name="type" id="type" x-model="text" class="input">
45 <option value="" disabled="true">
46 Select a resource type...
47 </option>
48 for _, l := range params.SortedResourceTypes {
49 <option value={ l }>
50 { db.ToTitleCase(l.String()) }
51 </option>
52 }
53 </select>
54 </div>
55 </div>
56 </div>
57 <div class="flex flex-col gap-2">
58 <label for="link" class="font-medium text-sm">Author</label>
59 <div x-data={ templ.JSONString(JsonText{Text: params.Resource.Author}) }>
60 <input
61 x-model="text"
62 id="author"
63 name="author"
64 placeholder="e.g., Jinwoo Kim"
65 class="input w-full"
66 maxLength="100"
67 />
68 <div class="text-right text-sm text-text-muted mt-1">
69 <span x-text="text.length"></span> / 100
70 </div>
71 </div>
72 </div>
73 <div class="flex flex-col gap-2">
74 <label for="link" class="font-medium text-sm">Link (optional)</label>
75 {{
76 link := ""
77 if params.Resource.Link != nil {
78 link = *params.Resource.Link
79 }
80 }}
81 <div
82 x-data={ fmt.Sprintf(`{
83 link: '%s',
84 isValid: true,
85 validateLink() {
86 if (this.link.trim() === '') {
87 this.isValid = true;
88 return;
89 }
90 this.isValid = this.link.startsWith('http://') || this.link.startsWith('https://');
91 }
92 }`, link) }
93 >
94 <input
95 type="url"
96 value={ link }
97 name="link"
98 id="link"
99 class="input w-full"
100 placeholder="https://..."
101 x-model="link"
102 @input="validateLink()"
103 :class="{ 'border-red-500 focus:border-red-500 focus:ring-red-500': !isValid }"
104 />
105 <div class="flex items-center text-sm text-text-muted mt-1">
106 <span x-show="!isValid" x-transition class="text-red-500">
107 Please enter a valid URL starting with http:// or https://
108 </span>
109 <div class="ml-auto">
110 <span x-text="link.length"></span> / 2000
111 </div>
112 </div>
113 </div>
114 </div>
115 <div class="flex flex-col gap-2 w-full">
116 <label for="description" class="font-medium text-sm">Description</label>
117 <div x-data="{ text: '' }" x-init="text = $el.querySelector('textarea').value">
118 <textarea
119 x-model="text"
120 id="description"
121 name="description"
122 placeholder="Brief description of how this resource helped you..."
123 class="input w-full"
124 maxLength="256"
125 >
126 { params.Resource.Description }
127 </textarea>
128 <div class="text-right text-sm text-text-muted mt-1">
129 <span x-text="text.length"></span> / 256
130 </div>
131 </div>
132 </div>
133 <div class="flex flex-col sm:flex-row gap-4 mt-4">
134 <button id="save-button" type="submit" class="btn btn-primary">
135 <i class="w-4 h-4" data-lucide="save"></i>
136 Save
137 <i class="w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" data-lucide="loader-circle"></i>
138 </button>
139 <button
140 id="cancel-button"
141 onclick="window.location.href = '/profile/resources'"
142 type="button"
143 class="btn btn-secondary"
144 >
145 Cancel
146 </button>
147 </div>
148 </form>
149 <div class="flex flex-col sm:flex-row gap-4 items-center bg-bg-light text-red-500 rounded border w-full p-6">
150 <div class="flex flex-col gap-1">
151 <h3 class="font-medium">Delete this resource</h3>
152 <p class="text-sm text-text-muted">
153 This action cannot be undone. This will permanently delete this resource.
154 </p>
155 </div>
156 <button
157 id="delete-button"
158 hx-disabled-elt="#delete-button,#save-button,#cancel-button"
159 hx-delete={ templ.SafeURL(fmt.Sprintf("/resource/%s", params.Resource.Rkey)) }
160 class="btn btn-dangerous w-full sm:w-fit whitespace-nowrap group"
161 type="button"
162 >
163 <i class="w-4 h-4" data-lucide="trash-2"></i>
164 Delete Resource
165 <i class="w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" data-lucide="loader-circle"></i>
166 </button>
167 </div>
168 </div>
169 }
170}