Yōten: A social tracker for your language learning journey built on the atproto.
at master 170 lines 5.7 kB view raw
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}