Skip to content

Commit

Permalink
Merge pull request #184 from kufu/features/friends
Browse files Browse the repository at this point in the history
Friends
  • Loading branch information
kinoppyd authored May 10, 2024
2 parents a3e20ed + 3189a19 commit 0592dd9
Show file tree
Hide file tree
Showing 22 changed files with 294 additions and 49 deletions.
1 change: 1 addition & 0 deletions app/controllers/concerns/q_rcode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def url_to_svg_qrcode(url:, color: '000', shape_rendering: 'crispEdges', module_
shape_rendering:,
module_size:,
standalone: true,
viewbox: true,
use_path: true
)
end
Expand Down
58 changes: 58 additions & 0 deletions app/controllers/friends_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

class FriendsController < ApplicationController
include QRcode

before_action :make_sure_user_logged_in

def new
@trigger = Trigger.find_by(description: trigger_description)

if @trigger
@trigger.key = SecureRandom.uuid
@trigger.expires_at = Time.zone.now + 30.seconds
@trigger.amount = 1
else
@trigger = new_trigger
end

raise unless @trigger.save

@qrcode = url_to_svg_qrcode(url: trigger_url(@trigger, key: @trigger.key))
end

private

def trigger_description
"friends:#{@user.profile.uid}"
end

def new_trigger
Trigger.build(
description: trigger_description,
key: SecureRandom.uuid,
action: [
{
model: 'Friend',
target: 'Profile',
props: {
from: @user.profile.id,
to: :target
},
action: :craete
},
{
model: 'Friend',
target: 'Profile',
props: {
from: :target,
to: @user.profile.id
},
action: :craete
}
],
expires_at: Time.zone.now + 30.seconds,
amount: 1
)
end
end
6 changes: 6 additions & 0 deletions app/controllers/schedules_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ class SchedulesController < ApplicationController
def index
@schedules = @event.schedules.includes(:speakers, :track).order(:start_at)
@schedule_table = Schedule::Tables.new(@schedules)

return unless @user&.profile

@friends_schedules_map = @user.profile.friend_profiles.to_h do |profile|
[profile.id, profile.user.plans.find_by(event: @event)&.plan_schedules&.map(&:schedule_id) || []]
end
end

def dialog
Expand Down
4 changes: 4 additions & 0 deletions app/helpers/friends_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

module FriendsHelper
end
33 changes: 33 additions & 0 deletions app/javascript/controllers/friend_code_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="friend-code"
export default class extends Controller {
static targets = ['counter']
static values = { expiresAt: String }

connect() {
this.calc();
this.timer = setInterval(() => {
this.calc();
}, 1000);
}

disconnect() {
clearInterval(this.timer);
}

calc () {
const expiresAt = Date.parse(this.expiresAtValue);
const now = Date.now();

if (now < expiresAt) {
this.counterTarget.innerHTML = Math.trunc((expiresAt - now) / 1000);
} else {
this.counterTarget.innerHTML = 0;

fetch('/profile/friends/new', { headers: { Accept: "text/vnd.turbo-stream.html" } })
.then(r => r.text())
.then(html => Turbo.renderStreamMessage(html));
}
}
}
3 changes: 3 additions & 0 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { application } from "./application"
import DialogController from "./dialog_controller"
application.register("dialog", DialogController)

import FriendCodeController from "./friend_code_controller"
application.register("friend-code", FriendCodeController)

import HamburgerController from "./hamburger_controller"
application.register("hamburger", HamburgerController)

Expand Down
9 changes: 9 additions & 0 deletions app/models/friend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class Friend < ApplicationRecord
belongs_to :from_profile, class_name: 'Profile', foreign_key: :from
belongs_to :to_profile, class_name: 'Profile', foreign_key: :to

validates :from, presence: true
validates :to, presence: true
end
2 changes: 2 additions & 0 deletions app/models/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class Profile < ApplicationRecord
has_many :teams, through: :team_profiles
has_many :profile_trophies, dependent: :destroy
has_many :trophies, through: :profile_trophies
has_many :friends, foreign_key: :from
has_many :friend_profiles, through: :friends, source: :to_profile

