Skip to content
Open
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
33 changes: 33 additions & 0 deletions Migrations/Version20260626100000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Override;

final class Version20260626100000 extends AbstractMigration
{
#[Override]
public function getDescription(): string
{
return 'Add subtrip hidden state and trip notification preference';
}

public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE IF NOT EXISTS member_subtrip_hidden (id INT AUTO_INCREMENT NOT NULL, member_id INT NOT NULL, subtrip_id INT NOT NULL, created DATETIME NOT NULL, INDEX IDX_B1EC0277597D3FE (member_id), INDEX IDX_B1EC027F2BD7DD7 (subtrip_id), UNIQUE INDEX member_subtrip_hidden_unique (member_id, subtrip_id), CONSTRAINT FK_B1EC0277597D3FE FOREIGN KEY (member_id) REFERENCES member (id) ON DELETE CASCADE, CONSTRAINT FK_B1EC027F2BD7DD7 FOREIGN KEY (subtrip_id) REFERENCES sub_trips (id) ON DELETE CASCADE, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE IF NOT EXISTS member_trip_notification_sent (id INT AUTO_INCREMENT NOT NULL, member_id INT NOT NULL, trip_id INT NOT NULL, created DATETIME NOT NULL, INDEX IDX_9D9311917597D3FE (member_id), INDEX IDX_9D931191A5BC2E0E (trip_id), UNIQUE INDEX member_trip_notification_sent_unique (member_id, trip_id), CONSTRAINT FK_9D9311917597D3FE FOREIGN KEY (member_id) REFERENCES member (id) ON DELETE CASCADE, CONSTRAINT FK_9D931191A5BC2E0E FOREIGN KEY (trip_id) REFERENCES trips (id) ON DELETE CASCADE, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql("INSERT INTO preferences (position, codeName, codeDescription, Description, created, DefaultValue, PossibleValues, Status) SELECT 65, 'TripsNotifications', 'trips.notifications', 'How often the member wants notifications for trips in their area', NOW(), 'No', 'No;Immediately;Daily;Weekly;Monthly', 'Normal' WHERE NOT EXISTS (SELECT 1 FROM preferences WHERE codeName = 'TripsNotifications')");
$this->addSql("UPDATE preferences SET PossibleValues = 'No;Immediately;Daily;Weekly;Monthly' WHERE codeName = 'TripsNotifications'");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would only change the database if the previous statement didn't. For new setups that will always be the case (the situation that needs cleanup can only arise during development).

}

#[Override]
public function down(Schema $schema): void
{
$this->addSql("DELETE mp FROM memberspreferences mp INNER JOIN preferences p ON p.id = mp.IdPreference WHERE p.codeName = 'TripsNotifications'");
$this->addSql("DELETE FROM preferences WHERE codeName = 'TripsNotifications'");
$this->addSql('DROP TABLE IF EXISTS member_trip_notification_sent');
$this->addSql('DROP TABLE IF EXISTS member_subtrip_hidden');
}
}
6 changes: 5 additions & 1 deletion assets/js/landing/landing.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ document.addEventListener('DOMContentLoaded', function() {
tabElements.forEach(tab => {
tab.addEventListener('show.bs.tab', Home.onTabChange);
});
const tabFromHash = Array.from(tabElements).find(tab => tab.getAttribute('href') === window.location.hash);
if (tabFromHash) {
window.bootstrap.Tab.getOrCreateInstance(tabFromHash).show();
}

const allRadio = document.getElementById('all');
if (allRadio) {
Expand Down Expand Up @@ -239,4 +243,4 @@ var Home = {
});
});
}
};
};
27 changes: 25 additions & 2 deletions assets/tailwindcss/components/_components.dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@
overflow: hidden;
}

.c-dashboard__trip-actions {
display: flex;
flex-wrap: wrap;
align-content: center;
justify-content: center;
gap: 4px;
width: 68px;
margin-left: auto;
flex-shrink: 0;
}

.c-dashboard__trip-actions form {
display: flex;
margin: 0;
}

@media (max-width: 480px) {
.c-dashboard__item--trip {
height: auto;
min-height: 80px;
padding-top: 8px;
padding-bottom: 8px;
}
}

.c-dashboard__message-item {
display: grid;
align-items: center;
Expand All @@ -33,5 +58,3 @@
overflow: hidden;
grid-template-columns: 80px auto 16px;
}


