Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ GEM

PLATFORMS
arm64-darwin-23
arm64-darwin-24
x86_64-darwin-22
x86_64-linux

Expand Down
88 changes: 82 additions & 6 deletions app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
@@ -1,33 +1,109 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
body {
@apply antialiased;
}

h1 {
@apply text-2xl font-bold;
@apply text-4xl md:text-5xl font-bold tracking-tight text-charcoal;
}

h2 {
@apply text-xl font-semibold;
@apply text-2xl md:text-3xl font-semibold tracking-tight text-charcoal;
}

h3 {
@apply text-lg font-medium;
@apply text-xl md:text-2xl font-medium text-charcoal;
}

p {
@apply text-gray-700 leading-relaxed;
}

a {
@apply transition-colors duration-200;
}
}

@layer components {
.btn-primary {
@apply inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-full shadow-soft text-white bg-ruby-500 hover:bg-ruby-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ruby-500 transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5;
}

.btn-secondary {
@apply inline-flex items-center px-6 py-3 border border-ruby-200 text-base font-medium rounded-full text-ruby-600 bg-white hover:bg-ruby-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ruby-500 transition-all duration-200 hover:shadow-md;
}

.card {
@apply bg-white rounded-2xl shadow-soft hover:shadow-xl transition-all duration-300 overflow-hidden;
}

.gradient-text {
@apply bg-gradient-to-r from-ruby-500 to-ruby-700 bg-clip-text text-transparent;
}
}

.coc a {
@apply text-ruby underline;
@apply text-ruby-500 underline hover:text-ruby-600;
}

.coc p {
@apply mb-2;
@apply mb-4;
}

a.external-link {
@apply inline-flex items-center gap-1 text-ruby-500 hover:text-ruby-600;
}

a.external-link::after {
content: '';
display: inline-block;
width: 1em;
height: 1em;
margin-left: 0.25em;
background-size: 1em;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iI2Q2NDA0NSIgY2xhc3M9InNpemUtNCI+CiAgPHBhdGggZD0iTTYuMjIgOC43MmEuNzUuNzUgMCAwIDAgMS4wNiAxLjA2bDUuMjItNS4yMnYxLjY5YS43NS43NSAwIDAgMCAxLjUgMHYtMy41YS43NS43NSAwIDAgMC0uNzUtLjc1aC0zLjVhLjc1Ljc1IDAgMCAwIDAgMS41aDEuNjlMNi4yMiA4LjcyWiIgLz4KICA8cGF0aCBkPSJNMy41IDYuNzVjMC0uNjkuNTYtMS4yNSAxLjI1LTEuMjVIN0EuNzUuNzUgMCAwIDAgNyA0SDQuNzVBMi43NSAyLjc1IDAgMCAwIDIgNi43NXY0LjVBMi43NSAyLjc1IDAgMCAwIDQuNzUgMTRoNC41QTIuNzUgMi43NSAwIDAgMCAxMiAxMS4yNVY5YS43NS43NSAwIDAgMC0xLjUgMHYyLjI1YzAgLjY5LS41NiAxLjI1LTEuMjUgMS4yNWgtNC41Yy0uNjkgMC0xLjI1LS41Ni0xLjI1LTEuMjV2LTQuNVoiIC8+Cjwvc3ZnPgo=");
transition: transform 0.2s ease;
}

a.external-link:hover::after {
transform: translate(2px, -2px);
}

/* Smooth scroll behavior */
html {
scroll-behavior: smooth;
}

/* Custom animations */
.animate-in {
animation: fade-in 0.5s ease-in-out;
}

.animate-up {
animation: slide-up 0.5s ease-out;
}

/* ConvertKit form input overrides for better visibility */
.formkit-form[data-uid="4c3ae9b5e4"] .formkit-input {
@apply px-4 py-3 border border-gray-300 rounded-lg shadow-sm transition-colors;
border-color: rgb(209, 213, 219) !important;
}

