Compare commits

..

6 Commits

Author SHA1 Message Date
Gitea Actions Bot f7a8765f58 chore: bump version to 1.0.108 (backend + frontend) 2025-09-02 15:46:57 +00:00
paul 214f120f7a chore: update system metrics
Mirror to GitHub / mirror (push) Successful in 45s
Test and Lint / backend-test (push) Successful in 1m43s
Test and Lint / frontend-test (push) Successful in 2m14s
Version and Release / version-bump (push) Successful in 1m5s
Version and Release / trigger-drone (push) Successful in 3s
2025-09-02 17:40:23 +02:00
paul f26becad1d fix: resolve feedback validation issues from GitHub issue #16
- Fixed 400 Bad Request error when submitting feedback with name/email required
- Updated backend validation to properly handle empty/undefined name/email fields
- Modified frontend components to send undefined instead of empty strings when fields are not provided
- Fixed thumbnail display issue in moderation view by using correct admin API endpoints
- Updated FeedbackModerationPanel and EventFeedbackPage to display thumbnails correctly

The issue was caused by the validation logic treating empty strings differently than undefined values.
Frontend components now properly send undefined when name/email are not provided, and the backend
validation correctly handles both cases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 17:40:23 +02:00
paul 67ff415840 fix: resolve feedback validation issues from GitHub issue #16
- Fixed backend validation to properly handle empty strings in validateGuestRequirements
- Added Boolean conversion for SQLite boolean values in feedback settings API response
- Created FeedbackIdentityModal component for collecting name/email when required
- Updated PhotoLikes, PhotoRating, and PhotoFavorites components to show modal when requireNameEmail is true
- Fixed issue where require_name_email field was not reaching frontend due to missing boolean conversion

This ensures that when 'Require Name & Email' is enabled, guests are prompted with a modal to provide their information before submitting feedback, preventing 400 Bad Request errors.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 17:40:23 +02:00
Gitea Actions Bot 88659f1fa6 chore: bump frontend version to 1.0.107 2025-09-02 14:14:52 +00:00
paul c1e10f14a3 fix: resolve translation interpolation issue for download button
Mirror to GitHub / mirror (push) Successful in 42s
Test and Lint / backend-test (push) Successful in 1m42s
Test and Lint / frontend-test (push) Successful in 2m19s
Version and Release / version-bump (push) Successful in 1m13s
Version and Release / trigger-drone (push) Successful in 3s
Fixed the download selected button not displaying count properly.
The translation key 'gallery.downloadSelected' was not receiving
the count parameter for interpolation, causing "{{count}}" to
display literally instead of the actual number.

