Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Friends #184

Merged
merged 7 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 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? %>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません仕様をよく分かっていないのですが、adminじゃないとQRコードを表示できない仕様でしょうか?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、説明不足でした!!!
これはリリースをRubyKaigiDay0に合わせたいなと思って塞いでいます、Day0の日にこのコミットをRevertして全員に使えるようにしようと思います

<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" />
Copy link
Collaborator

@e-ikuta e-ikuta May 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

モバイルだとスケジュールに友達のアイコンが表示されませんでした。これから対応予定でしたらすみません🙏

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

モバイルの存在忘れてました

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

モバイル対応はこのPRでやります?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別PRでやります

<% 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
Loading