This commit is contained in:
24c02 2026-01-29 17:06:26 -05:00
parent 2100894475
commit bbf815cf30
9 changed files with 166 additions and 174 deletions

View file

@ -12,7 +12,7 @@ class Components::Admin::Search::Index < Components::Base
end
def view_template
div(class: "container-lg p-4") do
div(style: "max-width: 1200px; margin: 0 auto; padding: 24px;") do
header_section
tabs_section
search_form
@ -23,9 +23,9 @@ class Components::Admin::Search::Index < Components::Base
private
def header_section
div(class: "mb-4") do
h1(class: "h2 mb-1") { "Admin Search" }
p(class: "color-fg-muted f5") do
header(style: "margin-bottom: 24px;") do
h1(style: "font-size: 2rem; font-weight: 600; margin: 0;") { "Admin Search" }
p(style: "color: var(--fgColor-muted, #656d76); margin: 8px 0 0; font-size: 14px;") do
"Search users and uploads by ID, email, filename, URL, etc."
end
end
@ -40,20 +40,20 @@ class Components::Admin::Search::Index < Components::Base
end
def search_form
div(class: "mb-4 mt-3") do
form_with url: admin_search_path, method: :get, class: "d-flex gap-2" do
div(style: "margin-bottom: 24px; margin-top: 16px;") do
form_with url: admin_search_path, method: :get, style: "display: flex; gap: 8px;" do
input(type: "hidden", name: "type", value: @type)
input(
type: "search",
name: "q",
placeholder: search_placeholder,
value: @query,
class: "form-control flex-1",
style: "max-width: 600px;",
class: "form-control",
style: "flex: 1; max-width: 600px;",
autofocus: true
)
render Primer::Beta::Button.new(type: :submit, scheme: :primary) do |button|
button.with_leading_visual_icon(icon: :search)
button(type: "submit", class: "btn btn-primary") do
render Primer::Beta::Octicon.new(icon: :search, mr: 1)
plain "Search"
end
end
@ -78,10 +78,10 @@ class Components::Admin::Search::Index < Components::Base
end
def users_section
div(class: "mb-5") do
h2(class: "h4 mb-3") do
div(style: "margin-bottom: 32px;") do
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") do
plain "Users "
render Primer::Beta::Label.new(scheme: :secondary) { @users.size.to_s }
render(Primer::Beta::Label.new(scheme: :secondary)) { plain @users.size.to_s }
end
render Primer::Beta::BorderBox.new do |box|
@users.each do |user|
@ -94,40 +94,25 @@ class Components::Admin::Search::Index < Components::Base
end
def user_row(user)
div(class: "d-flex flex-justify-between flex-items-center") do
div(style: "display: flex; justify-content: space-between; align-items: center;") do
div do
div(class: "text-bold") { user.name || "Unnamed" }
div(class: "f6 color-fg-muted") do
div(style: "font-weight: 500;") { user.name || "Unnamed" }
div(style: "font-size: 12px; color: var(--fgColor-muted);") do
plain user.email
plain " · "
code(class: "f6") { user.public_id }
code(style: "font-size: 11px;") { user.public_id }
end
end
div(class: "d-flex flex-items-center gap-3") do
div(class: "text-right f6 color-fg-muted") do
div(style: "display: flex; align-items: center; gap: 16px;") do
div(style: "text-align: right; font-size: 12px; color: var(--fgColor-muted);") do
div { "#{user.total_files} files" }
div { user.total_storage_formatted }
end
if user.is_admin?
render Primer::Beta::Label.new(scheme: :accent) { "ADMIN" }
render(Primer::Beta::Label.new(scheme: :accent)) { plain "ADMIN" }
end
div(class: "d-flex gap-2") do
render Primer::Beta::IconButton.new(
icon: :eye,
"aria-label": "View user",
href: admin_user_path(user),
tag: :a,
size: :small
)
button_to admin_user_path(user), method: :delete, class: "d-inline", data: { turbo_confirm: "Delete user #{user.name || user.email} and all their uploads?" } do
render Primer::Beta::IconButton.new(
icon: :trash,
"aria-label": "Delete user",
scheme: :danger,
size: :small,
tag: :span
)
end
link_to admin_user_path(user), class: "btn btn-sm", title: "View user" do
render Primer::Beta::Octicon.new(icon: :eye, size: :small)
end
end
end
@ -135,9 +120,9 @@ class Components::Admin::Search::Index < Components::Base
def uploads_section
div do
h2(class: "h4 mb-3") do
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") do
plain "Uploads "
render Primer::Beta::Label.new(scheme: :secondary) { @uploads.size.to_s }
render(Primer::Beta::Label.new(scheme: :secondary)) { plain @uploads.size.to_s }
end
render Primer::Beta::BorderBox.new do |box|
@uploads.each do |upload|