validates :provider, presence: true
validates :uid, presence: true
Expand Down
7 changes: 7 additions & 0 deletions app/views/friends/_qrcode.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="w-full">
<%= @qrcode.html_safe %>
<div class="flex flex-col w-full mt-4" data-controller="friend-code" data-friend-code-expires-at-value="<%= @trigger.expires_at %>">
<p>expires in <span data-friend-code-target="counter"></span> seconds</p>
<a href="<%= new_profile_friend_path %>" class="normal-button mr-2"><%= I18n.t('button.reload') %></a>
</div>
</div>
5 changes: 5 additions & 0 deletions app/views/friends/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%= turbo_frame_tag 'friend-qr' do %>
<div id="friend-qr-code">
<%= render 'qrcode' %>
</div>
<% end %>
3 changes: 3 additions & 0 deletions app/views/friends/new.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= turbo_stream.update 'friend-qr-code' do %>
<%= render 'qrcode' %>
<% end %>
137 changes: 90 additions & 47 deletions app/views/profiles/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,37 @@
</button>
<%= render 'dialog', profile: @profile %>
</div>
<% if @user.admin? %>
<div class="ml-2" data-controller="dialog" data-dialog-element-id-value="add-friend-dialog">
<button
class="p-2 text-sm min-h-[calc(0.587143rem+18px)] border-[rgb(214,211,208)] bg-white text-[rgb(35,34,30)] normal-button"
data-action="click->dialog#open"
>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" class=" smarthr-ui-Icon" role="img" aria-hidden="true" focusable="false" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm144 276c0 6.6-5.4 12-12 12h-92v92c0 6.6-5.4 12-12 12h-56c-6.6 0-12-5.4-12-12v-92h-92c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h92v-92c0-6.6 5.4-12 12-12h56c6.6 0 12 5.4 12 12v92h92c6.6 0 12 5.4 12 12v56z"></path></svg>
<%= I18n.t('button.add_friend') %>
</button>
<dialog id="add-friend-dialog" class="dialog">
<div class="border-b-[1px] border-[rgb(214,211,208)] py-4 px-6 flex flex-col justify-start">
<p class="text-xl"><%= I18n.t('dialog.read_qrcode_to_be_friend') %></p>
</div>
<div class="max-h-[calc(100vh-212px)] overflow-auto">
<div class="w-full sm:w-[656px] p-6">
<%= turbo_frame_tag 'friend-qr', src: new_profile_friend_path, loading: :lazy %>
</div>
</div>
<div class="border-t-[1px] border-[rgb(214,211,208)] flex flex-col py-4 px-6">
<div class="flex justify-end">
<a href="#" class="normal-button mr-2" data-action="click->dialog#close"><%= I18n.t('button.close')%></a>
</div>
</div>
</dialog>
</div>
<% end %>
<% end %>

</div>

<% unless my_profile? %>
<% if !my_profile? && @profile.user.current_plan %>
<div>
<a class="text-blue-700 visited:text-purple-700" href="<%= event_plan_path(@profile.user.current_plan, event_name: @profile.user.current_plan.event.name) %>">
<p><%= @profile.user.current_plan.title %></p>
Expand Down Expand Up @@ -66,60 +92,77 @@
</div>
</div>

<div class="w-full sm:w-1/2">
<div class="w-full sm:min-w-[400px] h-fit p-4 shadow-[0_1px_2px_0_rgba(3,3,2,0.3)] rounded-md bg-white">
<h2 class="text-xl sm:text-4xl">Team</h2>
<% if @profile.belongs_to_any_team? %>
<div class="flex items-center py-4">
<% if @profile.friend_profiles.present? %>
<div class="w-full sm:w-1/2">
<div class="w-full sm:min-w-[400px] h-fit p-4 shadow-[0_1px_2px_0_rgba(3,3,2,0.3)] rounded-md bg-white">
<h2 class="text-xl sm:text-4xl">Friends</h2>
<div class="flex flex-wrap p-2 gap-4">
<% @profile.friend_profiles.each do |friend| %>
<a href="/profiles/<%= friend.name %>">
<img src="<%= friend.avatar_url %>" class="h-12 w-12 rounded-full border-2 border-black" />
</a>
<% end %>
</div>
</div>
</div>
<% end %>

<% if my_profile? %>
<a href="<%= team_path(@user.profile.current_team) %>">
<p><%= @user.profile.current_team.name %>
(<%= @user.profile.team_profiles.find_by(team: @user.profile.current_team).human_attribute_enum(:role) %>)
<% if my_profile? || @profile.belongs_to_any_team? %>
<div class="w-full sm:w-1/2">
<div class="w-full sm:min-w-[400px] h-fit p-4 shadow-[0_1px_2px_0_rgba(3,3,2,0.3)] rounded-md bg-white">
<h2 class="text-xl sm:text-4xl">Team</h2>
<% if @profile.belongs_to_any_team? %>
<div class="flex items-center py-4">

<% if my_profile? %>
<a href="<%= team_path(@user.profile.current_team) %>">
<p><%= @user.profile.current_team.name %>
(<%= @user.profile.team_profiles.find_by(team: @user.profile.current_team).human_attribute_enum(:role) %>)
</p>
</a>
<% unless @user.profile.current_team.admin?(@user) %>
<div class="flex-grow flex justify-end">
<%= form_with(url: team_member_path(@user.profile, team_id: @user.profile.current_team), method: :delete) do |f| %>
<%= f.submit 'exit', class: 'danger-button' %>
<% end %>
</div>
<% end %>
<% else %>
<p><%= @profile.current_team.name %>
(<%= @profile.team_profiles.find_by(team: @profile.current_team).human_attribute_enum(:role) %>)
</p>
</a>
<% unless @user.profile.current_team.admin?(@user) %>
<div class="flex-grow flex justify-end">
<%= form_with(url: team_member_path(@user.profile, team_id: @user.profile.current_team), method: :delete) do |f| %>
<%= f.submit 'exit', class: 'danger-button' %>
<% end %>
</div>
<% end %>
<% else %>
<p><%= @profile.current_team.name %>
(<%= @profile.team_profiles.find_by(team: @profile.current_team).human_attribute_enum(:role) %>)
</p>
<% end %>

