๐Ÿ“๐Ÿ–ผ๏ธ๐Ÿน A small thing where I can upload a file and get a link back. https://media.strawmelonjuice.com/
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

woohoo ui design is my passion

+409 -150
+409 -150
dashboard/src/strawmediajuice_fe.gleam
··· 16 16 import lustre/effect.{type Effect} 17 17 import lustre/element.{type Element} 18 18 import lustre/element/html 19 + import lustre/element/svg 19 20 import lustre/event 20 21 import plinth/browser/window 21 22 import plinth/javascript/console ··· 342 343 case float.compare(size, 0.1) { 343 344 order.Lt -> { 344 345 let kb = float.round(size *. 1024.0) 345 - int.to_string(kb) <> " KB" 346 + case int.to_string(kb) { 347 + "0" -> "small (less than 1 KB)" 348 + o -> o <> " KB" 349 + } 346 350 } 347 351 _ -> float.to_string(size) <> " MB" 348 352 } ··· 351 355 fn view_login_form(model: Model) -> Element(Msg) { 352 356 html.div( 353 357 [ 354 - attribute.class( 355 - "flex flex-col items-center justify-center min-h-screen bg-base-200", 356 - ), 358 + attribute.class("hero min-h-screen bg-base-200"), 357 359 ], 358 360 [ 359 - html.h2([attribute.class("text-2xl font-bold mb-4 text-primary")], [ 360 - html.text("Login"), 361 - ]), 362 - html.form( 363 - [ 364 - attribute.class("bg-base-100 p-6 rounded shadow-md w-full max-w-sm"), 365 - event.on_submit(UserSubmittedLoginForm), 366 - ], 367 - [ 368 - html.div([attribute.class("mb-4")], [ 369 - html.label( 361 + html.div([attribute.class("hero-content flex-col lg:flex-row-reverse")], [ 362 + html.div([attribute.class("text-center lg:text-left")], [ 363 + html.h1([attribute.class("text-5xl font-bold text-primary")], [ 364 + html.text("Login"), 365 + ]), 366 + html.p([attribute.class("py-6 text-base-content")], [ 367 + html.text("Access your StrawMEDIAjuice dashboard"), 368 + ]), 369 + ]), 370 + html.div( 371 + [ 372 + attribute.class( 373 + "card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100", 374 + ), 375 + ], 376 + [ 377 + html.form( 370 378 [ 371 - attribute.for("username"), 372 - attribute.class("block text-sm font-medium text-primary"), 379 + attribute.class("card-body"), 380 + event.on_submit(UserSubmittedLoginForm), 373 381 ], 374 - [html.text("Username")], 375 - ), 376 - html.input([ 377 - attribute.type_("text"), 378 - attribute.id("username"), 379 - attribute.name("username"), 380 - attribute.placeholder("Enter username"), 381 - attribute.class("input input-bordered w-full"), 382 - ]), 383 - ]), 384 - html.div([attribute.class("mb-4")], [ 385 - html.label( 386 382 [ 387 - attribute.for("password"), 388 - attribute.class("block text-sm font-medium text-primary"), 383 + html.div([attribute.class("form-control")], [ 384 + html.label([attribute.class("label")], [ 385 + html.span( 386 + [attribute.class("label-text text-base-content")], 387 + [ 388 + html.text("Username"), 389 + ], 390 + ), 391 + ]), 392 + html.input([ 393 + attribute.type_("text"), 394 + attribute.id("username"), 395 + attribute.name("username"), 396 + attribute.placeholder("Enter username"), 397 + attribute.class( 398 + "input input-bordered focus:input-primary transition duration-200", 399 + ), 400 + ]), 401 + ]), 402 + html.div([attribute.class("form-control")], [ 403 + html.label([attribute.class("label")], [ 404 + html.span( 405 + [attribute.class("label-text text-base-content")], 406 + [ 407 + html.text("Password"), 408 + ], 409 + ), 410 + ]), 411 + html.input([ 412 + attribute.type_("password"), 413 + attribute.id("password"), 414 + attribute.name("password"), 415 + attribute.placeholder("Enter password"), 416 + attribute.class( 417 + "input input-bordered focus:input-primary transition duration-200", 418 + ), 419 + ]), 420 + ]), 421 + html.div([attribute.class("form-control mt-6")], [ 422 + html.button( 423 + [ 424 + attribute.type_("submit"), 425 + attribute.class( 426 + "btn btn-primary hover:btn-primary-focus transition duration-200", 427 + ), 428 + attribute.disabled(model.is_loading), 429 + ], 430 + [ 431 + case model.is_loading { 432 + True -> html.text("Loading...") 433 + False -> html.text("Login") 434 + }, 435 + ], 436 + ), 437 + ]), 438 + case model.error_message { 439 + option.Some(error) -> 440 + html.div([attribute.class("alert alert-error mt-4")], [ 441 + html.svg( 442 + [ 443 + attribute.class( 444 + "stroke-current flex-shrink-0 h-6 w-6", 445 + ), 446 + attribute.attribute("fill", "none"), 447 + attribute.attribute("viewBox", "0 0 24 24"), 448 + ], 449 + [ 450 + svg.path([ 451 + attribute.attribute("stroke-linecap", "round"), 452 + attribute.attribute("stroke-linejoin", "round"), 453 + attribute.attribute("stroke-width", "2"), 454 + attribute.attribute( 455 + "d", 456 + "M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z", 457 + ), 458 + ]), 459 + ], 460 + ), 461 + html.span([attribute.class("text-sm")], [html.text(error)]), 462 + ]) 463 + option.None -> html.text("") 464 + }, 389 465 ], 390 - [html.text("Password")], 391 466 ), 392 - html.input([ 393 - attribute.type_("password"), 394 - attribute.id("password"), 395 - attribute.name("password"), 396 - attribute.placeholder("Enter password"), 397 - attribute.class("input input-bordered w-full"), 398 - ]), 399 - ]), 400 - html.button( 401 - [ 402 - attribute.type_("submit"), 403 - attribute.class("btn btn-primary w-full"), 404 - attribute.disabled(model.is_loading), 405 - ], 406 - [ 407 - case model.is_loading { 408 - True -> html.text("Loading...") 409 - False -> html.text("Login") 410 - }, 411 - ], 412 - ), 413 - ], 414 - ), 415 - case model.error_message { 416 - option.Some(error) -> 417 - html.p([attribute.class("mt-4 text-error text-sm")], [ 418 - html.text(error), 419 - ]) 420 - option.None -> html.text("") 421 - }, 467 + ], 468 + ), 469 + ]), 422 470 ], 423 471 ) 424 472 } 425 473 426 474 fn view_file_list(model: Model) -> Element(Msg) { 427 475 html.div([attribute.class("p-6 bg-base-200 min-h-screen")], [ 428 - html.h2([attribute.class("text-2xl font-bold mb-4 text-primary")], [ 429 - html.text("Your Files"), 430 - ]), 431 - case model.is_loading { 432 - True -> 433 - html.div([attribute.class("text-center text-primary")], [ 434 - html.text("Loading..."), 435 - ]) 436 - False -> 437 - case model.files { 438 - [] -> 439 - html.p([attribute.class("text-base-content")], [ 440 - html.text("No files found"), 441 - ]) 442 - files -> 443 - html.table( 444 - [attribute.class("table w-full bg-base-100 shadow-md rounded")], 476 + html.div([attribute.class("max-w-7xl mx-auto")], [ 477 + html.div([attribute.class("mb-8")], [ 478 + html.h2([attribute.class("text-3xl font-bold text-primary mb-2")], [ 479 + html.text("Your Files"), 480 + ]), 481 + html.p([attribute.class("text-base-content opacity-70")], [ 482 + html.text("Manage your uploaded media files"), 483 + ]), 484 + ]), 485 + case model.is_loading { 486 + True -> 487 + html.div([attribute.class("flex justify-center items-center py-12")], [ 488 + html.div( 445 489 [ 446 - html.thead([], [ 447 - html.tr([], [ 448 - html.th([attribute.class("px-4 py-2 text-left")], [ 449 - html.text("Filename"), 450 - ]), 451 - html.th([attribute.class("px-4 py-2 text-left")], [ 452 - html.text("Size"), 453 - ]), 454 - html.th([attribute.class("px-4 py-2 text-left")], [ 455 - html.text("Owner"), 456 - ]), 457 - html.th([attribute.class("px-4 py-2 text-left")], [ 458 - html.text("Actions"), 459 - ]), 490 + attribute.class( 491 + "loading loading-spinner loading-lg text-primary", 492 + ), 493 + ], 494 + [], 495 + ), 496 + html.span([attribute.class("ml-4 text-base-content")], [ 497 + html.text("Loading files..."), 498 + ]), 499 + ]) 500 + False -> 501 + case model.files { 502 + [] -> 503 + html.div([attribute.class("card bg-base-100 shadow-xl")], [ 504 + html.div([attribute.class("card-body text-center py-16")], [ 505 + html.div([attribute.class("text-6xl opacity-20 mb-4")], [ 506 + html.text("๐Ÿ“"), 507 + ]), 508 + html.h3( 509 + [ 510 + attribute.class( 511 + "text-xl font-semibold text-base-content mb-2", 512 + ), 513 + ], 514 + [ 515 + html.text("No files found"), 516 + ], 517 + ), 518 + html.p([attribute.class("text-base-content opacity-70")], [ 519 + html.text("Upload some files to get started"), 460 520 ]), 461 521 ]), 462 - html.tbody( 463 - [], 464 - list.map(files, fn(file) { 465 - html.tr([], [ 466 - html.td([attribute.class("border px-4 py-2")], [ 467 - html.text(file.filename), 468 - ]), 469 - html.td([attribute.class("border px-4 py-2")], [ 470 - html.text(file_size_display(file.size_mb)), 471 - ]), 472 - html.td([attribute.class("border px-4 py-2")], [ 473 - html.text(file.owner), 474 - ]), 475 - html.td([attribute.class("border px-4 py-2")], [ 476 - html.button( 522 + ]) 523 + files -> 524 + html.div([attribute.class("card bg-base-100 shadow-xl")], [ 525 + html.div([attribute.class("overflow-x-auto")], [ 526 + html.table([attribute.class("table table-zebra w-full")], [ 527 + html.thead([], [ 528 + html.tr([], [ 529 + html.th( 530 + [ 531 + attribute.class( 532 + "bg-base-200 text-base-content font-semibold", 533 + ), 534 + ], 535 + [ 536 + html.text("File"), 537 + ], 538 + ), 539 + html.th( 540 + [ 541 + attribute.class( 542 + "bg-base-200 text-base-content font-semibold", 543 + ), 544 + ], 545 + [ 546 + html.text("Size"), 547 + ], 548 + ), 549 + html.th( 550 + [ 551 + attribute.class( 552 + "bg-base-200 text-base-content font-semibold", 553 + ), 554 + ], 555 + [ 556 + html.text("Owner"), 557 + ], 558 + ), 559 + html.th( 560 + [ 561 + attribute.class( 562 + "bg-base-200 text-base-content font-semibold", 563 + ), 564 + ], 477 565 [ 478 - attribute.class("btn btn-error"), 479 - event.on_click(UserClickedDeleteFile(file.id)), 566 + html.text("Actions"), 480 567 ], 481 - [html.text("Delete")], 482 568 ), 483 569 ]), 484 - ]) 485 - }), 486 - ), 570 + ]), 571 + html.tbody( 572 + [], 573 + list.map(files, fn(file) { 574 + html.tr( 575 + [ 576 + attribute.class( 577 + "hover:bg-base-200 transition duration-200", 578 + ), 579 + ], 580 + [ 581 + html.td( 582 + [attribute.class("font-medium text-base-content")], 583 + [ 584 + html.div( 585 + [attribute.class("flex items-center gap-3")], 586 + [ 587 + html.a( 588 + [ 589 + attribute.class( 590 + "font-medium link link-primary", 591 + ), 592 + attribute.href( 593 + window.origin() <> "/file/" <> file.id, 594 + ), 595 + ], 596 + [html.text(file.filename)], 597 + ), 598 + ], 599 + ), 600 + ], 601 + ), 602 + html.td([attribute.class("text-base-content")], [ 603 + html.div([attribute.class("badge badge-ghost")], [ 604 + html.text(file_size_display(file.size_mb)), 605 + ]), 606 + ]), 607 + html.td([attribute.class("text-base-content")], [ 608 + html.div( 609 + [attribute.class("flex items-center gap-2")], 610 + [ 611 + html.div( 612 + [attribute.class("avatar placeholder")], 613 + [ 614 + html.div( 615 + [ 616 + attribute.class( 617 + "bg-primary text-primary-content rounded-full w-6", 618 + ), 619 + ], 620 + [ 621 + html.span( 622 + [attribute.class("text-xs")], 623 + [ 624 + html.text( 625 + string.slice(file.owner, 0, 1) 626 + |> string.uppercase, 627 + ), 628 + ], 629 + ), 630 + ], 631 + ), 632 + ], 633 + ), 634 + html.span([attribute.class("text-sm")], [ 635 + html.text(file.owner), 636 + ]), 637 + ], 638 + ), 639 + ]), 640 + html.td([], [ 641 + html.button( 642 + [ 643 + attribute.class( 644 + "btn btn-error btn-sm hover:btn-error-focus transition duration-200", 645 + ), 646 + event.on_click(UserClickedDeleteFile(file.id)), 647 + ], 648 + [ 649 + html.svg( 650 + [ 651 + attribute.class("w-4 h-4 mr-1"), 652 + attribute.attribute("fill", "none"), 653 + attribute.attribute( 654 + "stroke", 655 + "currentColor", 656 + ), 657 + attribute.attribute( 658 + "viewBox", 659 + "0 0 24 24", 660 + ), 661 + ], 662 + [ 663 + svg.path([ 664 + attribute.attribute( 665 + "stroke-linecap", 666 + "round", 667 + ), 668 + attribute.attribute( 669 + "stroke-linejoin", 670 + "round", 671 + ), 672 + attribute.attribute("stroke-width", "2"), 673 + attribute.attribute( 674 + "d", 675 + "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16", 676 + ), 677 + ]), 678 + ], 679 + ), 680 + html.text("Delete"), 681 + ], 682 + ), 683 + ]), 684 + ], 685 + ) 686 + }), 687 + ), 688 + ]), 689 + ]), 690 + ]) 691 + } 692 + }, 693 + case model.error_message { 694 + option.Some(error) -> 695 + html.div([attribute.class("alert alert-error mt-6")], [ 696 + html.svg( 697 + [ 698 + attribute.class("stroke-current flex-shrink-0 h-6 w-6"), 699 + attribute.attribute("fill", "none"), 700 + attribute.attribute("viewBox", "0 0 24 24"), 487 701 ], 488 - ) 489 - } 490 - }, 491 - case model.error_message { 492 - option.Some(error) -> 493 - html.p([attribute.class("mt-4 text-error text-sm")], [ 494 - html.text(error), 495 - ]) 496 - option.None -> html.text("") 497 - }, 702 + [ 703 + svg.path([ 704 + attribute.attribute("stroke-linecap", "round"), 705 + attribute.attribute("stroke-linejoin", "round"), 706 + attribute.attribute("stroke-width", "2"), 707 + attribute.attribute( 708 + "d", 709 + "M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z", 710 + ), 711 + ]), 712 + ], 713 + ), 714 + html.span([], [html.text(error)]), 715 + ]) 716 + option.None -> html.text("") 717 + }, 718 + ]), 498 719 ]) 499 720 } 500 721 501 722 fn view(model: Model) -> Element(Msg) { 502 - html.div([attribute.class("container mx-auto")], [ 723 + html.div([attribute.class("min-h-screen bg-base-100")], [ 503 724 html.div( 725 + [attribute.class("navbar bg-primary text-primary-content shadow-lg")], 504 726 [ 505 - attribute.class( 506 - "bg-primary text-primary-content p-4 flex justify-between items-center", 507 - ), 508 - ], 509 - [ 510 - html.h1([attribute.class("text-xl font-bold")], [ 511 - html.text("StrawMEDIAjuice Dashboard"), 727 + html.div([attribute.class("navbar-start")], [ 728 + html.h1([attribute.class("text-xl font-bold")], [ 729 + html.text("StrawMEDIAjuice Dashboard"), 730 + ]), 512 731 ]), 513 - case model.username { 514 - option.Some(username) -> 515 - html.div([attribute.class("flex items-center")], [ 516 - html.p([attribute.class("mr-4")], [ 517 - html.text("Welcome, "), 518 - html.span([attribute.class("font-bold")], [ 519 - html.text(username), 732 + html.div([attribute.class("navbar-end")], [ 733 + case model.username { 734 + option.Some(username) -> 735 + html.div([attribute.class("flex items-center gap-4")], [ 736 + html.div([attribute.class("flex items-center gap-2")], [ 737 + html.div([attribute.class("avatar placeholder")], [ 738 + html.div( 739 + [ 740 + attribute.class( 741 + "bg-primary-focus text-primary-content rounded-full w-8", 742 + ), 743 + ], 744 + [ 745 + html.span([attribute.class("text-sm")], [ 746 + html.text( 747 + string.slice(username, 0, 1) |> string.uppercase, 748 + ), 749 + ]), 750 + ], 751 + ), 752 + ]), 753 + html.span([attribute.class("text-sm font-medium")], [ 754 + html.text("Welcome, " <> username), 755 + ]), 520 756 ]), 521 - ]), 522 - html.button( 523 - [ 524 - attribute.class("btn btn-error"), 525 - event.on_click(UserClickedLogout), 526 - ], 527 - [html.text("Logout")], 528 - ), 529 - ]) 530 - option.None -> html.text("") 531 - }, 757 + html.button( 758 + [ 759 + attribute.class( 760 + "btn btn-error btn-sm hover:btn-error-focus transition duration-200", 761 + ), 762 + event.on_click(UserClickedLogout), 763 + ], 764 + [ 765 + html.svg( 766 + [ 767 + attribute.class("w-4 h-4 mr-1"), 768 + attribute.attribute("fill", "none"), 769 + attribute.attribute("stroke", "currentColor"), 770 + attribute.attribute("viewBox", "0 0 24 24"), 771 + ], 772 + [ 773 + svg.path([ 774 + attribute.attribute("stroke-linecap", "round"), 775 + attribute.attribute("stroke-linejoin", "round"), 776 + attribute.attribute("stroke-width", "2"), 777 + attribute.attribute( 778 + "d", 779 + "M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1", 780 + ), 781 + ]), 782 + ], 783 + ), 784 + html.text("Logout"), 785 + ], 786 + ), 787 + ]) 788 + option.None -> html.text("") 789 + }, 790 + ]), 532 791 ], 533 792 ), 534 793 case model.username {