View file

@ -9,7 +9,7 @@ class Components::Admin::Users::Show < Components::Base
end
def view_template
div(class: "container-md p-4") do
div(style: "max-width: 800px; margin: 0 auto; padding: 24px;") do
header_section
stats_section
api_keys_section
@ -20,29 +20,29 @@ class Components::Admin::Users::Show < Components::Base
private
def header_section
div(class: "mb-4") do
div(class: "d-flex flex-justify-between flex-items-start") do
header(style: "margin-bottom: 24px;") do
div(style: "display: flex; justify-content: space-between; align-items: flex-start;") do
div do
div(class: "d-flex flex-items-center gap-2 mb-1") do
h1(class: "h2 mb-0") { @user.name || "Unnamed User" }
div(style: "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;") do
h1(style: "font-size: 2rem; font-weight: 600; margin: 0;") { @user.name || "Unnamed User" }
if @user.is_admin?
render Primer::Beta::Label.new(scheme: :accent) { "ADMIN" }
render(Primer::Beta::Label.new(scheme: :accent)) { plain "ADMIN" }
end
end
div(class: "color-fg-muted f5") do
p(style: "color: var(--fgColor-muted, #656d76); margin: 0; font-size: 14px;") do
plain @user.email
plain " · "
code(class: "f6") { @user.public_id }
code(style: "font-size: 12px;") { @user.public_id }
end
if @user.slack_id.present?
div(class: "color-fg-muted f6 mt-1") do
p(style: "color: var(--fgColor-muted); margin: 4px 0 0; font-size: 12px;") do
plain "Slack: "
code { @user.slack_id }
end
end
end
render Primer::Beta::Button.new(href: admin_search_path, tag: :a) do |button|
button.with_leading_visual_icon(icon: :"arrow-left")
link_to admin_search_path, class: "btn" do
render Primer::Beta::Octicon.new(icon: :"arrow-left", mr: 1)
plain "Back to Search"
end
end
@ -50,7 +50,7 @@ class Components::Admin::Users::Show < Components::Base
end
def stats_section
div(class: "d-grid gap-3 mb-4", style: "grid-template-columns: repeat(3, 1fr);") do
div(style: "display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;") do
stat_card("Total Files", @user.total_files.to_s)
stat_card("Total Storage", @user.total_storage_formatted)
stat_card("Member Since", @user.created_at.strftime("%b %d, %Y"))
@ -60,8 +60,8 @@ class Components::Admin::Users::Show < Components::Base
def stat_card(label, value)
render Primer::Beta::BorderBox.new do |box|
box.with_body(padding: :normal) do
div(class: "f6 color-fg-muted") { label }
div(class: "h3 mt-1") { value }
div(style: "font-size: 12px; color: var(--fgColor-muted);") { label }
div(style: "font-size: 24px; font-weight: 600; margin-top: 4px;") { value }
end
end
end
@ -70,8 +70,8 @@ class Components::Admin::Users::Show < Components::Base
api_keys = @user.api_keys.recent
return if api_keys.empty?
div(class: "mb-4") do
h2(class: "h4 mb-3") { "API Keys" }
div(style: "margin-bottom: 24px;") do
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") { "API Keys" }
render Primer::Beta::BorderBox.new do |box|
api_keys.each do |api_key|
box.with_row do
@ -83,21 +83,21 @@ class Components::Admin::Users::Show < Components::Base
end
def api_key_row(api_key)
div(class: "d-flex flex-justify-between flex-items-center") do
div(style: "display: flex; justify-content: space-between; align-items: center;") do
div do
div(class: "text-bold") { api_key.name }
code(class: "f6 color-fg-muted") { api_key.masked_token }
div(style: "font-weight: 500;") { api_key.name }
code(style: "font-size: 12px; color: var(--fgColor-muted);") { api_key.masked_token }
end
div(class: "d-flex flex-items-center gap-3") do
div(style: "display: flex; align-items: center; gap: 12px;") do
if api_key.revoked?
render Primer::Beta::Label.new(scheme: :danger) { "REVOKED" }
render(Primer::Beta::Label.new(scheme: :danger)) { plain "REVOKED" }
else
render Primer::Beta::Label.new(scheme: :success) { "ACTIVE" }
button_to helpers.admin_api_key_path(api_key), method: :delete, class: "d-inline", data: { confirm: "Revoke this API key?" } do
render Primer::Beta::Button.new(scheme: :danger, size: :small, tag: :span) { "Revoke" }
render(Primer::Beta::Label.new(scheme: :success)) { plain "ACTIVE" }
button_to helpers.admin_api_key_path(api_key), method: :delete, class: "btn btn-sm btn-danger", data: { confirm: "Revoke this API key?" } do
plain "Revoke"
end
end
span(class: "f6 color-fg-muted") { api_key.created_at.strftime("%b %d, %Y") }
span(style: "font-size: 12px; color: var(--fgColor-muted);") { api_key.created_at.strftime("%b %d, %Y") }
end
end
end
@ -107,7 +107,7 @@ class Components::Admin::Users::Show < Components::Base
return if uploads.empty?
div do
h2(class: "h4 mb-3") { "Recent Uploads" }
h2(style: "font-size: 1.25rem; font-weight: 600; margin-bottom: 12px;") { "Recent Uploads" }
render Primer::Beta::BorderBox.new do |box|
uploads.each do |upload|
box.with_row do

