element we wrap around attachments. Otherwise,
+ * images in galleries will be squished by the max-width: 33%; rule.
+*/
+.trix-content .attachment-gallery > action-text-attachment,
+.trix-content .attachment-gallery > .attachment {
+ flex: 1 0 33%;
+ padding: 0 0.5em;
+ max-width: 33%;
+}
+
+.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
+.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
+.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
+ flex-basis: 50%;
+ max-width: 50%;
+}
+
+.trix-content action-text-attachment .attachment {
+ padding: 0 !important;
+ max-width: 100% !important;
+}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index b1fd3ab70..9861da89b 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -1,4 +1,5 @@
@import "bootstrap";
+
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
@@ -14,6 +15,18 @@
*= require_tree .
*= require_self
*/
+body {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ margin: 0;
+ padding: 0;
+}
+
+main {
+ flex-grow: 1;
+}
+
h1 {
font-family: 'Poppins', sans-serif;
font-style: normal;
@@ -22,16 +35,19 @@ h1 {
line-height: 120%;
color: #04021D;
}
+
h2 {
font-family: 'Poppins', sans-serif;
font-style: normal;
font-weight: 600;
}
+
h3 {
font-family: 'Poppins', sans-serif;
font-style: normal;
font-weight: 500;
}
+
p {
font-family: 'Poppins', sans-serif;
font-style: normal;
@@ -41,6 +57,11 @@ p {
/* Text / Dark - 60% */
color: #686777;
}
+
+li {
+ text-align: match-parent;
+}
+
button {
font-family: 'Poppins', sans-serif;
font-style: normal;
@@ -48,40 +69,170 @@ button {
font-size: 16px;
line-height: 24px;
}
+
b {
font-family: 'Poppins', sans-serif;
font-style: normal;
font-weight: bolder;
color: #262626;
}
+
.btn-primary {
background: #554AF0;
border-radius: 12px;
-
}
+
.btn-primary:hover {
background-color: #2112ee !important;
}
+
.btn-secondary {
background: #eaeaea;
border-radius: 12px;
border-color: #eaeaea;
color: #554AF0;
}
+
+.btn-info {
+ background-color: #c48828;
+ border-color: #c48828;
+ color: white;
+ font-size: 24px;
+}
+
+.btn-info:hover {
+ background-color: #c48828;
+ border-color: #c48828;
+ color: white;
+ text-decoration: underline;
+ font-size: 24px;
+}
+
.form-text {
font-family: 'Poppins', sans-serif;
font-style: normal;
font-weight: 400;
}
+
.form-label {
font-family: 'Poppins', sans-serif;
font-style: normal;
font-weight: 500;
}
-.name-display{
+
+.name-display {
color: #554AF0;
}
+
.form-select {
text-align-last: center;
}
+#footer {
+ background-color: #003262;
+ box-sizing: border-box;
+ color: #ffffff;
+ margin: 36px 0;
+ margin-bottom: 0;
+ font-size: 13px;
+ display: block;
+}
+
+#footer a {
+ color: #ffffffda;
+}
+
+#footer p,
+#footer span {
+ color: #ffffffda;
+}
+
+#footer h2 {
+ font-size: 23px;
+}
+
+.element-invisible {
+ position: absolute !important;
+ clip: rect(1px 1px 1px 1px);
+ clip: rect(1px, 1px, 1px, 1px);
+ overflow: hidden;
+ height: 1px;
+}
+
+.social-links ul li {
+ display: inline;
+ margin: 10px;
+ font-size: 18px;
+}
+
+.nav-pills>li+li {
+ margin-left: 2px;
+ float: left;
+}
+
+.nav>li {
+ position: relative;
+ display: block;
+}
+
+.nav-active {
+ color: white;
+}
+
+.nav-dull {
+ color: darkgray;
+}
+
+ul.nav,
+ul.navl li,
+ul.dropdown-menu,
+ul.dropdown-menu li {
+ list-style: none outside none !important;
+}
+
+.cry {
+ margin: 0;
+ padding: 0;
+ background: #003262;
+}
+
+#primary-nav>ul>li.menu-fields-menu-link {
+ position: static;
+}
+
+#primary-nav>ul>li.menu-fields-menu-link>.dropdown-menu {
+ left: 0;
+ padding: 0;
+ width: 100%;
+}
+
+#primary-nav>ul>li.menu-fields-menu-link>.dropdown-menu>li>.openberkeley-megamenu {
+ padding: 20px;
+ width: 100%;
+}
+
+.leaf a {
+ color: #999;
+ /* Set the generic color to a lighter gray */
+ transition: color 0.3s;
+ /* Add a transition effect for smooth color change */
+ text-decoration: none;
+ float: left;
+}
+
+.leaf a:hover {
+ color: #555;
+ /* Slightly darken the color when hovered over */
+}
+
+#footer-bottom ul li {
+ display: grid;
+}
+
+#footer-bottom {
+ margin-top: -15px;
+}
+
+.berkeley-color {
+ background-color: #003262;
+}
\ No newline at end of file
diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb
new file mode 100644
index 000000000..28c9d91d4
--- /dev/null
+++ b/app/controllers/admin/events_controller.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class Admin::EventsController < ApplicationController
+ before_action :check_if_admin
+ before_action :set_event, only: [:edit, :update, :show, :destroy]
+
+ def index
+ @events = Event.all
+ render "admin/events/index"
+ end
+
+ def new
+ @event = Event.new
+ end
+
+ def create
+ @event = Event.new(event_params)
+ if @event.save
+ redirect_to admin_events_path, flash: { success: "Event created successfully." }
+ else
+ flash.now[:alert] = "Missing values. Failed to create event."
+ render :new
+ end
+ end
+
+ def edit
+ # event set by set_event
+ end
+
+ def show
+ # event set by set_event
+ redirect_to admin_events_path
+ end
+
+ def update
+ if @event.update(event_params)
+ redirect_to admin_events_path, flash: { success: "Event updated successfully." }
+ else
+ flash.now[:alert] = "Failed to update event."
+ render :edit
+ end
+ end
+
+ def destroy
+ Rails.logger.debug("PARAMS: #{params.inspect}")
+ Rails.logger.debug("REQUEST METHOD: #{request.method}")
+ if @event.destroy
+ redirect_to admin_events_path, flash: { success: "Event deleted successfully." }
+ else
+ redirect_to admin_events_path, flash: { error: "Failed to delete event." }
+ end
+ end
+
+ def export_events
+ @events = Event.all
+ respond_to do |format|
+ format.csv { send_data Event.to_csv, filename: "events-#{Date.today}.csv" }
+ end
+ end
+
+ private
+ def event_params
+ params.require(:event).permit(:title, :date, :start_time, :end_time, :location, :description, :flyer) # may change later, not sure what we want to include
+ end
+
+ def set_event
+ @event = Event.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ redirect_to admin_events_path, alert: "Event not found."
+ end
+end
diff --git a/app/controllers/admins_controller.rb b/app/controllers/admins_controller.rb
index f0c6ba7e3..141b9dff3 100644
--- a/app/controllers/admins_controller.rb
+++ b/app/controllers/admins_controller.rb
@@ -7,7 +7,7 @@ class AdminsController < ApplicationController
def index; end
def view_checkin_records
- if (!params.key? :page) || (params[:page] < 1)
+ if (!params.key? :page) || (params[:page].to_i < 1)
redirect_to view_checkin_records_path(page: 1)
# return is needed here, otherwise the app will continue execute
# the following instructions after redirect
@@ -18,10 +18,134 @@ def view_checkin_records
@has_next_page = @checkin_records.size == 20
end
- private
+ def manage_advisors
+ @advisors = Advisor.all
+ end
+
+ def manage_scholarships
+ @scholarships = Scholarship.all
+ render "admins/scholarships/manage"
+ end
+
+ def manage_user_roles
+ @users = User.all
+ end
+
+ def new
+ @scholarship = Scholarship.new
+ render "admins/scholarships/new"
+ end
+
+ def create
+ @scholarship = Scholarship.new(scholarship_params)
+ if @scholarship.save
+ redirect_to manage_scholarships_path, notice: "Scholarship was successfully created."
+ else
+ render "admins/scholarships/new"
+ end
+ end
+
+ def edit
+ @scholarship = Scholarship.find(params[:id])
+ render "admins/scholarships/edit"
+ end
+
+ def update
+ @scholarship = Scholarship.find(params[:id])
+ if @scholarship.update(scholarship_params)
+ redirect_to manage_scholarships_path, notice: "Scholarship was successfully updated."
+ else
+ render "admins/scholarships/edit"
+ end
+ end
+
+ def destroy
+ @scholarship = Scholarship.find(params[:id])
+ @scholarship.destroy
+ redirect_to manage_scholarships_path, notice: "Scholarship was successfully deleted."
+ end
+
+ def batch_delete
+ scholarship_ids = params[:scholarship_ids]
+ if scholarship_ids.present?
+ Scholarship.where(id: scholarship_ids).destroy_all
+ redirect_to manage_scholarships_path, notice: "Selected scholarships were successfully deleted."
+ else
+ redirect_to manage_scholarships_path, alert: "No scholarships were selected for deletion."
+ end
+ end
+
+ def manage_courses
+ @courses = Course.all
+ render "admins/courses/manage" # Explicitly specify the template to render
+ end
+
+ def new_course
+ @course = Course.new
+ render "admins/courses/new"
+ end
+ def create_course
+ @course = Course.new(course_params)
+ if @course.save
+ redirect_to manage_courses_path, notice: "Course was successfully created."
+ else
+ render "admins/courses/new"
+ end
+ end
+
+ def edit_course
+ @course = Course.find(params[:id])
+ render "admins/courses/edit"
+ end
+
+ def update_course
+ @course = Course.find(params[:id])
+ if @course.update(course_params)
+ redirect_to manage_courses_path, notice: "Course was successfully updated."
+ else
+ render "admins/courses/edit"
+ end
+ end
+
+ def destroy_course
+ @course = Course.find(params[:id])
+ @course.destroy
+ redirect_to manage_courses_path, notice: "Course was successfully deleted."
+ end
+
+ def export_courses
+ @courses = Course.all
+ respond_to do |format|
+ format.csv { send_data Course.to_csv, filename: "courses-#{Date.today}.csv" }
+ end
+ end
+
+ def export_scholarships
+ @scholarships = Scholarship.all
+ respond_to do |format|
+ format.csv { send_data Scholarship.to_csv, filename: "scholarships-#{Date.today}.csv" }
+ end
+ end
+
+ private
def check_permission
admin = Admin.find_by_id(session[:current_user_id])
redirect_to root_path, flash: { error: "You don't have the permission to do that!" } if !admin || !admin.is_admin
end
+
+ def scholarship_params
+ params.require(:scholarship).permit(:name, :description, :status_text, :application_url)
+ end
+
+ def course_params
+ params.require(:course).permit(:code, :title, :description, :units, :semester, :schedule, :ccn, :location, :available)
+ end
+
+ def require_admin
+ unless current_user&.admin?
+ flash[:error] = "You must be an admin to access this page."
+ redirect_to root_path
+ end
+ end
end
diff --git a/app/controllers/advisors_controller.rb b/app/controllers/advisors_controller.rb
new file mode 100644
index 000000000..259036977
--- /dev/null
+++ b/app/controllers/advisors_controller.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+class AdvisorsController < ApplicationController
+ before_action :set_advisor, only: [:edit, :update, :destroy]
+
+ def new
+ @advisor = Advisor.new
+ end
+
+ def create
+ @advisor = Advisor.new(advisor_params)
+ if @advisor.save
+ redirect_to manage_advisors_path, flash: { success: "Advisor created successfully." }
+ else
+ render :new, flash: { error: "Failed to create advisor." }
+ end
+ end
+ def edit
+ end
+
+ def update
+ if @advisor.update(advisor_params)
+ redirect_to manage_advisors_path, flash: { success: "Advisor updated successfully." }
+ else
+ render :edit, flash: { error: "Failed to update advisor." }
+ end
+ end
+
+ def destroy
+ @advisor = Advisor.find(params[:id])
+ if @advisor.destroy
+ redirect_to manage_advisors_path, flash: { success: "Advisor deleted successfully." }
+ else
+ redirect_to manage_advisors_path, flash: { error: "Failed to delete advisor." }
+ end
+ end
+
+ def show
+ @advisor = Advisor.find(params[:id])
+ end
+
+ private
+ def set_advisor
+ @advisor = Advisor.find(params[:id])
+ end
+
+ def advisor_params
+ params.require(:advisor).permit(:name, :description, :calendar, :active)
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7944f9f99..32e3d9b0e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,4 +1,8 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
+ def check_if_admin
+ admin = Admin.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "You must be an admin to access this page" } if !admin || !admin.is_admin
+ end
end
diff --git a/app/controllers/appointments_controller.rb b/app/controllers/appointments_controller.rb
index 763563123..668a225ee 100644
--- a/app/controllers/appointments_controller.rb
+++ b/app/controllers/appointments_controller.rb
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
class AppointmentsController < ApplicationController
- before_action :require_login
+ before_action :require_login
- def advisors
- end
+ def advisors
+ @advisors = Advisor.where(active: true)
+ end
- def require_login
- unless session.has_key? :current_user_id and Student.find_by_id(session[:current_user_id])
- redirect_to login_path, :method => :get
- end
+ def require_login
+ unless (session.has_key? :current_user_id) && Student.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "Please log in first!" }
end
-
-end
\ No newline at end of file
+ end
+end
diff --git a/app/controllers/checkin_controller.rb b/app/controllers/checkin_controller.rb
index c2dba3bda..11abacd85 100644
--- a/app/controllers/checkin_controller.rb
+++ b/app/controllers/checkin_controller.rb
@@ -2,28 +2,55 @@
class CheckinController < ApplicationController
before_action :require_login
+ before_action :set_user_and_reasons
def new
flash.clear
- @reasons = Checkin.reasons
+ @checkin = Checkin.new
+ set_default_reason
end
def create
flash.clear
- @user = Student.find_by_id(session[:current_user_id])
- @checkin = Checkin.new
- @checkin.update(time: Time.current, student_id: @user.id, reason: params[:checkin][:reason])
+ reason = params.dig(:checkin, :reason)
+
+ # Only check if reason is present
+ if reason.blank?
+ flash[:error] = "Something went wrong, please try again"
+ redirect_to root_path
+ return
+ end
+
+ @checkin = @user.checkins.build(
+ reason: reason,
+ time: Time.current
+ )
+
if @checkin.save
redirect_to root_path, flash: { success: "Success! You've been checked in!" }
else
- redirect_to root_path, flash: { error: 'Something went wrong, please try again' }
+ flash[:error] = "Something went wrong, please try again"
+ redirect_to root_path
end
end
private
+ def set_user_and_reasons
+ @user = Student.find_by_id(session[:current_user_id])
+ @reasons = Checkin.reasons
+ end
+
+ def set_default_reason
+ @default_reason = if @user && @user.checkins.exists?
+ @user.checkins.order(time: :desc).first.reason
+ else
+ @reasons.first
+ end
+ end
def require_login
- redirect_to root_path, flash: { error: 'Please log-in first!' } unless
- session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id])
+ unless session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "Please log-in first!" }
+ end
end
end
diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb
new file mode 100644
index 000000000..5f8a9f2d0
--- /dev/null
+++ b/app/controllers/courses_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CoursesController < ApplicationController
+ before_action :require_login, only: [:index]
+
+ def index
+ @courses = Course.where(available: true)
+ end
+
+ private
+ def require_login
+ unless session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "Please log in first!" }
+ end
+ end
+end
diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb
new file mode 100644
index 000000000..60e03579e
--- /dev/null
+++ b/app/controllers/events_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class EventsController < ApplicationController
+ before_action :require_login
+
+ def index
+ @upcoming_events = Event.where("date >= ?", Date.today).order(:date)
+ @past_events = Event.where("date < ?", Date.today).order(date: :desc).limit(5)
+ end
+
+ def require_login
+ unless session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "Please log in first!" }
+ end
+ end
+end
diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb
index c955108d4..bfab335d1 100644
--- a/app/controllers/login_controller.rb
+++ b/app/controllers/login_controller.rb
@@ -3,6 +3,21 @@
class LoginController < ApplicationController
def confirm
@user = User.find_by(id: session[:current_user_id])
- redirect_to root_path, flash: { error: 'Please log-in first!' } if @user.nil?
+ redirect_to root_path, flash: { error: "Please log-in first!" } if @user.nil?
+ end
+
+ def canvas_login
+ unless Rails.application.credentials[:CANVAS_CLIENT_ID] && Rails.application.credentials[:CANVAS_URL] && Rails.application.credentials[:CANVAS_REDIRECT_URI]
+ redirect_to root_path, flash: { error: "Canvas configuration is missing" }
+ return
+ end
+ params = {
+ client_id: Rails.application.credentials[:CANVAS_CLIENT_ID],
+ response_type: "code",
+ redirect_uri: Rails.application.credentials[:CANVAS_REDIRECT_URI],
+ scope: "url:GET|/api/v1/users/self"
+ }
+
+ redirect_to "#{Rails.application.credentials[:CANVAS_URL]}/login/oauth2/auth?#{params.to_query}", allow_other_host: true
end
end
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 3c8bce8d1..187089a38 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -6,15 +6,14 @@ class PagesController < ApplicationController
def index; end
private
-
def authenticate
@user_type = []
user = User.find_by_id(session[:current_user_id])
if user
@name = user.name
- @user_type.push 'Admin' if user.is_admin
- @user_type.push 'Staff' if user.is_staff
- @user_type.push 'Student' if user.is_student
+ @user_type.push "Admin" if user.is_admin
+ @user_type.push "Staff" if user.is_staff
+ @user_type.push "Student" if user.is_student
end
@logged_out = !user
end
diff --git a/app/controllers/podcasts_controller.rb b/app/controllers/podcasts_controller.rb
new file mode 100644
index 000000000..ccc2bdd11
--- /dev/null
+++ b/app/controllers/podcasts_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# frozen_string_literal: true
+
+class PodcastsController < ApplicationController
+ before_action :require_login
+
+ def index
+ end
+
+ def require_login
+ unless session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "Please log in first!" }
+ end
+ end
+end
diff --git a/app/controllers/scholarships_controller.rb b/app/controllers/scholarships_controller.rb
new file mode 100644
index 000000000..b886f3df4
--- /dev/null
+++ b/app/controllers/scholarships_controller.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+class ScholarshipsController < ApplicationController
+ before_action :require_login
+ # Add admin permission check, only applies to actions that modify data
+ before_action :check_admin_permission, only: [:new, :create, :edit, :update, :destroy]
+ # Add set_scholarship callback, only applies to actions requiring a specific scholarship record
+ before_action :set_scholarship, only: [:edit, :update, :show, :destroy] # Add :show if needed
+
+ # GET /scholarships (replaces the original index)
+ def index
+ # This index might now be for all logged-in users, or you can adjust as needed
+ @scholarships = Scholarship.all
+ # Add pagination if necessary
+ end
+
+ # GET /scholarships/:id/edit (from admin)
+ def edit
+ # @scholarship is set by set_scholarship
+ end
+
+ # PATCH/PUT /scholarships/:id (from admin)
+ def update
+ if @scholarship.update(scholarship_params)
+ flash[:notice] = "Scholarship updated successfully."
+ # Redirect to the scholarships list page, adjust the path according to your routes
+ redirect_to scholarships_path # Was admin_scholarships_path
+ else
+ flash.now[:alert] = "Failed to update scholarship."
+ render :edit, status: :unprocessable_entity
+ end
+ end
+
+ # GET /scholarships/new (from admin)
+ def new
+ @scholarship = Scholarship.new
+ end
+
+ # POST /scholarships (from admin)
+ def create
+ @scholarship = Scholarship.new(scholarship_params)
+ if @scholarship.save
+ flash[:notice] = "Scholarship created successfully."
+ # Redirect to the scholarships list page
+ redirect_to scholarships_path # Was admin_scholarships_path
+ else
+ flash.now[:alert] = "Failed to create scholarship."
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE /scholarships/:id (from admin)
+ def destroy
+ @scholarship.destroy
+ flash[:notice] = "Scholarship deleted successfully."
+ # Redirect to the scholarships list page
+ redirect_to scholarships_path # Was admin_scholarships_path
+ end
+
+ # If you need a show action
+ def show
+ @scholarship = Scholarship.find(params[:id])
+ end
+
+ private # Move the original require_login under private and add other private methods
+ def require_login
+ unless session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id])
+ redirect_to root_path, flash: { error: "Please log in first!" }
+ end
+ end
+
+ # set_scholarship from admin
+ def set_scholarship
+ @scholarship = Scholarship.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ flash[:alert] = "Scholarship not found."
+ # Adjust the redirect path as needed
+ redirect_to scholarships_path # Was admin_scholarships_path
+ end
+
+ # scholarship_params from admin
+ def scholarship_params
+ # Ensure you permit all the attributes you want to be updatable through the form
+ params.require(:scholarship).permit(:name, :description, :status_text, :application_url)
+ end
+
+ # check_admin_permission from admin
+ def check_admin_permission
+ # Implement your admin permission check logic here
+ # Check if the current user is an admin
+ # Note: This assumes admin info is stored in the Admin model and associated via session[:current_user_id]
+ # You might need to adjust this logic to match your user and permission model
+ # E.g., if the Student model has an is_admin flag:
+ # current_student = Student.find_by_id(session[:current_user_id])
+ # unless current_student&.is_admin
+
+ # Or if admins are a different model:
+ admin = Admin.find_by_id(session[:current_user_id]) # Assuming admins also use this session key
+ unless admin # Or admin&.is_admin if Admin model has an is_admin field
+ redirect_to root_path, flash: { error: "You don't have the permission to do that!" }
+ end
+ # If your admin and student user systems are separate, you need more complex logic to handle sessions
+ end
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 03d9f66e3..acac67fa2 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -3,7 +3,7 @@
class SessionsController < ApplicationController
def google_auth
# Get access tokens from the google server
- access_token = request.env['omniauth.auth']
+ access_token = request.env["omniauth.auth"]
existing_user = User.where(email: access_token.info.email).first
if existing_user.present?
session[:current_user_id] = existing_user.id
@@ -20,11 +20,11 @@ def google_auth
# set appropriate permissions
user = set_user_permission(user, access_token.info.email)
if !user
- redirect_to root_path, flash: { error: 'Something went wrong, please try again later.' }
+ redirect_to root_path, flash: { error: "Something went wrong, please try again later." }
elsif user.save
user_first_login(user)
else
- redirect_to root_path, flash: { error: 'Something went wrong, please try again' }
+ redirect_to root_path, flash: { error: "Something went wrong, please try again" }
end
end
@@ -33,8 +33,88 @@ def google_auth_logout
redirect_to root_path, flash: { success: "You've been successfully logged-out!" }
end
- private
+ # Canvas authentication
+ def canvas_callback
+ if ENV["MOCK_CANVAS_LOGIN"] == "true"
+ # Read the role from params (default to "student" if none)
+ role = params[:mock_role] || "student"
+
+ fake_email = case role
+ when "admin"
+ "169reentryadmin@berkeley.edu"
+ else
+ "studentuser@berkeley.edu"
+ end
+
+ user = User.find_or_initialize_by(email: fake_email)
+
+ if user.new_record?
+ user.first_name = role.capitalize
+ user.last_name = "Mock"
+ user.email = fake_email
+ if role == "student"
+ user.sid = "12345678"
+ end
+ end
+
+ user = set_user_permission(user, fake_email)
+ user.save!
+ session[:current_user_id] = user.id
+
+ redirect_to root_path, flash: { success: "Logged in as #{role.capitalize} mock user." }
+ return
+ end
+ if params[:error].present? || params[:code].blank?
+ redirect_to root_path, alert: "Authentication failed. Please try again."
+ return
+ end
+
+ access_token = get_access_token(params[:code])
+
+ response = Faraday.get("#{Rails.application.credentials[:CANVAS_URL]}/api/v1/users/self?") do |req|
+ req.headers["Authorization"] = "Bearer #{access_token.token}"
+ end
+
+ if response.success?
+ user_data = JSON.parse(response.body)
+ existing_user = User.where(email: user_data["email"]).first
+ if existing_user.present?
+ session[:current_user_id] = existing_user.id
+ redirect_to root_path, flash: { success: "Success! You've been logged-in!" }
+ return
+ end
+ end
+ user = User.from_canvas(user_data)
+ # user.canvas_token = access_token.token
+ # user.canvas_refresh_token = access_token.credentials.refresh_token if access_token.credentials.refresh_token.present?
+ user = set_user_permission(user, user_data["email"])
+ if user.save
+ session[:current_user_id] = user.id
+ redirect_to root_path, flash: { success: "Success! You've been logged-in!" }
+ else
+ redirect_to root_path, flash: { error: "Something went wrong, please try again." }
+ end
+ end
+
+ def get_access_token(code)
+ client = OAuth2::Client.new(
+ Rails.application.credentials[:CANVAS_CLIENT_ID],
+ Rails.application.credentials[:CANVAS_CLIENT_SECRET],
+ site: Rails.application.credentials[:CANVAS_URL],
+ token_url: "/login/oauth2/token"
+ )
+ client.auth_code.get_token(code, redirect_uri: :canvas_callback)
+ end
+
+ def canvas_auth_logout
+ session.delete(:current_user_id)
+ redirect_to root_path, flash: { success: "You've been successfully logged-out!" }
+ end
+
+
+
+ private
def user_first_login(user)
session[:current_user_id] = user.id
if user.is_student
@@ -48,8 +128,8 @@ def user_first_login(user)
def set_user_permission(user, email)
# Get the official admins
- admins = ENV['ADMINS'].split(',')
- staff = ENV['STAFF'].split(',')
+ admins = Rails.application.credentials[:ADMINS].split(",")
+ staff = Rails.application.credentials[:STAFF].split(",")
return nil if admins.blank? || staff.blank?
user.is_admin = admins.include? email
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index bdc8bbc31..49e77d9e2 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -8,18 +8,18 @@ def update
@user = User.find_by(id: session[:current_user_id])
sid = params[:user][:sid]
email = params[:user][:email]
- if sid.blank? || (sid.length != 10)
- redirect_to login_confirm_path, flash: { error: 'Invalid Student ID Number.' }
+ if sid.blank? || sid.length < 8 || sid.length > 10 # TODO: check the format of sid in the future
+ redirect_to login_confirm_path, flash: { error: "Invalid Student ID Number." }
return
end
if email.blank? || !email.match(/.+(@berkeley.edu)/)
- redirect_to login_confirm_path, flash: { error: 'Please use your berkeley email to log-in.' }
+ redirect_to login_confirm_path, flash: { error: "Please use your berkeley email to log-in." }
return
end
if @user.update(user_params)
redirect_to user_profile_new_path
else
- redirect_to login_confirm_path, flash: { error: 'Something went wrong, please try again.' }
+ redirect_to login_confirm_path, flash: { error: "Something went wrong, please try again." }
end
end
@@ -36,7 +36,7 @@ def profile_update
@user.update!(profile_params)
end
if edited
- redirect_to root_path, flash: { success: 'Success! Your profile has been updated.' }
+ redirect_to root_path, flash: { success: "Success! Your profile has been updated." }
else
redirect_to root_path, flash: { success: "Success! You've been logged-in!" }
end
@@ -47,10 +47,22 @@ def profile_edit
@user = Student.find_by_id(session[:current_user_id])
end
- private
+ def edit_user_role
+ @user = User.find(params[:id])
+ end
+ def update_user_role
+ @user = User.find(params[:id])
+ if @user.update(user_role_params)
+ redirect_to manage_user_roles_path, flash: { success: "User role updated successfully." }
+ else
+ redirect_to manage_user_roles_path, flash: { error: "Failed to update user role." }
+ end
+ end
+
+ private
def require_login
- redirect_to root_path, flash: { error: 'Only students have access to profiles.' } unless
+ redirect_to root_path, flash: { error: "Only students have access to profiles." } unless
session.key?(:current_user_id) && Student.find_by_id(session[:current_user_id]).present?
end
@@ -61,4 +73,8 @@ def profile_params
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :sid)
end
+
+ def user_role_params
+ params.require(:user).permit(:first_name, :last_name, :email, :sid, :is_admin, :is_advisor, :is_student)
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 15b06f0f6..5ef82a482 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,4 +1,21 @@
# frozen_string_literal: true
module ApplicationHelper
+ # Removed the markdown method
+
+ # Helper method to check if the current user is an admin
+ def admin?
+ # Assuming admin status is determined by the presence of an Admin record
+ # matching the session's user ID, similar to check_admin_permission
+ # Note: Ensure the Admin model exists and this logic matches your authentication setup.
+ # Also ensure session[:current_user_id] is correctly set upon login.
+ # Return false if no user is logged in.
+ return false unless session[:current_user_id]
+
+ Admin.exists?(id: session[:current_user_id])
+ rescue NameError
+ # Handle case where Admin model might not exist (optional, for robustness)
+ Rails.logger.error "Admin model not found. Cannot perform admin check."
+ false
+ end
end
diff --git a/app/javascript/application.js b/app/javascript/application.js
index 0accd18f8..b29606c7d 100644
--- a/app/javascript/application.js
+++ b/app/javascript/application.js
@@ -5,4 +5,6 @@ import "controllers"
//= require jquery_ujs
//= require bootstrap-sprockets
//= require turbolinks
-//= require_tree .
\ No newline at end of file
+//= require_tree .
+import "trix"
+import "@rails/actiontext"
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index d84cb6e71..5cc63a0c6 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
- default from: 'from@example.com'
- layout 'mailer'
+ default from: "from@example.com"
+ layout "mailer"
end
diff --git a/app/models/advisor.rb b/app/models/advisor.rb
new file mode 100644
index 000000000..0dbb51dac
--- /dev/null
+++ b/app/models/advisor.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Advisor < ApplicationRecord
+ validates :name, :description, presence: true
+ validates :calendar, format: { with: URI::DEFAULT_PARSER.make_regexp, message: "must be a valid URL" }, allow_blank: true
+end
diff --git a/app/models/checkin.rb b/app/models/checkin.rb
index 09afadf97..0d7021e4d 100644
--- a/app/models/checkin.rb
+++ b/app/models/checkin.rb
@@ -11,6 +11,6 @@ def self.get_20_checkin_records(n)
end
def self.reasons
- ['Peer Support', 'Counseling Appointment', 'Studying', 'OWLs Meeting', 'Other']
+ ["Peer Support", "Drop-in Advising", "Studying", "OWLs Meeting", "Other"]
end
end
diff --git a/app/models/course.rb b/app/models/course.rb
new file mode 100644
index 000000000..66d872c74
--- /dev/null
+++ b/app/models/course.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class Course < ApplicationRecord
+ validates :code, presence: true
+ validates :title, presence: true
+ validates :description, presence: true
+ validates :units, presence: true
+
+ def self.to_csv
+ require "csv"
+ attributes = %w{id code title description units semester schedule ccn location available created_at updated_at}
+
+ CSV.generate(headers: true) do |csv|
+ csv << attributes
+
+ all.each do |course|
+ csv << attributes.map { |attr| course.send(attr) }
+ end
+ end
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
new file mode 100644
index 000000000..00978efcc
--- /dev/null
+++ b/app/models/event.rb
@@ -0,0 +1,28 @@
+# app/models/event.rb
+# frozen_string_literal: true
+
+class Event < ApplicationRecord
+ validates :title, :date, :start_time, :location, :description, presence: true
+
+ # Helper method for displaying formatted time range
+ def formatted_time
+ if end_time.present?
+ "#{start_time.strftime('%l:%M %p').strip} - #{end_time.strftime('%l:%M %p').strip}"
+ else
+ start_time.strftime("%l:%M %p").strip
+ end
+ end
+
+ # Helper method for displaying formatted date
+ def formatted_date
+ date.strftime("%A, %B %d, %Y")
+ end
+
+ def self.to_csv
+ require "csv"
+ CSV.generate(headers: true) do |csv|
+ csv << column_names
+ all.each { |event| csv << event.attributes.values_at(*column_names) }
+ end
+ end
+end
diff --git a/app/models/scholarship.rb b/app/models/scholarship.rb
new file mode 100644
index 000000000..e1ab33043
--- /dev/null
+++ b/app/models/scholarship.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "csv"
+
+class Scholarship < ApplicationRecord
+ validates :name, presence: true
+ validates :description, presence: true
+ validates :status_text, presence: true
+ validates :application_url, format: { with: URI.regexp(%w[http https]), message: "must be a valid URL" }, allow_blank: true
+
+ def self.to_csv
+ attributes = %w[id name description status_text application_url created_at updated_at]
+
+ CSV.generate(headers: true) do |csv|
+ csv << attributes
+
+ all.each do |scholarship|
+ csv << attributes.map { |attr| scholarship.send(attr) }
+ end
+ end
+ end
+end
diff --git a/app/models/student.rb b/app/models/student.rb
index 3e52a8123..768170b15 100644
--- a/app/models/student.rb
+++ b/app/models/student.rb
@@ -8,6 +8,6 @@ class Student < User
validate :check_is_student
def check_is_student
- raise 'This user must be a student!!' if is_student == false
+ raise "This user must be a student!!" if is_student == false
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c711e2cc8..bda64e3e3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -11,6 +11,15 @@ def self.from_omniauth(auth)
end
end
+ def self.from_canvas(user_data)
+ where(email: user_data["email"]).first_or_initialize do |user|
+ user.first_name = user_data["first_name"]
+ user.last_name = user_data["last_name"]
+ user.email = user_data["email"]
+ user.sid = user_data["sis_user_id"]
+ end
+ end
+
def name
"#{first_name} #{last_name}"
end
diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb
new file mode 100644
index 000000000..49ba357dd
--- /dev/null
+++ b/app/views/active_storage/blobs/_blob.html.erb
@@ -0,0 +1,14 @@
+ attachment--<%= blob.filename.extension %>">
+ <% if blob.representable? %>
+ <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
+ <% end %>
+
+
+ <% if caption = blob.try(:caption) %>
+ <%= caption %>
+ <% else %>
+ <%= blob.filename %>
+ <%= number_to_human_size blob.byte_size %>
+ <% end %>
+
+
diff --git a/app/views/admin/events/edit.html.erb b/app/views/admin/events/edit.html.erb
new file mode 100644
index 000000000..01f22947e
--- /dev/null
+++ b/app/views/admin/events/edit.html.erb
@@ -0,0 +1,61 @@
+Edit Event
+
+<% if flash[:notice] %>
+
+ <%= flash[:notice] %>
+
+<% end %>
+
+<% if flash[:alert] %>
+
+ <%= flash[:alert] %>
+
+<% end %>
+
+
+ <%= form_with model: [:admin, @event], local: true do |form| %>
+
+ <%= form.label :title, class: 'form-label' %>
+ <%= form.text_field :title, class: 'form-control' %>
+
+
+
+ <%= form.label :date %>
+ <%= form.date_field :date, class: 'form-control' %>
+
+
+
+
+
+ <%= form.label :location %>
+ <%= form.text_field :location, class: 'form-control' %>
+
+
+
+ <%= form.label :description, class: 'form-label' %>
+ <%= form.text_area :description, class: 'form-control', rows: 3 %>
+
+
+
+ <%= form.label :flyer, class: 'form-label' %>
+ <%= form.file_field :flyer, class: 'form-control' %>
+ Upload an image file for the event flyer (max 5MB)
+
+
+
+ <%= form.submit 'Update Event', class: 'mt-3 mb-3 d-block mx-auto btn-lg btn-primary' %>
+
+ <% end %>
+
+<%= link_to 'Back to Events', admin_events_path, class: 'btn btn-secondary btn-lg d-block mx-auto mb-5' %>
+
+
diff --git a/app/views/admin/events/index.html.erb b/app/views/admin/events/index.html.erb
new file mode 100644
index 000000000..be010388d
--- /dev/null
+++ b/app/views/admin/events/index.html.erb
@@ -0,0 +1,62 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
+
Manage Events
+
+
+ <%= link_to "Export", export_admin_events_path(format: :csv), class: "btn btn-secondary" %>
+
+
+
+
+
+
+ Title
+ Description
+ Date
+ Time
+ Location
+ Actions
+
+
+
+ <% @events.each do |event| %>
+
+ <%= event.title %>
+ <%= event.description %>
+ <%= event.date.strftime('%B %d, %Y') %>
+ <%= event.formatted_time %>
+ <%= event.location %>
+
+ <%= link_to "Edit", edit_admin_event_path(event), class: 'btn btn-primary btn-sm' %>
+ <%= button_to "Delete", admin_event_path(event), method: :delete, data: { confirm: 'Are you sure you want to delete this event?' }, class: 'btn btn-danger btn-sm' %>
+
+
+ <% end %>
+
+
+
+
+
+
+
+ <%= link_to "Back to Admin Dashboard".html_safe, admins_path, class: "btn btn-secondary btn-lg" %>
+ <%= link_to "Add New Event".html_safe, new_admin_event_path, class: "btn-lg btn-success" %>
+
+
+ <%= javascript_include_tag 'https://code.jquery.com/jquery-3.6.0.min.js' %>
+ <%= javascript_include_tag 'https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js' %>
+
+
+
+
diff --git a/app/views/admin/events/new.html.erb b/app/views/admin/events/new.html.erb
new file mode 100644
index 000000000..7dd33f691
--- /dev/null
+++ b/app/views/admin/events/new.html.erb
@@ -0,0 +1,61 @@
+Create New Event
+
+<% if flash[:notice] %>
+
+ <%= flash[:notice] %>
+
+<% end %>
+
+<% if flash[:alert] %>
+
+ <%= flash[:alert] %>
+
+<% end %>
+
+
+ <%= form_with model: @event, url: admin_events_path, method: :post, local: true do |form| %>
+
+ <%= form.label :title, class: 'form-label' %>
+ <%= form.text_field :title, class: 'form-control' %>
+
+
+
+ <%= form.label :date %>
+ <%= form.date_field :date, class: 'form-control' %>
+
+
+
+
+
+ <%= form.label :location %>
+ <%= form.text_field :location, class: 'form-control' %>
+
+
+
+ <%= form.label :description, class: 'form-label' %>
+ <%= form.text_area :description, class: 'form-control', rows: 3 %>
+
+
+
+ <%= form.label :flyer, class: 'form-label' %>
+ <%= form.file_field :flyer, class: 'form-control' %>
+ Upload an image file for the event flyer (max 5MB)
+
+
+
+ <%= form.submit 'Create Event', class: 'mt-3 mb-3 d-block mx-auto btn-lg btn-primary' %>
+
+ <% end %>
+
+<%= link_to 'Back to Events', admin_events_path, class: 'btn btn-secondary btn-lg d-block mx-auto mb-5' %>
+
+
diff --git a/app/views/admins/courses/_form.html.erb b/app/views/admins/courses/_form.html.erb
new file mode 100644
index 000000000..25d9995d0
--- /dev/null
+++ b/app/views/admins/courses/_form.html.erb
@@ -0,0 +1,62 @@
+<%= form_with(model: course, url: course.persisted? ? update_course_path(course) : create_course_path, method: course.persisted? ? :patch : :post, local: true) do |form| %>
+ <% if course.errors.any? %>
+
+
<%= pluralize(course.errors.count, "error") %> prohibited this course from being saved:
+
+ <% course.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :code, "Course Code (e.g., 'L&S 198')", class: "form-label" %>
+ <%= form.text_field :code, class: "form-control" %>
+
+
+
+ <%= form.label :title, "Course Title (e.g., 'Re-entry Transition Course for Adult Learners')", class: "form-label" %>
+ <%= form.text_field :title, class: "form-control" %>
+
+
+
+ <%= form.label :description, "Course Description", class: "form-label" %>
+ <%= form.text_area :description, class: "form-control", rows: 5 %>
+
+
+
+ <%= form.label :units, "Units (e.g., 'One Unit – Pass/Not Pass')", class: "form-label" %>
+ <%= form.text_field :units, class: "form-control" %>
+
+
+
+ <%= form.label :semester, "Semester (e.g., 'Spring 2025')", class: "form-label" %>
+ <%= form.text_field :semester, class: "form-control" %>
+
+
+
+ <%= form.label :schedule, "Schedule (e.g., 'Wednesdays 3:00pm - 4:00 pm')", class: "form-label" %>
+ <%= form.text_field :schedule, class: "form-control" %>
+
+
+
+ <%= form.label :ccn, "Course Control Number (CCN#)", class: "form-label" %>
+ <%= form.text_field :ccn, class: "form-control" %>
+
+
+
+ <%= form.label :location, "Location", class: "form-label" %>
+ <%= form.text_field :location, class: "form-control" %>
+
+
+
+ <%= form.check_box :available, class: "form-check-input" %>
+ <%= form.label :available, "Make this course available on the public courses page", class: "form-check-label" %>
+
+
+
+ <%= form.submit class: "btn btn-primary" %>
+ <%= link_to "Cancel", manage_courses_path, class: "btn btn-secondary" %>
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/admins/courses/edit.html.erb b/app/views/admins/courses/edit.html.erb
new file mode 100644
index 000000000..c728be8dc
--- /dev/null
+++ b/app/views/admins/courses/edit.html.erb
@@ -0,0 +1,10 @@
+
+
+ <%= render "shared/flash_messages" %>
+
Edit Course
+
+ <%= render 'admins/courses/form', course: @course %>
+
+ <%= link_to "Back to Courses", manage_courses_path, class: "btn btn-secondary mt-3" %>
+
+
\ No newline at end of file
diff --git a/app/views/admins/courses/manage.html.erb b/app/views/admins/courses/manage.html.erb
new file mode 100644
index 000000000..e3a3919b6
--- /dev/null
+++ b/app/views/admins/courses/manage.html.erb
@@ -0,0 +1,63 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
+
Manage Courses
+
+
+ <%= link_to "Export", export_courses_path(format: :csv), class: "btn btn-secondary" %>
+
+
+
+
+
+ Code
+ Title
+ Units
+ Semester
+ Schedule
+ Available
+ Actions
+
+
+
+ <% @courses.each do |course| %>
+
+ <%= course.code %>
+ <%= course.title %>
+ <%= course.units %>
+ <%= course.semester %>
+ <%= course.schedule %>
+ <%= course.available ? "Yes" : "No" %>
+
+ <%= link_to "Edit", edit_course_path(course), class: "btn btn-warning btn-sm" %>
+ <%= button_to "Delete", destroy_course_path(course),
+ method: :delete,
+ data: { turbo_confirm: "Are you sure you want to delete this course?" },
+ class: "btn btn-danger btn-sm mt-1" %>
+
+
+ <% end %>
+
+
+
+
+ <%= link_to "Add New Course", new_course_path, class: "btn btn-primary me-2" %>
+ <%= link_to "Back to Admin Dashboard", admins_path, class: "btn btn-secondary" %>
+
+
+
+ <%= javascript_include_tag 'https://code.jquery.com/jquery-3.6.0.min.js' %>
+ <%= javascript_include_tag 'https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js' %>
+
+
+
\ No newline at end of file
diff --git a/app/views/admins/courses/new.html.erb b/app/views/admins/courses/new.html.erb
new file mode 100644
index 000000000..3040a1819
--- /dev/null
+++ b/app/views/admins/courses/new.html.erb
@@ -0,0 +1,10 @@
+
+
+ <%= render "shared/flash_messages" %>
+
New Course
+
+ <%= render 'admins/courses/form', course: @course %>
+
+ <%= link_to "Back to Courses", manage_courses_path, class: "btn btn-secondary mt-3" %>
+
+
\ No newline at end of file
diff --git a/app/views/admins/index.html.erb b/app/views/admins/index.html.erb
index 0267b5214..b95091464 100644
--- a/app/views/admins/index.html.erb
+++ b/app/views/admins/index.html.erb
@@ -4,7 +4,14 @@
<%= image_tag("reentry-owl.jpg", width: 130, height: 130, class: "d-block rounded mx-auto mb-4") %>
Admin Dashboard
- <%= link_to "view check-in records".html_safe, view_checkin_records_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
+ <%= link_to "View Check-in Records".html_safe, view_checkin_records_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
+ <%= link_to "Manage Events".html_safe, admin_events_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
+ <%= link_to "Manage Scholarships".html_safe, manage_scholarships_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
+
+
+ <%= link_to "Manage Advisors".html_safe, manage_advisors_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
+ <%= link_to "Manage Courses".html_safe, manage_courses_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
+ <%= link_to "Manage User Role".html_safe, manage_user_roles_path, class: "btn btn-primary mt-1 btn-lg px-4 gap-3" %>
diff --git a/app/views/admins/manage_advisors.html.erb b/app/views/admins/manage_advisors.html.erb
new file mode 100644
index 000000000..35e8652a2
--- /dev/null
+++ b/app/views/admins/manage_advisors.html.erb
@@ -0,0 +1,54 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
+
Manage Advisors
+
+
+
+
+ Name
+ Description
+ Calendar
+ Active
+ Actions
+
+
+
+ <% @advisors.each do |advisor| %>
+
+ <%= advisor.name %>
+ <%= advisor.description %>
+ <%= link_to advisor.calendar, advisor.calendar, target: "_blank" %>
+ <%= advisor.active ? "Yes" : "No" %>
+
+
+ <%= link_to "Edit", edit_advisor_path(advisor), class: "btn btn-primary btn-sm" %>
+ <%= link_to "Delete", delete_advisor_path(advisor), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-sm" %>
+
+
+ <% end %>
+
+
+
+
+ <%= link_to "Back to Admin Dashboard", admins_path, class: "btn btn-secondary" %>
+ <%= link_to "Add New Advisor", advisors_new_path, class: "btn btn-success mb-3" %>
+
+
+
+
+ <%= javascript_include_tag 'https://code.jquery.com/jquery-3.6.0.min.js' %>
+ <%= javascript_include_tag 'https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js' %>
+
+
+
\ No newline at end of file
diff --git a/app/views/admins/manage_user_roles.html.erb b/app/views/admins/manage_user_roles.html.erb
new file mode 100644
index 000000000..8f072038c
--- /dev/null
+++ b/app/views/admins/manage_user_roles.html.erb
@@ -0,0 +1,53 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
+
Manage User Roles
+
+
+
+
+ SID
+ Name
+ Email
+ Is Student?
+ Is Staff?
+ Is Admin?
+ Actions
+
+
+
+ <% @users.each do |user| %>
+
+ <%= user.sid %>
+ <%= user.name%>
+ <%= user.email %>
+ <%= user.is_student ? "Yes" : "No" %>
+ <%= user.is_staff ? "Yes" : "No" %>
+ <%= user.is_admin ? "Yes" : "No" %>
+ <%= link_to "Edit", edit_user_role_path(user), class: "btn btn-primary btn-sm" %>
+
+ <% end %>
+
+
+
+
+
+ <%= link_to "Back to Admin Dashboard".html_safe, admins_path, class: "btn btn-secondary" %>
+
+
+
+ <%= javascript_include_tag 'https://code.jquery.com/jquery-3.6.0.min.js' %>
+ <%= javascript_include_tag 'https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js' %>
+
+
+
\ No newline at end of file
diff --git a/app/views/admins/scholarships/_form.html.erb b/app/views/admins/scholarships/_form.html.erb
new file mode 100644
index 000000000..f7864462e
--- /dev/null
+++ b/app/views/admins/scholarships/_form.html.erb
@@ -0,0 +1,37 @@
+<%= form_with(model: scholarship, url: scholarship.persisted? ? update_scholarship_path(scholarship) : create_scholarship_path, method: scholarship.persisted? ? :patch : :post, local: true) do |form| %>
+ <% if scholarship.errors.any? %>
+
+
<%= pluralize(scholarship.errors.count, "error") %> prohibited this scholarship from being saved:
+
+ <% scholarship.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, class: "form-label fw-bold" %>
+ <%= form.text_field :name, class: "form-control", placeholder: "Enter scholarship name" %>
+
+
+
+ <%= form.label :description, class: "form-label fw-bold" %>
+ <%= form.text_area :description, class: "form-control", rows: 5, placeholder: "Enter scholarship description" %>
+
+
+
+ <%= form.label :status_text, class: "form-label fw-bold" %>
+ <%= form.text_field :status_text, class: "form-control", placeholder: "e.g., Open, Closed, Coming Soon" %>
+
+
+
+ <%= form.label :application_url, class: "form-label fw-bold" %>
+ <%= form.url_field :application_url, class: "form-control", placeholder: "https://example.com/apply" %>
+
+
+
+ <%= form.submit class: "btn btn-primary" %>
+ <%= link_to "Cancel", manage_scholarships_path, class: "btn btn-secondary" %>
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/admins/scholarships/edit.html.erb b/app/views/admins/scholarships/edit.html.erb
new file mode 100644
index 000000000..baf468d76
--- /dev/null
+++ b/app/views/admins/scholarships/edit.html.erb
@@ -0,0 +1,27 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
Edit Scholarship
+
+
+
+
+
+
+ <%= render 'admins/scholarships/form', scholarship: @scholarship %>
+
+
+
+
+ <%= link_to "Back to Scholarships", manage_scholarships_path, class: "btn btn-secondary" %>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/admins/scholarships/index.html.erb b/app/views/admins/scholarships/index.html.erb
new file mode 100644
index 000000000..e9141401c
--- /dev/null
+++ b/app/views/admins/scholarships/index.html.erb
@@ -0,0 +1,78 @@
+
+
+
+ <%= link_to "Home", root_path, method: :get, class: "btn btn-outline-primary", style: "color: darkgray; border-color: darkgray;"%>
+
+
+
+
+
+
+ <%= link_to "Check In".html_safe, checkin_path, class: "nav-link", style:"color: darkgray;"%>
+
+
+ <%= link_to "Make Appointments".html_safe, appointments_path, method: :get, class: "nav-link", style:"color: darkgray;"%>
+
+
+ <%= link_to "Scholarships".html_safe, scholarships_path, class: "nav-link", style:"color: white;"%>
+
+
+ <%= link_to "Courses".html_safe, courses_path, class: "nav-link", style:"color: darkgray;"%>
+
+
+ <%= link_to "Events".html_safe, events_path, class: "nav-link", style:"color: darkgray;"%>
+
+
+
+
+
+
+
+
Scholarships & Awards
+
+
Scholarship Opportunities for Re-entry Students
+
The Re-entry Student Program offers two scholarships: Crankstart for newly admitted students,
+ and Osher for continuing students.
+
Applications for the 2024 Osher Scholarship opened in May 2024 and closed in July 2024.
+ We will open the Osher Scholarship for continuing students again in May 2025 .
+
Applications for the 2024 Crankstart Scholarship opened in August 2024 and
+ closed in October 2024. We will open the Crankstart Scholarship for new students again in August 2025.
+
+
+
+ <% @scholarships.each do |scholarship| %>
+
<%# Add margin-bottom for spacing between scholarships %>
+
+
+
+
+ <%= simple_format(scholarship.description) %>
+
+ <%# Display the status text and link %>
+ <% if scholarship.application_url.present? %>
+ <%= link_to scholarship.status_text.presence || "Apply Now",
+ scholarship.application_url,
+ class: "btn btn-info",
+ target: "_blank" %>
+ <% else %>
+
<%= scholarship.status_text.presence || "Information not available" %>
+ <% end %>
+
+
+
+
+ <% end %>
+
+ <% if @scholarships.empty? %>
+
+
There are currently no scholarships listed.
+
+ <% end %>
+
+
+
+
+ <%= link_to "Back", root_path, method: :get, class:"btn btn-secondary btn-lg my-4", style:"color:#003262" %>
+
+
+
diff --git a/app/views/admins/scholarships/manage.html.erb b/app/views/admins/scholarships/manage.html.erb
new file mode 100644
index 000000000..3996ed3fe
--- /dev/null
+++ b/app/views/admins/scholarships/manage.html.erb
@@ -0,0 +1,77 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
+
Manage Scholarships
+
+
+ <%= link_to "Export", export_scholarships_path(format: :csv), class: "btn btn-secondary" %>
+
+
+
+
+
+ Name
+ Description
+ Status
+ Application URL
+ Actions
+
+
+
+ <% @scholarships.each do |scholarship| %>
+
+ <%= scholarship.name %>
+ <%= truncate(scholarship.description, length: 100) %>
+ <%= scholarship.status_text %>
+
+ <% if scholarship.application_url.present? %>
+ <%= link_to "View Link", scholarship.application_url, target: "_blank", class: "btn btn-sm btn-outline-primary" %>
+ <% else %>
+ No URL
+ <% end %>
+
+
+
+ <%= link_to "Edit", edit_scholarship_path(scholarship), class: "btn btn-warning btn-sm me-2" %>
+ <%= button_to "Delete", destroy_scholarship_path(scholarship),
+ method: :delete,
+ data: { turbo_confirm: "Are you sure you want to delete this scholarship?" },
+ class: "btn btn-danger btn-sm" %>
+
+
+
+ <% end %>
+
+
+
+
+ <%= link_to "Add New Scholarship", new_scholarship_path, class: "btn btn-primary me-2" %>
+ <%= link_to "Back to Admin Dashboard", admins_path, class: "btn btn-secondary" %>
+
+
+
+ <%= javascript_include_tag 'https://code.jquery.com/jquery-3.6.0.min.js' %>
+ <%= javascript_include_tag 'https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js' %>
+
+
+
\ No newline at end of file
diff --git a/app/views/admins/scholarships/new.html.erb b/app/views/admins/scholarships/new.html.erb
new file mode 100644
index 000000000..b053e8e19
--- /dev/null
+++ b/app/views/admins/scholarships/new.html.erb
@@ -0,0 +1,27 @@
+
+
+ <%= stylesheet_link_tag 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css' %>
+
+
+
+
+ <%= render "shared/flash_messages" %>
+
New Scholarship
+
+
+
+
+
+
+ <%= render 'admins/scholarships/form', scholarship: @scholarship %>
+
+
+
+
+ <%= link_to "Back to Scholarships", manage_scholarships_path, class: "btn btn-secondary" %>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/advisors/edit.html.erb b/app/views/advisors/edit.html.erb
new file mode 100644
index 000000000..55fcee4d1
--- /dev/null
+++ b/app/views/advisors/edit.html.erb
@@ -0,0 +1,23 @@
+
+
Edit Advisor
+ <%= form_with model: @advisor, local: true do |form| %>
+
+ <%= form.label :name, class: "form-label" %>
+ <%= form.text_field :name, class: "form-control" %>
+
+
+ <%= form.label :description, class: "form-label" %>
+ <%= form.text_area :description, class: "form-control" %>
+
+
+ <%= form.label :calendar, class: "form-label" %>
+ <%= form.text_field :calendar, class: "form-control" %>
+
+
+ <%= form.label :active, class: "form-label" %>
+ <%= form.check_box :active, class: "form-check-input" %>
+
+ <%= form.submit "Save Changes", class: "btn btn-primary" %>
+ <%= link_to "Cancel", manage_advisors_path, class: "btn btn-secondary" %>
+ <% end %>
+
\ No newline at end of file
diff --git a/app/views/advisors/new.html.erb b/app/views/advisors/new.html.erb
new file mode 100644
index 000000000..7a47a4271
--- /dev/null
+++ b/app/views/advisors/new.html.erb
@@ -0,0 +1,23 @@
+
+
Add New Advisor
+ <%= form_with model: @advisor, local: true do |form| %>
+
+ <%= form.label :name, class: "form-label" %>
+ <%= form.text_field :name, class: "form-control" %>
+
+
+ <%= form.label :description, class: "form-label" %>
+ <%= form.text_area :description, class: "form-control" %>
+
+
+ <%= form.label :calendar, class: "form-label" %>
+ <%= form.text_field :calendar, class: "form-control" %>
+
+
+ <%= form.label :active, class: "form-label" %>
+ <%= form.check_box :active, class: "form-check-input" %>
+
+ <%= form.submit "Create Advisor", class: "btn btn-primary" %>
+ <%= link_to "Cancel", manage_advisors_path, class: "btn btn-secondary" %>
+ <% end %>
+
\ No newline at end of file
diff --git a/app/views/appointments/advisors.html.erb b/app/views/appointments/advisors.html.erb
index da9bc554b..a112fbda0 100644
--- a/app/views/appointments/advisors.html.erb
+++ b/app/views/appointments/advisors.html.erb
@@ -1,18 +1,24 @@
-
-
Create an Appointment with a Counselor
-
-
-
- <%= link_to "Back", root_path, method: :get, class:"btn btn-secondary btn-lg my-4" %>
-
+
+
+
+
Make an Appointment with a Counselor
+
+
+ <% @advisors.each do |advisor| %>
+ <% if advisor.active %>
+
+
<%= advisor.name %>
+
<%= advisor.description %>
+
+
+ <% end %>
+ <% end %>
-
\ No newline at end of file
+
+
+ <%= link_to "Back", root_path, method: :get, class: "btn btn-secondary btn-lg my-4", style: "color:#003262" %>
+
+
+
\ No newline at end of file
diff --git a/app/views/checkin/new.html.erb b/app/views/checkin/new.html.erb
index 393b9bafb..23ebd3b62 100644
--- a/app/views/checkin/new.html.erb
+++ b/app/views/checkin/new.html.erb
@@ -1,15 +1,26 @@
-
+
+
-
Check-in to the Berkeley Reentry Student Program Study Space
- <%= form_with model: Checkin.new, url: checkin_path do |f| %>
+
Check-in to the Berkeley RSP Community Space
+ <%= form_with model: @checkin, url: checkin_path, local: true do |f| %>
<%= f.label :reason, "Please select a reason for check-in today ✅".html_safe, class: "form-label" %>
- <%= f.select :reason, @reasons, { :include_blank => 'Select One' }, {:required => true, class: "form-select form-control form-select-lg"} %>
+ <%= select_tag "checkin[reason]",
+ options_for_select(@reasons, @default_reason),
+ class: "form-select form-control form-select-lg",
+ id: "checkin_reason",
+ required: true,
+ data: { default: @default_reason } %>
- <%= link_to "Back", root_path, method: :get, class: "btn btn-secondary btn-lg" %>
- <%= f.submit "Submit", class: "btn btn-primary btn-lg ms-2" %>
- <% end %>
+
+
+ <%= link_to "Back", root_path, class: "btn btn-secondary btn-lg", style: "color:#003262" %>
+ <%= f.submit "Submit", class: "btn btn-primary btn-lg ms-2 berkeley-color" %>
+
+
+ <% end %>
+
diff --git a/app/views/courses/index.html.erb b/app/views/courses/index.html.erb
new file mode 100644
index 000000000..7b35c0496
--- /dev/null
+++ b/app/views/courses/index.html.erb
@@ -0,0 +1,45 @@
+
+
+
Courses
+
+ <% if @courses.empty? %>
+
+ No courses are currently available. Please check back later.
+
+ <% else %>
+ <% @courses.each do |course| %>
+
+
+
+
+
+
<%= course.description %>
+
+
+ Units: <%= course.units %>
+ <% if course.semester.present? %>
+ Semester: <%= course.semester %>
+ Schedule: <%= course.schedule %>
+ <% if course.ccn.present? %> (CCN#: <%= course.ccn %>)<% end %>
+ Location: <%= course.location %>
+ <% end %>
+
+
+
+ <% if course.code == "L&S 198" %>
+
+ <% end %>
+
+
+
+
+ <% end %>
+ <% end %>
+
+
+ <%= link_to "Back", root_path, class: "btn btn-secondary btn-lg", style: "color: white;" %>
+
+
+
diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb
new file mode 100644
index 000000000..8a90f4033
--- /dev/null
+++ b/app/views/events/index.html.erb
@@ -0,0 +1,51 @@
+
+
Events
+
+ <% if @upcoming_events.present? %>
+
Upcoming Events
+
+ <% @upcoming_events.each do |event| %>
+
+
+
<%= event.title %>
+
<%= event.description %>
+
+
+
Details:
+
+ Date: <%= event.formatted_date %>
+ Time: <%= event.formatted_time %>
+ Location: <%= event.location %>
+
+
+
+
+ <% end %>
+ <% else %>
+
+
No upcoming events at this time. Check back soon!
+
+ <% end %>
+
+ <% if @past_events.present? %>
+
Past Events
+
+
+ <% @past_events.each do |event| %>
+
<%= event.title %>
+
<%= event.formatted_date %>, <%= event.formatted_time %>
+
Location: <%= event.location %>
+
<%= event.description %>
+ <% unless event == @past_events.last %>
+
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+
+
+
+ <%= link_to "Back", root_path, class: "btn btn-secondary btn-lg", style: "color:#003262" %>
+
+
\ No newline at end of file
diff --git a/app/views/layouts/action_text/contents/_content.html.erb b/app/views/layouts/action_text/contents/_content.html.erb
new file mode 100644
index 000000000..9e3c0d0df
--- /dev/null
+++ b/app/views/layouts/action_text/contents/_content.html.erb
@@ -0,0 +1,3 @@
+
+ <%= yield -%>
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index f418df856..6539177cc 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -14,6 +14,7 @@
+ <%= render 'shared/navbar' %>
<%= yield %>