Private
Public Access
1
0

Compare commits

..

8 Commits

Author SHA1 Message Date
199bb7e525 chg: pkg: fix all eslint issues - & add example of the testing env #10 2026-04-28 08:10:18 +02:00
5daaf71ae7 chg: pkg: new version release !skipChangelog 2026-04-23 21:42:21 +02:00
0aeec47996 new: pkg: add tracking code for the app #10
All checks were successful
Deploy to Production / deploy (push) Successful in 30s
2026-04-23 21:41:47 +02:00
3d67b8f2d9 chg: pkg: new version release !skipChangelog 2026-04-22 12:15:29 +02:00
dd9a190fd9 fix: usr: the error message cannot be seen during avatar changing #10
All checks were successful
Deploy to Production / deploy (push) Successful in 3m7s
2026-04-22 12:15:06 +02:00
f5e5019ea8 chg: pkg: new version release !skipChangelog 2026-04-21 22:47:04 +02:00
3f51eb5db6 chg: usr: increase the 2 MB avatar maximum file size to 10 MB #10
All checks were successful
Deploy to Production / deploy (push) Successful in 29s
2026-04-21 22:46:44 +02:00
55ef7c9301 chg: pkg: new version release !skipChangelog 2026-04-21 21:05:54 +02:00
9 changed files with 120 additions and 34 deletions

View File

@@ -1,6 +1,34 @@
# Changelog # Changelog
## v2026.2.9-0 (2026-04-23)
### New
* Add tracking code for the app #10. [Lang]
## v2026.2.8-3 (2026-04-22)
### Fix
* The error message cannot be seen during avatar changing #10. [Lang]
## v2026.2.8-2 (2026-04-21)
### Changes
* Increase the 2 MB avatar maximum file size to 10 MB #10. [Lang]
## v2026.2.8-1 (2026-04-21)
### Changes
* Upgrade front-end & back-end deps to the latest available version #10. [Lang]
## v2026.2.8-0 (2026-04-21) ## v2026.2.8-0 (2026-04-21)
### New ### New

View File

@@ -23,6 +23,7 @@ RUN install-php-extensions \
sodium sodium
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN printf '[PHP]\nupload_max_filesize=10M\npost_max_size=11M\n' > "$PHP_INI_DIR/conf.d/uploads.ini"
RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \ RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \
> "$PHP_INI_DIR/conf.d/opcache.ini" > "$PHP_INI_DIR/conf.d/opcache.ini"

View File

