Compare commits

..

2 commits

Author SHA1 Message Date
f8c0bcc191 Add map pins: load and create subjects via Leaflet 2026-06-05 18:50:54 +00:00
dd78bf42e3 Add proper auth pages 2026-06-05 18:41:50 +00:00
8 changed files with 88 additions and 9 deletions

View file

@ -2,13 +2,56 @@ import { Controller } from '@hotwired/stimulus';
import L from 'leaflet'; import L from 'leaflet';
import 'leaflet/dist/leaflet.min.css'; import 'leaflet/dist/leaflet.min.css';
L.Icon.Default.mergeOptions({
iconUrl: '/leaflet/marker-icon.png',
iconRetinaUrl: '/leaflet/marker-icon-2x.png',
shadowUrl: '/leaflet/marker-shadow.png',
});
export default class extends Controller { export default class extends Controller {
connect() { connect() {
const map = L.map(this.element).setView([51.0543, 3.7174], 13); this.map = L.map(this.element).setView([51.0543, 3.7174], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors', attribution: '© OpenStreetMap contributors',
maxZoom: 19, maxZoom: 19,
}).addTo(map); }).addTo(this.map);
this.loadSubjects();
this.map.on('click', (e) => this.createSubject(e.latlng));
}
loadSubjects() {
fetch('/api/subjects')
.then(r => r.json())
.then(data => {
data.payload.subjects.forEach(s => {
if (s.latitude && s.longitude) {
this.addMarker(s.latitude, s.longitude, s.name);
}
});
});
}
createSubject(latlng) {
const name = prompt('Name of this place?');
if (!name) return;
fetch('/api/subjects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name,
latitude: latlng.lat,
longitude: latlng.lng,
}),
})
.then(r => r.json())
.then(() => this.addMarker(latlng.lat, latlng.lng, name));
}
addMarker(lat, lng, name) {
L.marker([lat, lng]).addTo(this.map).bindPopup(name);
} }
} }

View file

@ -19,7 +19,11 @@ security:
main: main:
lazy: true lazy: true
provider: app_user_provider provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
logout:
path: app_logout
# Activate different ways to authenticate: # Activate different ways to authenticate:
# https://symfony.com/doc/current/security.html#the-firewall # https://symfony.com/doc/current/security.html#the-firewall
@ -28,8 +32,9 @@ security:
# Note: Only the *first* matching rule is applied # Note: Only the *first* matching rule is applied
access_control: access_control:
# - { path: ^/admin, roles: ROLE_ADMIN } - {path: ^/account/login, roles: PUBLIC_ACCESS}
# - { path: ^/profile, roles: ROLE_USER } - {path: ^/account/register, roles: PUBLIC_ACCESS}
- {path: ^/, roles: ROLE_USER}
when@test: when@test:
security: security:

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View file

@ -16,7 +16,7 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class AccountController extends AbstractController class AccountController extends AbstractController
{ {
#[Route('/account/login')] #[Route('/account/login', name: 'app_login')]
public function login(AuthenticationUtils $authUtils): Response public function login(AuthenticationUtils $authUtils): Response
{ {
$error = $authUtils->getLastAuthenticationError(); $error = $authUtils->getLastAuthenticationError();
@ -28,7 +28,7 @@ class AccountController extends AbstractController
]); ]);
} }
#[Route('/account/register')] #[Route('/account/register', name: 'app_register')]
public function register( public function register(
Request $request, Request $request,
UserPasswordHasherInterface $passwordHasher, UserPasswordHasherInterface $passwordHasher,
@ -58,11 +58,17 @@ class AccountController extends AbstractController
$em->persist($user); $em->persist($user);
$em->flush(); $em->flush();
return $this->redirectToRoute('app_account_login'); return $this->redirectToRoute('app_login');
} }
return $this->render('account/register.html.twig', [ return $this->render('account/register.html.twig', [
'form' => $form, 'form' => $form,
]); ]);
} }
#[Route('/account/logout', name: 'app_logout')]
public function logout(): void
{
throw new \LogicException('This should never be reached.');
}
} }

View file

@ -5,10 +5,12 @@ namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class IndexController extends AbstractController class IndexController extends AbstractController
{ {
#[Route('/')] #[Route('/')]
#[IsGranted('ROLE_USER')]
public function index(): Response public function index(): Response
{ {
return $this->render('index/page.html.twig'); return $this->render('index/page.html.twig');

View file

@ -0,0 +1,23 @@
{% extends 'base.html.twig' %}
{% block body %}
<h1>Log in</h1>
{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<form method="post">
<label for="email">Email</label>
<input type="email" id="email" name="_username" value="{{ last_username }}">
<label for="password">Password</label>
<input type="password" id="password" name="_password">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button type="submit">Log in</button>
</form>
<p>No account yet? <a href="{{ path('app_register') }}">Register</a></p>
{% endblock %}