Fixes the issue where the button showed:
- "Download {{count}} Selected" instead of "Download 2 Selected"
- "{{count}} ausgewählte herunterladen" instead of "3 ausgewählte herunterladen"

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 16:08:14 +02:00
17 changed files with 1091 additions and 148 deletions
+790 -70
View File
@@ -1,122 +1,842 @@
[
{
"timestamp": 1756499536862,
"timestamp": 1756825534957,
"memoryTotal": 25769803776,
"memoryUsed": 25283330048,
"memoryFree": 486473728,
"memoryUsagePercent": 98.11223347981772,
"memoryEfficiency": 1.8877665201822822,
"memoryUsed": 24041455616,
"memoryFree": 1728348160,
"memoryUsagePercent": 93.29312642415366,
"memoryEfficiency": 6.706873575846345,
"cpuCount": 14,
"cpuLoad": 0.21784319196428573,
"cpuLoad": 0.24497767857142858,
"platform": "darwin",
"uptime": 5185690
"uptime": 93586
},
{
"timestamp": 1756499566862,
"timestamp": 1756825564958,
"memoryTotal": 25769803776,
"memoryUsed": 25547767808,
"memoryFree": 222035968,
"memoryUsagePercent": 99.13838704427084,
"memoryEfficiency": 0.8616129557291572,
"memoryUsed": 24151605248,
"memoryFree": 1618198528,
"memoryUsagePercent": 93.72056325276694,
"memoryEfficiency": 6.279436747233063,
"cpuCount": 14,
"cpuLoad": 0.181396484375,
"cpuLoad": 0.21958705357142858,
"platform": "darwin",
"uptime": 5185720
"uptime": 93616
},
{
"timestamp": 1756499596863,
"timestamp": 1756825594958,
"memoryTotal": 25769803776,
"memoryUsed": 25573294080,
"memoryFree": 196509696,
"memoryUsagePercent": 99.23744201660156,
"memoryEfficiency": 0.7625579833984375,
"memoryUsed": 24196726784,
"memoryFree": 1573076992,
"memoryUsagePercent": 93.89565785725912,
"memoryEfficiency": 6.104342142740876,
"cpuCount": 14,
"cpuLoad": 0.17428152901785715,
"cpuLoad": 0.20281110491071427,
"platform": "darwin",
"uptime": 5185750
"uptime": 93646
},
{
"timestamp": 1756499626864,
"timestamp": 1756825624960,
"memoryTotal": 25769803776,
"memoryUsed": 25607421952,
"memoryFree": 162381824,
"memoryUsagePercent": 99.3698755900065,
"memoryEfficiency": 0.6301244099934991,
"memoryUsed": 24166842368,
"memoryFree": 1602961408,
"memoryUsagePercent": 93.77969106038412,
"memoryEfficiency": 6.220308939615876,
"cpuCount": 14,
"cpuLoad": 0.17811802455357142,
"cpuLoad": 0.19098772321428573,
"platform": "darwin",
"uptime": 5185780
"uptime": 93676
},
{
"timestamp": 1756499656866,
"timestamp": 1756825654961,
"memoryTotal": 25769803776,
"memoryUsed": 25676906496,
"memoryFree": 92897280,
"memoryUsagePercent": 99.63951110839844,
"memoryEfficiency": 0.3604888916015625,
"memoryUsed": 24138383360,
"memoryFree": 1631420416,
"memoryUsagePercent": 93.66925557454428,
"memoryEfficiency": 6.33074442545572,
"cpuCount": 14,
"cpuLoad": 0.14320591517857142,
"cpuLoad": 0.22391183035714285,
"platform": "darwin",
"uptime": 5185810
"uptime": 93706
},
{
"timestamp": 1756499686866,
"timestamp": 1756825684962,
"memoryTotal": 25769803776,
"memoryUsed": 25690243072,
"memoryFree": 79560704,
"memoryUsagePercent": 99.69126383463541,
"memoryEfficiency": 0.3087361653645928,
"memoryUsed": 24151572480,
"memoryFree": 1618231296,
"memoryUsagePercent": 93.7204360961914,
"memoryEfficiency": 6.279563903808594,
"cpuCount": 14,
"cpuLoad": 0.12803431919642858,
"cpuLoad": 0.17843191964285715,
"platform": "darwin",
"uptime": 5185840
"uptime": 93736
},
{
"timestamp": 1756499716867,
"timestamp": 1756825714964,
"memoryTotal": 25769803776,
"memoryUsed": 25551355904,
"memoryFree": 218447872,
"memoryUsagePercent": 99.15231068929037,
"memoryEfficiency": 0.8476893107096259,
"memoryUsed": 24201838592,
"memoryFree": 1567965184,
"memoryUsagePercent": 93.91549428304037,
"memoryEfficiency": 6.084505716959626,
"cpuCount": 14,
"cpuLoad": 0.17728097098214285,
"cpuLoad": 0.18014090401785715,
"platform": "darwin",
"uptime": 5185870
"uptime": 93766
},
{
"timestamp": 1756499746868,
"timestamp": 1756825744964,
"memoryTotal": 25769803776,
"memoryUsed": 25028608000,
"memoryFree": 741195776,
"memoryUsagePercent": 97.1237818400065,
"memoryEfficiency": 2.876218159993499,
"memoryUsed": 24876957696,
"memoryFree": 892846080,
"memoryUsagePercent": 96.5353012084961,
"memoryEfficiency": 3.4646987915039062,
"cpuCount": 14,
"cpuLoad": 0.14596121651785715,
"cpuLoad": 0.19855608258928573,
"platform": "darwin",
"uptime": 5185900
"uptime": 93796
},
{
"timestamp": 1756499776869,
"timestamp": 1756825774964,
"memoryTotal": 25769803776,
"memoryUsed": 25660850176,
"memoryFree": 108953600,
"memoryUsagePercent": 99.57720438639322,
"memoryEfficiency": 0.4227956136067803,
"memoryUsed": 24367546368,
"memoryFree": 1402257408,
"memoryUsagePercent": 94.55852508544922,
"memoryEfficiency": 5.441474914550781,
"cpuCount": 14,
"cpuLoad": 0.16322544642857142,
"cpuLoad": 0.16741071428571427,
"platform": "darwin",
"uptime": 5185930
"uptime": 93826
},
{
"timestamp": 1756499806870,
"timestamp": 1756825804966,
"memoryTotal": 25769803776,
"memoryUsed": 25489965056,
"memoryFree": 279838720,
"memoryUsagePercent": 98.91408284505209,
"memoryEfficiency": 1.0859171549479072,
"memoryUsed": 24649596928,
"memoryFree": 1120206848,
"memoryUsagePercent": 95.65302530924478,
"memoryEfficiency": 4.346974690755218,
"cpuCount": 14,
"cpuLoad": 0.15792410714285715,
"cpuLoad": 0.20465959821428573,
"platform": "darwin",
"uptime": 5185960
"uptime": 93856
},
{
"timestamp": 1756825834967,
"memoryTotal": 25769803776,
"memoryUsed": 24542707712,
"memoryFree": 1227096064,
"memoryUsagePercent": 95.23824055989584,
"memoryEfficiency": 4.761759440104157,
"cpuCount": 14,
"cpuLoad": 0.19587053571428573,
"platform": "darwin",
"uptime": 93886
},
{
"timestamp": 1756825864968,
"memoryTotal": 25769803776,
"memoryUsed": 24563548160,
"memoryFree": 1206255616,
"memoryUsagePercent": 95.31911214192709,
"memoryEfficiency": 4.680887858072907,
"cpuCount": 14,
"cpuLoad": 0.20664760044642858,
"platform": "darwin",
"uptime": 93916
},
{
"timestamp": 1756825894969,
"memoryTotal": 25769803776,
"memoryUsed": 24623235072,
"memoryFree": 1146568704,
"memoryUsagePercent": 95.55072784423828,
"memoryEfficiency": 4.449272155761719,
"cpuCount": 14,
"cpuLoad": 0.22220284598214285,
"platform": "darwin",
"uptime": 93946
},
{
"timestamp": 1756825924971,
"memoryTotal": 25769803776,
"memoryUsed": 24605114368,
"memoryFree": 1164689408,
"memoryUsagePercent": 95.48041025797525,
"memoryEfficiency": 4.519589742024749,
"cpuCount": 14,
"cpuLoad": 0.2533482142857143,
"platform": "darwin",
"uptime": 93976
},
{
"timestamp": 1756825954971,
"memoryTotal": 25769803776,
"memoryUsed": 24649580544,
"memoryFree": 1120223232,
"memoryUsagePercent": 95.65296173095703,
"memoryEfficiency": 4.347038269042969,
"cpuCount": 14,
"cpuLoad": 0.2626953125,
"platform": "darwin",
"uptime": 94006
},
{
"timestamp": 1756825984971,
"memoryTotal": 25769803776,
"memoryUsed": 24654905344,
"memoryFree": 1114898432,
"memoryUsagePercent": 95.67362467447916,
"memoryEfficiency": 4.326375325520843,
"cpuCount": 14,
"cpuLoad": 0.26157924107142855,
"platform": "darwin",
"uptime": 94036
},
{
"timestamp": 1756826014972,
"memoryTotal": 25769803776,
"memoryUsed": 24997396480,
"memoryFree": 772407296,
"memoryUsagePercent": 97.00266520182291,
"memoryEfficiency": 2.997334798177093,
"cpuCount": 14,
"cpuLoad": 0.26890345982142855,
"platform": "darwin",
"uptime": 94066
},
{
"timestamp": 1756826044976,
"memoryTotal": 25769803776,
"memoryUsed": 25328959488,
"memoryFree": 440844288,
"memoryUsagePercent": 98.28929901123047,
"memoryEfficiency": 1.7107009887695312,
"cpuCount": 14,
"cpuLoad": 0.23458426339285715,
"platform": "darwin",
"uptime": 94096
},
{
"timestamp": 1756826074988,
"memoryTotal": 25769803776,
"memoryUsed": 25015320576,
"memoryFree": 754483200,
"memoryUsagePercent": 97.07221984863281,
"memoryEfficiency": 2.9277801513671875,
"cpuCount": 14,
"cpuLoad": 0.20040457589285715,
"platform": "darwin",
"uptime": 94126
},
{
"timestamp": 1756826104990,
"memoryTotal": 25769803776,
"memoryUsed": 25042386944,
"memoryFree": 727416832,
"memoryUsagePercent": 97.17725118001303,
"memoryEfficiency": 2.8227488199869697,
"cpuCount": 14,
"cpuLoad": 0.19234793526785715,
"platform": "darwin",
"uptime": 94156
},
{
"timestamp": 1756826134991,
"memoryTotal": 25769803776,
"memoryUsed": 25320079360,
"memoryFree": 449724416,
"memoryUsagePercent": 98.25483957926431,
"memoryEfficiency": 1.7451604207356866,
"cpuCount": 14,
"cpuLoad": 0.2527553013392857,
"platform": "darwin",
"uptime": 94186
},
{
"timestamp": 1756826164992,
"memoryTotal": 25769803776,
"memoryUsed": 25509527552,
"memoryFree": 260276224,
"memoryUsagePercent": 98.98999532063803,
"memoryEfficiency": 1.0100046793619697,
"cpuCount": 14,
"cpuLoad": 0.2566615513392857,
"platform": "darwin",
"uptime": 94216
},
{
"timestamp": 1756826194992,
"memoryTotal": 25769803776,
"memoryUsed": 25272778752,
"memoryFree": 497025024,
"memoryUsagePercent": 98.0712890625,
"memoryEfficiency": 1.9287109375,
"cpuCount": 14,
"cpuLoad": 0.26175362723214285,
"platform": "darwin",
"uptime": 94246
},
{
"timestamp": 1756826224994,
"memoryTotal": 25769803776,
"memoryUsed": 25385271296,
"memoryFree": 384532480,
"memoryUsagePercent": 98.50781758626303,
"memoryEfficiency": 1.4921824137369697,
"cpuCount": 14,
"cpuLoad": 0.26021902901785715,
"platform": "darwin",
"uptime": 94276
},
{
"timestamp": 1756826254997,
"memoryTotal": 25769803776,
"memoryUsed": 25342541824,
"memoryFree": 427261952,
"memoryUsagePercent": 98.34200541178384,
"memoryEfficiency": 1.6579945882161553,
"cpuCount": 14,
"cpuLoad": 0.24567522321428573,
"platform": "darwin",
"uptime": 94306
},
{
"timestamp": 1756826284999,
"memoryTotal": 25769803776,
"memoryUsed": 25338478592,
"memoryFree": 431325184,
"memoryUsagePercent": 98.32623799641928,
"memoryEfficiency": 1.6737620035807197,
"cpuCount": 14,
"cpuLoad": 0.20933314732142858,
"platform": "darwin",
"uptime": 94336
},
{
"timestamp": 1756826315001,
"memoryTotal": 25769803776,
"memoryUsed": 25375752192,
"memoryFree": 394051584,
"memoryUsagePercent": 98.47087860107422,
"memoryEfficiency": 1.5291213989257812,
"cpuCount": 14,
"cpuLoad": 0.23611886160714285,
"platform": "darwin",
"uptime": 94367
},
{
"timestamp": 1756826345003,
"memoryTotal": 25769803776,
"memoryUsed": 25635946496,
"memoryFree": 133857280,
"memoryUsagePercent": 99.4805653889974,
"memoryEfficiency": 0.5194346110025947,
"cpuCount": 14,
"cpuLoad": 0.2561732700892857,
"platform": "darwin",
"uptime": 94397
},
{
"timestamp": 1756826375003,
"memoryTotal": 25769803776,
"memoryUsed": 25137889280,
"memoryFree": 631914496,
"memoryUsagePercent": 97.5478490193685,
"memoryEfficiency": 2.452150980631501,
"cpuCount": 14,
"cpuLoad": 0.32655552455357145,
"platform": "darwin",
"uptime": 94427
},
{
"timestamp": 1756826405002,
"memoryTotal": 25769803776,
"memoryUsed": 24939528192,
"memoryFree": 830275584,
"memoryUsagePercent": 96.77810668945312,
"memoryEfficiency": 3.221893310546875,
"cpuCount": 14,
"cpuLoad": 0.31944056919642855,
"platform": "darwin",
"uptime": 94457
},
{
"timestamp": 1756826435006,
"memoryTotal": 25769803776,
"memoryUsed": 25179537408,
"memoryFree": 590266368,
"memoryUsagePercent": 97.70946502685547,
"memoryEfficiency": 2.2905349731445312,
"cpuCount": 14,
"cpuLoad": 0.2578125,
"platform": "darwin",
"uptime": 94487
},
{
"timestamp": 1756826465007,
"memoryTotal": 25769803776,
"memoryUsed": 25199656960,
"memoryFree": 570146816,
"memoryUsagePercent": 97.78753916422525,
"memoryEfficiency": 2.212460835774749,
"cpuCount": 14,
"cpuLoad": 0.20860072544642858,
"platform": "darwin",
"uptime": 94517
},
{
"timestamp": 1756826495008,
"memoryTotal": 25769803776,
"memoryUsed": 25294798848,
"memoryFree": 475004928,
"memoryUsagePercent": 98.15673828125,
"memoryEfficiency": 1.84326171875,
"cpuCount": 14,
"cpuLoad": 0.234130859375,
"platform": "darwin",
"uptime": 94547
},
{
"timestamp": 1756826525010,
"memoryTotal": 25769803776,
"memoryUsed": 25043976192,
"memoryFree": 725827584,
"memoryUsagePercent": 97.18341827392578,
"memoryEfficiency": 2.8165817260742188,
"cpuCount": 14,
"cpuLoad": 0.20884486607142858,
"platform": "darwin",
"uptime": 94577
},
{
"timestamp": 1756826555010,
"memoryTotal": 25769803776,
"memoryUsed": 25060130816,
"memoryFree": 709672960,
"memoryUsagePercent": 97.24610646565756,
"memoryEfficiency": 2.7538935343424384,
"cpuCount": 14,
"cpuLoad": 0.20936802455357142,
"platform": "darwin",
"uptime": 94607
},
{
"timestamp": 1756826585012,
"memoryTotal": 25769803776,
"memoryUsed": 25209454592,
"memoryFree": 560349184,
"memoryUsagePercent": 97.825558980306,
"memoryEfficiency": 2.174441019694001,
"cpuCount": 14,
"cpuLoad": 0.1728515625,
"platform": "darwin",
"uptime": 94637
},
{
"timestamp": 1756826615014,
"memoryTotal": 25769803776,
"memoryUsed": 24942018560,
"memoryFree": 827785216,
"memoryUsagePercent": 96.78777058919272,
"memoryEfficiency": 3.212229410807282,
"cpuCount": 14,
"cpuLoad": 0.16081891741071427,
"platform": "darwin",
"uptime": 94667
},
{
"timestamp": 1756826645015,
"memoryTotal": 25769803776,
"memoryUsed": 25453395968,
"memoryFree": 316407808,
"memoryUsagePercent": 98.77217610677084,
"memoryEfficiency": 1.2278238932291572,
"cpuCount": 14,
"cpuLoad": 0.14090401785714285,
"platform": "darwin",
"uptime": 94697
},
{
"timestamp": 1756826675016,
"memoryTotal": 25769803776,
"memoryUsed": 24923848704,
"memoryFree": 845955072,
"memoryUsagePercent": 96.7172622680664,
"memoryEfficiency": 3.2827377319335938,
"cpuCount": 14,
"cpuLoad": 0.15269252232142858,
"platform": "darwin",
"uptime": 94727
},
{
"timestamp": 1756826705018,
"memoryTotal": 25769803776,
"memoryUsed": 24894341120,
"memoryFree": 875462656,
"memoryUsagePercent": 96.6027577718099,
"memoryEfficiency": 3.3972422281900947,
"cpuCount": 14,
"cpuLoad": 0.12876674107142858,
"platform": "darwin",
"uptime": 94757
},
{
"timestamp": 1756826735019,
"memoryTotal": 25769803776,
"memoryUsed": 24909316096,
"memoryFree": 860487680,
"memoryUsagePercent": 96.66086832682291,
"memoryEfficiency": 3.339131673177093,
"cpuCount": 14,
"cpuLoad": 0.12656947544642858,
"platform": "darwin",
"uptime": 94787
},
{
"timestamp": 1756826765019,
"memoryTotal": 25769803776,
"memoryUsed": 24916410368,
"memoryFree": 853393408,
"memoryUsagePercent": 96.68839772542319,
"memoryEfficiency": 3.3116022745768134,
"cpuCount": 14,
"cpuLoad": 0.1640625,
"platform": "darwin",
"uptime": 94817
},
{
"timestamp": 1756826795020,
"memoryTotal": 25769803776,
"memoryUsed": 25095012352,
"memoryFree": 674791424,
"memoryUsagePercent": 97.38146464029947,
"memoryEfficiency": 2.6185353597005303,
"cpuCount": 14,
"cpuLoad": 0.18324497767857142,
"platform": "darwin",
"uptime": 94847
},
{
"timestamp": 1756826825022,
"memoryTotal": 25769803776,
"memoryUsed": 25344131072,
"memoryFree": 425672704,
"memoryUsagePercent": 98.34817250569662,
"memoryEfficiency": 1.651827494303376,
"cpuCount": 14,
"cpuLoad": 0.18830217633928573,
"platform": "darwin",
"uptime": 94877
},
{
"timestamp": 1756826855023,
"memoryTotal": 25769803776,
"memoryUsed": 25274499072,
"memoryFree": 495304704,
"memoryUsagePercent": 98.07796478271484,
"memoryEfficiency": 1.9220352172851562,
"cpuCount": 14,
"cpuLoad": 0.20138113839285715,
"platform": "darwin",
"uptime": 94907
},
{
"timestamp": 1756826885024,
"memoryTotal": 25769803776,
"memoryUsed": 25084575744,
"memoryFree": 685228032,
"memoryUsagePercent": 97.3409652709961,
"memoryEfficiency": 2.6590347290039062,
"cpuCount": 14,
"cpuLoad": 0.21358816964285715,
"platform": "darwin",
"uptime": 94937
},
{
"timestamp": 1756826915025,
"memoryTotal": 25769803776,
"memoryUsed": 25114312704,
"memoryFree": 655491072,
"memoryUsagePercent": 97.45635986328125,
"memoryEfficiency": 2.54364013671875,
"cpuCount": 14,
"cpuLoad": 0.225830078125,
"platform": "darwin",
"uptime": 94967
},
{
"timestamp": 1756826945027,
"memoryTotal": 25769803776,
"memoryUsed": 25646907392,
"memoryFree": 122896384,
"memoryUsagePercent": 99.52309926350912,
"memoryEfficiency": 0.47690073649087594,
"cpuCount": 14,
"cpuLoad": 0.253662109375,
"platform": "darwin",
"uptime": 94997
},
{
"timestamp": 1756826975029,
"memoryTotal": 25769803776,
"memoryUsed": 25129713664,
"memoryFree": 640090112,
"memoryUsagePercent": 97.51612345377603,
"memoryEfficiency": 2.483876546223968,
"cpuCount": 14,
"cpuLoad": 0.24700055803571427,
"platform": "darwin",
"uptime": 95027
},
{
"timestamp": 1756827005031,
"memoryTotal": 25769803776,
"memoryUsed": 25107628032,
"memoryFree": 662175744,
"memoryUsagePercent": 97.430419921875,
"memoryEfficiency": 2.569580078125,
"cpuCount": 14,
"cpuLoad": 0.27413504464285715,
"platform": "darwin",
"uptime": 95057
},
{
"timestamp": 1756827035032,
"memoryTotal": 25769803776,
"memoryUsed": 25110970368,
"memoryFree": 658833408,
"memoryUsagePercent": 97.44338989257812,
"memoryEfficiency": 2.556610107421875,
"cpuCount": 14,
"cpuLoad": 0.26297433035714285,
"platform": "darwin",
"uptime": 95087
},
{
"timestamp": 1756827065033,
"memoryTotal": 25769803776,
"memoryUsed": 25142231040,
"memoryFree": 627572736,
"memoryUsagePercent": 97.564697265625,
"memoryEfficiency": 2.435302734375,
"cpuCount": 14,
"cpuLoad": 0.30775669642857145,
"platform": "darwin",
"uptime": 95117
},
{
"timestamp": 1756827095034,
"memoryTotal": 25769803776,
"memoryUsed": 25162514432,
"memoryFree": 607289344,
"memoryUsagePercent": 97.6434071858724,
"memoryEfficiency": 2.3565928141275947,
"cpuCount": 14,
"cpuLoad": 0.2882603236607143,
"platform": "darwin",
"uptime": 95147
},
{
"timestamp": 1756827125035,
"memoryTotal": 25769803776,
"memoryUsed": 25144721408,
"memoryFree": 625082368,
"memoryUsagePercent": 97.57436116536459,
"memoryEfficiency": 2.425638834635407,
"cpuCount": 14,
"cpuLoad": 0.24358258928571427,
"platform": "darwin",
"uptime": 95177
},
{
"timestamp": 1756827155038,
"memoryTotal": 25769803776,
"memoryUsed": 25206685696,
"memoryFree": 563118080,
"memoryUsagePercent": 97.81481424967447,
"memoryEfficiency": 2.1851857503255303,
"cpuCount": 14,
"cpuLoad": 0.22834123883928573,
"platform": "darwin",
"uptime": 95207
},
{
"timestamp": 1756827185040,
"memoryTotal": 25769803776,
"memoryUsed": 25193136128,
"memoryFree": 576667648,
"memoryUsagePercent": 97.76223500569662,
"memoryEfficiency": 2.237764994303376,
"cpuCount": 14,
"cpuLoad": 0.21718052455357142,
"platform": "darwin",
"uptime": 95237
},
{
"timestamp": 1756827215041,
"memoryTotal": 25769803776,
"memoryUsed": 25223643136,
"memoryFree": 546160640,
"memoryUsagePercent": 97.8806177775065,
"memoryEfficiency": 2.119382222493499,
"cpuCount": 14,
"cpuLoad": 0.21327427455357142,
"platform": "darwin",
"uptime": 95267
},
{
"timestamp": 1756827245043,
"memoryTotal": 25769803776,
"memoryUsed": 25654329344,
"memoryFree": 115474432,
"memoryUsagePercent": 99.55190022786459,
"memoryEfficiency": 0.4480997721354072,
"cpuCount": 14,
"cpuLoad": 0.19541713169642858,
"platform": "darwin",
"uptime": 95297
},
{
"timestamp": 1756827275044,
"memoryTotal": 25769803776,
"memoryUsed": 25666355200,
"memoryFree": 103448576,
"memoryUsagePercent": 99.59856669108072,
"memoryEfficiency": 0.4014333089192803,
"cpuCount": 14,
"cpuLoad": 0.22160993303571427,
"platform": "darwin",
"uptime": 95327
},
{
"timestamp": 1756827305046,
"memoryTotal": 25769803776,
"memoryUsed": 25087606784,
"memoryFree": 682196992,
"memoryUsagePercent": 97.35272725423178,
"memoryEfficiency": 2.6472727457682197,
"cpuCount": 14,
"cpuLoad": 0.21271623883928573,
"platform": "darwin",
"uptime": 95357
},
{
"timestamp": 1756827335047,
"memoryTotal": 25769803776,
"memoryUsed": 25130172416,
"memoryFree": 639631360,
"memoryUsagePercent": 97.51790364583334,
"memoryEfficiency": 2.482096354166657,
"cpuCount": 14,
"cpuLoad": 0.20082310267857142,
"platform": "darwin",
"uptime": 95387
},
{
"timestamp": 1756827365048,
"memoryTotal": 25769803776,
"memoryUsed": 25414418432,
"memoryFree": 355385344,
"memoryUsagePercent": 98.62092336018881,
"memoryEfficiency": 1.3790766398111884,
"cpuCount": 14,
"cpuLoad": 0.235107421875,
"platform": "darwin",
"uptime": 95417
},
{
"timestamp": 1756827395050,
"memoryTotal": 25769803776,
"memoryUsed": 25483345920,
"memoryFree": 286457856,
"memoryUsagePercent": 98.88839721679688,
"memoryEfficiency": 1.111602783203125,
"cpuCount": 14,
"cpuLoad": 0.25125558035714285,
"platform": "darwin",
"uptime": 95447
},
{
"timestamp": 1756827425053,
"memoryTotal": 25769803776,
"memoryUsed": 25531826176,
"memoryFree": 237977600,
"memoryUsagePercent": 99.07652537027994,
"memoryEfficiency": 0.9234746297200616,
"cpuCount": 14,
"cpuLoad": 0.23116629464285715,
"platform": "darwin",
"uptime": 95477
},
{
"timestamp": 1756827455055,
"memoryTotal": 25769803776,
"memoryUsed": 25622839296,
"memoryFree": 146964480,
"memoryUsagePercent": 99.42970275878906,
"memoryEfficiency": 0.5702972412109375,
"cpuCount": 14,
"cpuLoad": 0.21351841517857142,
"platform": "darwin",
"uptime": 95507
},
{
"timestamp": 1756827485056,
"memoryTotal": 25769803776,
"memoryUsed": 25581453312,
"memoryFree": 188350464,
"memoryUsagePercent": 99.26910400390625,
"memoryEfficiency": 0.73089599609375,
"cpuCount": 14,
"cpuLoad": 0.21365792410714285,
"platform": "darwin",
"uptime": 95537
},
{
"timestamp": 1756827515058,
"memoryTotal": 25769803776,
"memoryUsed": 25511133184,
"memoryFree": 258670592,
"memoryUsagePercent": 98.99622599283853,
"memoryEfficiency": 1.0037740071614678,
"cpuCount": 14,
"cpuLoad": 0.20347377232142858,
"platform": "darwin",
"uptime": 95567
},
{
"timestamp": 1756827545060,
"memoryTotal": 25769803776,
"memoryUsed": 25709674496,
"memoryFree": 60129280,
"memoryUsagePercent": 99.76666768391928,
"memoryEfficiency": 0.2333323160807197,
"cpuCount": 14,
"cpuLoad": 0.22722516741071427,
"platform": "darwin",
"uptime": 95597
},
{
"timestamp": 1756827575062,
"memoryTotal": 25769803776,
"memoryUsed": 25696010240,
"memoryFree": 73793536,
"memoryUsagePercent": 99.71364339192709,
"memoryEfficiency": 0.2863566080729072,
"cpuCount": 14,
"cpuLoad": 0.23095703125,
"platform": "darwin",
"uptime": 95627
},
{
"timestamp": 1756827605063,
"memoryTotal": 25769803776,
"memoryUsed": 25077661696,
"memoryFree": 692142080,
"memoryUsagePercent": 97.31413523356119,
"memoryEfficiency": 2.6858647664388116,
"cpuCount": 14,
"cpuLoad": 0.24972098214285715,
"platform": "darwin",
"uptime": 95657
}
]
Binary file not shown.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "picpeak-backend",
"version": "1.0.106",
"version": "1.0.108",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "picpeak-backend",
"version": "1.0.106",
"version": "1.0.108",
"dependencies": {
"@aws-sdk/client-s3": "^3.850.0",
"@aws-sdk/lib-storage": "^3.850.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picpeak-backend",
"version": "1.0.106",
"version": "1.0.108",
"description": "Backend for PicPeak event photo sharing platform",
"main": "server.js",
"scripts": {
+8 -7
View File
@@ -24,14 +24,15 @@ router.get('/:slug/feedback-settings',
const settings = await feedbackService.getEventFeedbackSettings(event.id);
// Only send relevant settings to guests
// Convert SQLite boolean values (0/1) to proper booleans
const guestSettings = {
feedback_enabled: settings.feedback_enabled,
allow_ratings: settings.allow_ratings,
allow_likes: settings.allow_likes,
allow_comments: settings.allow_comments,
allow_favorites: settings.allow_favorites,
require_name_email: settings.require_name_email,
show_feedback_to_guests: settings.show_feedback_to_guests
feedback_enabled: Boolean(settings.feedback_enabled),
allow_ratings: Boolean(settings.allow_ratings),
allow_likes: Boolean(settings.allow_likes),
allow_comments: Boolean(settings.allow_comments),
allow_favorites: Boolean(settings.allow_favorites),
require_name_email: Boolean(settings.require_name_email),
show_feedback_to_guests: Boolean(settings.show_feedback_to_guests)
};
res.json(guestSettings);
+8 -2
View File
@@ -227,11 +227,17 @@ async function validateGuestRequirements(settings, guestData) {
const errors = [];
if (!guestData.guest_name || guestData.guest_name.trim().length === 0) {
// Check for name - handle both undefined and empty strings
const name = guestData.guest_name;
if (!name || (typeof name === 'string' && name.trim().length === 0)) {
errors.push('Name is required');
}
if (!guestData.guest_email || !validator.isEmail(guestData.guest_email)) {
// Check for email - handle both undefined and empty strings
const email = guestData.guest_email;
if (!email || (typeof email === 'string' && email.trim().length === 0)) {
errors.push('Email is required');
} else if (email && typeof email === 'string' && !validator.isEmail(email.trim())) {
errors.push('Valid email is required');
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "picpeak-frontend",
"version": "1.0.106",
"version": "1.0.108",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "picpeak-frontend",
"version": "1.0.106",
"version": "1.0.108",
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"@tiptap/extension-character-count": "^2.26.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "picpeak-frontend",
"private": true,
"version": "1.0.106",
"version": "1.0.108",
"type": "module",
"scripts": {
"dev": "vite",
@@ -126,11 +126,22 @@ export const FeedbackModerationPanel: React.FC<FeedbackModerationPanelProps> = (
)}
</span>
</div>
<p className="mt-1 text-sm text-neutral-700">{item.comment}</p>
{item.photo_filename && (
<p className="mt-1 text-xs text-neutral-500">
{t('feedback.onPhoto', 'On photo')}: {item.photo_filename}
</p>
<p className="mt-1 text-sm text-neutral-700">{item.comment_text || item.comment}</p>
{item.photo_id && (
<div className="mt-2 flex items-center gap-2">
<img
src={`/api/admin/photos/${eventId}/thumbnail/${item.photo_id}`}
alt={item.filename || 'Photo'}
className="w-16 h-16 object-cover rounded"
onError={(e) => {
// Hide image if thumbnail fails to load
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
<p className="text-xs text-neutral-500">
{t('feedback.onPhoto', 'On photo')}: {item.filename || item.photo_filename || `#${item.photo_id}`}
</p>
</div>
)}
</div>
</div>
@@ -0,0 +1,104 @@
import React, { useState } from 'react';
import { X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button, Input } from '../common';
interface FeedbackIdentityModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (name: string, email: string) => void;
feedbackType: string;
}
export const FeedbackIdentityModal: React.FC<FeedbackIdentityModalProps> = ({
isOpen,
onClose,
onSubmit,
feedbackType
}) => {
const { t } = useTranslation();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [errors, setErrors] = useState<Record<string, string>>({});
if (!isOpen) return null;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const newErrors: Record<string, string> = {};
if (!name.trim()) {
newErrors.name = t('feedback.nameRequired', 'Name is required');
}
if (!email.trim()) {
newErrors.email = t('feedback.emailRequired', 'Email is required');
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
newErrors.email = t('feedback.invalidEmail', 'Invalid email address');
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
onSubmit(name.trim(), email.trim());
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black bg-opacity-50" onClick={onClose} />
<div className="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6">
<button
onClick={onClose}
className="absolute top-4 right-4 p-1 hover:bg-neutral-100 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-neutral-600" />
</button>
<h2 className="text-lg font-semibold text-neutral-900 mb-2">
{t('feedback.identityRequired', 'Your Information Required')}
</h2>
<p className="text-sm text-neutral-600 mb-4">
{t('feedback.identityReason', 'Please provide your name and email to submit {{type}}.', { type: feedbackType })}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<Input
label={t('feedback.yourName', 'Your Name')}
value={name}
onChange={(e) => setName(e.target.value)}
error={errors.name}
placeholder={t('feedback.namePlaceholder', 'Enter your name')}
required
/>
<Input
type="email"
label={t('feedback.yourEmail', 'Your Email')}
value={email}
onChange={(e) => setEmail(e.target.value)}
error={errors.email}
placeholder={t('feedback.emailPlaceholder', 'Enter your email')}
required
/>
<div className="flex gap-2 pt-2">
<Button
type="submit"
variant="primary"
className="flex-1"
>
{t('feedback.submitFeedback', 'Submit Feedback')}
</Button>
<Button
type="button"
variant="ghost"
onClick={onClose}
className="flex-1"
>
{t('common.cancel', 'Cancel')}
</Button>
</div>
</form>
</div>
</div>
);
};
@@ -194,7 +194,7 @@ export const GallerySidebar: React.FC<GallerySidebarProps> = ({
disabled={isDownloading}
className="w-full"
>
{t('gallery.downloadSelected')} ({selectedCount})
{t('gallery.downloadSelected', { count: selectedCount })} ({selectedCount})
</Button>
)}
</div>
@@ -103,8 +103,8 @@ export const PhotoComments: React.FC<PhotoCommentsProps> = ({
submitCommentMutation.mutate({
comment_text: commentText.trim(),
guest_name: guestName.trim(),
guest_email: guestEmail.trim()
guest_name: guestName.trim() || undefined,
guest_email: guestEmail.trim() || undefined
});
};
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { feedbackService } from '../../services/feedback.service';
import { toast } from 'react-toastify';
import { FeedbackIdentityModal } from './FeedbackIdentityModal';
interface PhotoFavoritesProps {
photoId: string;
@@ -11,6 +12,7 @@ interface PhotoFavoritesProps {
isFavorited: boolean;
favoriteCount: number;
isEnabled: boolean;
requireNameEmail?: boolean;
onFavoriteChange?: (favorited: boolean) => void;
}
@@ -20,17 +22,22 @@ export const PhotoFavorites: React.FC<PhotoFavoritesProps> = ({
isFavorited,
favoriteCount,
isEnabled,
requireNameEmail = false,
onFavoriteChange
}) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isSubmitting, setIsSubmitting] = useState(false);
const [animating, setAnimating] = useState(false);
const [showIdentityModal, setShowIdentityModal] = useState(false);
const [savedIdentity, setSavedIdentity] = useState<{ name: string; email: string } | null>(null);
const submitFavoriteMutation = useMutation({
mutationFn: () =>
mutationFn: (data: { guest_name?: string; guest_email?: string } = {}) =>
feedbackService.submitFeedback(gallerySlug, photoId, {
feedback_type: 'favorite'
feedback_type: 'favorite',
guest_name: data.guest_name || undefined,
guest_email: data.guest_email || undefined
}),
onMutate: async () => {
setIsSubmitting(true);
@@ -62,13 +69,25 @@ export const PhotoFavorites: React.FC<PhotoFavoritesProps> = ({
const handleFavoriteClick = () => {
if (!isEnabled || isSubmitting) return;
submitFavoriteMutation.mutate();
if (requireNameEmail && !savedIdentity) {
setShowIdentityModal(true);
} else {
submitFavoriteMutation.mutate(savedIdentity || {});
}
};
const handleIdentitySubmit = (name: string, email: string) => {
setSavedIdentity({ name, email });
setShowIdentityModal(false);
submitFavoriteMutation.mutate({ guest_name: name, guest_email: email });
};
if (!isEnabled) return null;
return (
<button
<>
<button
onClick={handleFavoriteClick}
disabled={isSubmitting}
className={`group flex items-center gap-2 px-3 py-2 rounded-lg transition-all ${
@@ -88,6 +107,13 @@ export const PhotoFavorites: React.FC<PhotoFavoritesProps> = ({
<span className="text-sm font-medium">
{favoriteCount > 0 ? favoriteCount : ''}
</span>
</button>
</button>
<FeedbackIdentityModal
isOpen={showIdentityModal}
onClose={() => setShowIdentityModal(false)}
onSubmit={handleIdentitySubmit}
feedbackType={t('feedback.favorite', 'favorite')}
/>
</>
);
};
@@ -26,7 +26,11 @@ export const PhotoFeedback: React.FC<PhotoFeedbackProps> = ({
// Fetch feedback settings for the gallery
const { data: settings, isLoading: settingsLoading } = useQuery({
queryKey: ['gallery-feedback-settings', gallerySlug],
queryFn: () => feedbackService.getGalleryFeedbackSettings(gallerySlug),
queryFn: async () => {
const data = await feedbackService.getGalleryFeedbackSettings(gallerySlug);
console.log('PhotoFeedback received settings:', data);
return data;
},
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
});
@@ -104,6 +108,7 @@ export const PhotoFeedback: React.FC<PhotoFeedbackProps> = ({
averageRating={Number(feedbackData?.summary?.average_rating) || 0}
totalRatings={Number(feedbackData?.summary?.total_ratings) || 0}
isEnabled={true}
requireNameEmail={settings.require_name_email || false}
onRatingChange={handleRatingChange}
/>
)}
@@ -118,6 +123,7 @@ export const PhotoFeedback: React.FC<PhotoFeedbackProps> = ({
isLiked={isLiked}
likeCount={likeCount}
isEnabled={true}
requireNameEmail={settings.require_name_email || false}
onLikeChange={handleLikeChange}
/>
)}
@@ -128,6 +134,7 @@ export const PhotoFeedback: React.FC<PhotoFeedbackProps> = ({
isFavorited={isFavorited}
favoriteCount={favoriteCount}
isEnabled={true}
requireNameEmail={settings.require_name_email || false}
onFavoriteChange={handleFavoriteChange}
/>
)}
+31 -5
View File
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { feedbackService } from '../../services/feedback.service';
import { toast } from 'react-toastify';
import { FeedbackIdentityModal } from './FeedbackIdentityModal';
interface PhotoLikesProps {
photoId: string;
@@ -11,6 +12,7 @@ interface PhotoLikesProps {
isLiked: boolean;
likeCount: number;
isEnabled: boolean;
requireNameEmail?: boolean;
onLikeChange?: (liked: boolean) => void;
}
@@ -20,17 +22,22 @@ export const PhotoLikes: React.FC<PhotoLikesProps> = ({
isLiked,
likeCount,
isEnabled,
requireNameEmail = false,
onLikeChange
}) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isSubmitting, setIsSubmitting] = useState(false);
const [animating, setAnimating] = useState(false);
const [showIdentityModal, setShowIdentityModal] = useState(false);
const [savedIdentity, setSavedIdentity] = useState<{ name: string; email: string } | null>(null);
const submitLikeMutation = useMutation({
mutationFn: () =>
mutationFn: (data: { guest_name?: string; guest_email?: string } = {}) =>
feedbackService.submitFeedback(gallerySlug, photoId, {
feedback_type: 'like'
feedback_type: 'like',
guest_name: data.guest_name || undefined,
guest_email: data.guest_email || undefined
}),
onMutate: async () => {
setIsSubmitting(true);
@@ -62,13 +69,25 @@ export const PhotoLikes: React.FC<PhotoLikesProps> = ({
const handleLikeClick = () => {
if (!isEnabled || isSubmitting) return;
submitLikeMutation.mutate();
if (requireNameEmail && !savedIdentity) {
setShowIdentityModal(true);
} else {
submitLikeMutation.mutate(savedIdentity || {});
}
};
const handleIdentitySubmit = (name: string, email: string) => {
setSavedIdentity({ name, email });
setShowIdentityModal(false);
submitLikeMutation.mutate({ guest_name: name, guest_email: email });
};
if (!isEnabled) return null;
return (
<button
<>
<button
onClick={handleLikeClick}
disabled={isSubmitting}
className={`group flex items-center gap-2 px-3 py-2 rounded-lg transition-all ${
@@ -88,6 +107,13 @@ export const PhotoLikes: React.FC<PhotoLikesProps> = ({
<span className="text-sm font-medium">
{likeCount > 0 ? likeCount : ''}
</span>
</button>
</button>
<FeedbackIdentityModal
isOpen={showIdentityModal}
onClose={() => setShowIdentityModal(false)}
onSubmit={handleIdentitySubmit}
feedbackType={t('feedback.like', 'like')}
/>
</>
);
};
+75 -39
View File
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { feedbackService } from '../../services/feedback.service';
import { toast } from 'react-toastify';
import { FeedbackIdentityModal } from './FeedbackIdentityModal';
interface PhotoRatingProps {
photoId: string;
@@ -12,6 +13,7 @@ interface PhotoRatingProps {
averageRating?: number;
totalRatings?: number;
isEnabled: boolean;
requireNameEmail?: boolean;
onRatingChange?: (rating: number) => void;
}
@@ -22,6 +24,7 @@ export const PhotoRating: React.FC<PhotoRatingProps> = ({
averageRating = 0,
totalRatings = 0,
isEnabled,
requireNameEmail = false,
onRatingChange
}) => {
// Ensure averageRating is a valid number
@@ -30,18 +33,23 @@ export const PhotoRating: React.FC<PhotoRatingProps> = ({
const queryClient = useQueryClient();
const [hoveredRating, setHoveredRating] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const [showIdentityModal, setShowIdentityModal] = useState(false);
const [pendingRating, setPendingRating] = useState(0);
const [savedIdentity, setSavedIdentity] = useState<{ name: string; email: string } | null>(null);
const submitRatingMutation = useMutation({
mutationFn: (rating: number) =>
mutationFn: (data: { rating: number; guest_name?: string; guest_email?: string }) =>
feedbackService.submitFeedback(gallerySlug, photoId, {
feedback_type: 'rating',
rating
rating: data.rating,
guest_name: data.guest_name || undefined,
guest_email: data.guest_email || undefined
}),
onMutate: async (rating) => {
onMutate: async (data) => {
setIsSubmitting(true);
// Optimistic update
if (onRatingChange) {
onRatingChange(rating);
onRatingChange(data.rating);
}
},
onSuccess: () => {
@@ -69,47 +77,75 @@ export const PhotoRating: React.FC<PhotoRatingProps> = ({
// If clicking the same rating, remove it
const newRating = rating === currentRating ? 0 : rating;
submitRatingMutation.mutate(newRating);
if (requireNameEmail && !savedIdentity) {
setPendingRating(newRating);
setShowIdentityModal(true);
} else {
submitRatingMutation.mutate({
rating: newRating,
guest_name: savedIdentity?.name,
guest_email: savedIdentity?.email
});
}
};
const handleIdentitySubmit = (name: string, email: string) => {
setSavedIdentity({ name, email });
setShowIdentityModal(false);
submitRatingMutation.mutate({
rating: pendingRating,
guest_name: name,
guest_email: email
});
};
if (!isEnabled) return null;
return (
<div className="flex flex-col items-center gap-2">
{/* Star Rating Input */}
<div className="flex items-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
onClick={() => handleRatingClick(star)}
onMouseEnter={() => setHoveredRating(star)}
onMouseLeave={() => setHoveredRating(0)}
disabled={isSubmitting}
className={`p-1 transition-all ${
isSubmitting ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:scale-110'
}`}
aria-label={t('feedback.rateStar', 'Rate {{count}} stars', { count: star })}
>
<Star
className={`w-6 h-6 transition-colors ${
star <= (hoveredRating || currentRating)
? 'fill-yellow-500 text-yellow-500'
: 'text-neutral-300 hover:text-yellow-400'
<>
<div className="flex flex-col items-center gap-2">
{/* Star Rating Input */}
<div className="flex items-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
onClick={() => handleRatingClick(star)}
onMouseEnter={() => setHoveredRating(star)}
onMouseLeave={() => setHoveredRating(0)}
disabled={isSubmitting}
className={`p-1 transition-all ${
isSubmitting ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:scale-110'
}`}
/>
</button>
))}
</div>
{/* Average Rating Display */}
{totalRatings > 0 && (
<div className="text-sm text-neutral-600">
<span className="font-medium">{safeAverageRating.toFixed(1)}</span>
<span className="text-neutral-400 ml-1">
({t('feedback.ratingsCount', '{{count}} ratings', { count: totalRatings })})
</span>
aria-label={t('feedback.rateStar', 'Rate {{count}} stars', { count: star })}
>
<Star
className={`w-6 h-6 transition-colors ${
star <= (hoveredRating || currentRating)
? 'fill-yellow-500 text-yellow-500'
: 'text-neutral-300 hover:text-yellow-400'
}`}
/>
</button>
))}
</div>
)}
</div>
{/* Average Rating Display */}
{totalRatings > 0 && (
<div className="text-sm text-neutral-600">
<span className="font-medium">{safeAverageRating.toFixed(1)}</span>
<span className="text-neutral-400 ml-1">
({t('feedback.ratingsCount', '{{count}} ratings', { count: totalRatings })})
</span>
</div>
)}
</div>
<FeedbackIdentityModal
isOpen={showIdentityModal}
onClose={() => setShowIdentityModal(false)}
onSubmit={handleIdentitySubmit}
feedbackType={t('feedback.rating', 'rating')}
/>
</>
);
};
+11 -5
View File
@@ -250,11 +250,17 @@ export const EventFeedbackPage: React.FC = () => {
{feedbackData?.feedback?.map((item: PhotoFeedback) => (
<Card key={item.id} className="overflow-hidden">
<div className="p-4 flex items-start gap-4">
<img
src={`/thumbnails/${item.path}`}
alt={item.filename}
className="w-16 h-16 object-cover rounded"
/>
{item.photo_id && (
<img
src={`/api/admin/photos/${eventId}/thumbnail/${item.photo_id}`}
alt={item.filename || 'Photo'}
className="w-16 h-16 object-cover rounded"
onError={(e) => {
// Hide image if thumbnail fails to load
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
)}
<div className="flex-1">
<div className="flex items-start justify-between">
<div>