View file

@ -8,14 +8,14 @@ class Components::APIKeys::Row < Components::Base
end
def view_template
div(class: "d-flex flex-justify-between flex-items-start gap-3") do
div(class: "flex-1 min-width-0") do
div(class: "d-flex flex-items-center gap-2 mb-1") do
div(style: "display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;") do
div(style: "flex: 1; min-width: 0;") do
div(style: "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;") do
render Primer::Beta::Octicon.new(icon: :key, size: :small, color: :muted)
span(class: "f5 text-bold") { api_key.name }
span(style: "font-size: 14px; font-weight: 500;") { api_key.name }
end
code(class: "f6 color-fg-muted") { api_key.masked_token }
div(class: "f6 color-fg-muted mt-1") do
code(style: "font-size: 12px; color: var(--fgColor-muted, #656d76);") { api_key.masked_token }
div(style: "font-size: 12px; color: var(--fgColor-muted, #656d76); margin-top: 4px;") do
plain "Created #{time_ago_in_words(api_key.created_at)} ago"
end
end
@ -34,17 +34,19 @@ class Components::APIKeys::Row < Components::Base
render Primer::Beta::Octicon.new(icon: :trash)
end
dialog.with_header(variant: :large) do
h1(class: "h3") { "Revoke \"#{api_key.name}\"?" }
h1(style: "margin: 0;") { "Revoke \"#{api_key.name}\"?" }
end
dialog.with_body do
p(class: "color-fg-muted") do
p(style: "margin: 0;") do
plain "This action cannot be undone. Any applications using this API key will immediately lose access."
end
end
dialog.with_footer do
div(class: "d-flex flex-justify-end gap-2") do
form_with url: api_key_path(api_key), method: :delete, class: "d-inline" do
render Primer::Beta::Button.new(type: :submit, scheme: :danger) { "Revoke key" }
div(style: "display: flex; justify-content: flex-end; gap: 8px;") do
form_with url: api_key_path(api_key), method: :delete, style: "display: inline;" do
button(type: "submit", class: "btn btn-danger") do
plain "Revoke key"
end
end
end
end

View file