@@ -28,6 +28,21 @@
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4); box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
} }
#profile-avatar-root {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.profile-avatar__error {
font-size: 11px;
color: #e57373;
text-align: center;
max-width: 120px;
line-height: 1.3;
}
.profile-avatar { .profile-avatar {
position: relative; position: relative;
width: 80px; width: 80px;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
import React, { useMemo, useRef } from 'react'; import React, { Fragment, useMemo, useRef } from 'react';
import { string } from 'prop-types'; import { string } from 'prop-types';
import { useProfileDataProvider } from '@mine-hooks/useGameDataProvider'; import { useProfileDataProvider } from '@mine-hooks/useGameDataProvider';
@@ -16,6 +16,8 @@ export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
const [thumbUrl, setThumbUrl] = React.useState(initialThumbUrl || null); const [thumbUrl, setThumbUrl] = React.useState(initialThumbUrl || null);
const { uploadAvatarMutation: { isPending, error, mutate } } = useProfileDataProvider(); const { uploadAvatarMutation: { isPending, error, mutate } } = useProfileDataProvider();
const errorMessage = useMemo(() => error?.message ?? null, [error]);
const handleChange = e => { const handleChange = e => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
@@ -40,9 +42,8 @@ export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
}); });
}; };
const errorMessage = useMemo(() => error?.message ?? null, [error]);
return ( return (
<Fragment>
<div <div
className={`profile-avatar${isPending ? ' profile-avatar--loading' : ''}`} className={`profile-avatar${isPending ? ' profile-avatar--loading' : ''}`}
title="Click to change profile picture" title="Click to change profile picture"
@@ -62,10 +63,11 @@ export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleChange} onChange={handleChange}
/> />
{errorMessage && <div className="profile-avatar__error">{errorMessage}</div>}
</div> </div>
{errorMessage && <div className="profile-avatar__error">{errorMessage}</div>}
</Fragment>
); );
} };
AvatarUpload.propTypes = { AvatarUpload.propTypes = {
uploadUrl: string.isRequired, uploadUrl: string.isRequired,

View File

@@ -2,6 +2,25 @@
MineSeeker-specific testing setup and workflows. For general PHPUnit/Symfony testing, see [Symfony Testing Docs](https://symfony.com/doc/current/testing.html). MineSeeker-specific testing setup and workflows. For general PHPUnit/Symfony testing, see [Symfony Testing Docs](https://symfony.com/doc/current/testing.html).
## Example of the current tests
```shell
$ bin/phpunit (master->origin/master|✚1…2⚑1)
PHPUnit 13.1.7 by Sebastian Bergmann and contributors.
Runtime: PHP 8.5.5
Configuration: /var/www/splendid/Mine/phpunit.dist.xml
................................................................. 65 / 71 ( 91%)
...... 71 / 71 (100%)
Time: 00:07.319, Memory: 86.50 MB
OK (71 tests, 227 assertions)
Faker seed used: 918823
```
## Quick Start ## Quick Start
```bash ```bash

View File

@@ -27,6 +27,7 @@ use Doctrine\ORM\EntityManagerInterface;
use League\Flysystem\FilesystemException; use League\Flysystem\FilesystemException;
use League\Flysystem\FilesystemOperator; use League\Flysystem\FilesystemOperator;
use Liip\ImagineBundle\Imagine\Cache\CacheManager; use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Liip\ImagineBundle\Service\FilterService;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException; use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -140,6 +141,7 @@ class ProfileController extends AbstractController
Request $request, Request $request,
EntityManagerInterface $em, EntityManagerInterface $em,
CacheManager $cacheManager, CacheManager $cacheManager,
FilterService $filterService,
#[Autowire(service: 'mineseeker.media.storage')] FilesystemOperator $mediaStorage, #[Autowire(service: 'mineseeker.media.storage')] FilesystemOperator $mediaStorage,
): JsonResponse { ): JsonResponse {
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
@@ -153,8 +155,8 @@ class ProfileController extends AbstractController
return $this->json(['error' => 'No file uploaded.'], 400); return $this->json(['error' => 'No file uploaded.'], 400);
} }
if ($file->getSize() > 2 * 1024 * 1024) { if ($file->getSize() > 10 * 1024 * 1024) {
return $this->json(['error' => 'File is too large. Maximum 2 MB.'], 400); return $this->json(['error' => 'File is too large. Maximum 10 MB.'], 400);
} }
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; $allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
@@ -182,6 +184,7 @@ class ProfileController extends AbstractController
$mediaStorage->writeStream($newPath, $stream); $mediaStorage->writeStream($newPath, $stream);
} catch (FilesystemException $e) { } catch (FilesystemException $e) {
$this->logger->error('Unable to write new avatar: ' . $e->getMessage()); $this->logger->error('Unable to write new avatar: ' . $e->getMessage());
fclose($stream);
throw new RuntimeException('Unable to write new avatar: ' . $e->getMessage()); throw new RuntimeException('Unable to write new avatar: ' . $e->getMessage());
} }
fclose($stream); fclose($stream);
@@ -190,7 +193,7 @@ class ProfileController extends AbstractController
$em->flush(); $em->flush();
return $this->json([ return $this->json([
'thumbUrl' => $cacheManager->generateUrl($newPath, 'avatar_thumb'), 'thumbUrl' => $filterService->getUrlOfFilteredImage($newPath, 'avatar_thumb'),
]); ]);
} }