2 changes: 2 additions & 0 deletions assets/tailwindcss/objects/_objects.rounded.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: 0;
border-radius: 100%;
background-color: var(--u-color-bewelcome);
flex-shrink: 0;
Expand Down
9 changes: 9 additions & 0 deletions fixtures/preferences.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,12 @@ App\Entity\Preference:
DefaultValue: No
PossibleValues: No;Yes
Status: 'Normal'
TRIP_NOTIFICATIONS:
position: 65
codename: 'TripsNotifications'
codeDescription: 'trips.notifications'
description: 'How often the member wants notifications for trips in their area'
created: <DateTime()>
DefaultValue: No
PossibleValues: No;Immediately;Daily;Weekly;Monthly
Status: 'Normal'
59 changes: 59 additions & 0 deletions src/Command/SendTripNotificationsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Command;

use App\Entity\Preference;
use App\Model\TripModel;
use DateInterval;
use DateTimeImmutable;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'trips:notifications:send',
description: 'Send scheduled trip notification emails',
)]
class SendTripNotificationsCommand extends Command
{
private const array FREQUENCIES = [
'daily' => [Preference::TRIP_NOTIFICATIONS_DAILY, 'P1D'],
'weekly' => [Preference::TRIP_NOTIFICATIONS_WEEKLY, 'P7D'],
'monthly' => [Preference::TRIP_NOTIFICATIONS_MONTHLY, 'P31D'],
];

public function __construct(
private readonly TripModel $tripModel,
) {
parent::__construct();
}

protected function configure(): void
{
$this->addArgument('frequency', InputArgument::REQUIRED, 'daily, weekly or monthly');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$frequency = strtolower((string) $input->getArgument('frequency'));

if (!isset(self::FREQUENCIES[$frequency])) {
$io->error('Frequency must be daily, weekly or monthly.');

return Command::INVALID;
}

[$preferenceValue, $period] = self::FREQUENCIES[$frequency];
$until = new DateTimeImmutable();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I always thought about trip notifications as information about upcoming trips. This seems to target trips in the past.

$since = $until->sub(new DateInterval($period));
$sent = $this->tripModel->sendScheduledTripNotifications($preferenceValue, $since, $until);

$io->success(\sprintf('Sent %d trip notification emails.', $sent));

return Command::SUCCESS;
}
}
17 changes: 17 additions & 0 deletions src/Controller/TripController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class TripController extends AbstractController
Expand Down Expand Up @@ -100,6 +101,7 @@ public function create(Request $request, EntityManagerInterface $entityManager):

$entityManager->persist($trip);
$entityManager->flush();
$this->tripModel->notifyHostsAboutTrip($trip);

$this->addTranslatedFlash('success', 'trip.created');

Expand Down Expand Up @@ -179,6 +181,21 @@ public function copy(Trip $trip): Response
return $this->redirectToRoute('trip_edit', ['id' => $newTrip->getId()]);
}

#[Route(path: '/trip/leg/{id}/hide', name: 'trip_hide', requirements: ['id' => '\d+'], methods: ['POST'])]
#[IsCsrfTokenValid('hide_subtrip')]
public function hideSubtrip(Request $request, Subtrip $subtrip): RedirectResponse
{
/** @var Member $member */
$member = $this->getUser();
$this->tripModel->markSubtripAsHidden($member, $subtrip);

if ('homepage' === $request->request->get('redirectTo')) {
return $this->redirectToRoute('homepage', ['_fragment' => 'visitors']);
}

return $this->redirectToRoute('visitors');
}

/**
* Show all trip legs that are in the vicinity of a member.
*/
Expand Down
56 changes: 56 additions & 0 deletions src/Entity/MemberSubtripHidden.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Entity;

use DateTime;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Table(name: 'member_subtrip_hidden')]
#[ORM\UniqueConstraint(name: 'member_subtrip_hidden_unique', columns: ['member_id', 'subtrip_id'])]
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class MemberSubtripHidden
{
#[ORM\JoinColumn(name: 'member_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(targetEntity: Member::class)]
private Member $member;

#[ORM\JoinColumn(name: 'subtrip_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(targetEntity: Subtrip::class)]
private Subtrip $subtrip;

#[ORM\Column(name: 'created', type: 'datetime', nullable: false)]
private DateTime $created;

#[ORM\Column(name: 'id', type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private int $id;

public function __construct(Member $member, Subtrip $subtrip)
{
$this->member = $member;
$this->subtrip = $subtrip;
}

public function getMember(): Member
{
return $this->member;
}

public function getSubtrip(): Subtrip
{
return $this->subtrip;
}

public function getCreated(): DateTime
{
return $this->created;
}

#[ORM\PrePersist]
public function onPrePersist(): void
{
$this->created = new DateTime('now');
}
}
56 changes: 56 additions & 0 deletions src/Entity/MemberTripNotificationSent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Entity;