@ -9,7 +9,7 @@ class Components::APIKeys::Index < Components::Base
end
def view_template
div(class: "container-lg p-4") do
div(style: "max-width: 1200px; margin: 0 auto; padding: 24px;") do
header_section
new_token_alert if new_token
create_form
@ -22,11 +22,11 @@ class Components::APIKeys::Index < Components::Base
attr_reader :api_keys, :new_token
def header_section
div(class: "mb-4") do
h1(class: "h2 mb-1") { "API Keys" }
p(class: "color-fg-muted f5") do
header(style: "margin-bottom: 24px;") do
h1(style: "font-size: 2rem; font-weight: 600; margin: 0;") { "API Keys" }
p(style: "color: var(--fgColor-muted, #656d76); margin: 8px 0 0; font-size: 14px;") do
plain "Manage your API keys for programmatic access. "
a(href: "/docs/api", class: "Link") { "View API documentation" }
a(href: "/docs/api", style: "color: var(--fgColor-accent, #0969da);") { "View API documentation" }
end
end
end
@ -35,9 +35,11 @@ class Components::APIKeys::Index < Components::Base
render Primer::Beta::Flash.new(scheme: :success, mb: 4) do |component|
component.with_icon(icon: :check)
div do
p(class: "text-bold mb-1") { "API key created successfully!" }
p(class: "mb-2") { "Copy your API key now. You won't be able to see it again!" }
code(class: "d-block p-2 color-bg-subtle rounded-2 f5 text-mono") { new_token }
p(style: "margin: 0 0 8px; font-weight: 600;") { "API key created successfully!" }
p(style: "margin: 0 0 8px;") { "Copy your API key now. You won't be able to see it again!" }
code(style: "display: block; padding: 12px; background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default); border-radius: 6px; font-size: 14px; word-break: break-all;") do
plain new_token
end
end
end
end
@ -45,23 +47,25 @@ class Components::APIKeys::Index < Components::Base
def create_form
render Primer::Beta::BorderBox.new(mb: 4) do |box|
box.with_header do
h2(class: "f5 text-bold") { "Create new API key" }
h2(style: "font-size: 14px; font-weight: 600; margin: 0;") { "Create new API key" }
end
box.with_body do
form_with url: api_keys_path, method: :post do
div(class: "mb-3", style: "max-width: 400px;") do
label(for: "api_key_name", class: "f5 text-bold d-block mb-2") { "Key name" }
div(style: "margin-bottom: 12px; max-width: 400px;") do
label(for: "api_key_name", style: "display: block; font-size: 14px; font-weight: 600; margin-bottom: 8px;") do
plain "Key name"
end
input(
type: "text",
name: "api_key[name]",
id: "api_key_name",
placeholder: "e.g., The Coolest App That's Ever Lived",
placeholder: "e.g., My App",
required: true,
class: "form-control width-full"
class: "form-control"
)
end
render Primer::Beta::Button.new(type: :submit, scheme: :primary) do |button|
button.with_leading_visual_icon(icon: :key)
button(type: "submit", class: "btn btn-primary") do
render Primer::Beta::Octicon.new(icon: :key, mr: 1)
plain "Create key"
end
end
@ -71,7 +75,7 @@ class Components::APIKeys::Index < Components::Base
def api_keys_list
div do
h2(class: "h4 mb-3") { "Your API keys" }
h2(style: "font-size: 1.25rem; font-weight: 600; margin: 0 0 16px;") { "Your API keys" }
if api_keys.any?
render Primer::Beta::BorderBox.new do |box|

View file

@ -61,9 +61,11 @@ class Components::StaticPages::Home < Components::StaticPages::Base
end
def recent_uploads_list
div(style: "background: var(--bgColor-default, #fff); border: 1px solid var(--borderColor-default, #d0d7de); border-radius: 6px; overflow: hidden;") do
stats[:recent_uploads].each_with_index do |upload, index|
render Components::Uploads::Row.new(upload: upload, index: index, compact: true)
render Primer::Beta::BorderBox.new do |box|
stats[:recent_uploads].each do |upload|
box.with_row do
render Components::Uploads::Row.new(upload: upload, compact: true)
end
end
end
end

View file