.formkit-form[data-uid="4c3ae9b5e4"] .formkit-input:focus {
@apply ring-1 ring-ruby-500 border-ruby-500;
border-color: rgb(214, 64, 69) !important;
}

.formkit-form[data-uid="4c3ae9b5e4"] .formkit-submit {
@apply px-6 py-3 bg-ruby-500 hover:bg-ruby-600 rounded-full transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5;
background-color: rgb(214, 64, 69) !important;
}

.formkit-form[data-uid="4c3ae9b5e4"] .formkit-submit:hover {
background-color: rgb(185, 28, 28) !important;
}
7 changes: 0 additions & 7 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,8 @@ def all
end
end

def show
@event = Event.with_all_rich_text.find_by!(slug: params[:slug])
rescue ActiveRecord::RecordNotFound
redirect_to all_events_path, error: 'Event not found'
end

def past
@events = Event.with_all_rich_text.past
render :index
end

def next
Expand Down
51 changes: 29 additions & 22 deletions app/javascript/controllers/clipboard_controller.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="clipboard"
export default class extends Controller {
static targets = ["button", "source"]
static targets = ["source", "button", "default", "success"]

static values = {
successContent: String,
successDuration: {
type: Number,
default: 2000,
}
successContent: { type: String, default: "✅" },
successDuration: { type: Number, default: 2000 }
}

connect() {
Expand All @@ -19,23 +17,32 @@ export default class extends Controller {

copy(event) {
event.preventDefault()

const text = this.sourceTarget.innerHTML || this.sourceTarget.value

navigator.clipboard.writeText(text).then(() => this.copied())

const text = this.sourceTarget.value || this.sourceTarget.innerHTML
navigator.clipboard.writeText(text).then(() => {
// Show success state
if (this.hasDefaultTarget && this.hasSuccessTarget) {
this.defaultTarget.classList.add('hidden')
this.successTarget.classList.remove('hidden')

// Reset after specified duration
setTimeout(() => {
this.defaultTarget.classList.remove('hidden')
this.successTarget.classList.add('hidden')
}, this.successDurationValue)
} else if (this.hasButtonTarget) {
// Fallback for old implementation
const originalContent = this.buttonTarget.innerHTML
this.buttonTarget.innerHTML = this.successContentValue

setTimeout(() => {
this.buttonTarget.innerHTML = originalContent
}, this.successDurationValue)
}
})
}

copied() {
if (!this.hasButtonTarget) return

if (this.timeout) {
clearTimeout(this.timeout)
}

this.buttonTarget.innerHTML = this.successContentValue

this.timeout = setTimeout(() => {
this.buttonTarget.innerHTML = this.originalContent
}, this.successDurationValue)
get successContentValue() {
return this.data.get("successContentValue")
}
}
165 changes: 129 additions & 36 deletions app/views/admin/events/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,43 +1,136 @@
<%= form_with(model: [:admin, event]) do |form| %>
<% if event.errors.any? %>
<div style="color: red">
<h2><%= pluralize(event.errors.count, "error") %>
prohibited this event from being saved:</h2>

<ul>
<% event.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
<div class="rounded-lg bg-red-50 p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
<%= pluralize(event.errors.count, "error") %> prohibited this event from being saved:
</h3>
<div class="mt-2 text-sm text-red-700">
<ul class="list-disc list-inside space-y-1">
<% event.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
</div>
</div>
</div>
<% end %>

<div class='flex flex-col'>
<%= form.label :name %>
<%= form.text_field :name, required: true %>
<%= form.label :location %>
<%= form.rich_text_area :location, required: true %>
<%= form.label :rsvp_link %>
<%= form.text_field :rsvp_link, required: true %>
<%= form.label :description %>
<%= form.rich_text_area :description %>
<%= form.label :sponsor %>
<%= form.text_field :sponsor %>
<%= form.label :sponsor_link %>
<%= form.text_field :sponsor_link %>
<%= form.label :sponsor_logo %>
<%= form.text_field :sponsor_logo %>
<%= form.label :start_at %>
<%= form.datetime_field :start_at,
include_seconds: false,
value:
event
&.start_at
&.in_time_zone("Eastern Time (US & Canada)")
.presence ||
Time.zone.now.in_time_zone("Eastern Time (US & Canada)") %>
<%= form.label :status %>
<%= form.select :status, options_for_select(Event.statuses_for_select, event.status) %>
<%= form.submit %>
<div class="space-y-6">
<!-- Basic Information -->
<div class="card p-6">
<h3 class="text-lg font-semibold mb-4">Basic Information</h3>

<div class="space-y-4">
<div>
<%= form.label :name, class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.text_field :name, required: true, class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors" %>
</div>

<div>
<%= form.label :start_at, class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.datetime_field :start_at,
include_seconds: false,
value: event&.start_at&.in_time_zone("Eastern Time (US & Canada)").presence || Time.zone.now.in_time_zone("Eastern Time (US & Canada)"),
class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors" %>
<p class="mt-1 text-sm text-gray-500">Eastern Time (US & Canada)</p>
</div>

<div>
<%= form.label :status, class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.select :status,
options_for_select(Event.statuses_for_select, event.status),
{},
class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors" %>
</div>

<div>
<%= form.label :rsvp_link, "RSVP Link", class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.text_field :rsvp_link, required: true, placeholder: "https://...", class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors placeholder-gray-400" %>
</div>
</div>
</div>

<!-- Content -->
<div class="card p-6">
<h3 class="text-lg font-semibold mb-4">Event Details</h3>

<div class="space-y-4">
<div>
<%= form.label :location, class: "block text-sm font-medium text-gray-700 mb-1" %>
<div class="prose-editor">
<%= form.rich_text_area :location, required: true, class: "block w-full" %>
</div>
</div>

<div>
<%= form.label :description, "Agenda", class: "block text-sm font-medium text-gray-700 mb-1" %>
<div class="prose-editor">
<%= form.rich_text_area :description, class: "block w-full" %>
</div>
</div>
</div>
</div>

<!-- Sponsor Information -->
<div class="card p-6">
<h3 class="text-lg font-semibold mb-4">Sponsor Information</h3>
<p class="text-sm text-gray-600 mb-4">Optional: Add sponsor details if this event has a sponsor</p>

<div class="space-y-4">
<div>
<%= form.label :sponsor, "Sponsor Name", class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.text_field :sponsor, class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors" %>
</div>

<div>
<%= form.label :sponsor_link, "Sponsor Website", class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.text_field :sponsor_link, placeholder: "https://...", class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors placeholder-gray-400" %>
</div>

<div>
<%= form.label :sponsor_logo, "Logo Filename", class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.text_field :sponsor_logo, placeholder: "company.png", class: "block w-full px-4 py-3 rounded-lg border border-gray-300 shadow-sm focus:border-ruby-500 focus:ring-1 focus:ring-ruby-500 transition-colors placeholder-gray-400" %>
<p class="mt-1 text-sm text-gray-500">Logo should be placed in app/assets/images/sponsors/</p>
</div>
</div>
</div>

<!-- Form Actions -->
<div class="flex items-center justify-end gap-4 pt-6">
<%= link_to "Cancel", admin_events_path, class: "btn-secondary" %>
<%= form.submit class: "btn-primary" %>
</div>
</div>
<% end %>

<style>
/* Rich text editor styling */
.prose-editor {
@apply rounded-lg border border-gray-300 overflow-hidden shadow-sm;
}

.prose-editor:focus-within {
@apply ring-1 ring-ruby-500 border-ruby-500;
}

.prose-editor trix-editor {
@apply min-h-[150px] px-4 py-3;
}

.prose-editor trix-toolbar {
@apply border-b border-gray-200 px-2;
}

/* Ensure trix editor has consistent padding */
.prose-editor trix-editor:empty:not(:focus)::before {
@apply text-gray-400;
}
</style>
Loading