use DateTime;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Table(name: 'member_trip_notification_sent')]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As you hide legs of a trip notifications should be sent for legs as well, shouldn't they?

#[ORM\UniqueConstraint(name: 'member_trip_notification_sent_unique', columns: ['member_id', 'trip_id'])]
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class MemberTripNotificationSent
{
#[ORM\JoinColumn(name: 'member_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(targetEntity: Member::class)]
private Member $member;

#[ORM\JoinColumn(name: 'trip_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(targetEntity: Trip::class)]
private Trip $trip;

#[ORM\Column(name: 'created', type: 'datetime', nullable: false)]
private DateTime $created;

#[ORM\Column(name: 'id', type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private int $id;

public function __construct(Member $member, Trip $trip)
{
$this->member = $member;
$this->trip = $trip;
}

public function getMember(): Member
{
return $this->member;
}

public function getTrip(): Trip
{
return $this->trip;
}

public function getCreated(): DateTime
{
return $this->created;
}

#[ORM\PrePersist]
public function onPrePersist(): void
{
$this->created = new DateTime('now');
}
}
6 changes: 6 additions & 0 deletions src/Entity/Preference.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class Preference
public const READ_COMMENT_GUIDELINES = 'ReadCommentGuidelines';
public const FORUM_ORDER_LIST_ASC = 'PreferenceForumOrderListAsc';
public const TRIPS_VICINITY_RADIUS = 'TripLegsVicinityRadius';
public const TRIP_NOTIFICATIONS = 'TripsNotifications';
public const TRIP_NOTIFICATIONS_NEVER = 'No';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should be Never.

public const TRIP_NOTIFICATIONS_IMMEDIATELY = 'Immediately';
public const TRIP_NOTIFICATIONS_DAILY = 'Daily';
public const TRIP_NOTIFICATIONS_WEEKLY = 'Weekly';
public const TRIP_NOTIFICATIONS_MONTHLY = 'Monthly';
public const SHOW_PROFILE_VISITORS = 'PreferenceShowProfileVisits';
public const SHOW_FORUMS_POSTS = 'MyForumPostsPagePublic';
public const SEARCH_OPTIONS = 'SearchOptions';
Expand Down
14 changes: 14 additions & 0 deletions src/Form/SignupFormFinalizeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Form;

use App\Doctrine\AccommodationType;
use App\Entity\Preference;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
Expand Down Expand Up @@ -143,6 +144,19 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'label' => 'signup.label.local_events',
'required' => false,
])
->add('trips_notifications', ChoiceType::class, [
'label' => 'label.trips_notifications',
'help' => 'help.trips_notifications',
'choices' => [
'trips.no' => Preference::TRIP_NOTIFICATIONS_NEVER,
'trips.immediately' => Preference::TRIP_NOTIFICATIONS_IMMEDIATELY,
'trips.daily' => Preference::TRIP_NOTIFICATIONS_DAILY,
'trips.weekly' => Preference::TRIP_NOTIFICATIONS_WEEKLY,
'trips.monthly' => Preference::TRIP_NOTIFICATIONS_MONTHLY,
],
'data' => Preference::TRIP_NOTIFICATIONS_NEVER,
'required' => true,
])
;
}

Expand Down
12 changes: 12 additions & 0 deletions src/Model/SignupModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ public function updateMember(Member $member, array $data): void
;
$this->entityManager->persist($localEventsPreference);

$preference = $this->entityManager->getRepository(Preference::class)->findOneBy([
'codename' => Preference::TRIP_NOTIFICATIONS,
]);

$tripNotificationsPreference = new MemberPreference();
$tripNotificationsPreference
->setMember($member)
->setPreference($preference)
->setValue($data['trips_notifications'])
;
$this->entityManager->persist($tripNotificationsPreference);

$this->entityManager->flush();
}

Expand Down
Loading
Loading