@ -11,7 +11,7 @@ class Components::Uploads::Row < Components::Base
end
def view_template
div(class: "d-flex flex-justify-between flex-items-#{compact ? 'center' : 'start'} gap-3") do
div(style: "display: flex; justify-content: space-between; align-items: #{compact ? 'center' : 'flex-start'}; gap: 16px;") do
if compact
compact_content
else
@ -25,50 +25,42 @@ class Components::Uploads::Row < Components::Base
attr_reader :upload, :compact, :admin
def compact_content
div(class: "flex-1 min-width-0") do
div(class: "f5 text-bold text-truncate") do
div(style: "flex: 1; min-width: 0;") do
div(style: "font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;") do
render Primer::Beta::Octicon.new(icon: file_icon_for(upload.content_type), size: :small, mr: 1)
plain upload.filename.to_s
end
div(class: "f6 color-fg-muted mt-1") do
div(style: "font-size: 12px; color: var(--fgColor-muted, #656d76); margin-top: 4px;") do
plain "#{upload.human_file_size}#{time_ago_in_words(upload.created_at)} ago"
end
end
div(class: "d-flex gap-2 flex-items-center") do
render Primer::Beta::Button.new(
href: upload.cdn_url,
tag: :a,
size: :small,
target: "_blank",
rel: "noopener"
) { "View" }
div(style: "display: flex; gap: 8px; align-items: center;") do
a(href: upload.cdn_url, target: "_blank", rel: "noopener", class: "btn btn-sm") do
plain "View"
end
render_delete_dialog
end
end
def full_content
div(class: "flex-1 min-width-0") do
div(class: "d-flex flex-items-center gap-2 mb-1") do
div(style: "flex: 1; min-width: 0;") do
div(style: "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;") do
render Primer::Beta::Octicon.new(icon: file_icon_for(upload.content_type), size: :small)
span(class: "f5 text-bold text-truncate") { upload.filename.to_s }
render Primer::Beta::Label.new(scheme: :secondary, size: :small) { upload.provenance.titleize }
div(style: "font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;") do
plain upload.filename.to_s
end
render(Primer::Beta::Label.new(scheme: :secondary, size: :small)) { plain upload.provenance.titleize }
end
div(class: "f6 color-fg-muted") do
div(style: "font-size: 12px; color: var(--fgColor-muted, #656d76);") do
plain "#{upload.human_file_size}#{upload.content_type}#{time_ago_in_words(upload.created_at)} ago"
end
end
div(class: "d-flex gap-2 flex-items-center") do
render Primer::Beta::Button.new(
href: upload.cdn_url,
tag: :a,
size: :small,
target: "_blank",
rel: "noopener"
) do |button|
button.with_leading_visual_icon(icon: :link)
div(style: "display: flex; gap: 8px; align-items: center;") do
a(href: upload.cdn_url, target: "_blank", rel: "noopener", class: "btn btn-sm") do
render Primer::Beta::Octicon.new(icon: :link, mr: 1)
plain "View"
end
@ -82,17 +74,19 @@ class Components::Uploads::Row < Components::Base
render Primer::Beta::Octicon.new(icon: :trash)
end
dialog.with_header(variant: :large) do
h1(class: "h3") { "Delete #{upload.filename}?" }
h1(style: "margin: 0;") { "Delete #{upload.filename}?" }
end
dialog.with_body do
p(class: "color-fg-muted") do
p(style: "margin: 0;") do
plain "This action cannot be undone. The file will be permanently removed from the CDN."
end
end
dialog.with_footer do
div(class: "d-flex flex-justify-end gap-2") do
form_with url: (admin ? admin_upload_path(upload) : upload_path(upload)), method: :delete, class: "d-inline" do
render Primer::Beta::Button.new(type: :submit, scheme: :danger) { "Delete" }
div(style: "display: flex; justify-content: flex-end; gap: 8px;") do
form_with url: (admin ? admin_upload_path(upload) : upload_path(upload)), method: :delete, style: "display: inline;" do
button(type: "submit", class: "btn btn-danger") do
plain "Delete"
end
end
end
end

View file

