diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb index 45eba931..ad347997 100644 --- a/app/controllers/api/events_controller.rb +++ b/app/controllers/api/events_controller.rb @@ -2,8 +2,19 @@ module Api class EventsController < ApplicationController + + def show + event = Event.find(params[:id]) + + if event.present? + render status: 200, json: { data: event.as_json(include: %i[event_speakers speakers]) } + else + render status: 404, json: { data: 'No event found' } + end + end + def past - events = Event.where('date < ?', DateTime.current).order(date: :desc) + events = Event.includes([:speakers]).by_event_date.where('date < ?', DateTime.current) events_by_date = events .group_by { |event| event.date.year } @@ -11,21 +22,21 @@ def past events_by_year.group_by { |event| event.date.strftime('%B') } # group by month name end - render status: 200, json: { data: events_by_date.as_json(include: [:event_speakers, :speakers]) } + render status: 200, json: { data: events_by_date.as_json(include: %i[event_speakers speakers]) } end - def past_by_month - event_date = DateTime.new(params[:year].to_i, params[:month].to_i) - event = Event.where(date: event_date..event_date.end_of_month).first - if event.present? - render status: 200, json: { data: event.as_json(include: [:event_speakers, :speakers]) } + def by_month + events_by_month = Event.includes([:speakers]).by_event_date.by_month_year(params[:month].to_i, params[:year].to_i) + + if events_by_month.present? + render status: 200, json: { data: events_by_month.as_json(include: %i[event_speakers speakers]) } else render status: 404, json: { data: 'No events found' } end end def upcoming - events = Event.where('date > ?', DateTime.current).order(date: :asc) + events = Event.includes([:speakers]).by_event_date.where('date > ?', DateTime.current) events_by_date = events .group_by { |event| event.date.year } diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index fc479346..ec79f56b 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -12,6 +12,6 @@ def jobs; end def jobs_authenticate; end def donate; end - - def past_meetup; end + + def meetup; end end diff --git a/app/javascript/components/PageTitleWithContainer.jsx b/app/javascript/components/PageTitleWithContainer.jsx index 58edadc4..61f48495 100644 --- a/app/javascript/components/PageTitleWithContainer.jsx +++ b/app/javascript/components/PageTitleWithContainer.jsx @@ -4,7 +4,7 @@ import PageTitle from 'components/PageTitle'; const PageTitleWithContainer = ({ text }) => { return ( -
+
); diff --git a/app/javascript/components/layout/Footer.jsx b/app/javascript/components/layout/Footer.jsx index e41b8389..92eb08de 100644 --- a/app/javascript/components/layout/Footer.jsx +++ b/app/javascript/components/layout/Footer.jsx @@ -50,7 +50,7 @@ const Footer = () => ( Job Board - Past Meetups + Meetups
diff --git a/app/javascript/components/layout/Header.jsx b/app/javascript/components/layout/Header.jsx index 6ff2a0ae..74e47daf 100644 --- a/app/javascript/components/layout/Header.jsx +++ b/app/javascript/components/layout/Header.jsx @@ -57,12 +57,13 @@ const Header = () => {
  • Job Board
  • -
  • - Past Meetups -
  • +
  • Sponsor Us
  • +
  • + Meetups +
  • diff --git a/app/javascript/components/past_meetups/SpeakerBiosBlock.jsx b/app/javascript/components/meetup/SpeakerBiosBlock.jsx similarity index 100% rename from app/javascript/components/past_meetups/SpeakerBiosBlock.jsx rename to app/javascript/components/meetup/SpeakerBiosBlock.jsx diff --git a/app/javascript/components/past_meetups/SpeakerVideoBlock.jsx b/app/javascript/components/meetup/SpeakerVideoBlock.jsx similarity index 94% rename from app/javascript/components/past_meetups/SpeakerVideoBlock.jsx rename to app/javascript/components/meetup/SpeakerVideoBlock.jsx index 2a45dddf..79dd1c65 100644 --- a/app/javascript/components/past_meetups/SpeakerVideoBlock.jsx +++ b/app/javascript/components/meetup/SpeakerVideoBlock.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import VideoBlock from 'components/past_meetups/VideoBlock'; +import VideoBlock from 'components/meetup/VideoBlock'; export default function SpeakerVideoBlock({ speaker, eventSpeaker }) { const { id, image_url: imageUrl, name, tagline } = speaker; diff --git a/app/javascript/components/past_meetups/VideoBlock.jsx b/app/javascript/components/meetup/VideoBlock.jsx similarity index 100% rename from app/javascript/components/past_meetups/VideoBlock.jsx rename to app/javascript/components/meetup/VideoBlock.jsx diff --git a/app/javascript/components/pages/PastMeetup.jsx b/app/javascript/components/pages/Meetup.jsx similarity index 50% rename from app/javascript/components/pages/PastMeetup.jsx rename to app/javascript/components/pages/Meetup.jsx index e207544f..b5afb573 100644 --- a/app/javascript/components/pages/PastMeetup.jsx +++ b/app/javascript/components/pages/Meetup.jsx @@ -1,95 +1,34 @@ import React, { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; -import PropTypes from 'prop-types'; -import { getPastMeetup } from '../../datasources'; +import { getMeetup } from '../../datasources'; import SharedLayout from 'components/layout/SharedLayout'; -import PageTitleWithContainer from 'components/PageTitle'; -import SpeakersList from '../SpeakersList'; -import Microphone from '../icons/Microphone'; -import LoadingSpinner from 'components/LoadingSpinner'; -import 'stylesheets/page'; -import 'stylesheets/meetup'; - -const VideoBlock = ({ videoUrl, title }) => { - if (!videoUrl) { - return null; - } - return ( -
    - ; -
    - ); -}; -VideoBlock.propTypes = { - videoUrl: PropTypes.string, - title: PropTypes.string, -}; - -const SpeakerBiosBlock = ({ speakers }) => { - return ( -
    -
    - -

    About the speakers

    -
    -
    - {speakers?.map(({ id, bio }) => ( -
    {bio}
    - ))} -
    -
    - ); -}; +import PageTitleWithContainer from 'components/PageTitleWithContainer'; +import SpeakersList from 'components/SpeakersList'; +import LoadingSpinner from 'components/LoadingSpinner'; -SpeakerBiosBlock.propTypes = { - speakers: PropTypes.arrayOf(PropTypes.object), -}; +import SpeakerVideoBlock from 'components/meetup/SpeakerVideoBlock'; +import SpeakerBiosBlock from 'components/meetup/SpeakerBiosBlock'; +import VideoBlock from 'components/meetup/VideoBlock'; -const SpeakerVideoBlock = ({ speaker, eventSpeaker }) => { - const { id, image_url: imageUrl, name, tagline } = speaker; - const { - talk_title: title, - talk_description: description, - talk_video_link: videoLink, - } = eventSpeaker; - return ( - <> - -
    - -
    -

    {name}

    -

    {tagline}

    -
    -
    -

    {description}

    - - ); -}; +import 'stylesheets/page'; +import 'stylesheets/meetup'; -SpeakerVideoBlock.propTypes = { - speaker: PropTypes.object, - eventSpeaker: PropTypes.object, -}; +const Meetup = () => { + const id_event = window.id_event; -const PastMeetup = () => { - const year = window.year; - const month = window.month; - const eventDate = new Date(year, Number(month - 1)); - const monthName = eventDate.toLocaleDateString('en-US', { month: 'long' }); const [loading, setLoading] = useState(true); const [meetup, setMeetup] = useState({}); useEffect(() => { const fetchData = async () => { - const data = await getPastMeetup(year, month); + const data = await getMeetup(id_event); setMeetup(data); setLoading(false); }; fetchData(); - }, [year, month]); + }, [id_event]); const { title, @@ -98,15 +37,14 @@ const PastMeetup = () => { event_speakers: eventSpeakers, panel_video_link: panelVideoUrl, } = meetup; + return ( <> {`${title} | WNB.rb`} -
    - -
    +
    {loading ? ( @@ -137,7 +75,7 @@ const PastMeetup = () => { })} )} -
    +
    @@ -148,4 +86,4 @@ const PastMeetup = () => { ); }; -export default PastMeetup; +export default Meetup; diff --git a/app/javascript/components/pages/Meetups.jsx b/app/javascript/components/pages/Meetups.jsx index 03b48f6d..b967dc01 100644 --- a/app/javascript/components/pages/Meetups.jsx +++ b/app/javascript/components/pages/Meetups.jsx @@ -1,97 +1,17 @@ import React, { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; -import PropTypes from 'prop-types'; + import { getPastMeetups } from '../../datasources'; import SharedLayout from 'components/layout/SharedLayout'; import LoadingSpinner from 'components/LoadingSpinner'; import PageTitleWithContainer from 'components/PageTitleWithContainer'; -import 'stylesheets/page'; -import 'stylesheets/meetup'; - -const YearSection = ({ children, year }) => ( -
    -

    {year}

    -
      {children}
    -
    -); -YearSection.propTypes = { - year: PropTypes.string, - children: PropTypes.node, -}; - -const MonthSection = ({ children, month }) => ( -
  • -
    -

    - {month} -

    -
    -
    -
      - {children} -
    -
  • -); - -MonthSection.propTypes = { - month: PropTypes.string, - children: PropTypes.node, -}; +import MonthSection from 'components/meetups/MonthSection'; +import YearSection from 'components/meetups/YearSection'; +import MeetupCard from 'components/meetups/MeetupCard'; -const Meetup = ({ speakers, title = '', event_speakers, year, month }) => { - const eventWithSpeaker = event_speakers.map((talk) => { - const speaker = speakers.find((speak) => speak.id === talk.speaker_id); - return { ...talk, speaker }; - }); - return ( -
  • -
    -
    -

    {title}

    - {eventWithSpeaker.length > 0 && - eventWithSpeaker.map(({ id, talk_title, speaker }) => ( -
    -
    - {talk_title} -
    -
    - -
    -

    - {speaker.name} -

    -

    - {speaker.tagline} -

    -
    -
    -
    - ))} -
    - -
    -
  • - ); -}; - -Meetup.propTypes = { - speakers: PropTypes.array, - title: PropTypes.string, - event_speakers: PropTypes.array, - year: PropTypes.string, - month: PropTypes.string, -}; +import 'stylesheets/page'; +import 'stylesheets/meetup'; const Meetups = () => { const [loading, setLoading] = useState(true); @@ -109,10 +29,10 @@ const Meetups = () => { return ( <> - Past Meetups | WNB.rb + Meetups | WNB.rb - + {loading ? ( ) : ( @@ -136,8 +56,9 @@ const Meetups = () => { const numericMonth = new Date(date).getMonth() + 1; return ( - { + const year = window.year; + const month = window.month; + const eventDate = new Date(year, Number(month - 1)); + const monthName = eventDate.toLocaleDateString('en-US', { month: 'long' }); + const [loading, setLoading] = useState(true); + const [meetupsList, setMeetupsList] = useState({}); + useEffect(() => { + const fetchData = async () => { + const data = await getMonthlyMeetups(year, month); + setMeetupsList(Object.entries(data)); + setLoading(false); + }; + + fetchData(); + }, [year, month]); + window.meetupsList = meetupsList; + + return ( + <> + + {`${year}-${month} Meetups | WNB.rb`} + + + + {loading ? ( + + ) : ( +
    + {meetupsList.length > 0 ? ( + meetupsList.reverse().map((meetup) => { + return ( + <> +
      + +
    + + ); + }) + ) : ( + <> +

    + No Meetups for this month +

    + + )} +
    + )} +
    + + ); +}; + +export default MonthlyMeetups; diff --git a/app/javascript/datasources/index.js b/app/javascript/datasources/index.js index a307606d..5f3c1239 100644 --- a/app/javascript/datasources/index.js +++ b/app/javascript/datasources/index.js @@ -75,8 +75,14 @@ const productionDonationAmounts = [ { value: 1000, link: 'https://buy.stripe.com/8wMeVAbl710mdEY009' }, ]; -export const getPastMeetup = async (year, month) => { +export const getMonthlyMeetups = async (year, month) => { const result = await fetch(`${API_ROOT}/events/${year}/${month}`); const json = await result.json(); return json.data; }; + +export const getMeetup = async (id) => { + const result = await fetch(`${API_ROOT}/events/${id}`); + const json = await result.json(); + return json.data; +}; diff --git a/app/javascript/packs/past_meetup.jsx b/app/javascript/packs/meetup.jsx similarity index 71% rename from app/javascript/packs/past_meetup.jsx rename to app/javascript/packs/meetup.jsx index 2a2e16e5..4ebf580a 100644 --- a/app/javascript/packs/past_meetup.jsx +++ b/app/javascript/packs/meetup.jsx @@ -1,10 +1,10 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import PastMeetup from '../components/pages/PastMeetup'; +import Meetup from '../components/pages/Meetup'; document.addEventListener('DOMContentLoaded', () => { const container = document.getElementById('root'); const root = createRoot(container); - root.render(); + root.render(); }); diff --git a/app/javascript/packs/monthly_meetups.jsx b/app/javascript/packs/monthly_meetups.jsx new file mode 100644 index 00000000..4d09fc5d --- /dev/null +++ b/app/javascript/packs/monthly_meetups.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import MonthlyMeetups from '../components/pages/MonthlyMeetups'; + +document.addEventListener('DOMContentLoaded', () => { + const body = document.createElement('div'); + body.style = 'min-height: 100vh'; + + ReactDOM.render(, document.body.appendChild(body)); +}); diff --git a/app/javascript/stylesheets/header.scss b/app/javascript/stylesheets/header.scss index ea015f7b..5dca4f86 100644 --- a/app/javascript/stylesheets/header.scss +++ b/app/javascript/stylesheets/header.scss @@ -43,6 +43,12 @@ header { } } + &.highlight { + a { + color: #f56960 + } + } + &.is-active { a { @apply text-gray-500 font-bold md:font-normal; diff --git a/app/javascript/stylesheets/meetup.scss b/app/javascript/stylesheets/meetup.scss index 3581a0b8..0516226b 100644 --- a/app/javascript/stylesheets/meetup.scss +++ b/app/javascript/stylesheets/meetup.scss @@ -1,35 +1,39 @@ .meetups__month--name { - width: 128px; + width: 128px; } .meetups__month:first-of-type > .meetups__card--border { - margin-top: 80px; + margin-top: 80px; } .meetups__month:last-of-type > .meetups__card--border { - align-self: flex-start; - padding-top: 50px; - line-height: 0; + align-self: flex-start; + padding-top: 50px; + line-height: 0; } .meetups__month:first-of-type > .meetups__month--name, .meetups__month:last-of-type > .meetups__month--name { - margin-top: 50px; + margin-top: 50px; } .meetups__card--border::before { - display: block; - position: relative; - left: -10px; - font-size: 50px; - color: #d1d5db; - content: '•'; + display: block; + position: relative; + left: -10px; + font-size: 50px; + color: #d1d5db; + content: '•'; +} + +.meetup__item li div:first-of-type { + max-width: 100%; } .meetups__month:first-of-type > .meetups__card--border::before { - top: -28px; + top: -28px; } .meetups__month:last-of-type > .meetups__card--border::before { - top: 14px; + top: 14px; } diff --git a/app/javascript/stylesheets/page-title.scss b/app/javascript/stylesheets/page-title.scss index c81cfc90..2c657dc4 100644 --- a/app/javascript/stylesheets/page-title.scss +++ b/app/javascript/stylesheets/page-title.scss @@ -1,7 +1,6 @@ .page-title { @apply relative min-h-[10rem]; - /* overflow-auto; */ } .ruby-outline { @@ -9,15 +8,14 @@ } .title-container { - @apply pt-10 pl-20; + @apply pt-10 pl-10 md:pl-20; } .title-text { @apply text-wnbrb-pink-default text-5xl sm:text-6xl - font-black - md:whitespace-nowrap; + font-black; -webkit-text-stroke-width: 2px; -webkit-text-stroke-color: white; diff --git a/app/models/event.rb b/app/models/event.rb index ee29f3a4..63a18902 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -5,4 +5,13 @@ class Event < ApplicationRecord has_many :speakers, through: :event_speakers validates :title, :location, :date, presence: true + + scope :by_event_date, -> { order(date: :desc) } + + scope :by_month_year, + ->(month, year) { + first_day = Date.new(year, month, 1) + last_day = Date.new(year, month, 1).next_month - 1.second + where(date: first_day..last_day) + } end diff --git a/app/views/site/meetup.html.erb b/app/views/site/meetup.html.erb new file mode 100644 index 00000000..d3747c9b --- /dev/null +++ b/app/views/site/meetup.html.erb @@ -0,0 +1,6 @@ +<%= javascript_pack_tag 'meetup' %> +<%= stylesheet_pack_tag 'meetup' %> + +<%= javascript_tag do %> + var id_event = <%= raw(params[:id]) %> +<% end %> diff --git a/app/views/site/past_meetup.erb b/app/views/site/monthly_meetups.html.erb similarity index 51% rename from app/views/site/past_meetup.erb rename to app/views/site/monthly_meetups.html.erb index ab07506e..bb3e37d3 100644 --- a/app/views/site/past_meetup.erb +++ b/app/views/site/monthly_meetups.html.erb @@ -1,6 +1,6 @@ -<%= javascript_pack_tag 'past_meetup' %> -<%= stylesheet_pack_tag "past_meetup" %> +<%= javascript_pack_tag 'monthly_meetups' %> +<%= stylesheet_pack_tag 'monthly_meetups' %> <%= javascript_tag do %> var year = <%= raw(params[:year]) %> var month = <%= raw(params[:month]) %> -<% end %> \ No newline at end of file +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 8870ffda..d5f85ba1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,22 +8,26 @@ sign_up: 'new', registration: 'register', } + namespace :admin do get 'dashboard', to: 'dashboard#show' end + get '/sponsor-us', to: 'site#sponsor_us' get '/meetups', to: 'site#meetups' + get '/meetups/:id', to: 'site#meetup' + get '/jobs', to: 'site#jobs' get '/jobs/authenticate', to: 'site#jobs_authenticate' get '/donate', to: 'site#donate' - get '/meetups/:year/:month', to: 'site#past_meetup' root 'site#home' + namespace :api do - resources :events, only: [:none] do + resources :events, only: [:show] do collection do get 'past' - get '/:year/:month', to: 'events#past_by_month' + get '/:year/:month', to: 'events#by_month' get 'upcoming' end end diff --git a/spec/controllers/api/events_controller_spec.rb b/spec/controllers/api/events_controller_spec.rb index 53239dfd..059a1e79 100644 --- a/spec/controllers/api/events_controller_spec.rb +++ b/spec/controllers/api/events_controller_spec.rb @@ -87,20 +87,20 @@ end end - describe 'GET #past_by_month' do + describe 'GET #by_month' do before do Meetup.create(title: 'August event', location: 'virtual', date: DateTime.new(2021, 8, 1)) end it 'returns one event for a given month' do - get :past_by_month, params: { year: '2021', month: '8' } + get :by_month, params: { year: '2021', month: '8' } expect(response).to have_http_status(200) body = JSON.parse(response.body) - expect(body['data']['title']).to eq('August event') + expect(body['data'][0]['title']).to eq('August event') end it 'returns a 404 when no event exists for that month' do - get :past_by_month, params: { year: '2021', month: '11' } + get :by_month, params: { year: '2021', month: '11' } expect(response).to have_http_status(404) end end