View File

@@ -2,7 +2,9 @@
{% macro stat_val(value, suffix) %} {% macro stat_val(value, suffix) %}
{%- set abbr = value >= 1000 -%} {%- set abbr = value >= 1000 -%}
<span class="profile-stat__value"{% if abbr %} title="{{ value }}"{% endif %}>{% if abbr %}{{ (value / 1000)|round(1, 'floor') }}k{% else %}{{ value }}{% endif %}{% if suffix %}<small>{{ suffix }}</small>{% endif %}</span> <span
class="profile-stat__value"{% if abbr %} title="{{ value }}"{% endif %}>{% if abbr %}{{ (value / 1000)|round(1, 'floor') }}k{% else %}{{ value }}{% endif %}{% if suffix %}
<small>{{ suffix }}</small>{% endif %}</span>
{% endmacro %} {% endmacro %}
{% block title %} - Profile{% endblock %} {% block title %} - Profile{% endblock %}
@@ -31,7 +33,7 @@
<div class="profile-header"> <div class="profile-header">
<div id="profile-avatar-root" <div id="profile-avatar-root"
data-upload-url="{{ path('MineSeekerBundle_profile_avatar') }}" data-upload-url="{{ path('MineSeekerBundle_profile_avatar') }}"
data-thumb-url="{{ app.user.avatarPath ? app.user.avatarPath|imagine_filter('avatar_thumb') : '' }}" data-thumb-url="{{ app.user.avatarPath ? path('liip_imagine_filter', {path: app.user.avatarPath, filter: 'avatar_thumb'}) : '' }}"
data-initials="{{ app.user.username|slice(0, 2)|upper }}"> data-initials="{{ app.user.username|slice(0, 2)|upper }}">
</div> </div>
<div class="profile-info"> <div class="profile-info">
@@ -138,7 +140,9 @@
and (my_points > 25 or opp_points > 25)) %} and (my_points > 25 or opp_points > 25)) %}
{% set is_anonymous = game.oppIsGuest %} {% set is_anonymous = game.oppIsGuest %}
<div class="profile-game profile-game--{{ result }}{% if not is_finished and not is_anonymous %} profile-game--ongoing{% elseif is_anonymous %} profile-game--abandoned{% endif %}{% if loop.index0 >= 5 %} profile-game--hidden{% endif %}" data-game-index="{{ loop.index0 }}"> <div
class="profile-game profile-game--{{ result }}{% if not is_finished and not is_anonymous %} profile-game--ongoing{% elseif is_anonymous %} profile-game--abandoned{% endif %}{% if loop.index0 >= 5 %} profile-game--hidden{% endif %}"
data-game-index="{{ loop.index0 }}">
<span class="profile-game__badge"> <span class="profile-game__badge">
{% if is_finished %} {% if is_finished %}
{{ result == 'win' ? 'Win' : (result == 'loss' ? 'Loss' : 'Draw') }} {{ result == 'win' ? 'Win' : (result == 'loss' ? 'Loss' : 'Draw') }}

View File

@@ -28,6 +28,20 @@
<link rel="icon" href="{{ asset('/images/favicon/favicon.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ asset('/images/favicon/favicon.ico') }}" type="image/x-icon">
{% block metas %}{% endblock %} {% block metas %}{% endblock %}
<title>MineSeeker{% block title %}{% endblock %}</title> <title>MineSeeker{% block title %}{% endblock %}</title>
<script
defer src="https://umami.splendidbear.org/script.js"
data-website-id="825e02a9-d675-4cbd-9e68-72b98de2e4e9"
>
</script>
<script
defer
src="https://umami.splendidbear.org/recorder.js"
data-website-id="825e02a9-d675-4cbd-9e68-72b98de2e4e9"
data-sample-rate="0.15"
data-mask-level="moderate"
data-max-duration="300000"
>
</script>
{% block stylesheets %}{% endblock %} {% block stylesheets %}{% endblock %}
</head> </head>