@ -12,7 +12,7 @@ class Components::Uploads::Index < Components::Base
end
def view_template
div(class: "container-lg p-4") do
div(style: "max-width: 1200px; margin: 0 auto; padding: 24px;") do
header_section
search_section
uploads_list
@ -25,35 +25,35 @@ class Components::Uploads::Index < Components::Base
attr_reader :uploads, :query
def header_section
div(class: "d-flex flex-justify-between flex-items-center mb-4") do
header(style: "display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;") do
div do
h1(class: "h2 mb-1") { "Your Uploads" }
p(class: "color-fg-muted f5 mb-0") do
h1(style: "font-size: 2rem; font-weight: 600; margin: 0;") { "Your Uploads" }
p(style: "color: var(--fgColor-muted, #656d76); margin: 8px 0 0; font-size: 14px;") do
count = uploads.respond_to?(:total_count) ? uploads.total_count : uploads.size
plain "#{count} file#{count == 1 ? '' : 's'}"
end
end
render Primer::Beta::Button.new(href: new_upload_path, tag: :a, scheme: :primary) do |button|
button.with_leading_visual_icon(icon: :upload)
link_to new_upload_path, class: "btn btn-primary" do
render Primer::Beta::Octicon.new(icon: :upload, mr: 1)
plain "Upload File"
end
end
end
def search_section
div(class: "mb-4") do
form_with url: uploads_path, method: :get, class: "d-flex gap-2" do
div(style: "margin-bottom: 24px;") do
form_with url: uploads_path, method: :get, style: "display: flex; gap: 8px;" do
input(
type: "search",
name: "query",
placeholder: "Search files...",
value: query,
class: "form-control flex-1",
style: "max-width: 400px;"
class: "form-control",
style: "flex: 1; max-width: 400px;"
)
render Primer::Beta::Button.new(type: :submit) do |button|
button.with_leading_visual_icon(icon: :search)
button(type: "submit", class: "btn") do
render Primer::Beta::Octicon.new(icon: :search, mr: 1)
plain "Search"
end
end
@ -93,7 +93,7 @@ class Components::Uploads::Index < Components::Base
end
def pagination_section
div(class: "mt-4 text-center") do
div(style: "margin-top: 24px; text-align: center;") do
paginate uploads
end
end

View file

