grain.social is a photo sharing platform built on atproto.

update upload onboard flow

Changed files
+86 -23
static
+73 -23
main.tsx
··· 176 <GalleryPage favs={favs} gallery={gallery} currentUserDid={did} />, 177 ); 178 }), 179 - route("/upload", (_req, _params, ctx) => { 180 requireAuth(ctx); 181 const photos = getActorPhotos(ctx.currentUser.did, ctx); 182 ctx.state.meta = [{ title: "Upload — Grain" }, getPageMeta("/upload")]; 183 - return ctx.render(<UploadPage photos={photos} />); 184 }), 185 route("/dialogs/gallery/new", (_req, _params, ctx) => { 186 requireAuth(ctx); ··· 1165 {loggedInUserDid === profile.did 1166 ? ( 1167 <div class="flex self-start gap-2 w-full sm:w-fit flex-col sm:flex-row"> 1168 - <Button variant="secondary" class="w-full sm:w-fit" asChild> 1169 <a href="/upload"> 1170 <i class="fa-solid fa-upload mr-2" /> 1171 Upload ··· 1280 ); 1281 } 1282 1283 - function UploadPage({ photos }: Readonly<{ photos: PhotoView[] }>) { 1284 return ( 1285 - <div class="px-4 pt-4 mb-4"> 1286 - <Button variant="primary" class="mb-2" asChild> 1287 <label class="w-fit"> 1288 <i class="fa fa-plus"></i> Add photos 1289 <input ··· 1434 const isLoggedIn = !!currentUserDid; 1435 return ( 1436 <div class="px-4"> 1437 - <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between my-4 gap-2"> 1438 - <div> 1439 <div> 1440 - <h1 class="font-bold text-2xl"> 1441 - {(gallery.record as Gallery).title} 1442 - </h1> 1443 Gallery by{" "} 1444 <a 1445 href={profileLink(gallery.creator.handle)} ··· 1452 </span> 1453 </a> 1454 </div> 1455 - {(gallery.record as Gallery).description} 1456 </div> 1457 {isLoggedIn && isCreator 1458 ? ( ··· 1837 }>) { 1838 return ( 1839 <Dialog id="photo-select-dialog" class="z-30"> 1840 - <Dialog.Content class="w-full max-w-5xl dark:bg-zinc-950"> 1841 <Dialog.Title>Add photos</Dialog.Title> 1842 - <div class="grid grid-cols-2 sm:grid-cols-3 gap-4 my-4"> 1843 - {photos.map((photo) => ( 1844 - <PhotoSelectButton 1845 - key={photo.cid} 1846 - galleryUri={galleryUri} 1847 - itemUris={itemUris} 1848 - photo={photo} 1849 - /> 1850 - ))} 1851 - </div> 1852 <div class="w-full flex flex-col gap-2 mt-2"> 1853 <Dialog.Close class="w-full">Close</Dialog.Close> 1854 </div>
··· 176 <GalleryPage favs={favs} gallery={gallery} currentUserDid={did} />, 177 ); 178 }), 179 + route("/upload", (req, _params, ctx) => { 180 requireAuth(ctx); 181 + const url = new URL(req.url); 182 + const galleryRkey = url.searchParams.get("returnTo"); 183 const photos = getActorPhotos(ctx.currentUser.did, ctx); 184 ctx.state.meta = [{ title: "Upload — Grain" }, getPageMeta("/upload")]; 185 + return ctx.render( 186 + <UploadPage 187 + handle={ctx.currentUser.handle} 188 + photos={photos} 189 + returnTo={galleryRkey 190 + ? galleryLink(ctx.currentUser.handle, galleryRkey) 191 + : undefined} 192 + />, 193 + ); 194 }), 195 route("/dialogs/gallery/new", (_req, _params, ctx) => { 196 requireAuth(ctx); ··· 1175 {loggedInUserDid === profile.did 1176 ? ( 1177 <div class="flex self-start gap-2 w-full sm:w-fit flex-col sm:flex-row"> 1178 + <Button variant="primary" class="w-full sm:w-fit" asChild> 1179 <a href="/upload"> 1180 <i class="fa-solid fa-upload mr-2" /> 1181 Upload ··· 1290 ); 1291 } 1292 1293 + function UploadPage( 1294 + { handle, photos, returnTo }: Readonly< 1295 + { handle: string; photos: PhotoView[]; returnTo?: string } 1296 + >, 1297 + ) { 1298 return ( 1299 + <div class="flex flex-col px-4 pt-4 mb-4 space-y-4"> 1300 + {returnTo 1301 + ? ( 1302 + <a 1303 + href={returnTo} 1304 + class="hover:underline" 1305 + > 1306 + <i class="fa-solid fa-arrow-left mr-2" /> 1307 + Back to gallery 1308 + </a> 1309 + ) 1310 + : ( 1311 + <a href={profileLink(handle)} class="hover:underline"> 1312 + <i class="fa-solid fa-arrow-left mr-2" /> 1313 + Back to profile 1314 + </a> 1315 + )} 1316 + <Button variant="primary" class="mb-4" asChild> 1317 <label class="w-fit"> 1318 <i class="fa fa-plus"></i> Add photos 1319 <input ··· 1464 const isLoggedIn = !!currentUserDid; 1465 return ( 1466 <div class="px-4"> 1467 + <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between my-4"> 1468 + <div class="flex flex-col space-y-1"> 1469 + <h1 class="font-bold text-2xl"> 1470 + {(gallery.record as Gallery).title} 1471 + </h1> 1472 <div> 1473 Gallery by{" "} 1474 <a 1475 href={profileLink(gallery.creator.handle)} ··· 1482 </span> 1483 </a> 1484 </div> 1485 + <p>{(gallery.record as Gallery).description}</p> 1486 </div> 1487 {isLoggedIn && isCreator 1488 ? ( ··· 1867 }>) { 1868 return ( 1869 <Dialog id="photo-select-dialog" class="z-30"> 1870 + <Dialog.Content class="w-full max-w-5xl dark:bg-zinc-950 min-h-screen flex flex-col"> 1871 <Dialog.Title>Add photos</Dialog.Title> 1872 + <p class="my-2 text-center"> 1873 + Choose photos to add/remove from your gallery. Click close when done. 1874 + </p> 1875 + {photos?.length 1876 + ? ( 1877 + <div class="grid grid-cols-2 sm:grid-cols-3 gap-4 my-4 flex-1"> 1878 + {photos.map((photo) => ( 1879 + <PhotoSelectButton 1880 + key={photo.cid} 1881 + galleryUri={galleryUri} 1882 + itemUris={itemUris} 1883 + photo={photo} 1884 + /> 1885 + ))} 1886 + </div> 1887 + ) 1888 + : ( 1889 + <div class="flex-1 flex justify-center items-center"> 1890 + <p> 1891 + No photos yet.{" "} 1892 + <a 1893 + href={`/upload?returnTo=${new AtUri(galleryUri).rkey}`} 1894 + class="hover:underline font-semibold text-sky-500" 1895 + > 1896 + Upload 1897 + </a>{" "} 1898 + photos and return to add. 1899 + </p> 1900 + </div> 1901 + )} 1902 <div class="w-full flex flex-col gap-2 mt-2"> 1903 <Dialog.Close class="w-full">Close</Dialog.Close> 1904 </div>
+13
static/styles.css
··· 337 .h-full { 338 height: 100%; 339 } 340 .w-1\/3 { 341 width: calc(1/3 * 100%); 342 } ··· 399 } 400 .gap-4 { 401 gap: calc(var(--spacing) * 4); 402 } 403 .space-y-2 { 404 :where(& > :not(:last-child)) { ··· 487 } 488 .pt-4 { 489 padding-top: calc(var(--spacing) * 4); 490 } 491 .text-left { 492 text-align: left;
··· 337 .h-full { 338 height: 100%; 339 } 340 + .min-h-screen { 341 + min-height: 100vh; 342 + } 343 .w-1\/3 { 344 width: calc(1/3 * 100%); 345 } ··· 402 } 403 .gap-4 { 404 gap: calc(var(--spacing) * 4); 405 + } 406 + .space-y-1 { 407 + :where(& > :not(:last-child)) { 408 + --tw-space-y-reverse: 0; 409 + margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); 410 + margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); 411 + } 412 } 413 .space-y-2 { 414 :where(& > :not(:last-child)) { ··· 497 } 498 .pt-4 { 499 padding-top: calc(var(--spacing) * 4); 500 + } 501 + .text-center { 502 + text-align: center; 503 } 504 .text-left { 505 text-align: left;