</div>
<% elsif my_profile? %>
<div class="flex flex-col justify-center items-center py-4 gap-4">
<div class="p-2">
<p class="text-xl"><%= I18n.t('teams.about') %></p>
<div class="p-4">
<ul class="list-disc list-inside leading-7">
<% I18n.t('teams.features').each do |feature| %>
<li><%= feature %></li>
</div>
<% elsif my_profile? %>
<div class="flex flex-col justify-center items-center py-4 gap-4">
<div class="p-2">
<p class="text-xl"><%= I18n.t('teams.about') %></p>
<div class="p-4">
<ul class="list-disc list-inside leading-7">
<% I18n.t('teams.features').each do |feature| %>
<li><%= feature %></li>
<% end %>
</ul>
</div>
</div>
<a href="<%= new_team_path %>" class="primary-button w-64"><%= I18n.t('button.create_new_team') %></a>
<% if @user.profile.invitations? %>
<span class="m-4">OR</span>
<ul>
<% TeamProfile.where(profile: @user.profile, role: [:invitation]).each_with_index do |invitation, i| %>
<%= form_with(url: team_member_path(@user.profile, team_id: invitation.team), method: :patch) do |f| %>
<%= f.hidden_field :role, value: :member %>
<li><%= f.submit "join to #{invitation.team.name}", class: "primary-button w-64" %></li>
<% end %>
<% end %>
</ul>
</div>
<% end %>
</div>
<a href="<%= new_team_path %>" class="primary-button w-64"><%= I18n.t('button.create_new_team') %></a>
<% if @user.profile.invitations? %>
<span class="m-4">OR</span>
<ul>
<% TeamProfile.where(profile: @user.profile, role: [:invitation]).each_with_index do |invitation, i| %>
<%= form_with(url: team_member_path(@user.profile, team_id: invitation.team), method: :patch) do |f| %>
<%= f.hidden_field :role, value: :member %>
<li><%= f.submit "join to #{invitation.team.name}", class: "primary-button w-64" %></li>
<% end %>
<% end %>
</ul>
<% end %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
<% end %>

</div>
<% else %>
Expand Down
9 changes: 9 additions & 0 deletions app/views/schedules/_table_row.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
<td>
<% if row.tracks[track] %>
<div class="p-2"><%= render("schedules/card", schedule: row.tracks[track], mode: :schedule, inactive: @selected) %></div>
<% if @user&.profile %>
<div class="flex flex-wrap ml-6 mb-4">
<% @user.profile.friend_profiles.each do |profile| %>
<% if @friends_schedules_map[profile.id].include?(row.tracks[track].id) %>
<img src="<%= profile.avatar_url %>" class="h-8 w-8 rounded-full border-2 border-black mt-[-4px] ml-[-16px] mt-1" />
<% end %>
<% end %>
</div>
<% end %>
<% else %>
<div class="sm:p-2"></div>
<% end %>
Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ en:
username_to_invite: "User name to invite"
delete_team: "Are you sure delete this team?"
edit_introduce: "Edit introduce yourself"
read_qrcode_to_be_friend: "Read QR code to add friend"
button:
settings: "Settings"
update_memo: "Update memo"
Expand All @@ -111,6 +112,8 @@ en:
remove: "Remove"
manage_members: "Manage members"
delete_team: "Delete team"
add_friend: "Add friend"
reload: "Reload"
default_vales:
plan_title: "My plans"

Expand Down
3 changes: 3 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ ja:
username_to_invite: "招待するユーザー名"
delete_team: "このチームを削除しますか?"
edit_introduce: "自己紹介を編集"
read_qrcode_to_be_friend: "QRコードを読み込んで友達追加"
button:
settings: "設定"
update_memo: "メモを更新"
Expand All @@ -80,6 +81,8 @@ ja:
remove: "削除"
manage_members: "メンバーを管理"
delete_team: "チームを削除"
add_friend: "友達を追加"
reload: "再読み込み"
default_vales:
plan_title: "視聴予定"

Expand Down
4 changes: 3 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
get '/auth/:provider/callback', to: 'sessions#create'
delete '/session', to: 'sessions#delete'

resource :profile, only: %i[show update]
resource :profile, only: %i[show update] do
resources :friends, only: %i[new]
end
resources :profiles, only: %i[show update]

scope '/:event_name', as: 'event' do
Expand Down
Loading

0 comments on commit 0592dd9

Please sign in to comment.