@ -5,7 +5,7 @@ class Components::Uploads::New < Components::Base
include Phlex::Rails::Helpers::LinkTo
def view_template
div(class: "container-lg p-4") do
div(style: "max-width: 1200px; margin: 0 auto; padding: 24px;") do
header_section
upload_form
end
@ -14,14 +14,14 @@ class Components::Uploads::New < Components::Base
private
def header_section
div(class: "mb-4") do
div(class: "d-flex flex-items-center gap-2 mb-2") do
render Primer::Beta::Button.new(href: uploads_path, tag: :a, scheme: :invisible, size: :small) do
header(style: "margin-bottom: 32px;") do
div(style: "display: flex; align-items: center; gap: 8px; margin-bottom: 16px;") do
link_to uploads_path, style: "color: var(--fgColor-muted, #656d76); text-decoration: none;" do
render Primer::Beta::Octicon.new(icon: :"arrow-left")
end
h1(class: "h2 mb-0") { "Upload File" }
h1(style: "font-size: 2rem; font-weight: 600; margin: 0;") { "Upload File" }
end
p(class: "color-fg-muted f5 mb-0") do
p(style: "color: var(--fgColor-muted, #656d76); margin: 0; font-size: 14px;") do
plain "Drop a file anywhere on this page or click to browse"
end
end
@ -30,20 +30,20 @@ class Components::Uploads::New < Components::Base
def upload_form
form_with url: uploads_path, method: :post, multipart: true, data: { dropzone_form: true } do
div(
class: "upload-area rounded-3 p-6 text-center",
style: "border: 3px dashed var(--borderColor-default); transition: all 0.2s ease;"
class: "upload-area",
style: upload_area_styles
) do
div(class: "py-6") do
render Primer::Beta::Octicon.new(icon: :upload, size: :medium, color: :muted)
h2(class: "h1 mt-4 mb-3") { "Drag & Drop" }
p(class: "color-fg-muted f4 mb-4") do
div(style: "text-align: center;") do
render Primer::Beta::Octicon.new(icon: :upload, size: :medium)
h2(style: "font-size: 32px; font-weight: 600; margin: 24px 0 16px;") { "Drag & Drop" }
p(style: "color: var(--fgColor-muted, #656d76); margin: 0 0 32px; font-size: 16px;") do
plain "Drop a file anywhere on this page to upload instantly"
end
label(
for: "file-input",
class: "btn btn-primary btn-large",
style: "cursor: pointer;"
style: "cursor: pointer; display: inline-block; font-size: 16px; padding: 12px 24px;"
) do
render Primer::Beta::Octicon.new(icon: :file, mr: 2)
plain "Choose File"
@ -54,17 +54,17 @@ class Components::Uploads::New < Components::Base
name: "file",
id: "file-input",
data: { dropzone_input: true },
class: "d-none"
style: "display: none;"
)
end
end
render Primer::Beta::BorderBox.new(mt: 5) do |box|
box.with_header do
h3(class: "f5 text-bold") { "How it works" }
h3(style: "font-size: 16px; font-weight: 600; margin: 0;") { "How it works" }
end
box.with_body do
ul(class: "color-fg-muted f5 pl-4 mb-0", style: "line-height: 1.8;") do
ul(style: "margin: 0; padding-left: 24px; font-size: 14px; color: var(--fgColor-muted, #656d76); line-height: 1.8;") do
li { "Drag and drop a file anywhere on this page for instant upload" }
li { "Or click the button above to browse and select a file" }
li { "Files are stored securely and accessible via CDN URLs" }
@ -74,4 +74,15 @@ class Components::Uploads::New < Components::Base
end
end
end
def upload_area_styles
<<~CSS.strip
border: 3px dashed var(--borderColor-default, #d0d7de);
border-radius: 16px;
padding: 96px 48px;
background: var(--bgColor-default, #fff);
text-align: center;
transition: all 0.2s ease;
CSS
end
end

View file

@ -4,46 +4,40 @@ icon: rocket
order: 1
---
# Getting Started~! ✨
# Getting Started
Welcome to Hack Club CDN, nya~! This guide will help you get your paws on file hosting in no time!
Welcome to Hack Club CDN. This guide will help you get started with file hosting.
## Sign In
Click the **Sign in with Hack Club** button on the homepage to authenticate with your Hack Club account! It's super easy, I promise~ 🐱
Click the **Sign in with Hack Club** button on the homepage to authenticate with your Hack Club account.
## Uploading Files
Once you're logged in, here's how to share your creations with the world:
Once you're logged in:
1. Pounce over to **My Files**
2. Click **Upload** or drag and drop files (like a cat batting at a toy~)
3. Your file gets a purrmanent URL! How neat is that?
1. Navigate to **My Files**
2. Click **Upload** or drag and drop files
3. Your file receives a permanent URL
## Sharing Files
Every uploaded file gets its own special URL you can share anywhere, nya~!
Every uploaded file gets a unique URL you can share anywhere:
```
https://cdn.hackclub.com/your-file-id
```
Copy it, share it, do whatever you want with it! It's *your* file now~ ✨
## File Limits
- Maximum file size: varies by account (no eating too much at once!)
- Supported formats: images, documents, archives, and more~
- Maximum file size: varies by account
- Supported formats: images, documents, archives, and more
## API Usage
For those of you who want to get technical (*adjusts glasses*), check out the [API documentation](https://github.com/hackclub/cdn#-api-usage) for programmatic uploads!
For programmatic uploads, check out the [API documentation](/docs/api).
## Need Help?
Don't be shy, nya~! We're here for you:
- Join the [#cdn channel on Slack](https://hackclub.enterprise.slack.com/archives/C016DEDUL87) and say hi!
- Open an issue on [GitHub](https://github.com/hackclub/cdn/issues) if something's broken
Now go upload something cool! I believe in you~ 💖
- Join the [#cdn-dev channel on Slack](https://hackclub.slack.com/archives/C08RYDPS36V)
- Open an issue on [GitHub](https://github.com/hackclub/cdn/issues)