Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d87c336de | ||
|
|
037ff6f7f9 | ||
|
|
b8dcd7f5e0 | ||
|
|
b35c99c8fb | ||
|
|
086e4ab8ba | ||
|
|
139d3fe241 | ||
|
|
6fe3ed7c8b | ||
|
|
81ae2df8ef | ||
|
|
997a7b8b98 | ||
|
|
331a5451b2 | ||
|
|
514c2488cc | ||
|
|
33e51bac0e | ||
|
|
c46d23cb83 | ||
|
|
acb685f819 | ||
|
|
18790ee379 | ||
|
|
70698fab44 | ||
|
|
c61572a715 | ||
|
|
00bdc278d8 | ||
|
|
5b1f3eafbc | ||
|
|
233c5fa28e | ||
|
|
aa877dea0a | ||
|
|
657dc79996 | ||
|
|
294155d5a3 | ||
|
|
1a342de24a | ||
|
|
00ce3bd919 | ||
|
|
adf1aac10f | ||
|
|
26a42b1f30 | ||
|
|
71fe2ae657 | ||
|
|
8d1dd45648 | ||
|
|
38c13eef28 | ||
|
|
8f54cd2791 | ||
|
|
610961d22c | ||
|
|
3f7eda05cf | ||
|
|
ebfd80da76 | ||
|
|
ede8a2db3d | ||
|
|
4e37475d00 | ||
|
|
6048cd526b | ||
|
|
1d52ab1d08 | ||
|
|
08cf4b6d9f | ||
|
|
4a05ddee50 | ||
|
|
c37280fa4a | ||
|
|
d00e086646 | ||
|
|
973875ca66 | ||
|
|
03c2bcb78e | ||
|
|
a2dd06bd68 | ||
|
|
7ba408c053 | ||
|
|
fcbc64cea8 | ||
|
|
2ee710b748 | ||
|
|
e3db42dcbc | ||
|
|
32210bb96a | ||
|
|
f26ae170bb | ||
|
|
a189aeb99c | ||
|
|
e118c30f47 | ||
|
|
fb11e15907 | ||
|
|
87c8bf4c45 | ||
|
|
efd1d12ba7 | ||
|
|
29437d9d7b | ||
|
|
ed27a6f6a5 | ||
|
|
d6579763f4 | ||
|
|
88cb2f13cc | ||
|
|
91ac7a5bde | ||
|
|
0b06f9f93a | ||
|
|
a13635ab4e | ||
|
|
31a93424aa | ||
|
|
8d4158c2c6 | ||
|
|
8c0619992b | ||
|
|
5668c620da | ||
|
|
6b9ddc8986 | ||
|
|
133dc6a502 | ||
|
|
25377090c1 | ||
|
|
d1d4b73da0 | ||
|
|
f88f433003 | ||
|
|
28a4fe1952 | ||
|
|
f85b8d0637 | ||
|
|
3d6455f23c | ||
|
|
f0c25366db | ||
|
|
d2aaf78c0e | ||
|
|
40214abe64 | ||
|
|
9efd193124 | ||
|
|
0e5d28d199 | ||
|
|
d6aaaabb00 | ||
|
|
10097f4238 | ||
|
|
a3c7b92c0c | ||
|
|
a374a7d7bc | ||
|
|
7e50299854 | ||
|
|
1da0e0f4b5 | ||
|
|
636136a077 | ||
|
|
3521a88395 | ||
|
|
768d299881 | ||
|
|
a76d6c9b12 | ||
|
|
0bf8d3c83c | ||
|
|
b2c19801e6 | ||
|
|
ee3846159c |
@@ -30,7 +30,7 @@ PROD=0
|
||||
PROJECT_DESCR_MAIN='__PROJECTS'
|
||||
SECRK=Askb38v23jjDFaoskBOWj92axXCQ
|
||||
TOKEN_LIFE=1m
|
||||
REFRESH_TOKEN_LIFE=14d
|
||||
REFRESH_TOKEN_LIFE=30d
|
||||
FTPSERVER_HOST=139.162.166.31
|
||||
FTPSERVER_PORT=21
|
||||
FTPSERVER_USER=ftpusrsrv_
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
DATABASE=test_PiuCheBuono
|
||||
DATABASE=test_FreePlanet
|
||||
UDB=paofreeplanet
|
||||
PDB=mypassword@1A
|
||||
SEND_EMAIL=0
|
||||
SEND_EMAIL_ORDERS=1
|
||||
PORT=3000
|
||||
appTelegram_TEST=["1","17"]
|
||||
appTelegram=["1","17"]
|
||||
appTelegram_DEVELOP=["17"]
|
||||
appTelegram_TEST=["1","13"]
|
||||
appTelegram=["1","13"]
|
||||
appTelegram_DEVELOP=["13"]
|
||||
DOMAIN=mongodb://localhost:27017/
|
||||
AUTH_MONGODB=0
|
||||
ENABLE_PUSHNOTIFICATION=1
|
||||
@@ -29,8 +29,8 @@ GCM_API_KEY=""
|
||||
PROD=0
|
||||
PROJECT_DESCR_MAIN='__PROJECTS'
|
||||
SECRK=Askb38v23jjDFaoskBOWj92axXCQ
|
||||
TOKEN_LIFE=2h
|
||||
REFRESH_TOKEN_LIFE=14d
|
||||
TOKEN_LIFE=30d
|
||||
REFRESH_TOKEN_LIFE=30d
|
||||
FTPSERVER_HOST=139.162.166.31
|
||||
FTPSERVER_PORT=21
|
||||
FTPSERVER_USER=ftpusrsrv_
|
||||
@@ -38,9 +38,10 @@ FTPSERVER_PWD=ftpmypwd@1A_
|
||||
AUTH_NEW_SITES=123123123
|
||||
SCRIPTS_DIR=admin_scripts
|
||||
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
|
||||
MIAB_HOST=box.lamiaposta.org
|
||||
MIAB_ADMIN_EMAIL=admin@lamiaposta.org
|
||||
MIAB_ADMIN_PASSWORD=passpao1pabox@1A
|
||||
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
|
||||
SERVER_A_URL="http://51.77.156.69:3000"
|
||||
API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK"
|
||||
OLLAMA_URL=http://localhost:11434
|
||||
OLLAMA_DEFAULT_MODEL=llama3.2:3b
|
||||
GROK_API="xai-PcNM5obgPaETtmnfDWPZk235D75ZgxENU2QmeqPfMQCHh9dwCDVeRRe0oVVA2YOpiUDh1uJieZsMasja"
|
||||
REPLICATE_API_TOKEN="r8_AVhM6igwvoOnUA65cHVZdhEDfTqBVk94WTB0u"
|
||||
FAL_KEY="7d251c88-21b5-4b55-8b3e-4bafd910f99f:b81c0a36a25b052f26eb8ac226c7efff"
|
||||
HF_TOKEN="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"
|
||||
@@ -30,11 +30,11 @@ PATH_SERVER_CRT=fullchain.pem
|
||||
PROD=1
|
||||
PROJECT_DESCR_MAIN='__PROJECTS'
|
||||
SECRK=iUUb38v23jjDFaosWj92axkBOXCQ
|
||||
TOKEN_LIFE=2h
|
||||
REFRESH_TOKEN_LIFE=14d
|
||||
TOKEN_LIFE=30d
|
||||
REFRESH_TOKEN_LIFE=30d
|
||||
AUTH_NEW_SITES=B234HDSAOJ734ndcsdKWNVZZ
|
||||
DOMAINS=[{"hostname":"piuchebuono.app","port":"3030"},{"hostname":"gruppomacro.app","port":"3010"}]
|
||||
DOMAINS_ALLOWED=[]
|
||||
DOMAINS_ALLOWED=["gruppomacro.app","piuchebuono.app"]
|
||||
SCRIPTS_DIR=admin_scripts
|
||||
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
|
||||
MIAB_HOST=box.lamiaposta.org
|
||||
|
||||
@@ -27,11 +27,12 @@ PATH_SERVER_CRT=fullchain.pem
|
||||
PROD=1
|
||||
PROJECT_DESCR_MAIN='__PROJECTS'
|
||||
SECRK=iUUb38v23jjDFaosWj92axkBOXCQ
|
||||
TOKEN_LIFE=2h
|
||||
REFRESH_TOKEN_LIFE=14d
|
||||
TOKEN_LIFE=30d
|
||||
REFRESH_TOKEN_LIFE=30d
|
||||
AUTH_NEW_SITES=B234HDSAOJ734ndcsdKWNV
|
||||
DOMAINS=[{"hostname":"riso.app","port":"3006"},{"hostname":"freeplanet.app","port":"3000"}]
|
||||
DOMAINS_ALLOWED=["comunitanuovomondo.app","kolibrilab.it","artenergetica.org","freeplanet.app","www.freeplanet.app","freeplanet.app:3000","freeplanet.app:3001","www.freeplanet.app:3000","www.freeplanet.app:3001"]
|
||||
DOMAINS=[{"hostname":"riso.app","port":"3006"},{"hostname":"freeplanet.app","port":"3000"},{"hostname":"nuovomondo.app","port":"3032"}]
|
||||
DOMAINS_NEW=[{"hostname":"riso.app","port":"3006"},{"hostname":"freeplanet.app","port":"3000"},{"hostname":"nuovomondo.app","port":"3032"},{"hostname":"germogliamo.app","port":"3042"}]
|
||||
DOMAINS_ALLOWED=["riso.app","germogliamo.app","comunitanuovomondo.app","nuovomondo.app","kolibrilab.it","artenergetica.org","freeplanet.app","www.freeplanet.app","freeplanet.app:3000","freeplanet.app:3001","www.freeplanet.app:3000","www.freeplanet.app:3001"]
|
||||
#DOMAINS=[{"hostname":"abitaregliiblei.it","port":"3021"},{"hostname":"riso.app","port":"3005"}]
|
||||
SCRIPTS_DIR=admin_scripts
|
||||
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
|
||||
|
||||
@@ -31,8 +31,8 @@ SECRK=iUUb38v23jjDFaosWj92axkBOXCQ
|
||||
TOKEN_LIFE=2h
|
||||
REFRESH_TOKEN_LIFE=14d
|
||||
AUTH_NEW_SITES=B234HDSAOJ734ndcsdKWNV
|
||||
DOMAINS=[{"hostname":"riso.app","port":"3005"}]
|
||||
DOMAINS_ALLOWED=["comunitanuovomondo.app","kolibrilab.it","artenergetica.org","freeplanet.app","www.freeplanet.app","freeplanet.app:3000","freeplanet.app:3001","www.freeplanet.app:3000","www.freeplanet.app:3001"]
|
||||
DOMAINS=[{"hostname":"riso.app","port":"3005"},{"hostname":"nuovomondo.app","port":"3033"}]
|
||||
DOMAINS_ALLOWED=["riso.app","comunitanuovomondo.app","kolibrilab.it","artenergetica.org","freeplanet.app","www.freeplanet.app","freeplanet.app:3000","freeplanet.app:3001","www.freeplanet.app:3000","www.freeplanet.app:3001"]
|
||||
SCRIPTS_DIR=admin_scripts
|
||||
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
|
||||
MIAB_HOST=box.lamiaposta.org
|
||||
|
||||
40
.vscode/launch.json
vendored
40
.vscode/launch.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
@@ -13,7 +13,7 @@
|
||||
"--inspect=9229" // Use "--inspect=0.0.0.0:9229" for remote debugging
|
||||
],
|
||||
"args": [
|
||||
"${workspaceFolder}/src/server/server.js"
|
||||
"${workspaceFolder}/src/server.js"
|
||||
], // Replace with your entry file
|
||||
"cwd": "${workspaceFolder}",
|
||||
"autoAttachChildProcesses": true,
|
||||
@@ -36,7 +36,7 @@
|
||||
"--trace-warnings" // Use "--inspect=0.0.0.0:9229" for remote debugging
|
||||
],
|
||||
"args": [
|
||||
"${workspaceFolder}/src/server/server.js"
|
||||
"${workspaceFolder}/src/server.js"
|
||||
], // Replace with your entry file
|
||||
"cwd": "${workspaceFolder}",
|
||||
"autoAttachChildProcesses": true,
|
||||
@@ -50,7 +50,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ServerSide",
|
||||
"program": "${workspaceFolder}/src/server/server.js",
|
||||
"program": "${workspaceFolder}/src/server.js",
|
||||
"request": "launch",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
@@ -61,5 +61,37 @@
|
||||
"TESTING_ON": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug CheckSmartBot",
|
||||
"program": "${workspaceFolder}/../checksmartbot/bot.js",
|
||||
"console": "integratedTerminal",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/../**/*.js"
|
||||
],
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"autoAttachChildProcesses": true,
|
||||
"smartStep": true
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Avvia bot con pm2 (attach)",
|
||||
"runtimeExecutable": "pm2",
|
||||
"runtimeArgs": [
|
||||
"start",
|
||||
"bot.js",
|
||||
"--name",
|
||||
"checksmartbot"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"restart": true,
|
||||
"port": 9229,
|
||||
"attach": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,7 @@ yarn
|
||||
|
||||
### Start the app in development mode (hot-code reloading, error reporting, etc.)
|
||||
```bash
|
||||
node src/server/server.js
|
||||
node src/server.js
|
||||
```
|
||||
|
||||
### Creating the ambient test
|
||||
|
||||
@@ -66,7 +66,7 @@ db.myelems.insertMany([
|
||||
"listcards": [],
|
||||
"list": [],
|
||||
"__v": 0,
|
||||
"containerHtml": "<style>\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background-color: #f0f0f0;\n color: #333;\n}\n\nh1 {\n color: #0056b3;\n text-align: center;\n}\n\n\np, li {\n line-height: 1.6;\n}\n\n\nul {\n list-style-type: none;\n padding: 0;\n}\n\n\nli {\n background-color: #fff !important;\n margin-bottom: 10px !important;\n padding: 10px !important;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; \n}\n\n\na {\n color: #007bff;\n text-decoration: none;\n}\n\n\na:hover {\n text-decoration: underline;\n}\n\n\n.container {\n max-width: 800px;\n margin: 0 auto;\n background-color: #fff;\n padding: 20px;\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n}\n\n\n.strong {\n font-weight: bold;\n}\n</style>\n\n\n<p><strong>“Abitare gli Iblei”</strong> è una rete aperta che ha lo scopo di riunire tutte quelle persone che vogliono valorizzare e qualificare la vita nel territorio degli Iblei. </p>\n \n <p>Chi aderisce alla rete si riconosce in una <strong>Carta dei valori comuni</strong> e usa la rete per scambiare conoscenze, esperienze, risorse e prodotti sviluppati nell’ambito delle proprie iniziative (profit e non profit) individuali o collettive.</p>\n\n\n <p>L’area territoriale di questa rete è quella dei <strong>Monti Iblei orientali e occidentali</strong> (Noto, Avola, Canicattini, Siracusa, Palazzolo, Buccheri, Ferla, Modica, …).</p>\n\n\n <p>La rete <strong>“Abitare gli Iblei”</strong> offre i seguenti servizi utili per il territorio ed i suoi abitanti, frutto di una costruzione collettiva:</p>\n \n <ul>\n <li><strong>1. Mappa delle attività virtuose:</strong> permette di identificare attività pubbliche e private nel territorio che possono essere utili nella vita quotidiana. Queste attività possono riguardare artigiani, produttori o fornitori di servizi di cui almeno un membro della rete conosca la qualità e l’affidabilità (agricoltori, falegnami, fabbri, idraulici, imprese edili, strutture ricettive, …). Altre informazioni utili possono riguardare associazioni/istituzioni operanti in vari settori. – <strong>Accesso pubblico</strong></li>\n <li><strong>2. Calendario:</strong> permette di accedere ad annunci di eventi utili alla crescita culturale del territorio. La pubblicazione di eventi è riservata ai soli membri della rete che possono presentare iniziative anche di altri organizzatori. – <strong>Accesso pubblico</strong></li>\n <li><strong>3. Scambi di servizi, prodotti e ospitalità:</strong> questa funzione è riservata ai soli membri della rete e si realizza attraverso la Rete italiana di scambi orizzontali (RISO). – <strong>Accesso riservato</strong></li>\n <li><strong>4. Segnalazione di pericoli per il territorio:</strong> attraverso questa mappa è possibile segnalare incendi, immondizia abbandonata, discariche abusive, fonti di inquinamento per corsi d’acqua e spiagge, presenza di inquinamento nell’aria, … – <strong>Accesso riservato</strong></li>\n </ul>\n \n <p>Se vuoi aderire alla rete puoi richiederne la registrazione utilizzando questo Link <a href=\"#\"><strong>(Pagina in Costruzione)</strong></a>.</p>\n\n",
|
||||
"containerHtml": "<style>\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background-color: #f0f0f0;\n color: #333;\n}\n\nh1 {\n color: #0056b3;\n text-align: center;\n}\n\n\np, li {\n line-height: 1.6;\n}\n\n\nul {\n list-style-type: none;\n padding: 0;\n}\n\n\nli {\n background-color: #fff !important;\n margin-bottom: 10px !important;\n padding: 10px !important;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; \n}\n\n\na {\n color: #007bff;\n text-decoration: none;\n}\n\n\na:hover {\n text-decoration: underline;\n}\n\n\n.container {\n max-width: 800px;\n margin: 0 auto;\n background-color: #fff;\n padding: 20px;\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n}\n\n\n.strong {\n font-weight: bold;\n}\n</style>\n\n\n<p><strong>“Abitare gli Iblei”</strong> è una rete aperta che ha lo scopo di riunire tutte quelle persone che vogliono valorizzare e qualificare la vita nel territorio degli Iblei. </p>\n \n <p>Chi aderisce alla rete si riconosce in una <strong>Carta dei valori comuni</strong> e usa la rete per scambiare conoscenze, esperienze, risorse e prodotti sviluppati nell’ambito delle proprie iniziative (profit e non profit) individuali o collettive.</p>\n\n\n <p>L’area territoriale di questa rete è quella dei <strong>Monti Iblei orientali e occidentali</strong> (Noto, Avola, Canicattini, Siracusa, Palazzolo, Buccheri, Ferla, Modica, …).</p>\n\n\n <p>La rete <strong>“Abitare gli Iblei”</strong> offre i seguenti servizi utili per il territorio ed i suoi abitanti, frutto di una costruzione collettiva:</p>\n \n <ul>\n <li><strong>1. Mappa delle attività virtuose:</strong> permette di identificare attività pubbliche e private nel territorio che possono essere utili nella vita quotidiana. Queste attività possono riguardare artigiani, produttori o fornitori di servizi di cui almeno un membro della rete conosca la qualità e l’affidabilità (agricoltori, falegnami, fabbri, idraulici, imprese edili, strutture ricettive, …). Altre informazioni utili possono riguardare associazioni/istituzioni operanti in vari settori. – <strong>Accesso pubblico</strong></li>\n <li><strong>2. Calendario:</strong> permette di accedere ad annunci di eventi utili alla crescita culturale del territorio. La pubblicazione di eventi è riservata ai soli membri della rete che possono presentare iniziative anche di altri organizzatori. – <strong>Accesso pubblico</strong></li>\n <li><strong>3. Scambi di servizi, prodotti e ospitalità:</strong> questa funzione è riservata ai soli membri della rete e si realizza attraverso la Rete Italiana scambio orizzontale (RISO). – <strong>Accesso riservato</strong></li>\n <li><strong>4. Segnalazione di pericoli per il territorio:</strong> attraverso questa mappa è possibile segnalare incendi, immondizia abbandonata, discariche abusive, fonti di inquinamento per corsi d’acqua e spiagge, presenza di inquinamento nell’aria, … – <strong>Accesso riservato</strong></li>\n </ul>\n \n <p>Se vuoi aderire alla rete puoi richiederne la registrazione utilizzando questo Link <a href=\"#\"><strong>(Pagina in Costruzione)</strong></a>.</p>\n\n",
|
||||
"anim": {
|
||||
"_id": new ObjectId("66db393e3b885ccdfaed28d6"),
|
||||
"name": "",
|
||||
|
||||
@@ -11,9 +11,9 @@ db.mypages.insertMany([
|
||||
"_id": new ObjectId("66db21118009ea4503bb6a03"),
|
||||
"order": 10,
|
||||
"idapp": "19",
|
||||
"path": "home_logout",
|
||||
"path": "presentazione",
|
||||
"active": true,
|
||||
"title": "Home NoLoggato",
|
||||
"title": "Presentazione",
|
||||
},
|
||||
{
|
||||
"_id": new ObjectId("66e322dd5a6360e3b3c71c5a"),
|
||||
|
||||
@@ -164,7 +164,9 @@ db.users.insertMany([
|
||||
"stepTutorial": 0,
|
||||
"noNameSurname": false,
|
||||
"noCircuit": false,
|
||||
"noComune": false,
|
||||
"noCircIta": false,
|
||||
"insert_circuito_ita": false,
|
||||
"noFoto": false,
|
||||
"resid_province": "",
|
||||
"resid_card": "",
|
||||
@@ -281,7 +283,9 @@ db.users.insertMany([
|
||||
"stepTutorial": 0,
|
||||
"noNameSurname": false,
|
||||
"noCircuit": false,
|
||||
"noComune": false,
|
||||
"noCircIta": false,
|
||||
"insert_circuito_ita": false,
|
||||
"noFoto": false,
|
||||
"resid_province": "",
|
||||
"resid_card": "",
|
||||
|
||||
1
aggiornaserver.txt
Executable file
1
aggiornaserver.txt
Executable file
@@ -0,0 +1 @@
|
||||
Aggiornamento Server...
|
||||
@@ -80,7 +80,8 @@ p {
|
||||
|
||||
/* Whitespace (imageless spacer) */
|
||||
.whitespace {
|
||||
line-height: 0; }
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.firma {
|
||||
font-size: 0.75rem; }
|
||||
|
||||
@@ -72,6 +72,11 @@ if [[ $risposta == "Y" || $risposta == "y" ]]; then
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
$CONFIG_JS "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/ecosystem.config.js"
|
||||
|
||||
echo ""
|
||||
echo "*** Copia del file aggiornaserver.txt ... "
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
aggiornaserver.txt "$REMOTE_USER@$REMOTE_HOST:/opt/scripts/aggiornaserver_pcb_prod.txt"
|
||||
|
||||
# Verifica il risultato
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Sincronizzazione completata con successo. SERVER PCB PRODUZIONE! "
|
||||
|
||||
@@ -72,6 +72,11 @@ if [[ $risposta == "Y" || $risposta == "y" ]]; then
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
$CONFIG_JS "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/ecosystem.config.js"
|
||||
|
||||
echo ""
|
||||
echo "*** Copia del file aggiornaserver.txt ... "
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
aggiornaserver.txt "$REMOTE_USER@$REMOTE_HOST:/opt/scripts/aggiornaserver_pcb_test.txt"
|
||||
|
||||
# Verifica il risultato
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Sincronizzazione completata con successo. SERVER PCB TEST! "
|
||||
|
||||
@@ -67,6 +67,12 @@ if [[ $risposta == "Y" || $risposta == "y" ]]; then
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
"$CONFIG_JS" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/ecosystem.config.js"
|
||||
|
||||
echo ""
|
||||
echo "*** Copia del file aggiornaserver.txt ... "
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
aggiornaserver.txt "$REMOTE_USER@$REMOTE_HOST:/opt/scripts/aggiornaserver_riso_prod.txt"
|
||||
|
||||
|
||||
# Verifica il risultato
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Sincronizzazione completata con successo. SERVER $REMOTE_DIR! "
|
||||
|
||||
@@ -75,6 +75,11 @@ if [[ $risposta == "Y" || $risposta == "y" ]]; then
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
$CONFIG_JS "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/ecosystem.config.js"
|
||||
|
||||
echo ""
|
||||
echo "*** Copia del file aggiornaserver.txt ... "
|
||||
rsync -avz -e "ssh $SSH_OPTIONS" \
|
||||
aggiornaserver.txt "$REMOTE_USER@$REMOTE_HOST:/opt/scripts/aggiornaserver_riso_test.txt"
|
||||
|
||||
|
||||
# Verifica il risultato
|
||||
if [ $? -eq 0 ]; then
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Define MIODOMINIO riso.app
|
||||
Define MIODOMINIO_COMPLETO test.riso.app
|
||||
Define MIOURL_API testapi.riso.app
|
||||
Define PORTA 3005
|
||||
@@ -0,0 +1,30 @@
|
||||
<VirtualHost 65.108.222.97:80>
|
||||
ServerName ${MIODOMINIO_COMPLETO}
|
||||
RewriteEngine On
|
||||
|
||||
# Forza la connessione HTTPS
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^ https://${MIODOMINIO_COMPLETO}%{REQUEST_URI} [L,R=301]
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost 65.108.222.97:443>
|
||||
ServerName ${MIODOMINIO_COMPLETO}
|
||||
DocumentRoot /var/www/${MIODOMINIO_COMPLETO}
|
||||
ServerAdmin surya@riso.app
|
||||
SSLEngine On
|
||||
SSLProtocol -ALL +TLSv1.2
|
||||
SSLCompression Off
|
||||
SSLHonorCipherOrder off
|
||||
SSLCipherSuite ECDH+AESGCM:ECDH+AES256:!aNULL:!MD5:!DSS:!DH:!AES128
|
||||
SSLVerifyDepth 10
|
||||
SSLCertificateFile /etc/letsencrypt/live/${MIODOMINIO}/cert.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${MIODOMINIO}/privkey.pem
|
||||
<Directory /var/www/${MIODOMINIO_COMPLETO}/>
|
||||
CGIPassAuth On
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
LogLevel warn
|
||||
ErrorLog "/var/www/${MIODOMINIO_COMPLETO}/logs/error.log"
|
||||
CustomLog "/var/www/${MIODOMINIO_COMPLETO}/logs/access.log" combined
|
||||
</VirtualHost>
|
||||
11
docs/apache2/sites-available/000-default-ssl.conf
Normal file
11
docs/apache2/sites-available/000-default-ssl.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
<VirtualHost *:443>
|
||||
ServerName _default_
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/riso.app/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/riso.app/privkey.pem
|
||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
Protocols h2 http/1.1
|
||||
|
||||
# Puoi anche fare un 403:
|
||||
Redirect 403 /
|
||||
</VirtualHost>
|
||||
@@ -0,0 +1,4 @@
|
||||
Define MIODOMINIO riso.app
|
||||
Define MIODOMINIO_COMPLETO riso.app
|
||||
Define MIOURL_API api.riso.app
|
||||
Define PORTA 3006
|
||||
3
docs/apache2/sites-available/websites_ssl.riso.app.conf
Normal file
3
docs/apache2/sites-available/websites_ssl.riso.app.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
Include /etc/apache2/sites-available/dominioinclude.riso.app.conf
|
||||
|
||||
Include /etc/apache2/sites-available/websites_ssl_virtualhost.conf
|
||||
@@ -0,0 +1,3 @@
|
||||
Include /etc/apache2/sites-available/dominioinclude_test.riso.app.conf
|
||||
|
||||
Include /etc/apache2/sites-available/websites_ssl_virtualhost_sottodomini.conf
|
||||
@@ -1,30 +1,94 @@
|
||||
<VirtualHost 65.108.222.97:80>
|
||||
ServerName ${MIODOMINIO_COMPLETO}
|
||||
RewriteEngine On
|
||||
# ==================== APEX (SITO) ====================
|
||||
# HTTP -> HTTPS
|
||||
<VirtualHost *:80>
|
||||
ServerName ${MIODOMINIO}
|
||||
ServerAlias www.${MIODOMINIO}
|
||||
|
||||
# Forza la connessione HTTPS
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^ https://${MIODOMINIO_COMPLETO}%{REQUEST_URI} [L,R=301]
|
||||
RewriteEngine On
|
||||
RewriteRule ^ https://${MIODOMINIO}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
ErrorLog /var/log/apache2/${MIODOMINIO}-error.log
|
||||
CustomLog /var/log/apache2/${MIODOMINIO}-access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost 65.108.222.97:443>
|
||||
ServerName ${MIODOMINIO_COMPLETO}
|
||||
DocumentRoot /var/www/${MIODOMINIO_COMPLETO}
|
||||
ServerAdmin surya@riso.app
|
||||
SSLEngine On
|
||||
SSLProtocol -ALL +TLSv1.2
|
||||
SSLCompression Off
|
||||
SSLHonorCipherOrder off
|
||||
SSLCipherSuite ECDH+AESGCM:ECDH+AES256:!aNULL:!MD5:!DSS:!DH:!AES128
|
||||
SSLVerifyDepth 10
|
||||
SSLCertificateFile /etc/letsencrypt/live/${MIODOMINIO}/cert.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${MIODOMINIO}/privkey.pem
|
||||
<Directory /var/www/${MIODOMINIO_COMPLETO}/>
|
||||
CGIPassAuth On
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
LogLevel warn
|
||||
ErrorLog "/var/www/${MIODOMINIO_COMPLETO}/logs/error.log"
|
||||
CustomLog "/var/www/${MIODOMINIO_COMPLETO}/logs/access.log" combined
|
||||
# HTTPS (servizio sito - NO proxy qui)
|
||||
<VirtualHost *:443>
|
||||
ServerName ${MIODOMINIO}
|
||||
ServerAlias www.${MIODOMINIO}
|
||||
|
||||
DocumentRoot /var/www/${MIODOMINIO_COMPLETO}
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/${MIODOMINIO}/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${MIODOMINIO}/privkey.pem
|
||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
Protocols h2 http/1.1
|
||||
|
||||
<Directory "/var/www/${MIODOMINIO_COMPLETO}">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
DirectoryIndex index.html
|
||||
|
||||
# (Opzionale per SPA Quasar/Vue)
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^ /index.html [L]
|
||||
|
||||
ErrorLog /var/log/apache2/${MIODOMINIO}-error.log
|
||||
CustomLog /var/log/apache2/${MIODOMINIO}-access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
|
||||
# ==================== API ====================
|
||||
# HTTP -> HTTPS
|
||||
<VirtualHost *:80>
|
||||
ServerName ${MIOURL_API}
|
||||
|
||||
RewriteEngine On
|
||||
RewriteRule ^ https://${MIOURL_API}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
ErrorLog /var/log/apache2/${MIOURL_API}-error.log
|
||||
CustomLog /var/log/apache2/${MIOURL_API}-access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
# HTTPS (proxy verso backend)
|
||||
<VirtualHost *:443>
|
||||
ServerName ${MIOURL_API}
|
||||
ServerAdmin surya@riso.app
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/${MIODOMINIO}/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${MIODOMINIO}/privkey.pem
|
||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
Protocols h2 http/1.1
|
||||
|
||||
ProxyPreserveHost On
|
||||
|
||||
# ===== SCEGLI UNO DEI DUE BLOCCHI, in base al TUO backend su ${PORTA} =====
|
||||
# --- Backend HTTP (più comune) ---
|
||||
# ProxyPass / http://127.0.0.1:${PORTA}/ retry=0 timeout=310 connectiontimeout=30
|
||||
# ProxyPassReverse / http://127.0.0.1:${PORTA}/
|
||||
|
||||
# --- Backend HTTPS (nel tuo caso probabile, visto l’“Empty reply” via HTTP) ---
|
||||
SSLProxyEngine On
|
||||
SSLProxyVerify none
|
||||
SSLProxyCheckPeerName off
|
||||
ProxyPass / https://127.0.0.1:${PORTA}/ retry=0 timeout=310 connectiontimeout=30
|
||||
ProxyPassReverse / https://127.0.0.1:${PORTA}/
|
||||
# ==========================================================================
|
||||
|
||||
<Proxy *>
|
||||
Require all granted
|
||||
</Proxy>
|
||||
|
||||
ProxyTimeout 310
|
||||
Timeout 310
|
||||
ProxyBadHeader Ignore
|
||||
|
||||
ErrorLog /var/log/apache2/${MIOURL_API}-error.log
|
||||
CustomLog /var/log/apache2/${MIOURL_API}-access.log combined
|
||||
</VirtualHost>
|
||||
@@ -0,0 +1,92 @@
|
||||
# ==================== APEX (SITO) ====================
|
||||
# HTTP -> HTTPS
|
||||
<VirtualHost *:80>
|
||||
ServerName ${MIODOMINIO_COMPLETO}
|
||||
|
||||
RewriteEngine On
|
||||
RewriteRule ^ https://${MIODOMINIO_COMPLETO}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
ErrorLog /var/log/apache2/${MIODOMINIO_COMPLETO}-error.log
|
||||
CustomLog /var/log/apache2/${MIODOMINIO_COMPLETO}-access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
# HTTPS (servizio sito - NO proxy qui)
|
||||
<VirtualHost *:443>
|
||||
ServerName ${MIODOMINIO_COMPLETO}
|
||||
|
||||
DocumentRoot /var/www/${MIODOMINIO_COMPLETO}
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/${MIODOMINIO}/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${MIODOMINIO}/privkey.pem
|
||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
Protocols h2 http/1.1
|
||||
|
||||
<Directory "/var/www/${MIODOMINIO_COMPLETO}">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
DirectoryIndex index.html
|
||||
|
||||
# (Opzionale per SPA Quasar/Vue)
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^ /index.html [L]
|
||||
|
||||
ErrorLog /var/log/apache2/${MIODOMINIO_COMPLETO}-error.log
|
||||
CustomLog /var/log/apache2/${MIODOMINIO_COMPLETO}-access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
|
||||
# ==================== API ====================
|
||||
# HTTP -> HTTPS
|
||||
<VirtualHost *:80>
|
||||
ServerName ${MIOURL_API}
|
||||
|
||||
RewriteEngine On
|
||||
RewriteRule ^ https://${MIOURL_API}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
ErrorLog /var/log/apache2/${MIOURL_API}-error.log
|
||||
CustomLog /var/log/apache2/${MIOURL_API}-access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
# HTTPS (proxy verso backend)
|
||||
<VirtualHost *:443>
|
||||
ServerName ${MIOURL_API}
|
||||
ServerAdmin surya@riso.app
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/${MIODOMINIO}/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/${MIODOMINIO}/privkey.pem
|
||||
Include /etc/letsencrypt/options-ssl-apache.conf
|
||||
Protocols h2 http/1.1
|
||||
|
||||
ProxyPreserveHost On
|
||||
|
||||
# ===== SCEGLI UNO DEI DUE BLOCCHI, in base al TUO backend su ${PORTA} =====
|
||||
# --- Backend HTTP (più comune) ---
|
||||
# ProxyPass / http://127.0.0.1:${PORTA}/ retry=0 timeout=310 connectiontimeout=30
|
||||
# ProxyPassReverse / http://127.0.0.1:${PORTA}/
|
||||
|
||||
# --- Backend HTTPS (nel tuo caso probabile, visto l’“Empty reply” via HTTP) ---
|
||||
SSLProxyEngine On
|
||||
SSLProxyVerify none
|
||||
SSLProxyCheckPeerName off
|
||||
ProxyPass / https://127.0.0.1:${PORTA}/ retry=0 timeout=310 connectiontimeout=30
|
||||
ProxyPassReverse / https://127.0.0.1:${PORTA}/
|
||||
# ==========================================================================
|
||||
|
||||
<Proxy *>
|
||||
Require all granted
|
||||
</Proxy>
|
||||
|
||||
ProxyTimeout 310
|
||||
Timeout 310
|
||||
ProxyBadHeader Ignore
|
||||
|
||||
ErrorLog /var/log/apache2/${MIOURL_API}-error.log
|
||||
CustomLog /var/log/apache2/${MIOURL_API}-access.log combined
|
||||
</VirtualHost>
|
||||
@@ -1 +1,12 @@
|
||||
<div class="row align-start" style="gap:0px;flex-direction:column;flex:1;display:flex;justify-content:space-between;"><!-- Parte superiore --><div class="justify-start" style="flex-grow:0"><span style="font-size:calc(12 * var(--scalecatalog) * 1px)" class="book-author">{autore}</span><div style="font-size:calc(16 * var(--scalecatalog) * 1px);font-weight:bold;" class="book-title">{titolo}</div><span class="book-descr">{descrizione}</span></div><!-- Parte inferiore (book-details e barcode) --><div style="margin-top:auto;width:100%"><div class="justify-end book-details" style="flex-grow:0;font-size:calc(10 * var(--scalecatalog) * 1px);">Pagine: <b>{pagine}</b><br />Formato: <b>{misure}</b><br />Prezzo: <b>{prezzo} €</b><br /></div></div></div>
|
||||
<div class="row align-start" style="gap:0px;flex-direction:column;flex:1;display:flex;justify-content:space-between;">
|
||||
<!-- Parte superiore -->
|
||||
<div class="justify-start" style="flex-grow:0"><span style="font-size:calc(12 * var(--scalecatalogx) * 1px)"
|
||||
class="book-author">{autore}</span>
|
||||
<div style="font-size:calc(16 * var(--scalecatalogx) * 1px);font-weight:bold;" class="book-title">{titolo}</div>
|
||||
<span class="book-descr">{descrizione}</span>
|
||||
</div><!-- Parte inferiore (book-details e barcode) -->
|
||||
<div style="margin-top:auto;width:100%">
|
||||
<div class="justify-end book-details" style="flex-grow:0;font-size:calc(10 * var(--scalecatalog) * 1px);">
|
||||
Pagine: <b>{pagine}</b><br />Formato: <b>{misure}</b><br />Prezzo: <b>{prezzo} €</b><br /></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@ module.exports = {
|
||||
apps : [
|
||||
{
|
||||
name: "FreePlanetServerSide",
|
||||
script: "./src/server/server.js",
|
||||
script: "./src/server.js",
|
||||
ignore_watch : ["node_modules"],
|
||||
watch: false,
|
||||
//autorestart: true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "PiuCheBuono (Prod)",
|
||||
script: "/var/www/nodejs_piuchebuono_server/src/server/server.js",
|
||||
name: "PCB-PROD",
|
||||
script: "/var/www/nodejs_piuchebuono_server/src/server.js",
|
||||
ignore_watch: ["node_modules", "logs"],
|
||||
interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
// interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
//autorestart: true,
|
||||
instances: 1,
|
||||
watch: false,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "PiuCheBuono (TEST)",
|
||||
script: "/var/www/nodejs_test.piuchebuono_server/src/server/server.js",
|
||||
name: "PCB-TEST",
|
||||
script: "/var/www/nodejs_test.piuchebuono_server/src/server.js",
|
||||
ignore_watch: ["node_modules", "logs"],
|
||||
interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
// interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
instances: 1,
|
||||
watch: false,
|
||||
env: {
|
||||
|
||||
@@ -2,9 +2,9 @@ module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "PRODUZIONE - FREEPLANET",
|
||||
script: "/var/www/www.freeplanet_server/src/server/server.js",
|
||||
script: "/var/www/www.freeplanet_server/src/server.js",
|
||||
ignore_watch: ["node_modules", "logs"],
|
||||
interpreter: "/root/.nvm/versions/node/v16.19.0/bin/node",
|
||||
// interpreter: "/root/.nvm/versions/node/v16.19.0/bin/node",
|
||||
//autorestart: true,
|
||||
instances: 1,
|
||||
watch: false,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "RISO (Prod)",
|
||||
script: "/var/www/nodejs_riso_server/src/server/server.js",
|
||||
name: "RISO-PROD",
|
||||
script: "/var/www/nodejs_riso_server/src/server.js",
|
||||
ignore_watch: ["node_modules", "logs"],
|
||||
interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
// interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
//autorestart: true,
|
||||
instances: 1,
|
||||
watch: false,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "RISO (TEST)",
|
||||
script: "./src/server/server.js",
|
||||
name: "RISO-TEST",
|
||||
script: "./src/server.js",
|
||||
ignore_watch: ["node_modules", "logs"],
|
||||
interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
// interpreter: "/root/.nvm/versions/node/v22.13.1/bin/node",
|
||||
watch: false,
|
||||
instances: 1,
|
||||
env_test: {
|
||||
|
||||
BIN
emails/.DS_Store
vendored
BIN
emails/.DS_Store
vendored
Binary file not shown.
492
emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
Executable file
492
emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
Executable file
@@ -0,0 +1,492 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.congrats-card {
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #f1f8f4 100%);
|
||||
border: 2px solid #7cb342;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.congrats-card .congrats-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.congrats-card h3 {
|
||||
font-size: 22px;
|
||||
color: #558b2f;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.congrats-card .territory-name {
|
||||
font-size: 20px;
|
||||
color: #7cb342;
|
||||
font-weight: 600;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.info-section h3 {
|
||||
font-size: 18px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-section p {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-section ul {
|
||||
margin: 12px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.info-section li {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||
border-left: 4px solid #f0ad4e;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.highlight-box h4 {
|
||||
font-size: 17px;
|
||||
color: #f0ad4e;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.highlight-box p {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.example-box {
|
||||
background: #e3f2fd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.example-box h4 {
|
||||
font-size: 16px;
|
||||
color: #1976d2;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.example-box .transaction {
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.example-box .benefit {
|
||||
background: #c8e6c9;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.steps-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.steps-box h4 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #7cb342;
|
||||
min-width: 40px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.step-content h5 {
|
||||
font-size: 16px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 32px 0;
|
||||
padding: 24px 0;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 18px 56px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 6px 20px rgba(124, 179, 66, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.community-box {
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #f1f8f4 100%);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.community-box h4 {
|
||||
font-size: 17px;
|
||||
color: #558b2f;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.community-box p {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.community-box a {
|
||||
color: #2196f3;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.congrats-card .congrats-icon {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.congrats-card h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 16px 40px;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
|
||||
h1 🎉 Benvenuto nel #{nomeTerritorio}!
|
||||
p.subtitle Sei stato abilitato con successo
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
| Ciao <strong>#{usernameMembro}</strong>,<br>
|
||||
| complimenti! Sei stato abilitato #{nomeTerritorio} da #{usernameInvitante}.
|
||||
|
||||
if linkProfiloAdmin
|
||||
.divider(style="margin: 16px 0;")
|
||||
p(style="text-align: center; margin: 16px 0;")
|
||||
a.profile-button(href=linkProfiloAdmin target="_blank" style="display: inline-block; padding: 10px 24px; font-size: 15px; font-weight: 600; color: #7cb342; background: white; border: 2px solid #7cb342; border-radius: 20px; text-decoration: none; transition: all 0.3s ease;")
|
||||
span(style="margin-right: 6px;") 👤
|
||||
| Profilo #{usernameInvitante}
|
||||
|
||||
//- Congratulazioni
|
||||
.congrats-card
|
||||
.congrats-icon ✅
|
||||
h3 Abilitazione Completata
|
||||
p(style="font-size: 15px; color: #555; margin-top: 8px;")
|
||||
| Ora puoi utilizzare i #{symbol} per i tuoi scambi nella comunità
|
||||
.territory-name 📍 #{nomeTerritorio}
|
||||
|
||||
//- Info comunità
|
||||
.community-box
|
||||
h4 💬 Unisciti alla Comunità Territoriale
|
||||
p
|
||||
| Entra nel gruppo Telegram di <strong>#{nomeTerritorio}</strong> per interagire con i partecipanti, rimanere aggiornato su eventi, mercatini e opportunità di scambio nella tua zona, e per poter inserire, anche tu, annunci di offro/cerco.
|
||||
|
||||
if linkTelegramTerritorio
|
||||
a.telegram-button(href=linkTelegramTerritorio target="_blank" style="display: inline-block; margin-top: 16px; padding: 14px 32px; font-size: 17px; font-weight: 600; color: white; background: linear-gradient(135deg, #0088cc 0%, #006699 100%); border-radius: 25px; text-decoration: none; box-shadow: 0 4px 12px rgba(0, 136, 204, 0.3); transition: all 0.3s ease;")
|
||||
span(style="font-size: 20px; margin-right: 8px; vertical-align: middle;") ✈️
|
||||
| Unisciti al gruppo Telegram
|
||||
|
||||
//- Cos'è RIS
|
||||
.info-section
|
||||
h3
|
||||
span 💰
|
||||
| Cosa sono i RIS?
|
||||
p
|
||||
| <strong>RIS</strong> (Rete Italiana Scambio) è un sistema di <strong>credito comunitario</strong> basato sulla fiducia reciproca. Non sono soldi tradizionali, ma un'unità di misura che rappresenta il valore degli scambi all'interno della comunità RISO.
|
||||
p
|
||||
| <strong>Parità con l'Euro:</strong> 1 RIS = 1 Euro (solo come riferimento di valore, non come convertibilità)
|
||||
|
||||
//- Come funziona la fiducia
|
||||
.highlight-box
|
||||
h4 🤝 Come Funziona la "Fiducia Concessa"
|
||||
p
|
||||
| <strong>Parti da 0 RIS</strong> - Non devi avere un "saldo positivo" per iniziare a scambiare!
|
||||
p
|
||||
| <strong>Quando ricevi</strong> un bene o servizio pagando in RIS → il tuo saldo <strong>diventa positivo</strong>
|
||||
p
|
||||
| <strong>Quando offri</strong> un bene o servizio ricevendo RIS → il tuo saldo <strong>diventa negativo</strong>
|
||||
p(style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #f0ad4e;")
|
||||
| 💡 <strong>Il saldo negativo non è un debito!</strong> È la fiducia che la comunità ti concede. Significa che hai ricevuto prima di aver dato, e la comunità si fida che restituirai nel tempo.
|
||||
|
||||
//- Esempio pratico
|
||||
.example-box
|
||||
h4 📖 Esempio Pratico
|
||||
.transaction
|
||||
| <strong>Situazione:</strong> Sei un grafico e vuoi comprare 100€ di verdure da un produttore locale
|
||||
.transaction
|
||||
| <strong>Transazione mista:</strong>
|
||||
| <br>• Paghi <strong>80€ in Euro</strong>
|
||||
| <br>• Paghi <strong>20 RIS</strong> (20% in RIS)
|
||||
| <br><br>🔻 Il tuo saldo RIS passa da 0 a <strong>-20 RIS</strong>
|
||||
.benefit
|
||||
| <strong>✓ Beneficio:</strong> Hai ridotto del 20% l'uso degli Euro, sostenendo il produttore locale e rafforzando la comunità! Puoi iniziare con percentuali basse (5-10%) e aumentare man mano che acquisisci fiducia.
|
||||
|
||||
//- Come riequilibrare
|
||||
.info-section
|
||||
h3
|
||||
span ⚖️
|
||||
| Come Riequilibrare il Saldo
|
||||
p
|
||||
| Per riportare il tuo saldo verso lo zero (o in positivo), puoi:
|
||||
ul
|
||||
li <strong>Offrire beni o servizi</strong> ricevendo RIS in cambio
|
||||
li <strong>Vendere prodotti</strong> accettando pagamenti parziali o totali in RIS
|
||||
li <strong>Mettere annunci</strong> sulla piattaforma specificando che accetti RIS
|
||||
li <strong>Partecipare ai mercatini</strong> locali della comunità RISO
|
||||
|
||||
//- Primi passi
|
||||
.steps-box
|
||||
h4 🚀 I Tuoi Primi Passi
|
||||
.step-item
|
||||
.step-number 1
|
||||
.step-content
|
||||
h5 Esplora la Piattaforma
|
||||
p Familiarizza con gli annunci, i membri e le funzionalità del #{nomeTerritorio}
|
||||
.step-item
|
||||
.step-number 2
|
||||
.step-content
|
||||
h5 Crea il Tuo Primo Annuncio
|
||||
p Pubblica cosa offri o cosa cerchi, specificando se accetti pagamenti in RIS
|
||||
.step-item
|
||||
.step-number 3
|
||||
.step-content
|
||||
h5 Inizia con Piccole Transazioni
|
||||
p Comincia con percentuali basse di RIS (5-10%) per prendere confidenza e vedi cosa succede. Più siamo aperti noi e più l'Universo ci aiuta e sostiene e ci dona quello di cui abbiamo bisogno.
|
||||
.step-item
|
||||
.step-number 4
|
||||
.step-content
|
||||
h5 Partecipa alla Comunità
|
||||
p Unisciti agli incontri locali e ai mercatini per conoscere altri membri. Se non ci sono membri che propongono incontri, puoi proporti anche tu!
|
||||
|
||||
//- CTA
|
||||
.cta-section
|
||||
.cta-title Inizia Subito a Usare i RIS!
|
||||
a.cta-button(href=strlinksito target="_blank")
|
||||
span.button-icon 🌾
|
||||
| Vai alla Piattaforma
|
||||
|
||||
//- Supporto
|
||||
.info-section
|
||||
h3
|
||||
span ❓
|
||||
| Hai Domande?
|
||||
p
|
||||
| Se hai dubbi sul funzionamento dei RIS o sulla piattaforma, non esitare a:
|
||||
ul
|
||||
li Contattare il facilitatore del tuo territorio
|
||||
li Chiedere nel gruppo Telegram locale
|
||||
li Partecipare agli incontri di comunità
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Benvenuto nella Rete Italiana Scambio orizzontale - #{nomeTerritorio}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp}
|
||||
1
emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
Executable file
1
emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`Abilitazione avvenuta su ${nomeTerritorio} in ${nomeapp} - (${usernameMembro})`
|
||||
401
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
Executable file
401
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
Executable file
@@ -0,0 +1,401 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.request-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f0f7ff 100%);
|
||||
border: 2px solid #2196f3;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.request-card h3 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: #2196f3;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.request-card .member-name {
|
||||
font-size: 28px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.request-card .member-detail {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.request-card .member-detail strong {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.territory-badge {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
|
||||
color: white;
|
||||
display: inline-block;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.question-box {
|
||||
background: #e1f5fe;
|
||||
border-left: 4px solid #2196f3;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.question-box p {
|
||||
font-size: 20px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 32px 0;
|
||||
padding: 24px 0;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 18px 56px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #2e7d32;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.responsibility-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.responsibility-box h3 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.responsibility-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.responsibility-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.responsibility-text {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.request-card .member-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.territory-badge {
|
||||
font-size: 14px;
|
||||
padding: 6px 16px;
|
||||
}
|
||||
|
||||
.question-box p {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 16px 40px;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.responsibility-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
|
||||
h1 🎯 Richiesta Abilitazione #{nomeTerritorio}
|
||||
p.subtitle Nuovo membro in attesa di attivazione
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
| Ciao <strong>#{nomeFacilitatore}</strong>,<br>
|
||||
| un nuovo membro richiede l'abilitazione alla fiducia al Circuito del tuo territorio!
|
||||
|
||||
//- Card richiesta
|
||||
.request-card
|
||||
h3 👤 Richiesta Ingresso Circuito
|
||||
.member-name #{usernameMembro}
|
||||
if nomeMembro
|
||||
if cognomeMembro
|
||||
.member-detail
|
||||
strong Nome:
|
||||
| #{nomeMembro} #{cognomeMembro}
|
||||
else
|
||||
.member-detail
|
||||
strong Nome:
|
||||
| #{nomeMembro}
|
||||
if emailMembro
|
||||
.member-detail
|
||||
strong Email:
|
||||
| #{emailMembro}
|
||||
if telegramMembro
|
||||
.member-detail
|
||||
strong Telegram:
|
||||
a(href=`https://t.me/${telegramMembro}` target="_blank" style="color: #2196f3; text-decoration: none;") @#{telegramMembro}
|
||||
if comuneResidenza
|
||||
.member-detail
|
||||
strong Comune:
|
||||
| #{comuneResidenza} (#{provinciaResidenza})
|
||||
|
||||
.territory-badge 📍 #{nomeTerritorio}
|
||||
|
||||
if usernameInvitante
|
||||
.divider(style="margin: 20px auto; width: 80%;")
|
||||
h3(style="font-size: 14px; text-transform: uppercase; color: #2196f3; margin-bottom: 12px;") 👥 Invitato da
|
||||
.member-detail
|
||||
strong Invitante:
|
||||
| #{usernameInvitante}
|
||||
if nomeInvitante
|
||||
if cognomeInvitante
|
||||
.member-detail
|
||||
strong Nome:
|
||||
| #{nomeInvitante} #{cognomeInvitante}
|
||||
else
|
||||
.member-detail
|
||||
strong Nome:
|
||||
| #{nomeInvitante}
|
||||
if telegramInvitante
|
||||
.member-detail
|
||||
strong Telegram:
|
||||
a(href=`https://t.me/${telegramInvitante}` target="_blank" style="color: #2196f3; text-decoration: none;") @#{telegramInvitante}
|
||||
|
||||
//- CTA principale
|
||||
.cta-section
|
||||
a.cta-button(href=linkAbilitazione target="_blank")
|
||||
span.button-icon ✓
|
||||
| Abilita fiducia
|
||||
|
||||
.divider(style="margin: 24px 0;")
|
||||
p(style="font-size: 16px; color: #666; margin-bottom: 16px;") Visualizza i profili
|
||||
.button-group(style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;")
|
||||
a.secondary-button(href=linkProfiloMembro target="_blank" style="display: inline-block; padding: 12px 24px; font-size: 16px; font-weight: 600; color: #2196f3; background: white; border: 2px solid #2196f3; border-radius: 25px; text-decoration: none; transition: all 0.3s ease;")
|
||||
span(style="margin-right: 6px;") 👤
|
||||
| Profilo Membro
|
||||
if linkProfiloInvitante
|
||||
a.secondary-button(href=linkProfiloInvitante target="_blank" style="display: inline-block; padding: 12px 24px; font-size: 16px; font-weight: 600; color: #7cb342; background: white; border: 2px solid #7cb342; border-radius: 25px; text-decoration: none; transition: all 0.3s ease;")
|
||||
span(style="margin-right: 6px;") 👥
|
||||
| Profilo Invitante
|
||||
|
||||
//- Responsabilità
|
||||
.responsibility-box
|
||||
h3 📋 Compiti del Facilitatore RISO
|
||||
.responsibility-item
|
||||
span.responsibility-icon 🔍
|
||||
span.responsibility-text
|
||||
strong Verifica:
|
||||
| Contatta il membro, se non lo conosci, oppure il suo invitante: #{usernameInvitante}
|
||||
.responsibility-item
|
||||
span.responsibility-icon 🌍
|
||||
span.responsibility-text
|
||||
strong Territorio:
|
||||
| Assicurati che il membro appartenga effettivamente al territorio di competenza
|
||||
.responsibility-item
|
||||
span.responsibility-icon 👥
|
||||
span.responsibility-text
|
||||
strong Integrazione:
|
||||
| Supporta il nuovo membro nell'attivazione e utilizzo del Circuito locale
|
||||
|
||||
//- Info box
|
||||
.info-box
|
||||
p
|
||||
| ✓ Dopo l'abilitazione, #{usernameMembro} potrà accedere al #{nomeTerritorio}
|
||||
p
|
||||
| ✓ Il membro riceverà una notifica automatica dell'avvenuta attivazione
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email in qualità di Facilitatore RISO per #{nomeTerritorio}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp}
|
||||
1
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/subject.pug
Executable file
1
emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`Richiesta ingresso di ${usernameMembro} - ${nomeMembro} ${cognomeMembro} su ${nomeTerritorio} in ${nomeapp}`
|
||||
370
emails/RISO/reg_chiedi_ammettere_all_invitante/it/html.pug
Executable file
370
emails/RISO/reg_chiedi_ammettere_all_invitante/it/html.pug
Executable file
@@ -0,0 +1,370 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.request-card {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||
border: 2px solid #f0ad4e;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.request-card h3 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: #f0ad4e;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.request-card .member-name {
|
||||
font-size: 28px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.request-card .member-detail {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.request-card .member-detail strong {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.question-box {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #f0ad4e;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.question-box p {
|
||||
font-size: 20px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 32px 0;
|
||||
padding: 24px 0;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 18px 56px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 6px 20px rgba(124, 179, 66, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #2e7d32;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #f8d7da;
|
||||
border-left: 4px solid #dc3545;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.warning-box p {
|
||||
margin: 0;
|
||||
color: #721c24;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.warning-box strong {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.responsibility-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.responsibility-box h3 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.responsibility-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.responsibility-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.responsibility-text {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.request-card .member-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.question-box p {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 16px 40px;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.responsibility-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
|
||||
h1 🔔 Richiesta di Ammissione
|
||||
p.subtitle Nuovo membro in attesa
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
| Ciao <strong>#{nomeInvitante}</strong>,<br>
|
||||
| hai una nuova richiesta di ammissione da confermare!
|
||||
|
||||
//- Card richiesta
|
||||
.request-card
|
||||
h3 👤 Richiesta di Ingresso
|
||||
.member-name #{usernameInvitato}
|
||||
if nomeInvitato
|
||||
.member-detail
|
||||
strong Nome:
|
||||
| #{nomeInvitato}
|
||||
if emailInvitato
|
||||
.member-detail
|
||||
strong Email:
|
||||
| #{emailInvitato}
|
||||
if userprofile && userprofile.profile.resid_str_comune && userprofile.profile.resid_province
|
||||
.member-detail
|
||||
strong Comune Residenza:
|
||||
| #{userprofile.profile.resid_str_comune} (#{userprofile.profile.resid_province})
|
||||
|
||||
//- Domanda di conferma
|
||||
.question-box
|
||||
p 🤔 Confermi di conoscere #{nomeInvitato}?
|
||||
|
||||
//- CTA principale
|
||||
.cta-section
|
||||
a.cta-button(href=linkAmmissione target="_blank")
|
||||
span.button-icon ✓
|
||||
| Sì, Confermo
|
||||
|
||||
//- Responsabilità
|
||||
.responsibility-box
|
||||
h3 📋 La tua responsabilità come invitante
|
||||
.responsibility-item
|
||||
span.responsibility-icon 🤝
|
||||
span.responsibility-text
|
||||
strong Conoscenza diretta:
|
||||
| Dovresti ammettere solo persone che conosci personalmente e di cui ti fidi
|
||||
.responsibility-item
|
||||
span.responsibility-icon 🛡️
|
||||
span.responsibility-text
|
||||
strong Fiducia:
|
||||
| L'ammissione si basa sulla fiducia reciproca nella comunità #{nomeapp}
|
||||
.responsibility-item
|
||||
span.responsibility-icon 👥
|
||||
span.responsibility-text
|
||||
strong Supporto:
|
||||
| Come invitante, aiuterai il nuovo membro a integrarsi nella comunità
|
||||
|
||||
//- Info box
|
||||
.info-box
|
||||
p
|
||||
| ✓ Dopo l'ammissione, #{usernameInvitato} potrà completare il profilo
|
||||
|
||||
//- Warning box
|
||||
.warning-box
|
||||
p
|
||||
strong ⚠️ Importante:
|
||||
| Se non conosci questa persona o hai dubbi sull'ammissione, ti consigliamo di non procedere. Puoi contattare il facilitatore locale per ulteriori informazioni o semplicemente non cliccare sul pulsante di conferma.
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché #{usernameInvitato} ha indicato te come invitante su #{nomeapp}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp}
|
||||
1
emails/RISO/reg_chiedi_ammettere_all_invitante/it/subject.pug
Executable file
1
emails/RISO/reg_chiedi_ammettere_all_invitante/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`🎉 ${name ? ', ' + name : username} chiede di essere ammesso su ${nomeapp} !`
|
||||
534
emails/RISO/reg_email_benvenuto_ammesso/it/html.pug
Executable file
534
emails/RISO/reg_email_benvenuto_ammesso/it/html.pug
Executable file
@@ -0,0 +1,534 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 80px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 16px;
|
||||
opacity: 0.95;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.intro-text strong {
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
|
||||
border-left: 4px solid #2E7D32;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-message h2 {
|
||||
color: #1B5E20;
|
||||
font-size: 20px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.welcome-message p {
|
||||
color: #2E7D32;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.credentials-box {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #2E7D32;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.credentials-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.credential-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.credential-row:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.credential-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
min-width: 120px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.credential-value {
|
||||
color: #1a1a1a;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.credential-value a {
|
||||
color: #2E7D32;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.credential-value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cta-buttons-wrapper {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 28px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(46, 125, 50, 0.3);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
max-width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(46, 125, 50, 0.4);
|
||||
}
|
||||
|
||||
.cta-button-secondary {
|
||||
background: linear-gradient(135deg, #388E3C 0%, #2E7D32 100%);
|
||||
box-shadow: 0 4px 12px rgba(56, 142, 60, 0.3);
|
||||
}
|
||||
|
||||
.cta-button-secondary:hover {
|
||||
box-shadow: 0 6px 16px rgba(56, 142, 60, 0.4);
|
||||
}
|
||||
|
||||
.cta-button-tertiary {
|
||||
background: linear-gradient(135deg, #66BB6A 0%, #4CAF50 100%);
|
||||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.cta-button-tertiary:hover {
|
||||
box-shadow: 0 6px 16px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.next-steps {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.next-steps-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #f8fdf9;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #4CAF50;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-content h3 {
|
||||
font-size: 16px;
|
||||
color: #1B5E20;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
border: 1px solid #A5D6A7;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #1B5E20;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.community-note {
|
||||
background: #fff3e0;
|
||||
border-left: 4px solid #FF9800;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.community-note p {
|
||||
color: #E65100;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 14px;
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.credentials-box {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.credential-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.credential-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.credential-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cta-buttons-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 24px;
|
||||
font-size: 15px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.step-content h3 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
.email-header
|
||||
- var baseimg = baseurl + '/';
|
||||
img.header-logo(src=baseimg+"images/logo.png" alt=nomeapp || 'Logo')
|
||||
h1 Benvenuto/a #{name || username} nella community RISO! 💚
|
||||
|
||||
.email-body
|
||||
.welcome-message
|
||||
if ammessoUtente
|
||||
h2 🎉 Il tuo invitante #{nomeInvitante} ti ha ammesso!
|
||||
p Sei ora un membro della Rete Italiana di Scambio Orizzontale
|
||||
p Inizia subito a scoprire beni, servizi e ospitalità nella tua comunità territoriale
|
||||
|
||||
.intro-text
|
||||
| La tua comunità RISO ti aspetta! 🌱 Qui potrai
|
||||
strong scambiare beni e servizi
|
||||
| usando i
|
||||
strong RIS
|
||||
| , conoscere persone straordinarie e contribuire a costruire
|
||||
strong un'economia più umana e solidale
|
||||
| .
|
||||
|
||||
.next-steps
|
||||
.next-steps-title 🎯 I tuoi primi passi nella community RISO
|
||||
|
||||
.step-item
|
||||
.step-number 1
|
||||
.step-content
|
||||
h3 ✅ Completa il tuo profilo
|
||||
p Collega il tuo profilo a Telegram ed accedi ai Circuiti Territoriali per poter iniziare ad usare i RIS. Un profilo completo aiuta gli altri membri a conoscerti meglio!
|
||||
|
||||
.step-item
|
||||
.step-number 2
|
||||
.step-content
|
||||
h3 🔍 Esplora gli annunci
|
||||
p Scopri cosa offrono gli altri membri nella tua area. Potresti trovare esattamente ciò che cerchi!
|
||||
|
||||
.step-item
|
||||
.step-number 3
|
||||
.step-content
|
||||
h3 📢 Pubblica il tuo primo annuncio
|
||||
p Cosa puoi offrire? Beni, servizi o ospitalità - ogni contributo arricchisce la comunità. Ricorda di includere come strumento di scambio il RIS e sperimentalo da subito!
|
||||
|
||||
.step-item
|
||||
.step-number 4
|
||||
.step-content
|
||||
h3 💬 Unisciti al gruppo territoriale
|
||||
p Partecipa alle conversazioni, agli eventi e alle iniziative della tua comunità locale sulle chat Telegram di RISO
|
||||
|
||||
.cta-section
|
||||
.cta-title 🚀 Accedi e inizia il tuo viaggio RISO
|
||||
.cta-subtitle Scegli come vuoi accedere alla piattaforma
|
||||
|
||||
.credentials-box
|
||||
.credentials-title 📋 I tuoi dati di accesso
|
||||
|
||||
if username
|
||||
.credential-row
|
||||
.credential-label Username:
|
||||
.credential-value #{username}
|
||||
|
||||
if emailto
|
||||
.credential-row
|
||||
.credential-label Email:
|
||||
.credential-value #{emailto}
|
||||
|
||||
if forgetpwd
|
||||
.credential-row
|
||||
.credential-label Password:
|
||||
.credential-value
|
||||
| (la password che hai inserito)
|
||||
br
|
||||
a(href=forgetpwd target="_blank") Hai dimenticato la password?
|
||||
|
||||
if strlinksito
|
||||
.cta-buttons-wrapper
|
||||
a.cta-button(href=strlinksito target="_blank")
|
||||
span.button-icon 🌐
|
||||
| Accedi da Web
|
||||
|
||||
a.cta-button.cta-button-secondary(href=strlinksito+'/installaapp' target="_blank")
|
||||
span.button-icon 📱
|
||||
| Installa l'App
|
||||
|
||||
a.cta-button.cta-button-tertiary(href=strlinksito+'/guida' target="_blank")
|
||||
span.button-icon 📖
|
||||
| Leggi la Guida
|
||||
|
||||
.info-box
|
||||
p
|
||||
strong 💰 Cos'è il RIS?
|
||||
br
|
||||
| Il RIS è l'unità di scambio della comunità RISO, basata sulla
|
||||
strong fiducia reciproca
|
||||
| . Parti da 0 RIS e accumula crediti offrendo i tuoi beni/servizi. Usalo per ottenere ciò di cui hai bisogno. Il bilancio totale della comunità è sempre zero - ogni credito corrisponde al debito di qualcun altro.
|
||||
|
||||
.community-note
|
||||
p
|
||||
| 🤝
|
||||
strong Ricorda:
|
||||
| Ogni scambio che fai, ogni annuncio che pubblichi, ogni interazione che hai rafforza la rete RISO. Non sei solo un utente -
|
||||
strong sei un membro attivo
|
||||
| di una comunità che crede nella solidarietà e nella condivisione!
|
||||
|
||||
|
||||
.email-footer
|
||||
.divider
|
||||
p 💚 Benvenuto/a nella famiglia RISO!
|
||||
p(style="margin-top: 8px;") Hai ricevuto questa email perché sei stato/a ammesso/a nella comunità #{nomeapp || 'RISO'}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| © #{new Date().getFullYear()} #{nomeapp || 'RISO'} - Rete Italiana di Scambio Orizzontale
|
||||
p(style="margin-top: 8px; font-size: 11px; color: #999;")
|
||||
| Costruiamo insieme un'economia più umana e solidale
|
||||
1
emails/RISO/reg_email_benvenuto_ammesso/it/subject.pug
Executable file
1
emails/RISO/reg_email_benvenuto_ammesso/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`Benvenuto in ${nomeapp}`
|
||||
394
emails/RISO/reg_notifica_all_invitante/it/html.pug
Executable file
394
emails/RISO/reg_notifica_all_invitante/it/html.pug
Executable file
@@ -0,0 +1,394 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: #fff8dc;
|
||||
border-left: 4px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.highlight-box p {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.member-card {
|
||||
background: linear-gradient(135deg, #f8fdf8 0%, #e8f5e9 100%);
|
||||
border: 2px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member-card h3 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: #558b2f;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.member-card .member-name {
|
||||
font-size: 24px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.member-card .member-detail {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.member-card .member-detail strong {
|
||||
color: #558b2f;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #2e7d32;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.tips-section h3 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.buttprof-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 48px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(124, 179, 66, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.thank-you-box {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.thank-you-box p {
|
||||
font-size: 17px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.thank-you-box strong {
|
||||
color: #558b2f;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.profile-button {
|
||||
display: inline-block;
|
||||
margin-top: 16px;
|
||||
padding: 12px 32px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 25px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 3px 10px rgba(124, 179, 66, 0.25);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 32px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.member-card .member-name {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp+' - Rete Italiana Scambio orizzontale')
|
||||
h1 🎉 Il tuo invito è stato accettato!
|
||||
p.subtitle Un nuovo membro si è unito a #{nomeapp}
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
| Ciao <strong>#{nomeInvitante}</strong>,<br>
|
||||
| la persona che hai invitato, o che ha usato il tuo username come invitante, si è appena registrata su #{nomeapp}!
|
||||
|
||||
//- Card nuovo membro
|
||||
.member-card
|
||||
h3 👤 Nuovo Membro Registrato
|
||||
.member-name #{nomeInvitato}
|
||||
if emailInvitato
|
||||
.member-detail
|
||||
strong Email:
|
||||
a(href=`mailto:${emailInvitato}` style="color: #667eea; text-decoration: none;") #{emailInvitato}
|
||||
if usernameInvitato
|
||||
.member-detail
|
||||
strong Username:
|
||||
| #{usernameInvitato}
|
||||
|
||||
//- Bottone profilo
|
||||
if usernameInvitato
|
||||
.buttprof-section
|
||||
a.profile-button(href=strlinksito + '/my/' + usernameInvitato target="_blank")
|
||||
| 👤 Visualizza Profilo di #{usernameInvitato}
|
||||
|
||||
//- Ringraziamento
|
||||
.thank-you-box
|
||||
p
|
||||
| 🙏 <strong>Grazie per aver contribuito alla crescita di #{nomeapp}!</strong>
|
||||
p
|
||||
| Ogni nuovo ingresso rende la nostra comunità più forte e ricca di opportunità.
|
||||
| Il tuo invito aiuta #{nomeInvitato} a scoprire un nuovo modo di fare economia,
|
||||
| basato su fiducia, comunità e scambi solidali.
|
||||
|
||||
//- Suggerimenti
|
||||
.tips-section
|
||||
h3 💡 Come puoi aiutare #{nomeInvitato}
|
||||
.tip-item
|
||||
span.tip-icon 🤝
|
||||
span.tip-text
|
||||
strong Connettiti con loro:
|
||||
| Aiutali a sentirsi parte della comunità e presentali ad altri membri del circuito
|
||||
.tip-item
|
||||
span.tip-icon 📱
|
||||
span.tip-text
|
||||
strong Mostra come funziona #{nomeapp}:
|
||||
| Spiega come creare annunci, usare i RIS e partecipare agli scambi nella tua zona
|
||||
.tip-item
|
||||
span.tip-icon 📲
|
||||
span.tip-text
|
||||
strong Gruppo Telegram:
|
||||
| Incoraggiali a unirsi al gruppo Telegram del vostro circuito provinciale
|
||||
.tip-item
|
||||
span.tip-icon 🎯
|
||||
span.tip-text
|
||||
strong Profilo completo:
|
||||
| Ricorda loro di completare il profilo per poter iniziare a scambiare
|
||||
|
||||
//- Info box
|
||||
.info-box
|
||||
p
|
||||
| ✓ #{nomeInvitato} ha ricevuto un'email di benvenuto con tutte le istruzioni
|
||||
p
|
||||
| ✓ Dovrà completare il profilo prima di poter usare i RIS
|
||||
p
|
||||
| ✓ Il facilitatore locale valuterà l'abilitazione all'uso dei RIS
|
||||
|
||||
//- CTA
|
||||
.cta-section
|
||||
.cta-title Visita la Piattaforma
|
||||
a.cta-button(href=strlinksito target="_blank") Vai su #{nomeapp}
|
||||
|
||||
//- Highlight box
|
||||
.highlight-box
|
||||
p
|
||||
| 🌱 <strong>Costruiamo insieme un'economia più solidale!</strong><br>
|
||||
| Continua a condividere #{nomeapp} con persone di fiducia della tua comunità.
|
||||
| Più siamo, più scambi possibili ci sono per tutti!
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché hai invitato #{nomeInvitato} su #{nomeapp} oppure la persona ha usato il tuo username come invitante
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp} - Rete Italiana Scambio orizzontale
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||
1
emails/RISO/reg_notifica_all_invitante/it/subject.pug
Executable file
1
emails/RISO/reg_notifica_all_invitante/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`🎉 Il tuo invito è stato accettato su RISO da ${name ? name : username} !`
|
||||
@@ -1,71 +0,0 @@
|
||||
p #{username} (#{name} #{surname}) si è appena Iscritto ad Arcadei Foundation su #{nomeapp}
|
||||
p Con i seguenti dati di accesso:
|
||||
span Nome:
|
||||
strong #{iscritto.name}<br>
|
||||
span Cognome:
|
||||
strong #{iscritto.surname}<br>
|
||||
span Email:
|
||||
strong #{iscritto.email}<br>
|
||||
span Email Secondaria:
|
||||
strong #{iscritto.email2}<br>
|
||||
span Indirizzo di Residenza:
|
||||
strong #{iscritto.residency_address}<br>
|
||||
span Città di Residenza:
|
||||
strong #{iscritto.residency_city}<br>
|
||||
span Provincia:
|
||||
strong #{iscritto.residency_province}<br>
|
||||
span CAP:
|
||||
strong #{iscritto.residency_zipcode}<br>
|
||||
span Nazione:
|
||||
strong #{iscritto.residency_country}<br>
|
||||
span Data di Nascita:
|
||||
strong #{data_nascita}<br>
|
||||
span Città di Nascita:
|
||||
strong #{iscritto.born_city}<br>
|
||||
span Provincia di Nascita:
|
||||
strong #{iscritto.born_province}<br>
|
||||
span Paese di Nascita:
|
||||
strong #{iscritto.born_country}<br>
|
||||
span Telefono:
|
||||
strong #{iscritto.cell_phone}<br>
|
||||
span Telefono2:
|
||||
strong #{iscritto.cell_phone2}<br>
|
||||
span Tipo di Documento :
|
||||
strong #{iscritto.doctype}<br>
|
||||
span Numero Documento :
|
||||
strong #{iscritto.documentnumber}<br>
|
||||
span Metodo di Pagamento :
|
||||
strong #{iscritto.metodo_pagamento}<br>
|
||||
span Quota scelta da versare :
|
||||
strong #{iscritto.quota_versata}<br>
|
||||
span Scrivi altre eventuali informazioni o comunicazioni:
|
||||
strong #{iscritto.altre_comunicazioni}<br>
|
||||
span Categorie d'Interesse :
|
||||
strong #{iscritto.categorie_interesse}<br>
|
||||
p <br>Saluti
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Nuova Iscrizione Arcadei di ${name} ${surname} (${emailto}) su ${nomeapp}`
|
||||
@@ -1,42 +1,185 @@
|
||||
p #{username} (#{name} #{surname}) si è appena Registrato su #{nomeapp}
|
||||
p Con i seguenti dati di accesso:
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
span Invitante:
|
||||
strong #{aportador_solidario}<br>
|
||||
span Nome:
|
||||
strong #{user.name}<br>
|
||||
span Cognome:
|
||||
strong #{user.surname}<br>
|
||||
span Cellulare:
|
||||
strong #{user.profile.intcode_cell} #{user.profile.cell}<br>
|
||||
span Nazionalità:
|
||||
strong #{user.profile.nationality}<br>
|
||||
span Gruppo:
|
||||
strong #{idMyGroup}<br>
|
||||
p <br>Saluti
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #027be3 0%, #0056b3 100%);
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
padding: 32px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
min-width: 140px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #1a1a1a;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
display: inline-block;
|
||||
background: #e3f2fd;
|
||||
color: #027be3;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
body
|
||||
.email-container
|
||||
.email-header
|
||||
h1 ✓ Nuova Registrazione su #{nomeapp}
|
||||
p #{nomeapp || 'Piattaforma'}
|
||||
|
||||
.email-body
|
||||
p(style="margin-bottom: 20px; font-size: 15px; color: #333;")
|
||||
| L'utente
|
||||
span.highlight #{username || 'N/D'}
|
||||
| si è appena registrato sulla piattaforma #{nomeapp}.
|
||||
|
||||
.user-info
|
||||
if username
|
||||
.info-row
|
||||
.info-label Username:
|
||||
.info-value #{username}
|
||||
|
||||
if emailto
|
||||
.info-row
|
||||
.info-label Email:
|
||||
.info-value #{emailto}
|
||||
|
||||
if user && user.name
|
||||
.info-row
|
||||
.info-label Nome:
|
||||
.info-value #{user.name}
|
||||
|
||||
if user && user.surname
|
||||
.info-row
|
||||
.info-label Cognome:
|
||||
.info-value #{user.surname}
|
||||
|
||||
if user && user.profile && (user.profile.cell || user.profile.intcode_cell)
|
||||
.info-row
|
||||
.info-label Cellulare:
|
||||
.info-value
|
||||
| #{user.profile.intcode_cell || ''} #{user.profile.cell || ''}
|
||||
|
||||
if user && user.profile && (user.profile.resid_str_comune && user.profile.resid_province)
|
||||
.info-row
|
||||
.info-label Comune di Residenza:
|
||||
.info-value
|
||||
| #{user.profile.resid_str_comune || ''} (#{user.profile.resid_province || ''})
|
||||
|
||||
if user && user.profile && user.profile.nationality
|
||||
.info-row
|
||||
.info-label Nazionalità:
|
||||
.info-value #{user.profile.nationality}
|
||||
|
||||
if aportador_solidario
|
||||
.info-row
|
||||
.info-label Invitante:
|
||||
.info-value #{aportador_solidario}
|
||||
|
||||
if idMyGroup
|
||||
.info-row
|
||||
.info-label Gruppo:
|
||||
.info-value #{idMyGroup}
|
||||
|
||||
.email-footer
|
||||
p Questa è una email automatica di notifica
|
||||
p(style="margin-top: 8px; font-size: 12px;")
|
||||
| © #{new Date().getFullYear()} #{nomeapp || ''}.
|
||||
375
emails/defaultSite/reg_chiedi_ammettere_all_invitante/it/html.pug
Executable file
375
emails/defaultSite/reg_chiedi_ammettere_all_invitante/it/html.pug
Executable file
@@ -0,0 +1,375 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.request-card {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||
border: 2px solid #f0ad4e;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.request-card h3 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: #f0ad4e;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.request-card .member-name {
|
||||
font-size: 28px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.request-card .member-detail {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.request-card .member-detail strong {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.question-box {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #f0ad4e;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.question-box p {
|
||||
font-size: 20px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 32px 0;
|
||||
padding: 24px 0;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 18px 56px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 6px 20px rgba(124, 179, 66, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #2e7d32;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #f8d7da;
|
||||
border-left: 4px solid #dc3545;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.warning-box p {
|
||||
margin: 0;
|
||||
color: #721c24;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.warning-box strong {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.responsibility-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.responsibility-box h3 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.responsibility-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.responsibility-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.responsibility-text {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.request-card .member-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.question-box p {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 16px 40px;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.responsibility-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp)
|
||||
h1 🔔 Richiesta di Ammissione
|
||||
p.subtitle Nuovo membro in attesa
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
| Ciao <strong>#{nomeInvitante}</strong>,<br>
|
||||
| hai una nuova richiesta di ammissione da confermare!
|
||||
|
||||
//- Card richiesta
|
||||
.request-card
|
||||
h3 👤 Richiesta di Ingresso
|
||||
.member-name #{usernameInvitato}
|
||||
if nomeInvitato
|
||||
.member-detail
|
||||
strong Nome:
|
||||
| #{nomeInvitato}
|
||||
if emailInvitato
|
||||
.member-detail
|
||||
strong Email:
|
||||
| #{emailInvitato}
|
||||
if userprofile && userprofile.profile.resid_str_comune && userprofile.profile.resid_province
|
||||
.member-detail
|
||||
strong Comune Residenza:
|
||||
| #{userprofile.profile.resid_str_comune} (#{userprofile.profile.resid_province})
|
||||
|
||||
//- Domanda di conferma
|
||||
.question-box
|
||||
p 🤔 Confermi di conoscere #{nomeInvitato}?
|
||||
|
||||
//- CTA principale
|
||||
.cta-section
|
||||
.cta-title Ammetti #{usernameInvitato} su #{nomeapp}
|
||||
a.cta-button(href=linkAmmissione target="_blank")
|
||||
span.button-icon ✓
|
||||
| Sì, Confermo
|
||||
|
||||
//- Responsabilità
|
||||
.responsibility-box
|
||||
h3 📋 La tua responsabilità come invitante
|
||||
.responsibility-item
|
||||
span.responsibility-icon 🤝
|
||||
span.responsibility-text
|
||||
strong Conoscenza diretta:
|
||||
| Dovresti ammettere solo persone che conosci personalmente e di cui ti fidi
|
||||
.responsibility-item
|
||||
span.responsibility-icon 🛡️
|
||||
span.responsibility-text
|
||||
strong Fiducia:
|
||||
| L'ammissione si basa sulla fiducia reciproca nella piattaforma su #{nomeapp}
|
||||
.responsibility-item
|
||||
span.responsibility-icon 👥
|
||||
span.responsibility-text
|
||||
strong Supporto:
|
||||
| Come invitante, aiuterai il nuovo membro a integrarsi nella comunità
|
||||
|
||||
//- Info box
|
||||
.info-box
|
||||
p
|
||||
| ✓ Dopo l'ammissione, #{usernameInvitato} potrà completare il profilo
|
||||
p
|
||||
| ✓ Il facilitatore locale valuterà l'abilitazione all'uso dei RIS
|
||||
|
||||
//- Warning box
|
||||
.warning-box
|
||||
p
|
||||
strong ⚠️ Importante:
|
||||
| Se non conosci questa persona o hai dubbi sull'ammissione, ti consigliamo di non procedere. Puoi contattare il facilitatore locale per ulteriori informazioni o semplicemente non cliccare sul pulsante di conferma.
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché #{usernameInvitato} ha indicato te come invitante su #{nomeapp}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp} - Rete Italiana Scambi Orizzontali
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||
1
emails/defaultSite/reg_chiedi_ammettere_all_invitante/it/subject.pug
Executable file
1
emails/defaultSite/reg_chiedi_ammettere_all_invitante/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`🎉 ${name ? ', ' + name : username} chiede di essere ammesso su ${nomeapp} !`
|
||||
471
emails/defaultSite/reg_email_benvenuto_ammesso/it/html.pug
Executable file
471
emails/defaultSite/reg_email_benvenuto_ammesso/it/html.pug
Executable file
@@ -0,0 +1,471 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 80px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 16px;
|
||||
opacity: 0.95;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.intro-text strong {
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
|
||||
border-left: 4px solid #2E7D32;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-message h2 {
|
||||
color: #1B5E20;
|
||||
font-size: 20px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.welcome-message p {
|
||||
color: #2E7D32;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.credentials-box {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #2E7D32;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.credentials-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.credential-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.credential-row:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.credential-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
min-width: 120px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.credential-value {
|
||||
color: #1a1a1a;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.credential-value a {
|
||||
color: #2E7D32;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.credential-value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cta-buttons-wrapper {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 28px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(46, 125, 50, 0.3);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
max-width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(46, 125, 50, 0.4);
|
||||
}
|
||||
|
||||
.cta-button-secondary {
|
||||
background: linear-gradient(135deg, #388E3C 0%, #2E7D32 100%);
|
||||
box-shadow: 0 4px 12px rgba(56, 142, 60, 0.3);
|
||||
}
|
||||
|
||||
.cta-button-secondary:hover {
|
||||
box-shadow: 0 6px 16px rgba(56, 142, 60, 0.4);
|
||||
}
|
||||
|
||||
.cta-button-tertiary {
|
||||
background: linear-gradient(135deg, #66BB6A 0%, #4CAF50 100%);
|
||||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.cta-button-tertiary:hover {
|
||||
box-shadow: 0 6px 16px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.next-steps {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.next-steps-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #f8fdf9;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #4CAF50;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-content h3 {
|
||||
font-size: 16px;
|
||||
color: #1B5E20;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
border: 1px solid #A5D6A7;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #1B5E20;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.community-note {
|
||||
background: #fff3e0;
|
||||
border-left: 4px solid #FF9800;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.community-note p {
|
||||
color: #E65100;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 14px;
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.credentials-box {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.credential-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.credential-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.credential-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cta-buttons-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 24px;
|
||||
font-size: 15px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.step-content h3 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
.email-header
|
||||
- var baseimg = baseurl + '/';
|
||||
img.header-logo(src=baseimg+"images/logo.png" alt=nomeapp || 'Logo')
|
||||
h1 💚 #{name || username ? ',' : ''} Benvenuto/a nella piattaforma #{nomeapp} 💚 !
|
||||
|
||||
.email-body
|
||||
.welcome-message
|
||||
if ammessoUtente
|
||||
h2 🎉 Il tuo invitante #{nomeInvitante} ti ha ammesso!
|
||||
p Sei ora un membro attivo della Rete Italiana di Scambio Orizzontale
|
||||
p Inizia subito a scoprire beni, servizi e ospitalità nella tua comunità territoriale
|
||||
|
||||
.credentials-box
|
||||
.credentials-title 📋 I tuoi dati di accesso
|
||||
|
||||
if username
|
||||
.credential-row
|
||||
.credential-label Username:
|
||||
.credential-value #{username}
|
||||
|
||||
if emailto
|
||||
.credential-row
|
||||
.credential-label Email:
|
||||
.credential-value #{emailto}
|
||||
|
||||
if forgetpwd
|
||||
.credential-row
|
||||
.credential-label Password:
|
||||
.credential-value
|
||||
| (la password che hai inserito)
|
||||
br
|
||||
a(href=forgetpwd target="_blank") Hai dimenticato la password?
|
||||
|
||||
.cta-section
|
||||
.cta-title 🚀 Accedi e inizia il tuo viaggio
|
||||
.cta-subtitle Scegli come vuoi accedere alla piattaforma
|
||||
|
||||
if strlinksito
|
||||
.cta-buttons-wrapper
|
||||
a.cta-button(href=strlinksito target="_blank")
|
||||
span.button-icon 🌐
|
||||
| Accedi da Web
|
||||
|
||||
.email-footer
|
||||
.divider
|
||||
p 💚 Benvenuto/a nella famiglia #{nomeapp}!
|
||||
p(style="margin-top: 8px;") Hai ricevuto questa email perché sei stato/a ammesso/a nella community di #{nomeapp}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| © #{new Date().getFullYear()} #{nomeapp}
|
||||
1
emails/defaultSite/reg_email_benvenuto_ammesso/it/subject.pug
Executable file
1
emails/defaultSite/reg_email_benvenuto_ammesso/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`Benvenuto in ${nomeapp}`
|
||||
342
emails/defaultSite/reg_notifica_all_invitante/it/html.pug
Executable file
342
emails/defaultSite/reg_notifica_all_invitante/it/html.pug
Executable file
@@ -0,0 +1,342 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: #fff8dc;
|
||||
border-left: 4px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.highlight-box p {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.member-card {
|
||||
background: linear-gradient(135deg, #f8fdf8 0%, #e8f5e9 100%);
|
||||
border: 2px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member-card h3 {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: #558b2f;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.member-card .member-name {
|
||||
font-size: 24px;
|
||||
color: #1a1a1a;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.member-card .member-detail {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.member-card .member-detail strong {
|
||||
color: #558b2f;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0 0 8px 0;
|
||||
color: #2e7d32;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.tips-section h3 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 48px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(124, 179, 66, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.thank-you-box {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.thank-you-box p {
|
||||
font-size: 17px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.thank-you-box strong {
|
||||
color: #558b2f;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 32px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.member-card .member-name {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt=nomeapp+' - Rete Italiana Scambio orizzontale')
|
||||
h1 🎉 Il tuo invito è stato accettato!
|
||||
p.subtitle Un nuovo membro si è unito a #{nomeapp}
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
| Ciao <strong>#{nomeInvitante}</strong>,<br>
|
||||
| la persona che hai invitato, o che ha usato il tuo username come invitante, si è appena registrata su #{nomeapp}!
|
||||
|
||||
//- Card nuovo membro
|
||||
.member-card
|
||||
h3 👤 Nuovo Membro Registrato
|
||||
.member-name #{nomeInvitato}
|
||||
if emailInvitato
|
||||
.member-detail
|
||||
strong Email:
|
||||
a(href=`mailto:${emailInvitato}` style="color: #667eea; text-decoration: none;") #{emailInvitato}
|
||||
if usernameInvitato
|
||||
.member-detail
|
||||
strong Username:
|
||||
| #{usernameInvitato}
|
||||
|
||||
//- Ringraziamento
|
||||
.thank-you-box
|
||||
p
|
||||
| 🙏 <strong>Grazie per aver contribuito alla crescita di #{nomeapp}!</strong>
|
||||
p
|
||||
| Ogni nuovo membro rende la nostra comunità più forte e ricca di opportunità.
|
||||
| Il tuo invito ha aiutato #{nomeInvitato} a scoprire un nuovo modo di fare economia,
|
||||
| basato su fiducia, comunità e scambi solidali.
|
||||
|
||||
//- Info box
|
||||
.info-box
|
||||
p
|
||||
| ✓ #{nomeInvitato} ha ricevuto un'email di benvenuto con tutte le istruzioni
|
||||
|
||||
//- CTA
|
||||
.cta-section
|
||||
.cta-title Visita la Piattaforma
|
||||
a.cta-button(href=strlinksito target="_blank") Vai su #{nomeapp}
|
||||
|
||||
//- Highlight box
|
||||
.highlight-box
|
||||
p
|
||||
| 🌱 <strong>Costruiamo insieme un'economia più solidale!</strong><br>
|
||||
| Continua a condividere #{nomeapp} con persone di fiducia della tua comunità.
|
||||
| Più siamo, più scambi possibili ci sono per tutti!
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché hai invitato #{nomeInvitato} su #{nomeapp} oppure la persona ha usato il tuo username come invitante
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||
1
emails/defaultSite/reg_notifica_all_invitante/it/subject.pug
Executable file
1
emails/defaultSite/reg_notifica_all_invitante/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`🎉 Il tuo invito è stato accettato su ${nomeapp} da ${name ? name : username} !`
|
||||
404
emails/defaultSite/reg_resend_email_to_verifiyng/it/html.pug
Executable file
404
emails/defaultSite/reg_resend_email_to_verifiyng/it/html.pug
Executable file
@@ -0,0 +1,404 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 80px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #1976D2 0%, #1565C0 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 16px;
|
||||
opacity: 0.95;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.verification-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.intro-text strong {
|
||||
color: #1976D2;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
background: linear-gradient(135deg, #E3F2FD 0%, #BBDEFB 100%);
|
||||
border-left: 4px solid #1976D2;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-message h2 {
|
||||
color: #1565C0;
|
||||
font-size: 18px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-message p {
|
||||
color: #1976D2;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #FFF8E1;
|
||||
border-left: 4px solid #FFA000;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.warning-box p {
|
||||
color: #E65100;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.warning-box strong {
|
||||
color: #BF360C;
|
||||
}
|
||||
|
||||
.email-info-box {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #1976D2;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-info-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.email-address {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1976D2;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
margin: 24px 0;
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.cta-subtitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 18px 40px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #1976D2 0%, #1565C0 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.35);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(25, 118, 210, 0.45);
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.link-fallback {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-fallback p {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.link-fallback a {
|
||||
font-size: 12px;
|
||||
color: #1976D2;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.expiry-notice {
|
||||
background: linear-gradient(135deg, #FFEBEE 0%, #FFCDD2 100%);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
border: 1px solid #EF9A9A;
|
||||
}
|
||||
|
||||
.expiry-notice p {
|
||||
margin: 0;
|
||||
color: #C62828;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.expiry-notice strong {
|
||||
color: #B71C1C;
|
||||
}
|
||||
|
||||
.help-section {
|
||||
background: #FAFAFA;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.help-section h3 {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help-section p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin: 8px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.help-section a {
|
||||
color: #1976D2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.help-section a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.security-note {
|
||||
background: #E8F5E9;
|
||||
border-left: 4px solid #4CAF50;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.security-note p {
|
||||
font-size: 13px;
|
||||
color: #2E7D32;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.email-header p {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 14px;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.email-address {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 16px 32px;
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.link-fallback a {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
.email-header
|
||||
- var baseimg = baseurl + '/';
|
||||
h1 ✉️ Verifica il tuo indirizzo email
|
||||
p Conferma la tua email per continuare a usare #{nomeapp}
|
||||
|
||||
.email-body
|
||||
.intro-text
|
||||
| Ciao
|
||||
strong #{name || username}
|
||||
| ! 👋
|
||||
br
|
||||
| Abbiamo ricevuto una richiesta per verificare nuovamente il tuo indirizzo email.
|
||||
|
||||
.info-message
|
||||
h2 📧 Perché devo verificare di nuovo?
|
||||
p Potrebbe essere perché hai cambiato email, per motivi di sicurezza, o perché la verifica precedente è scaduta.
|
||||
p Questa procedura ci aiuta a mantenere sicuro il tuo account.
|
||||
|
||||
if emailto
|
||||
.email-info-box
|
||||
.email-info-title Email da verificare
|
||||
.email-address #{emailto}
|
||||
|
||||
.cta-section
|
||||
.cta-title Conferma il tuo indirizzo email
|
||||
.cta-subtitle Clicca il bottone qui sotto per completare la verifica
|
||||
|
||||
if verifyLink
|
||||
a.cta-button(href=verifyLink target="_blank")
|
||||
span.button-icon ✓
|
||||
| Verifica Email
|
||||
|
||||
.link-fallback
|
||||
p Se il bottone non funziona, copia e incolla questo link nel browser:
|
||||
a(href=verifyLink) #{verifyLink}
|
||||
|
||||
.expiry-notice
|
||||
p ⏰ Questo link scadrà tra
|
||||
strong 24 ore
|
||||
| .
|
||||
p Se non completi la verifica in tempo, dovrai richiedere un nuovo link.
|
||||
|
||||
.warning-box
|
||||
p ⚠️
|
||||
strong Non hai richiesto questa verifica?
|
||||
| Ignora questa email. Il tuo account resterà al sicuro.
|
||||
|
||||
.security-note
|
||||
p 🔒 Per la tua sicurezza: non condividere mai questo link con nessuno. Il team di #{nomeapp} non ti chiederà mai la password via email.
|
||||
|
||||
.help-section
|
||||
h3 Hai bisogno di aiuto?
|
||||
p Non riesci a verificare la tua email?
|
||||
if supportEmail
|
||||
p Contattaci a
|
||||
a(href="mailto:" + supportEmail) #{supportEmail}
|
||||
if strlinksito
|
||||
p Oppure visita la nostra
|
||||
a(href=strlinksito + '/supporto' target="_blank") pagina di supporto
|
||||
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché è stata richiesta una verifica per il tuo account su #{nomeapp}
|
||||
p(style="margin-top: 8px; font-size: 12px; color: #999;")
|
||||
| Se non hai fatto tu questa richiesta, puoi ignorare questa email.
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| © #{new Date().getFullYear()} #{nomeapp}
|
||||
1
emails/defaultSite/reg_resend_email_to_verifiyng/it/subject.pug
Executable file
1
emails/defaultSite/reg_resend_email_to_verifiyng/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
Verifica la tua Email - ${nomeapp}`
|
||||
@@ -55,8 +55,7 @@ html
|
||||
|
||||
|
||||
tr
|
||||
td(class="whitespace", height="10")
|
||||
p
|
||||
td
|
||||
|
||||
- var totalPrice = orders.totalPrice
|
||||
- var note = orders.note
|
||||
@@ -64,6 +63,7 @@ html
|
||||
|
||||
each rec in orders.items
|
||||
- var descr = rec.order.product.productInfo.name
|
||||
- var code = rec.order.product.productInfo.code
|
||||
- var img = dirimg + rec.order.product.productInfo.imagefile
|
||||
- var price = rec.order.price
|
||||
- var after_price = rec.order.after_price
|
||||
@@ -104,6 +104,9 @@ html
|
||||
tr
|
||||
td(class="sectionContentTitle boldhigh", valign="top")
|
||||
p #{descr}
|
||||
tr
|
||||
td(class="sectionContent", valign="top")
|
||||
p Codice: #{code}
|
||||
tr
|
||||
td(class="sectionContent", valign="top")
|
||||
p Prezzo: #{price} € #{after_price}
|
||||
|
||||
@@ -19,7 +19,6 @@ html
|
||||
}
|
||||
|
||||
body(yahoofix, style="background: #ffffff")
|
||||
- var baseimg = baseurl + '/statics/'
|
||||
span(id='body_style', style='display:block')
|
||||
table(cellpadding="10", cellspacing="0", width="600", align="center")
|
||||
tr
|
||||
|
||||
@@ -52,7 +52,7 @@ html
|
||||
if (miordconfirmed)
|
||||
p!= miordconfirmed
|
||||
else
|
||||
p Puoi pertanto venire a ritirarli direttamente in sede, negli orari che ti sono stati indicati.
|
||||
p Puoi venire a ritirarli direttamente in sede, negli orari che ti sono stati indicati.
|
||||
|
||||
p
|
||||
|
||||
@@ -62,6 +62,7 @@ html
|
||||
|
||||
each rec in orders.items
|
||||
- var descr = rec.order.product.productInfo.name
|
||||
- var code = rec.order.product.productInfo.code
|
||||
- var img = dirimg + rec.order.product.productInfo.imagefile
|
||||
- var price = rec.order.product.price
|
||||
- var after_price = rec.order.product.after_price
|
||||
@@ -101,6 +102,9 @@ html
|
||||
tr
|
||||
td(class="sectionContentTitle boldhigh", valign="top")
|
||||
p #{descr}
|
||||
tr
|
||||
td(class="sectionContent", valign="top")
|
||||
p Codice: #{code}
|
||||
tr
|
||||
td(class="sectionContent", valign="top")
|
||||
p Prezzo: #{price} € #{after_price}
|
||||
|
||||
422
emails/invitaamico/it/html.pug
Normal file
422
emails/invitaamico/it/html.pug
Normal file
@@ -0,0 +1,422 @@
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: #fff8dc;
|
||||
border-left: 4px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.highlight-box p {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.riso-info {
|
||||
background: #fef9f3;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #7cb342;
|
||||
}
|
||||
|
||||
.riso-info h3 {
|
||||
font-size: 16px;
|
||||
color: #558b2f;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.riso-info p {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.benefits-list {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.benefits-list h3 {
|
||||
font-size: 17px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.benefit-text {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.values-section {
|
||||
background: linear-gradient(135deg, #fff8dc 0%, #fef9f3 100%);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.values-section h3 {
|
||||
font-size: 16px;
|
||||
color: #558b2f;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.value-item strong {
|
||||
color: #558b2f;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 48px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(210, 105, 30, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #2e7d32;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.telegram-box {
|
||||
background: #e3f2fd;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin: 16px 0;
|
||||
text-align: center;
|
||||
border: 1px solid #64b5f6;
|
||||
}
|
||||
|
||||
.telegram-box p {
|
||||
margin: 0;
|
||||
color: #1976d2;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.telegram-box strong {
|
||||
color: #0d47a1;
|
||||
}
|
||||
|
||||
.link-box {
|
||||
background: #fff3e0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin: 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-box a {
|
||||
color: #558b2f;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 32px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.benefit-item, .value-item {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt='RISO - Rete Italiana Scambio orizzontale')
|
||||
h1 Sei stato invitato ad unirti a #{nomeapp}!
|
||||
p.subtitle Rete Italiana Scambio orizzontale
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
.intro-text
|
||||
if usernameInvitante
|
||||
| <strong>#{usernameInvitante}</strong> ti ha invitato a far parte della comunità RISO, una rete di persone che credono in un'economia basata su fiducia, comunità e scambi solidali.
|
||||
else
|
||||
| Hai ricevuto un invito speciale per unirti alla comunità RISO, una rete di persone che credono in un'economia basata su fiducia, comunità e scambi solidali.
|
||||
|
||||
|
||||
.cta-section
|
||||
.cta-title Unisciti alla Comunità RISO 🍚💚☀️
|
||||
a.cta-button(href=linkRegistrazione target="_blank") Registrati Ora
|
||||
|
||||
//- Messaggio personalizzato
|
||||
if messaggioPersonalizzato
|
||||
.highlight-box
|
||||
p
|
||||
| 💌 <strong>Messaggio da #{usernameInvitante}:</strong><br>
|
||||
| "#{messaggioPersonalizzato}"
|
||||
|
||||
//- Cos'è RISO
|
||||
.riso-info
|
||||
h3 🍚 Cos'è il Progetto RISO?
|
||||
p
|
||||
| RISO è una <strong>rete di comunità territoriali</strong> in tutta Italia dove puoi scambiare beni, servizi e ospitalità con altre persone, usando il <strong>baratto, il dono</strong>, oppure in <strong>RIS</strong>.
|
||||
p
|
||||
| Non è un marketplace commerciale: è una <strong>comunità basata su fiducia e relazioni</strong> reali tra persone che si conoscono e si incontrano.
|
||||
|
||||
//- Benefici
|
||||
.benefits-list
|
||||
h3 ✨ Cosa puoi fare con RISO
|
||||
.benefit-item
|
||||
span.benefit-icon 🔄
|
||||
span.benefit-text Scambiare beni, servizi e ospitalità con baratto, dono, RIS o Euro
|
||||
.benefit-item
|
||||
span.benefit-icon 🤝
|
||||
span.benefit-text Connetterti con la tua comunità territoriale locale (circuito provinciale)
|
||||
.benefit-item
|
||||
span.benefit-icon 💰
|
||||
span.benefit-text Usare i RIS, uno strumento di scambio che parte da zero (nessun debito iniziale!)
|
||||
.benefit-item
|
||||
span.benefit-icon 🌱
|
||||
span.benefit-text Ridurre la dipendenza dall'economia tradizionale e vivere in modo più sostenibile
|
||||
.benefit-item
|
||||
span.benefit-icon 📱
|
||||
span.benefit-text Creare annunci di ciò che offri o cerchi direttamente dall'app
|
||||
.benefit-item
|
||||
span.benefit-icon 🎯
|
||||
span.benefit-text Partecipare a mercatini ed eventi locali organizzati dalla tua comunità
|
||||
|
||||
//- Valori
|
||||
.values-section
|
||||
h3 ⭐ I Valori della Comunità RISO
|
||||
.value-item
|
||||
span.benefit-icon 🤝
|
||||
| <strong>Comunità:</strong> Creiamo legami autentici tra persone
|
||||
.value-item
|
||||
span.benefit-icon ✅
|
||||
| <strong>Fiducia:</strong> Base del nostro sistema di scambio
|
||||
.value-item
|
||||
span.benefit-icon 🚫
|
||||
| <strong>Non speculativo:</strong> il RIS non è una moneta complementare, è un Sistema di Credito Naturale. Non serve a 'comprare' cose, ma a <strong>misurare e scambiare l'energia umana</strong>.
|
||||
.value-item
|
||||
span.benefit-icon 🌍
|
||||
| <strong>Sostenibilità:</strong> Promuoviamo stili di vita sani e naturali
|
||||
.value-item
|
||||
span.benefit-icon 👁️
|
||||
| <strong>Trasparenza:</strong> Tutte le transazioni in RIS sono visibili alla comunità
|
||||
|
||||
//- Info RIS
|
||||
.riso-info
|
||||
h3 💡 Come funzionano i RIS?
|
||||
p
|
||||
| • Non esiste una banca centrale che li crea<br>
|
||||
| • Ogni utente parte da <strong>0 RIS</strong><br>
|
||||
| • Quando ricevi vai in positivo (+), quando spendi vai in negativo (-)<br>
|
||||
| • I facilitatori locali ti abilitano all'uso in base a conoscenza e fiducia<br>
|
||||
| • I circuiti provinciali hanno bilancio sempre a zero (sostenibilità garantita!)
|
||||
|
||||
//- CTA
|
||||
.cta-section
|
||||
.cta-title Unisciti alla Comunità RISO 🍚💚☀️
|
||||
a.cta-button(href=linkRegistrazione target="_blank") Registrati Ora
|
||||
|
||||
//- Info pratica
|
||||
.info-box
|
||||
p
|
||||
| ✓ <strong>Piattaforma Libera e Gratuita</strong><br>
|
||||
| ✓ <strong>Registrazione ad invito</strong> · Necessario username Telegram<br>
|
||||
| ✓ <strong>App web installabile</strong> · Non serve store, installa da browser
|
||||
|
||||
//- Telegram
|
||||
.telegram-box
|
||||
p
|
||||
| 📲 <strong>Unisciti al gruppo Telegram della tua provincia!</strong><br>
|
||||
| Cerca "<strong>Progetto RISO</strong>" su Telegram e trova il gruppo nel messaggio fissato.<br>
|
||||
| Qui organizziamo incontri reali e mercatini. 🍚
|
||||
|
||||
//- Link utile
|
||||
.link-box
|
||||
p 📚 Vuoi saperne di più sul sistema RIS?
|
||||
a(href="https://sicrenacc.info/" target="_blank") Leggi il libro Si.cre.na.C.C →
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché #{usernameInvitante || 'un membro della comunità'} ti ha invitato su #{nomeapp}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp} - Rete Italiana Scambio orizzontale
|
||||
p(style="margin-top: 8px; font-size: 11px; color: #999;")
|
||||
| Se non sei interessato, puoi semplicemente ignorare questa email.
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||
1
emails/invitaamico/it/subject.pug
Executable file
1
emails/invitaamico/it/subject.pug
Executable file
@@ -0,0 +1 @@
|
||||
=`${usernameInvitante} ti ha invitato su ${nomeapp}`
|
||||
@@ -1,29 +0,0 @@
|
||||
p Ciao #{name},<br>Grazie per aver inviato la tua richiesta di adesione come Sostenitore di Arcadei !
|
||||
p Cordiali Saluti<br>Arcadei
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Richiesta di Adesione ad Arcadei (${name} ${surname})`
|
||||
@@ -156,9 +156,6 @@ html
|
||||
}
|
||||
|
||||
.whitespace {
|
||||
line-height:0;
|
||||
font-size:0;
|
||||
height:20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
@@ -179,7 +176,7 @@ html
|
||||
tr
|
||||
td.logoContainer
|
||||
a(href=baseurl)
|
||||
img.logo(src=baseurl+"/public/images/logo.png", alt="Logo")
|
||||
img.logo(src=baseurl+"/images/logo.png", alt="Logo")
|
||||
|
||||
if dataemail.templ.testoheadermail_out
|
||||
tr
|
||||
|
||||
@@ -1,37 +1,363 @@
|
||||
p Benvenuto #{name} nel portale di #{nomeapp}!
|
||||
p I tuoi dati di accesso da ricordare sono:
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span hai dimenticato la Password? :
|
||||
strong <a href=#{forgetpwd} target="_blank">Trovala qui</a><br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p Per confermare la registrazione clicca sul bottone, oppure su questo link: #{strlinkreg}
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Verifica Registrazione</a>
|
||||
|
||||
p Potrai cosi' accedere al sito digitando i tuoi dati di accesso.
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 48px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(124, 179, 66, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.alternative-link {
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
color: #777;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.alternative-link a {
|
||||
color: #558b2f;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.verification-process {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 24px 20px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.process-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.process-title .icon {
|
||||
font-size: 24px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.process-steps {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.process-step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.process-step.active {
|
||||
border-left-color: #7cb342;
|
||||
background: linear-gradient(135deg, #f8fdf8 0%, #e8f5e9 100%);
|
||||
}
|
||||
|
||||
.step-badge {
|
||||
min-width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #e0e0e0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.process-step.active .step-badge {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-content h4 {
|
||||
font-size: 16px;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-note {
|
||||
background: #fff8dc;
|
||||
border-left: 4px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.info-note p {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-note strong {
|
||||
color: #558b2f;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 32px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.verification-process {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.process-step {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.step-badge {
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
font-size: 16px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.step-content h4 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.step-content p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
- var baseimg = baseurl + '/';
|
||||
img.header-logo(src=baseimg+"images/logo.png" alt=nomeapp || 'RISO')
|
||||
h1 Benvenuto/a #{name ? name : username} su #{nomeapp}!
|
||||
p.subtitle Rete Italiana Scambio Orizzontale
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
if !verified_email
|
||||
.intro-text
|
||||
| Per iniziare il tuo percorso in RISO, verifica il tuo indirizzo email cliccando sul pulsante qui sotto.
|
||||
|
||||
.cta-section
|
||||
.cta-title 🔐 Verifica il tuo account
|
||||
|
||||
if strlinkreg
|
||||
a.cta-button(href=strlinkreg target="_blank")
|
||||
span.button-icon ✓
|
||||
| Verifica Email
|
||||
|
||||
.alternative-link
|
||||
| Oppure copia e incolla questo link nel tuo browser:
|
||||
br
|
||||
a(href=strlinkreg target="_blank") #{strlinkreg}
|
||||
|
||||
//- Processo di verifica
|
||||
.verification-process
|
||||
.process-title
|
||||
span.icon 🌱
|
||||
| Il tuo percorso di ingresso in RISO
|
||||
|
||||
.process-steps
|
||||
.process-step
|
||||
.step-badge 1
|
||||
.step-content
|
||||
h4 ✓ Verifica la tua email
|
||||
p Clicca sul pulsante sopra per confermare il tuo indirizzo email.
|
||||
|
||||
.process-step.active
|
||||
.step-badge 2
|
||||
.step-content
|
||||
h4 ⏳ In attesa di ammissione
|
||||
p Il tuo invitante vedrà la tua richiesta. Riceverai un'email appena sarai ammesso!
|
||||
|
||||
.process-step
|
||||
.step-badge 3
|
||||
.step-content
|
||||
h4 🎯 Completa il tuo profilo
|
||||
p Una volta ammesso, potrai completare il tuo profilo e iniziare a fare parte della comunità RISO.
|
||||
|
||||
.process-step
|
||||
.step-badge 4
|
||||
.step-content
|
||||
h4 🍚 Inizia a scambiare
|
||||
p Crea annunci, scambia beni e servizi, e partecipa agli eventi della tua comunità locale!
|
||||
|
||||
.info-note
|
||||
p
|
||||
strong 💡 Suggerimento:
|
||||
| Se dovessero passare più di 24 ore senza ammissione, contatta il tuo invitante per ricordargli di approvarti.
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché ti sei registrato su #{nomeapp || 'RISO'}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp || 'RISO'} - Rete Italiana Scambi Orizzontali
|
||||
p(style="margin-top: 8px; font-size: 11px; color: #999;")
|
||||
| Se non hai richiesto questa registrazione, puoi semplicemente ignorare questa email.
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||
@@ -1,39 +0,0 @@
|
||||
p Welcome #{name} to the portal #{nomeapp}!
|
||||
p Your access data to remember are:
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span Forgot your Password? :
|
||||
strong <a href=#{forgetpwd} target="_blank">Find it here</a><br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p To confirm the registration click on the button:
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Verify Registration</a>
|
||||
|
||||
|
||||
p You can then access your personal area by clicking on LOGIN and filling in all the STEPS required by :
|
||||
strong "Step by Step Guide" !<br>
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Confirm Registration to ${nomeapp}`
|
||||
@@ -1,38 +0,0 @@
|
||||
p ¡Bienvenido #{name} al portal #{nomeapp}!
|
||||
p Sus datos de acceso a recordar son:
|
||||
Nombre Usuario:
|
||||
strong #{username}<br>
|
||||
span ¿Olvidaste tu contraseña? :
|
||||
strong <a href=#{forgetpwd} target="_blank">Encuéntrelo aquí</a><br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p Para confirmar el registro haz clic en el botón, o aquì #{strlinkreg}:
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Verificar registro</a>
|
||||
|
||||
p A continuación, puede acceder a su área personal haciendo clic en INICIAR SESIÓN y rellenando todos los PASOS requeridos por :
|
||||
strong "Guía paso a paso"!<br>
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Confirmar registro a ${nomeapp}`
|
||||
@@ -1,43 +0,0 @@
|
||||
p Bienvenue #{name} sur le portail de #{nomeapp} !
|
||||
p Vos identifiants de connexion à retenir sont :
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span avez-vous oublié votre mot de passe ? :
|
||||
strong <a href=#{forgetpwd} target="_blank">Essayez ici</a><br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p Cliquez sur le bouton pour confirmer votre inscription ou cliquez ici #{strlinkreg}:
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Vérifier l'enregistrement</a>
|
||||
|
||||
p Vous pouvez ensuite accéder à votre espace personnel en entrant votre nom d'utilisateur et votre mot de passe et en cliquant sur
|
||||
strong ENTREZ<br>
|
||||
|
||||
p Donc, une fois que vous avez vérifié votre inscription, connectez-vous au site et
|
||||
strong COMPLÉTER TOUTES LES ÉTAPES
|
||||
de la portée du "Guide pas-à-pas" via ce lien:<br>
|
||||
span <a href=#{strlinksito} target="_blank">#{strlinksito}</a>
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Confirmer l'inscription à ${nomeapp}`
|
||||
@@ -1,43 +0,0 @@
|
||||
p Benvenuto #{name} nel portale di #{nomeapp}!
|
||||
p I tuoi dati di accesso da ricordare sono:
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span hai dimenticato la Password? :
|
||||
strong <a href=#{forgetpwd} target="_blank">Trovala qui</a><br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p Per confermare la registrazione clicca sul bottone, oppure <a href=#{strlinkreg} target="_blank">CLICCA QUI</a>
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Verifica Registrazione</a>
|
||||
|
||||
p Potrai cosi' accedere alla tua area personale inserendo lo username e la password e cliccando su
|
||||
strong ACCEDI <br>
|
||||
|
||||
p Pertanto una volta Verificata la Registrazione, accedi al sito e
|
||||
strong COMPLETA TUTTI I PASSI
|
||||
span della "Guida Passo Passo" tramite questo Link:<br>
|
||||
span <a href=#{strlinksito} target="_blank">#{strlinksito}</a>
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Confermare la Registrazione a ${nomeapp}`
|
||||
@@ -1,43 +0,0 @@
|
||||
p Bem-vindo #{name} ao portal de #{nomeapp}!
|
||||
p Os seus dados de login a lembrar são:
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span você esqueceu sua senha? :
|
||||
strong <a href=##{forgetpwd} target="_blank">Encontre-o aqui</a>>br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p Clique no botão para confirmar o seu registo ou clique aqui #{strlinkreg}:
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Verificação de registro</a>
|
||||
|
||||
p Você pode então acessar sua área pessoal digitando seu nome de usuário e senha e clicando em
|
||||
strong ENTRAR <br>
|
||||
|
||||
p Então, uma vez verificada a sua inscrição, inicie a sessão no site e
|
||||
strong COMPLETA TODOS OS PASSOS
|
||||
span do "Guia Passo a Passo" através deste link:<br>
|
||||
span <a href=#{strlinksito} target="_blank">#{strlinksito}</a>
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Confirme o registo em ${nomeapp}`
|
||||
@@ -1,44 +0,0 @@
|
||||
p Dobrodošli #{name} na portalu #{nomeapp}!
|
||||
p Vaši podatki za dostop so:
|
||||
span Username:
|
||||
strong #{username}<br>
|
||||
span ste pozabili geslo? :
|
||||
strong <a href=#{forgetpwd} target="_blank">Poiščite tukaj</a><br>
|
||||
span Email:
|
||||
strong #{emailto}<br>
|
||||
p Za potrditev registracije kliknite na gumb ali kliknite tukaj: #{strlinkreg}
|
||||
div.divbtn <a href=#{strlinkreg} target="_blank">
|
||||
button.btn.btn-lg Preveri registracijo</a>
|
||||
|
||||
p Tako boste lahko dostopali do svojega osebnega območja, tako da vnesete uporabniško ime in geslo in kliknete
|
||||
strong PRIJAVA <br>
|
||||
|
||||
|
||||
p Potem ko je registracija preverjena, obiščite spletno mesto in
|
||||
strong IZPOLNITE VSE KORAKE
|
||||
span "Vodnika po korakih" prek te povezave:<br>
|
||||
span <a href=#{strlinksito} target="_blank">#{strlinksito}</a>
|
||||
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: #027be3 !important;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
=`Potrdi registracijo za ${nomeapp}`
|
||||
@@ -1,26 +1,128 @@
|
||||
p Ciao #{name},
|
||||
p #{nomeapp} recentemente ha ricevuto una richiesta per una password dimenticata.
|
||||
|
||||
p Per cambiare la tua password di #{nomeapp}
|
||||
p <a href=#{strlinksetpassword} target="_blank">Clicca QUI</a>
|
||||
span Oppure inserisci il codice
|
||||
span.grande #{tokenforgot_code}
|
||||
span sulla APP
|
||||
p
|
||||
p P.S: Se non sei stato tu a richiedere questo cambiamento, non hai bisogno di fare niente.
|
||||
p Questo link scadrà tra 4 ore.<br>
|
||||
|
||||
p Cordiali Saluti
|
||||
p Supporto #{nomeapp}
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.grande {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
title Recupera Password - #{nomeapp}
|
||||
|
||||
body(style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background-color: #f5f7fa;")
|
||||
|
||||
// Container Principale
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 20px;")
|
||||
tr
|
||||
td(align="center")
|
||||
|
||||
// Card Email
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="600" style="max-width: 600px; background: #ffffff; border-radius: 24px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16); overflow: hidden;")
|
||||
|
||||
// Header con Icona
|
||||
tr
|
||||
td(style="background: linear-gradient(to bottom, rgba(103, 126, 234, 0.08), transparent); padding: 40px 32px 32px; text-align: center; border-bottom: 1px solid rgba(0, 0, 0, 0.06);")
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%")
|
||||
tr
|
||||
td(align="center")
|
||||
// Icona
|
||||
div(style="width: 100px; height: 100px; margin: 0 auto 20px; background: linear-gradient(135deg, rgba(25, 118, 210, 0.15), rgba(38, 166, 154, 0.15)); border-radius: 50%; display: flex; align-items: center; justify-content: center;")
|
||||
span(style="font-size: 48px; color: #1976d2;") 🔐
|
||||
|
||||
// Titolo
|
||||
h1(style="margin: 0 0 12px; font-size: 28px; font-weight: 600; color: #1a1a1a; line-height: 1.3;") Recupera la tua Password
|
||||
p(style="margin: 0; font-size: 16px; color: #666; line-height: 1.5;")
|
||||
| Ciao
|
||||
strong(style="color: #333;") #{name}
|
||||
| , abbiamo ricevuto una richiesta per reimpostare la tua password
|
||||
|
||||
// Contenuto Principale
|
||||
tr
|
||||
td(style="padding: 32px;")
|
||||
|
||||
// Messaggio
|
||||
p(style="margin: 0 0 24px; font-size: 16px; color: #333; line-height: 1.6;")
|
||||
| Per cambiare la tua password di
|
||||
strong #{nomeapp}
|
||||
| , puoi utilizzare uno dei seguenti metodi:
|
||||
|
||||
// Metodo 1: Bottone Link
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 32px;")
|
||||
tr
|
||||
td(style="background: linear-gradient(to right, rgba(25, 118, 210, 0.08), transparent); padding: 24px; border-radius: 16px; border: 1px solid rgba(25, 118, 210, 0.15);")
|
||||
p(style="margin: 0 0 16px; font-size: 14px; font-weight: 600; color: #1976d2; text-transform: uppercase; letter-spacing: 0.5px;") ✨ Metodo 1: Link Diretto
|
||||
|
||||
// Bottone CTA
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%")
|
||||
tr
|
||||
td(align="center" style="padding: 8px 0;")
|
||||
a(href=strlinksetpassword target="_blank" style="display: inline-block; padding: 16px 48px; background: linear-gradient(135deg, #1976d2, #42a5f5); color: #ffffff; text-decoration: none; border-radius: 12px; font-size: 16px; font-weight: 600; box-shadow: 0 4px 16px rgba(25, 118, 210, 0.3);")
|
||||
| 🔓 Reimposta Password
|
||||
|
||||
p(style="margin: 16px 0 0; font-size: 13px; color: #666; text-align: center;")
|
||||
| Il link è valido per
|
||||
strong(style="color: #f2c037;") 4 ore
|
||||
|
||||
// Divisore
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 24px 0;")
|
||||
tr
|
||||
td(style="position: relative; text-align: center;")
|
||||
div(style="position: relative; height: 1px; background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.12), transparent);")
|
||||
span(style="position: relative; top: -12px; display: inline-block; padding: 4px 16px; background: #ffffff; color: #666; font-size: 14px; font-weight: 500; border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.08);") oppure
|
||||
|
||||
// Metodo 2: Codice
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-bottom: 24px;")
|
||||
tr
|
||||
td(style="background: linear-gradient(to right, rgba(33, 186, 69, 0.08), transparent); padding: 24px; border-radius: 16px; border: 1px solid rgba(33, 186, 69, 0.15);")
|
||||
p(style="margin: 0 0 16px; font-size: 14px; font-weight: 600; color: #21ba45; text-transform: uppercase; letter-spacing: 0.5px;") 🔢 Metodo 2: Codice di Verifica
|
||||
|
||||
p(style="margin: 0 0 12px; font-size: 15px; color: #333;") Inserisci questo codice nell'app:
|
||||
|
||||
// Codice Box
|
||||
div(style="background: #ffffff; border: 2px dashed #21ba45; border-radius: 12px; padding: 20px; text-align: center; margin: 16px 0;")
|
||||
div(style="font-size: 36px; font-weight: 700; color: #1976d2; letter-spacing: 8px; font-family: 'Courier New', monospace;") #{tokenforgot_code}
|
||||
|
||||
p(style="margin: 12px 0 0; font-size: 13px; color: #666; text-align: center;")
|
||||
| Copia e incolla il codice nell'app per confermare
|
||||
|
||||
// Warning Box
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%")
|
||||
tr
|
||||
td(style="background: rgba(242, 192, 55, 0.1); padding: 16px; border-radius: 12px; border-left: 4px solid #f2c037;")
|
||||
p(style="margin: 0; font-size: 14px; color: #666; line-height: 1.6;")
|
||||
strong(style="color: #333;") ⚠️ Importante:
|
||||
br
|
||||
| Se non sei stato tu a richiedere questo cambiamento, ignora questa email. La tua password rimarrà invariata.
|
||||
|
||||
// Footer
|
||||
tr
|
||||
td(style="background: linear-gradient(to top, rgba(0, 0, 0, 0.02), transparent); padding: 32px; text-align: center; border-top: 1px solid rgba(0, 0, 0, 0.06);")
|
||||
p(style="margin: 0 0 8px; font-size: 15px; color: #333; font-weight: 600;") Cordiali Saluti
|
||||
p(style="margin: 0 0 20px; font-size: 15px; color: #1976d2; font-weight: 600;")
|
||||
| Supporto #{nomeapp}
|
||||
|
||||
// Info Footer
|
||||
p(style="margin: 0; font-size: 13px; color: #999; line-height: 1.6;")
|
||||
| Questa è una email automatica, per favore non rispondere.
|
||||
br
|
||||
| © #{new Date().getFullYear()} #{nomeapp}. Tutti i diritti riservati.
|
||||
|
||||
// Versione Mobile (per client email che non supportano media queries)
|
||||
table(role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 600px; margin: 0 auto; display: none;")
|
||||
tr
|
||||
td
|
||||
style(type="text/css").
|
||||
@media only screen and (max-width: 600px) {
|
||||
table[class="mobile-responsive"] {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td[class="mobile-padding"] {
|
||||
padding: 20px 16px !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
.code-box {
|
||||
font-size: 28px !important;
|
||||
letter-spacing: 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,307 @@
|
||||
p Ciao #{name},
|
||||
p Hai ricevuto
|
||||
strong #{qty} #{symbol}
|
||||
if groupDestoContoCom
|
||||
| sul conto
|
||||
strong #{groupDestoContoCom}
|
||||
span da parte di #{mittente} in data #{transactionDate} sul
|
||||
strong #{nomecircuito} !
|
||||
if causalDest
|
||||
p <br>
|
||||
p Descrizione: #{causalDest}
|
||||
if causale
|
||||
p <br>
|
||||
p Commento di #{mittente}: #{causale}
|
||||
p <br>
|
||||
p Apri
|
||||
strong <a href=#{strlinksito} target="_blank">#{nomeapp}</a>
|
||||
span per vedere il tuo nuovo saldo.
|
||||
p <br>
|
||||
p Cordiali Saluti
|
||||
p Supporto #{nomeapp}
|
||||
|
||||
style(type="text/css").
|
||||
html, body {
|
||||
padding: 0;
|
||||
doctype html
|
||||
html(lang="it")
|
||||
head
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
style(type="text/css").
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 4px; /* Imposta il margine a 0 per i paragrafi */
|
||||
}
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 120px;
|
||||
height: auto;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
color: white;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.email-header .subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 17px;
|
||||
opacity: 0.95;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.money-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.transaction-card {
|
||||
background: linear-gradient(135deg, #f8fdf8 0%, #e8f5e9 100%);
|
||||
border: 2px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.transaction-amount {
|
||||
font-size: 42px;
|
||||
color: #558b2f;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.transaction-label {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: #558b2f;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.transaction-detail {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
margin: 8px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.transaction-detail strong {
|
||||
color: #558b2f;
|
||||
}
|
||||
|
||||
.divider-line {
|
||||
height: 1px;
|
||||
background: #c8e6c9;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: #fff8dc;
|
||||
border-left: 4px solid #7cb342;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.highlight-box p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.highlight-box .label {
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
color: #558b2f;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 16px 48px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7cb342 0%, #558b2f 100%);
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 12px rgba(124, 179, 66, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e8f5e9;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #2e7d32;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
color: #777;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #e0e0e0, transparent);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.email-header {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.email-header h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.money-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.transaction-amount {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 14px 32px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.grande {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
body
|
||||
.email-container
|
||||
//- Header
|
||||
.email-header
|
||||
img.header-logo(src=baseurl+'/images/logo.png' alt='RISO - Rete Italiana Scambio Orizzontale')
|
||||
h1 Ciao <strong>#{username}</strong>, Hai ricevuto dei #{symbol}!
|
||||
p.subtitle Nuova transazione sul #{nomecircuito}
|
||||
|
||||
//- Body
|
||||
.email-body
|
||||
//- Intro
|
||||
|
||||
//- Transaction card
|
||||
.transaction-card
|
||||
.transaction-label Importo Ricevuto
|
||||
.transaction-amount +#{qty} #{symbol}
|
||||
|
||||
.divider-line
|
||||
|
||||
.transaction-detail(style="font-size: 18px; margin-top: 12px;")
|
||||
strong Nuovo Saldo:
|
||||
span(style="color: #558b2f; font-weight: 700; font-size: 20px;") #{saldoAttuale} #{symbol}
|
||||
|
||||
.transaction-detail
|
||||
strong Da:
|
||||
| #{mittente}
|
||||
|
||||
.transaction-detail
|
||||
strong Data:
|
||||
| #{transactionDate}
|
||||
|
||||
if groupDestoContoCom
|
||||
.transaction-detail
|
||||
strong Conto:
|
||||
| #{groupDestoContoCom}
|
||||
|
||||
.transaction-detail
|
||||
strong Circuito:
|
||||
| #{nomecircuito}
|
||||
|
||||
//- Descrizione
|
||||
if causalDest
|
||||
.highlight-box
|
||||
.label 📝 Descrizione
|
||||
p #{causalDest}
|
||||
|
||||
//- Commento mittente
|
||||
if causale
|
||||
.highlight-box
|
||||
.label 💬 Commento di #{mittente}
|
||||
p "#{causale}"
|
||||
|
||||
//- Info box
|
||||
.info-box
|
||||
p
|
||||
| ✓ La transazione è stata registrata con successo<br>
|
||||
| ✓ Il tuo saldo è stato aggiornato
|
||||
|
||||
//- CTA
|
||||
.cta-section
|
||||
.cta-title Accedi alla App
|
||||
a.cta-button(href=strlinksito target="_blank") Apri #{nomeapp}
|
||||
|
||||
//- Footer
|
||||
.email-footer
|
||||
.divider
|
||||
p Hai ricevuto questa email perché hai ricevuto una transazione su #{nomeapp}
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| #{new Date().getFullYear()} #{nomeapp} - Rete Italiana Scambi Orizzontali
|
||||
p(style="margin-top: 12px; font-size: 12px;")
|
||||
| 🍚 Comunità · Fiducia · Scambi Solidali · Sostenibilità
|
||||
78
filelog.txt
78
filelog.txt
@@ -4,3 +4,81 @@ Dom 02/03 ORE 16:32: USER [surya1977]: ciao
|
||||
Lun 10/03 ORE 15:52: USER [surya1977]: ciao
|
||||
|
||||
Lun 10/03 ORE 15:56: USER [surya1977]: ciao
|
||||
|
||||
Lun 07/07 ORE 10:45: USER [surya1977]: ciao
|
||||
|
||||
Gio 09/10 ORE 20:42: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 16:11: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 16:32: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 16:36: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 16:43: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 16:53: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 17:29: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 17:30: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 17:30: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 18:01: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 18:15: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 19:05: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 19:36: USER [surya1977]: ciao
|
||||
|
||||
Sab 11/10 ORE 19:38: USER [surya1977]: ciao
|
||||
|
||||
Lun 27/10 ORE 13:35: USER [surya1977]: ciao
|
||||
|
||||
Gio 06/11 ORE 11:46: USER [SuryaSecondo]: ciao
|
||||
|
||||
Gio 06/11 ORE 11:46: USER [SuryaSecondo]: chi sono
|
||||
|
||||
Gio 06/11 ORE 11:47: USER [SuryaSecondo]: exitontelegram
|
||||
|
||||
Sab 08/11 ORE 20:24: USER [SuryaSecondo]: ciao
|
||||
|
||||
Sab 08/11 ORE 20:24: USER [surya1977]: /start 4b989bfb3d9af38551a8459ddf4a902c82e12017600c29bc050cc56fb835a881
|
||||
|
||||
Sab 08/11 ORE 20:24: USER [SuryaSecondo]: /start 4b989bfb3d9af38551a8459ddf4a902c82e12017600c29bc050cc56fb835a881
|
||||
|
||||
Sab 08/11 ORE 20:24: USER [SuryaSecondo]: /start 4b989bfb3d9af38551a8459ddf4a902c82e12017600c29bc050cc56fb835a881
|
||||
|
||||
Sab 08/11 ORE 21:42: USER [surya4]: ciao
|
||||
|
||||
Dom 09/11 ORE 17:57: USER [surya4]: ciao
|
||||
|
||||
Dom 09/11 ORE 18:36: USER [surya4]: ciao
|
||||
|
||||
Dom 09/11 ORE 18:43: USER [surya4]: ciao
|
||||
|
||||
Gio 20/11 ORE 19:12: USER [surya4]: ciao
|
||||
|
||||
Gio 20/11 ORE 19:15: USER [surya4]: ok
|
||||
|
||||
Gio 20/11 ORE 19:41: USER [surya4]: ok
|
||||
|
||||
Gio 20/11 ORE 19:52: USER [surya4]: ciao
|
||||
|
||||
Gio 20/11 ORE 20:55: USER [surya1977]: ciao
|
||||
|
||||
Gio 20/11 ORE 21:15: USER [surya1977]: ciao
|
||||
|
||||
Gio 20/11 ORE 21:24: USER [surya5]: ciao
|
||||
|
||||
Sab 22/11 ORE 23:30: USER [surya1977]: ciao
|
||||
|
||||
Dom 23/11 ORE 00:02: USER [perseo5]: ciao
|
||||
|
||||
Dom 23/11 ORE 00:22: USER [perseo7]: ciao
|
||||
|
||||
Lun 24/11 ORE 13:56: USER [perseo7]: ciao
|
||||
|
||||
Lun 24/11 ORE 13:57: USER [perseo7]: chi sono
|
||||
|
||||
@@ -835,4 +835,30 @@ https://t.me/riso_canale/739
|
||||
In attesa di riscontri, salutiamo! 🍚💚
|
||||
Il gruppo dei Facilitatori Territoriali RISO
|
||||
Mer 24/04 ORE 22:02: 🤖: Da Sùrya (Paolo) (paoloar77):
|
||||
✅ la regolarizzazione può avv
|
||||
✅ la regolarizzazione può avv
|
||||
Lun 07/07 ORE 10:50: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ provatest7 è stato Ammesso correttamente (da surya1977)!
|
||||
Gio 09/10 ORE 20:45: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ prova123 è stato Ammesso correttamente (da surya1977)!
|
||||
Gio 06/11 ORE 11:47: 🤖: Da Surya Ar (SuryaSecondo):
|
||||
exitontelegram
|
||||
Gio 06/11 ORE 11:51: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ SuryaArena è stato Ammesso correttamente (da surya1977)!
|
||||
Gio 06/11 ORE 20:38: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ surya3 è stato Ammesso correttamente (da surya1977)!
|
||||
Gio 06/11 ORE 22:50: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ surya4 è stato Ammesso correttamente (da surya1977)!
|
||||
Sab 08/11 ORE 20:24: 🤖: Da Sùrya (surya1977):
|
||||
/start 4b989bfb3d9af38551a8459ddf4a902c82e12017600c29bc050cc56fb835a881
|
||||
Sab 08/11 ORE 20:24: 🤖: Da Surya Ar (SuryaSecondo):
|
||||
/start 4b989bfb3d9af38551a8459ddf4a902c82e12017600c29bc050cc56fb835a881
|
||||
Sab 08/11 ORE 20:24: 🤖: Da Surya Ar (SuryaSecondo):
|
||||
/start 4b989bfb3d9af38551a8459ddf4a902c82e12017600c29bc050cc56fb835a881
|
||||
Dom 09/11 ORE 18:36: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ surya4 è stato Ammesso correttamente (da surya1977)!
|
||||
Mar 18/11 ORE 22:31: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ surya8 è stato Ammesso correttamente (da surya1977)!
|
||||
Gio 27/11 ORE 00:43: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ test123 è stato Ammesso correttamente (da surya1977)!
|
||||
Gio 27/11 ORE 00:53: 🤖: Da Sùrya undefined (surya1977):
|
||||
✅ test123 è stato Ammesso correttamente (da surya1977)!
|
||||
53
logtrans.txt
53
logtrans.txt
@@ -483,4 +483,55 @@ Dom 23/03 ORE 22:24: [<b>Circuito RIS Italia</b>]: Inviate Monete da surya1977 a
|
||||
|
||||
Saldi:
|
||||
surya1977: 88.20 RIS]
|
||||
GruppoYurta: 6.00 RIS]
|
||||
GruppoYurta: 6.00 RIS]
|
||||
Ven 03/10 ORE 15:03: [<b>Euro</b>]: Inviate Monete da paoloar77 a piuchebuono 6 € [causale: Pagato Ordine n.442]
|
||||
|
||||
Saldi:
|
||||
paoloar77: -312.80 €]
|
||||
piuchebuono: 10552.82 €]
|
||||
Dom 23/11 ORE 00:52: [<b>Circuito RIS Italia</b>]: Inviate Monete da perseo9 a surya1977 1 RIS [causale: prova 1 Ciaoooooooo]
|
||||
|
||||
Saldi:
|
||||
perseo9: -1.00 RIS]
|
||||
surya1977: 123.95 RIS]
|
||||
Dom 23/11 ORE 00:56: [<b>Circuito RIS Italia</b>]: Inviate Monete da perseo9 a surya1977 2 RIS [causale: Eccolo!
|
||||
]
|
||||
|
||||
Saldi:
|
||||
perseo9: -3.00 RIS]
|
||||
surya1977: 125.95 RIS]
|
||||
Dom 23/11 ORE 01:00: [<b>Circuito RIS Italia</b>]: Inviate Monete da perseo9 a surya1977 3 RIS [causale: aaaaa]
|
||||
|
||||
Saldi:
|
||||
perseo9: -6.00 RIS]
|
||||
surya1977: 128.95 RIS]
|
||||
Dom 23/11 ORE 01:03: [<b>Circuito RIS Italia</b>]: Inviate Monete da perseo9 a surya1977 4 RIS [causale: BBB]
|
||||
|
||||
Saldi:
|
||||
perseo9: -14.00 RIS]
|
||||
surya1977: 136.95 RIS]
|
||||
Dom 23/11 ORE 01:10: [<b>Circuito RIS Italia</b>]: Inviate Monete da perseo9 a surya1977 5 RIS [causale: sdasdas]
|
||||
|
||||
Saldi:
|
||||
perseo9: -19.00 RIS]
|
||||
surya1977: 141.95 RIS]
|
||||
Gio 04/12 ORE 18:55: [<b>Circuito RIS Bologna</b>]: Inviate Monete da SurTest a ElenaEspx 0.1 RIS [causale: ]
|
||||
|
||||
Saldi:
|
||||
SurTest: 0.00 RIS]
|
||||
ElenaEspx: 38.05 RIS]
|
||||
Mar 09/12 ORE 21:36: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 11 RIS [causale: ssss]
|
||||
|
||||
Saldi:
|
||||
surya1977: 63.00 RIS]
|
||||
amandadi: 12.00 RIS]
|
||||
Mer 10/12 ORE 16:18: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 12 RIS [causale: Ciaoo]
|
||||
|
||||
Saldi:
|
||||
surya1977: 51.00 RIS]
|
||||
amandadi: 24.00 RIS]
|
||||
Mer 10/12 ORE 17:02: [<b>Circuito RIS Venezia</b>]: Inviate Monete da surya1977 a amandadi 52 RIS [causale: Ancora test]
|
||||
|
||||
Saldi:
|
||||
surya1977: -1.00 RIS]
|
||||
amandadi: 76.00 RIS]
|
||||
28
package.json
28
package.json
@@ -2,24 +2,29 @@
|
||||
"name": "freeplanet-serverside",
|
||||
"version": "1.0.2",
|
||||
"description": "freeplanet serverside",
|
||||
"main": "server/server.js",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node src/server/server.js",
|
||||
"dev": "NODE_ENV=development nodemon src/server/server.js",
|
||||
"start": "node src/server.js",
|
||||
"dev": "NODE_ENV=development nodemon src/server.js",
|
||||
"build": "gulp build",
|
||||
"watch": "gulp watch",
|
||||
"test": "export NODE_ENV=development || SET NODE_ENV=development && mocha src/server/**/*.test.js",
|
||||
"start:prod": "NODE_ENV=production node src/server/server.js",
|
||||
"starttest": "NODE_ENV=test node src/server/server.js",
|
||||
"start:prod": "NODE_ENV=production node src/server.js",
|
||||
"starttest": "NODE_ENV=test node src/server.js",
|
||||
"test-watch": "nodemon --exec 'npm test'"
|
||||
},
|
||||
"author": "Surya",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fal-ai/client": "^1.7.2",
|
||||
"axios": "^1.13.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.3",
|
||||
"canvas": "^3.2.0",
|
||||
"cheerio": "^1.0.0",
|
||||
"compress-pdf": "^0.5.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"country-codes-list": "^2.0.0",
|
||||
@@ -28,9 +33,15 @@
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"email-templates": "^12.0.2",
|
||||
"entities": "^7.0.0",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"fast-csv": "^5.0.5",
|
||||
"formidable": "^3.5.2",
|
||||
"fs-extra": "^11.3.2",
|
||||
"ghostscript4js": "^3.2.3",
|
||||
"groq-sdk": "^0.37.0",
|
||||
"helmet": "^8.1.0",
|
||||
"i18n": "^0.15.1",
|
||||
"image-downloader": "^4.3.0",
|
||||
"internet-available": "^1.0.0",
|
||||
@@ -40,6 +51,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mongodb": "^6.14.2",
|
||||
"mongoose": "^8.12.1",
|
||||
"morgan": "^1.10.1",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"mysql": "^2.18.1",
|
||||
"node-cron": "^3.0.3",
|
||||
@@ -50,13 +62,15 @@
|
||||
"node-telegram-bot-api": "^0.66.0",
|
||||
"nodemailer": "^6.10.0",
|
||||
"npm-check-updates": "^17.1.15",
|
||||
"openai": "^4.86.2",
|
||||
"openai": "^4.104.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pem": "^1.14.8",
|
||||
"preview-email": "^3.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "^24.9.0",
|
||||
"rate-limiter-flexible": "^5.0.5",
|
||||
"replicate": "^1.4.0",
|
||||
"request": "^2.88",
|
||||
"sanitize-html": "^2.14.0",
|
||||
"save": "^2.9.0",
|
||||
@@ -67,6 +81,8 @@
|
||||
"validator": "^13.12.0",
|
||||
"vhost": "^3.0.2",
|
||||
"web-push": "^3.6.7",
|
||||
"ws": "^8.18.3",
|
||||
"xlsx": "^0.18.5",
|
||||
"xml2js": "^0.6.2",
|
||||
"xoauth2": "^1.2.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const fs = require('fs');
|
||||
const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
const {google} = require('googleapis');
|
||||
var FILE = require('./file');
|
||||
|
||||
@@ -25,11 +25,11 @@ var file = `.env.${node_env}`;
|
||||
|
||||
// GLOBALI (Uguali per TUTTI)
|
||||
process.env.LINKVERIF_REG = '/vreg';
|
||||
process.env.CHECKREVERIF_EMAIL = '/reverif_email';
|
||||
process.env.LINK_REQUEST_NEWPASSWORD = '/requestnewpwd';
|
||||
process.env.ADD_NEW_SITE = '/addNewSite';
|
||||
process.env.LINK_UPDATE_PASSWORD = '/updatepassword';
|
||||
process.env.LINK_UPDATE_PWD = '/updatepwd';
|
||||
process.env.LINK_CHECK_UPDATES = '/checkupdates';
|
||||
process.env.KEY_APP_ID = 'KKPPAA5KJK435J3KSS9F9D8S9F8SD98F9SDF';
|
||||
|
||||
console.log("Starting Node with: " + file);
|
||||
492
src/controllers/UserController.js
Normal file
492
src/controllers/UserController.js
Normal file
@@ -0,0 +1,492 @@
|
||||
const UserService = require('../services/UserService');
|
||||
const AuthService = require('../services/AuthService');
|
||||
const RegistrationService = require('../services/RegistrationService');
|
||||
const { validateRegistration, validateLogin } = require('../validators/userValidators');
|
||||
const telegrambot = require('../telegram/telegrambot');
|
||||
const tools = require('../tools/general');
|
||||
const server_constants = require('../tools/server_constants');
|
||||
const shared_consts = require('../tools/shared_nodejs');
|
||||
|
||||
class UserController {
|
||||
constructor() {
|
||||
this.userService = new UserService();
|
||||
this.authService = new AuthService();
|
||||
this.registrationService = new RegistrationService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new user
|
||||
* POST /users
|
||||
*/
|
||||
async register(req, res) {
|
||||
try {
|
||||
tools.mylog('POST /users - Registration');
|
||||
|
||||
// Validate input
|
||||
const validationError = validateRegistration(req.body);
|
||||
if (validationError) {
|
||||
await tools.snooze(5000);
|
||||
return res.status(400).send({
|
||||
code: validationError.code,
|
||||
msg: validationError.message,
|
||||
});
|
||||
}
|
||||
|
||||
const userData = this._extractUserData(req.body);
|
||||
userData.ipaddr = tools.getiPAddressUser(req);
|
||||
|
||||
// Check security (IP bans, block words, etc.)
|
||||
const securityCheck = await this._performSecurityChecks(userData, req);
|
||||
if (securityCheck.blocked) {
|
||||
return res.status(securityCheck.status).send({
|
||||
code: securityCheck.code,
|
||||
msg: securityCheck.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Process registration
|
||||
const result = await this.registrationService.registerUser(userData, req);
|
||||
|
||||
if (result.error) {
|
||||
return res.status(400).send({
|
||||
code: result.code,
|
||||
msg: result.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Send response with tokens
|
||||
res.header('x-auth', result.token).header('x-refrtok', result.refreshToken).header('x-browser-random', result.browser_random).send(result.user);
|
||||
} catch (error) {
|
||||
console.error('Error in registration:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User login
|
||||
* POST /users/login
|
||||
*/
|
||||
async login(req, res) {
|
||||
try {
|
||||
console.log('LOGIN');
|
||||
const { username, password, idapp, keyappid, br } = req.body;
|
||||
|
||||
const browser_random = br;
|
||||
|
||||
// Validate API key
|
||||
if (keyappid !== process.env.KEY_APP_ID) {
|
||||
return res.status(400).send({ code: server_constants.RIS_CODE_ERR });
|
||||
}
|
||||
|
||||
// Validate input
|
||||
const validationError = validateLogin(req.body);
|
||||
if (validationError) {
|
||||
return res.status(400).send({
|
||||
code: validationError.code,
|
||||
msg: validationError.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Attempt login
|
||||
const result = await this.authService.authenticate(idapp, username, password, req, browser_random);
|
||||
|
||||
console.log('attempt...', result);
|
||||
|
||||
if (result.error) {
|
||||
return res.status(result.status).send({
|
||||
code: result.code,
|
||||
msg: result.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Send response with tokens
|
||||
res.header('x-auth', result.token).header('x-refrtok', result.refreshToken).header('x-browser-random', result.browser_random).send({
|
||||
usertosend: result.user,
|
||||
code: server_constants.RIS_CODE_OK,
|
||||
subsExistonDb: result.subsExistonDb,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in login:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_LOGIN_ERR_GENERIC,
|
||||
msgerr: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
* POST /users/profile
|
||||
*/
|
||||
async getProfile(req, res) {
|
||||
try {
|
||||
const usernameOrig = req.user?.username || '';
|
||||
const perm = req.user?.perm || tools.Perm.PERM_NONE;
|
||||
const { username, idapp, idnotif } = req.body;
|
||||
|
||||
// Mark notification as read if present
|
||||
if (idnotif) {
|
||||
await this.userService.markNotificationAsRead(idapp, usernameOrig, idnotif);
|
||||
}
|
||||
|
||||
// Get user profile
|
||||
const profile = await this.userService.getUserProfile(idapp, username, usernameOrig, perm);
|
||||
|
||||
res.send(profile);
|
||||
} catch (error) {
|
||||
console.error('Error in getProfile:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user saldo (balance)
|
||||
* POST /users/updatesaldo
|
||||
*/
|
||||
async updateSaldo(req, res) {
|
||||
try {
|
||||
const username = req.user.username;
|
||||
const { idapp, circuitId, groupname, lastdr } = req.body;
|
||||
|
||||
const result = await this.userService.updateUserBalance(idapp, username, circuitId, groupname, lastdr);
|
||||
|
||||
res.send({ ris: result });
|
||||
} catch (error) {
|
||||
console.error('Error in updateSaldo:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's friends
|
||||
* POST /users/friends
|
||||
*/
|
||||
async getFriends(req, res) {
|
||||
try {
|
||||
const username = req.user.username;
|
||||
const { idapp } = req.body;
|
||||
|
||||
const friends = await this.userService.getUserFriends(idapp, username);
|
||||
|
||||
res.send(friends);
|
||||
} catch (error) {
|
||||
console.error('Error in getFriends:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute friend command (add, remove, etc.)
|
||||
* POST /users/friends/cmd
|
||||
*/
|
||||
async executeFriendCommand(req, res) {
|
||||
try {
|
||||
const usernameLogged = req.user.username;
|
||||
const { idapp, usernameOrig, usernameDest, cmd, value } = req.body;
|
||||
|
||||
// Security check
|
||||
if (!this._canExecuteFriendCommand(req.user, usernameOrig, usernameDest, cmd)) {
|
||||
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({
|
||||
code: server_constants.RIS_CODE_ERR_UNAUTHORIZED,
|
||||
msg: '',
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.userService.executeFriendCommand(req, idapp, usernameOrig, usernameDest, cmd, value);
|
||||
|
||||
res.send(result);
|
||||
} catch (error) {
|
||||
console.error('Error in executeFriendCommand:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's groups
|
||||
* POST /users/groups
|
||||
*/
|
||||
async getGroups(req, res) {
|
||||
try {
|
||||
const username = req.user.username;
|
||||
const { idapp } = req.body;
|
||||
|
||||
const groups = await this.userService.getUserGroups(idapp, username, req);
|
||||
|
||||
res.send(groups);
|
||||
} catch (error) {
|
||||
console.error('Error in getGroups:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's circuits
|
||||
* POST /users/circuits
|
||||
*/
|
||||
async getCircuits(req, res) {
|
||||
try {
|
||||
const username = req.user.username;
|
||||
const { idapp, nummovTodownload } = req.body;
|
||||
|
||||
const circuits = await this.userService.getUserCircuits(idapp, username, req.user, nummovTodownload);
|
||||
|
||||
res.send(circuits);
|
||||
} catch (error) {
|
||||
console.error('Error in getCircuits:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh authentication token
|
||||
* POST /users/newtok
|
||||
*/
|
||||
async refreshToken(req, res) {
|
||||
try {
|
||||
const { refreshToken, br } = req.body;
|
||||
|
||||
const browser_random = br;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(400).send({ error: 'Refresh token mancante' });
|
||||
}
|
||||
|
||||
const result = await this.authService.refreshToken(refreshToken, req);
|
||||
|
||||
if (result.error) {
|
||||
return res.status(result.status).send({ error: result.message });
|
||||
}
|
||||
|
||||
res.status(200).send({
|
||||
token: result.token,
|
||||
refreshToken: result.refreshToken,
|
||||
browser_random,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in refreshToken:', error.message);
|
||||
res.status(500).send({ error: 'Errore interno del server' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user (remove token)
|
||||
* DELETE /users/me/token
|
||||
*/
|
||||
async logout(req, res) {
|
||||
try {
|
||||
await this.authService.removeToken(req.user, req.token);
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
console.error('Error in logout:', error.message);
|
||||
res.status(400).send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if username exists
|
||||
* GET /users/:idapp/:username
|
||||
*/
|
||||
async checkUsername(req, res) {
|
||||
try {
|
||||
const { username, idapp } = req.params;
|
||||
|
||||
const exists = await this.userService.checkUsernameExists(idapp, username);
|
||||
|
||||
if (!exists) {
|
||||
return res.status(404).send();
|
||||
}
|
||||
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
console.error('Error in checkUsername:', error.message);
|
||||
res.status(400).send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user permissions
|
||||
* POST /users/setperm
|
||||
*/
|
||||
async setPermissions(req, res) {
|
||||
try {
|
||||
const { idapp, username, perm } = req.body;
|
||||
|
||||
await this.userService.setUserPermissions(req.user._id, {
|
||||
idapp,
|
||||
username,
|
||||
perm,
|
||||
});
|
||||
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
console.error('Error in setPermissions:', error.message);
|
||||
res.status(400).send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Database operations (admin only)
|
||||
* POST /users/dbop
|
||||
*/
|
||||
async executeDbOperation(req, res) {
|
||||
try {
|
||||
const { mydata, idapp } = req.body;
|
||||
|
||||
// Check permissions
|
||||
if (!this._hasAdminPermissions(req.user)) {
|
||||
return res.status(404).send({
|
||||
code: server_constants.RIS_CODE_ERR_UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.userService.executeDbOperation(idapp, mydata, req, res);
|
||||
|
||||
res.send({
|
||||
code: server_constants.RIS_CODE_OK,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in executeDbOperation:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map information
|
||||
* POST /users/infomap
|
||||
*/
|
||||
async getMapInfo(req, res) {
|
||||
try {
|
||||
const { idapp } = req.body;
|
||||
|
||||
const mapData = await this.userService.getMapInformation(idapp);
|
||||
|
||||
res.send({
|
||||
code: server_constants.RIS_CODE_OK,
|
||||
ris: mapData,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in getMapInfo:', error.message);
|
||||
res.status(400).send({
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
msg: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PRIVATE HELPER METHODS =====
|
||||
|
||||
_extractUserData(body) {
|
||||
const fields = [
|
||||
'email',
|
||||
'password',
|
||||
'username',
|
||||
'group',
|
||||
'name',
|
||||
'surname',
|
||||
'idapp',
|
||||
'keyappid',
|
||||
'lang',
|
||||
'profile',
|
||||
'aportador_solidario',
|
||||
];
|
||||
|
||||
const userData = {};
|
||||
fields.forEach((field) => {
|
||||
if (body[field] !== undefined) {
|
||||
userData[field] = body[field];
|
||||
}
|
||||
});
|
||||
|
||||
// Normalize data
|
||||
if (userData.email) userData.email = userData.email.toLowerCase().trim();
|
||||
if (userData.username) userData.username = userData.username.trim();
|
||||
if (userData.name) userData.name = userData.name.trim();
|
||||
if (userData.surname) userData.surname = userData.surname.trim();
|
||||
|
||||
return userData;
|
||||
}
|
||||
|
||||
async _performSecurityChecks(userData, req) {
|
||||
const { User } = require('../models/user');
|
||||
|
||||
// Check for blocked words
|
||||
if (tools.blockwords(userData.username) || tools.blockwords(userData.name) || tools.blockwords(userData.surname)) {
|
||||
await tools.snooze(5000);
|
||||
return {
|
||||
blocked: true,
|
||||
status: 404,
|
||||
code: server_constants.RIS_CODE_ERR,
|
||||
};
|
||||
}
|
||||
|
||||
// Check IP ban
|
||||
const lastRecord = await User.getLastRec(userData.idapp);
|
||||
if (lastRecord && process.env.LOCALE !== '1') {
|
||||
if (lastRecord.ipaddr === userData.ipaddr && lastRecord.date_reg) {
|
||||
const isTooRecent = tools.isdiffSecDateLess(lastRecord.date_reg, 3);
|
||||
if (isTooRecent) {
|
||||
const msg = `${userData.ipaddr}: [${userData.username}] ${userData.name} ${userData.surname}`;
|
||||
tools.writeIPToBan(msg);
|
||||
await telegrambot.sendMsgTelegramToTheAdmin(userData.idapp, '‼️ BAN: ' + msg, true);
|
||||
await tools.snooze(5000);
|
||||
|
||||
return {
|
||||
blocked: true,
|
||||
status: 400,
|
||||
code: server_constants.RIS_CODE_BANIP,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { blocked: false };
|
||||
}
|
||||
|
||||
_canExecuteFriendCommand(user, usernameOrig, usernameDest, cmd) {
|
||||
const { User } = require('../models/user');
|
||||
|
||||
if (User.isAdmin(user.perm) || User.isManager(user.perm)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const allowedCommands = [shared_consts.FRIENDSCMD.SETFRIEND, shared_consts.FRIENDSCMD.SETHANDSHAKE];
|
||||
|
||||
if (allowedCommands.includes(cmd)) {
|
||||
return usernameOrig === user.username || usernameDest === user.username;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_hasAdminPermissions(user) {
|
||||
const { User } = require('../models/user');
|
||||
return User.isCollaboratore(user.perm);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserController;
|
||||
1020
src/controllers/articleController.js
Normal file
1020
src/controllers/articleController.js
Normal file
File diff suppressed because it is too large
Load Diff
398
src/controllers/assetController.js
Normal file
398
src/controllers/assetController.js
Normal file
@@ -0,0 +1,398 @@
|
||||
const Asset = require('../models/Asset');
|
||||
const imageGenerator = require('../services/imageGenerator');
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
|
||||
|
||||
const assetController = {
|
||||
// POST /assets/upload
|
||||
async upload(req, res) {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Nessun file caricato'
|
||||
});
|
||||
}
|
||||
|
||||
const { category = 'other', tags, description, isReusable = true } = req.body;
|
||||
const file = req.file;
|
||||
|
||||
// Ottieni dimensioni immagine
|
||||
let dimensions = {};
|
||||
try {
|
||||
const metadata = await sharp(file.path).metadata();
|
||||
dimensions = { width: metadata.width, height: metadata.height };
|
||||
} catch (e) {
|
||||
console.warn('Cannot read image dimensions');
|
||||
}
|
||||
|
||||
// Genera thumbnail
|
||||
const thumbDir = path.join(UPLOAD_DIR, 'thumbs');
|
||||
await fs.mkdir(thumbDir, { recursive: true });
|
||||
const thumbName = `thumb_${file.filename}`;
|
||||
const thumbPath = path.join(thumbDir, thumbName);
|
||||
|
||||
try {
|
||||
await sharp(file.path)
|
||||
.resize(300, 300, { fit: 'cover' })
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(thumbPath);
|
||||
} catch (e) {
|
||||
console.warn('Cannot create thumbnail');
|
||||
}
|
||||
|
||||
const asset = new Asset({
|
||||
type: 'image',
|
||||
category,
|
||||
sourceType: 'upload',
|
||||
file: {
|
||||
path: file.path,
|
||||
url: `/uploads/${file.filename}`,
|
||||
thumbnailPath: thumbPath,
|
||||
thumbnailUrl: `/uploads/thumbs/${thumbName}`,
|
||||
originalName: file.originalname,
|
||||
mimeType: file.mimetype,
|
||||
size: file.size,
|
||||
dimensions
|
||||
},
|
||||
metadata: {
|
||||
userId: req.user._id,
|
||||
tags: tags ? tags.split(',').map(t => t.trim()) : [],
|
||||
description,
|
||||
isReusable: isReusable === 'true' || isReusable === true
|
||||
}
|
||||
});
|
||||
|
||||
await asset.save();
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: asset
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// POST /assets/upload-multiple
|
||||
async uploadMultiple(req, res) {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Nessun file caricato'
|
||||
});
|
||||
}
|
||||
|
||||
const { category = 'other' } = req.body;
|
||||
const assets = [];
|
||||
|
||||
for (const file of req.files) {
|
||||
let dimensions = {};
|
||||
try {
|
||||
const metadata = await sharp(file.path).metadata();
|
||||
dimensions = { width: metadata.width, height: metadata.height };
|
||||
} catch (e) {}
|
||||
|
||||
const asset = new Asset({
|
||||
type: 'image',
|
||||
category,
|
||||
sourceType: 'upload',
|
||||
file: {
|
||||
path: file.path,
|
||||
url: `/uploads/${file.filename}`,
|
||||
originalName: file.originalname,
|
||||
mimeType: file.mimetype,
|
||||
size: file.size,
|
||||
dimensions
|
||||
},
|
||||
metadata: {
|
||||
userId: req.user._id
|
||||
}
|
||||
});
|
||||
|
||||
await asset.save();
|
||||
assets.push(asset);
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: assets
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// POST /assets/generate-ai
|
||||
async generateAi(req, res) {
|
||||
try {
|
||||
const {
|
||||
prompt,
|
||||
negativePrompt,
|
||||
provider = 'hf',
|
||||
category = 'other',
|
||||
aspectRatio = '9:16',
|
||||
model,
|
||||
seed,
|
||||
steps,
|
||||
cfg
|
||||
} = req.body;
|
||||
|
||||
if (!prompt) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Prompt richiesto'
|
||||
});
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const imageUrl = await imageGenerator.generate(provider, prompt, {
|
||||
negativePrompt,
|
||||
aspectRatio,
|
||||
model,
|
||||
seed,
|
||||
steps,
|
||||
cfg
|
||||
});
|
||||
const generationTime = Date.now() - startTime;
|
||||
|
||||
// Salva file
|
||||
const fileName = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`;
|
||||
const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
|
||||
let fileSize = 0;
|
||||
let dimensions = {};
|
||||
|
||||
if (imageUrl.startsWith('data:')) {
|
||||
const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, '');
|
||||
const buffer = Buffer.from(base64Data, 'base64');
|
||||
await fs.writeFile(filePath, buffer);
|
||||
fileSize = buffer.length;
|
||||
|
||||
const metadata = await sharp(buffer).metadata();
|
||||
dimensions = { width: metadata.width, height: metadata.height };
|
||||
} else {
|
||||
const fetch = require('node-fetch');
|
||||
const response = await fetch(imageUrl);
|
||||
const buffer = await response.buffer();
|
||||
await fs.writeFile(filePath, buffer);
|
||||
fileSize = buffer.length;
|
||||
|
||||
const metadata = await sharp(buffer).metadata();
|
||||
dimensions = { width: metadata.width, height: metadata.height };
|
||||
}
|
||||
|
||||
const asset = new Asset({
|
||||
type: 'image',
|
||||
category,
|
||||
sourceType: 'ai',
|
||||
file: {
|
||||
path: filePath,
|
||||
url: `/uploads/ai-generated/${fileName}`,
|
||||
mimeType: 'image/jpeg',
|
||||
size: fileSize,
|
||||
dimensions
|
||||
},
|
||||
aiGeneration: {
|
||||
prompt,
|
||||
negativePrompt,
|
||||
provider,
|
||||
model,
|
||||
seed,
|
||||
steps,
|
||||
cfg,
|
||||
requestedSize: aspectRatio,
|
||||
generationTime
|
||||
},
|
||||
metadata: {
|
||||
userId: req.user._id,
|
||||
isReusable: true
|
||||
}
|
||||
});
|
||||
|
||||
await asset.save();
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: asset
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /assets
|
||||
async list(req, res) {
|
||||
try {
|
||||
const {
|
||||
category,
|
||||
sourceType,
|
||||
page = 1,
|
||||
limit = 50
|
||||
} = req.query;
|
||||
|
||||
const query = {
|
||||
'metadata.userId': req.user._id,
|
||||
status: 'ready'
|
||||
};
|
||||
|
||||
if (category) query.category = category;
|
||||
if (sourceType) query.sourceType = sourceType;
|
||||
|
||||
const [assets, total] = await Promise.all([
|
||||
Asset.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip((page - 1) * limit)
|
||||
.limit(parseInt(limit)),
|
||||
Asset.countDocuments(query)
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: assets,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /assets/:id
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const asset = await Asset.findById(req.params.id);
|
||||
|
||||
if (!asset) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Asset non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: asset
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /assets/:id/file
|
||||
async getFile(req, res) {
|
||||
try {
|
||||
const asset = await Asset.findById(req.params.id);
|
||||
|
||||
if (!asset || !asset.file?.path) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'File non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
res.sendFile(path.resolve(asset.file.path));
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /assets/:id/thumbnail
|
||||
async getThumbnail(req, res) {
|
||||
try {
|
||||
const asset = await Asset.findById(req.params.id);
|
||||
|
||||
if (!asset) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Asset non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
const thumbPath = asset.file?.thumbnailPath || asset.file?.path;
|
||||
if (!thumbPath) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Thumbnail non disponibile'
|
||||
});
|
||||
}
|
||||
|
||||
res.sendFile(path.resolve(thumbPath));
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// DELETE /assets/:id
|
||||
async delete(req, res) {
|
||||
try {
|
||||
const asset = await Asset.findById(req.params.id);
|
||||
|
||||
if (!asset) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Asset non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
if (asset.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Non autorizzato'
|
||||
});
|
||||
}
|
||||
|
||||
// Elimina file
|
||||
try {
|
||||
if (asset.file?.path) await fs.unlink(asset.file.path);
|
||||
if (asset.file?.thumbnailPath) await fs.unlink(asset.file.thumbnailPath);
|
||||
} catch (e) {
|
||||
console.warn('File deletion warning:', e.message);
|
||||
}
|
||||
|
||||
await Asset.deleteOne({ _id: asset._id });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Asset eliminato'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = assetController;
|
||||
46
src/controllers/events.controller.js
Normal file
46
src/controllers/events.controller.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// @ts-check
|
||||
const EventModel = require('../models/Event');
|
||||
const { pick } = require('../utils/pick');
|
||||
|
||||
async function listEvents(req, res) {
|
||||
const { limit = '6', page = '1', from, to, sort = 'start' } = /** @type {any} */ (req.query);
|
||||
const q = {};
|
||||
if (from) q.start = { ...(q.start || {}), $gte: new Date(from) };
|
||||
if (to) q.start = { ...(q.start || {}), $lte: new Date(to) };
|
||||
|
||||
const lim = Math.min(Number(limit) || 6, 50);
|
||||
const skip = (Math.max(Number(page) || 1, 1) - 1) * lim;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
EventModel.find(q)
|
||||
.sort({ [sort]: 1 })
|
||||
.skip(skip)
|
||||
.limit(lim)
|
||||
.lean(),
|
||||
EventModel.countDocuments(q),
|
||||
]);
|
||||
|
||||
res.set('Cache-Control', 'public, max-age=30, stale-while-revalidate=120');
|
||||
return res.json({ items, total, page: Number(page), limit: lim });
|
||||
}
|
||||
|
||||
async function createEvent(req, res) {
|
||||
const data = pick(req.body, ['title', 'start', 'end', 'place', 'teaser', 'cover', 'to']);
|
||||
const doc = await EventModel.create(data);
|
||||
return res.status(201).json(doc);
|
||||
}
|
||||
|
||||
async function updateEvent(req, res) {
|
||||
const data = pick(req.body, ['title', 'start', 'end', 'place', 'teaser', 'cover', 'to']);
|
||||
const doc = await EventModel.findByIdAndUpdate(req.params.id, data, { new: true });
|
||||
if (!doc) return res.status(404).json({ message: 'Evento non trovato' });
|
||||
return res.json(doc);
|
||||
}
|
||||
|
||||
async function deleteEvent(req, res) {
|
||||
const r = await EventModel.findByIdAndDelete(req.params.id);
|
||||
if (!r) return res.status(404).json({ message: 'Evento non trovato' });
|
||||
return res.status(204).send();
|
||||
}
|
||||
|
||||
module.exports = { listEvents, createEvent, updateEvent, deleteEvent };
|
||||
18
src/controllers/home.controller.js
Normal file
18
src/controllers/home.controller.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// @ts-check
|
||||
const { HomeModel } = require('../models/Home');
|
||||
|
||||
async function getHome(req, res) {
|
||||
const doc = await HomeModel.findOne({});
|
||||
if (!doc) return res.status(404).json({ message: 'Home non configurata' });
|
||||
res.set('Cache-Control', 'public, max-age=60, stale-while-revalidate=300');
|
||||
res.set('ETag', `"${doc.updatedAt?.getTime?.() || Date.now()}"`);
|
||||
return res.json(doc);
|
||||
}
|
||||
|
||||
async function upsertHome(req, res) {
|
||||
const payload = req.body || {};
|
||||
const doc = await HomeModel.findOneAndUpdate({}, payload, { upsert: true, new: true, setDefaultsOnInsert: true });
|
||||
return res.json(doc);
|
||||
}
|
||||
|
||||
module.exports = { getHome, upsertHome };
|
||||
20
src/controllers/newsletter.controller.js
Normal file
20
src/controllers/newsletter.controller.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// @ts-check
|
||||
async function subscribe(req, res) {
|
||||
const { email } = req.body || {};
|
||||
if (!email || !/.+@.+\..+/.test(email)) {
|
||||
return res.status(400).json({ message: 'Email non valida' });
|
||||
}
|
||||
// Integra qui Mailchimp/Sendinblue se necessario
|
||||
return res.status(200).json({ ok: true });
|
||||
}
|
||||
|
||||
async function unsubscribe(req, res) {
|
||||
const { email } = req.body || {};
|
||||
if (!email || !/.+@.+\..+/.test(email)) {
|
||||
return res.status(400).json({ message: 'Email non valida' });
|
||||
}
|
||||
// Integra qui Mailchimp/Sendinblue se necessario
|
||||
return res.status(200).json({ ok: true });
|
||||
}
|
||||
|
||||
module.exports = { subscribe, unsubscribe };
|
||||
647
src/controllers/posterController.js
Normal file
647
src/controllers/posterController.js
Normal file
@@ -0,0 +1,647 @@
|
||||
const Poster = require('../models/Poster');
|
||||
const Template = require('../models/Template');
|
||||
const Asset = require('../models/Asset');
|
||||
const posterRenderer = require('../services/posterRenderer');
|
||||
const imageGenerator = require('../services/imageGenerator');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
|
||||
|
||||
const posterController = {
|
||||
// POST /posters
|
||||
async create(req, res) {
|
||||
try {
|
||||
const {
|
||||
templateId,
|
||||
name,
|
||||
description,
|
||||
content,
|
||||
assets,
|
||||
layerOverrides,
|
||||
autoRender = false
|
||||
} = req.body;
|
||||
|
||||
// Carica template
|
||||
const template = await Template.findById(templateId);
|
||||
if (!template) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Template non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
// Valida contenuti richiesti
|
||||
const requiredLayers = template.layers.filter(l => l.required);
|
||||
for (const layer of requiredLayers) {
|
||||
if (layer.type === 'title' && !content?.title) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: `Campo richiesto: ${layer.type}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const poster = new Poster({
|
||||
templateId,
|
||||
templateSnapshot: template.toObject(), // Snapshot per retrocompatibilità
|
||||
name: name || content?.title || 'Nuova Locandina',
|
||||
description,
|
||||
status: 'draft',
|
||||
content: content || {},
|
||||
assets: assets || {},
|
||||
layerOverrides: layerOverrides || {},
|
||||
renderEngineVersion: '1.0.0',
|
||||
metadata: {
|
||||
userId: req.user._id
|
||||
}
|
||||
});
|
||||
|
||||
await poster.save();
|
||||
|
||||
// Incrementa uso template
|
||||
await template.incrementUsage();
|
||||
|
||||
// Auto-render se richiesto
|
||||
if (autoRender) {
|
||||
await posterController._renderPoster(poster);
|
||||
await poster.save();
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: poster
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Poster create error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /posters
|
||||
async list(req, res) {
|
||||
try {
|
||||
const {
|
||||
status,
|
||||
templateId,
|
||||
search,
|
||||
page = 1,
|
||||
limit = 20,
|
||||
sortBy = 'createdAt',
|
||||
sortOrder = 'desc'
|
||||
} = req.query;
|
||||
|
||||
const query = { 'metadata.userId': req.user._id };
|
||||
|
||||
if (status) query.status = status;
|
||||
if (templateId) query.templateId = templateId;
|
||||
if (search) query.$text = { $search: search };
|
||||
|
||||
const sort = { [sortBy]: sortOrder === 'asc' ? 1 : -1 };
|
||||
|
||||
const [posters, total] = await Promise.all([
|
||||
Poster.find(query)
|
||||
.populate('templateId', 'name templateType thumbnailUrl')
|
||||
.sort(sort)
|
||||
.skip((page - 1) * limit)
|
||||
.limit(parseInt(limit))
|
||||
.select('-templateSnapshot -history'),
|
||||
Poster.countDocuments(query)
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: posters,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /posters/favorites
|
||||
async listFavorites(req, res) {
|
||||
try {
|
||||
const posters = await Poster.findFavorites(req.user._id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: posters
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /posters/recent
|
||||
async listRecent(req, res) {
|
||||
try {
|
||||
const { limit = 10 } = req.query;
|
||||
const posters = await Poster.findRecent(req.user._id, parseInt(limit));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: posters
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /posters/:id
|
||||
async getById(req, res) {
|
||||
try {
|
||||
const poster = await Poster.findById(req.params.id)
|
||||
.populate('templateId')
|
||||
.populate('assets.backgroundImage.assetId')
|
||||
.populate('assets.mainImage.assetId')
|
||||
.populate('assets.logos.assetId');
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
// Check ownership
|
||||
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Accesso negato'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: poster
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// PUT /posters/:id
|
||||
async update(req, res) {
|
||||
try {
|
||||
const poster = await Poster.findById(req.params.id);
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Non autorizzato'
|
||||
});
|
||||
}
|
||||
|
||||
const updateFields = [
|
||||
'name', 'description', 'content', 'assets', 'layerOverrides'
|
||||
];
|
||||
|
||||
updateFields.forEach(field => {
|
||||
if (req.body[field] !== undefined) {
|
||||
poster[field] = req.body[field];
|
||||
}
|
||||
});
|
||||
|
||||
// Invalida render precedente se contenuto modificato
|
||||
if (req.body.content || req.body.assets || req.body.layerOverrides) {
|
||||
poster.status = 'draft';
|
||||
poster.addHistory('updated', { fields: Object.keys(req.body) });
|
||||
}
|
||||
|
||||
await poster.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: poster
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// DELETE /posters/:id
|
||||
async delete(req, res) {
|
||||
try {
|
||||
const poster = await Poster.findById(req.params.id);
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Non autorizzato'
|
||||
});
|
||||
}
|
||||
|
||||
// Elimina file renderizzati
|
||||
if (poster.renderOutput) {
|
||||
const filesToDelete = [
|
||||
poster.renderOutput.png?.path,
|
||||
poster.renderOutput.jpg?.path,
|
||||
poster.renderOutput.webp?.path
|
||||
].filter(Boolean);
|
||||
|
||||
for (const filePath of filesToDelete) {
|
||||
try {
|
||||
await fs.unlink(filePath);
|
||||
} catch (e) {
|
||||
console.warn('File not found:', filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Poster.deleteOne({ _id: poster._id });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Poster eliminato'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// POST /posters/:id/render
|
||||
async render(req, res) {
|
||||
try {
|
||||
const poster = await Poster.findById(req.params.id)
|
||||
.populate('templateId');
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Non autorizzato'
|
||||
});
|
||||
}
|
||||
|
||||
poster.status = 'processing';
|
||||
await poster.save();
|
||||
|
||||
try {
|
||||
await posterController._renderPoster(poster);
|
||||
await poster.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
status: poster.status,
|
||||
renderOutput: poster.renderOutput
|
||||
}
|
||||
});
|
||||
} catch (renderError) {
|
||||
poster.setError(renderError.message);
|
||||
await poster.save();
|
||||
throw renderError;
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// POST /posters/:id/regenerate-ai
|
||||
async regenerateAi(req, res) {
|
||||
try {
|
||||
const { assetType, prompt, provider = 'hf' } = req.body;
|
||||
const poster = await Poster.findById(req.params.id);
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Non autorizzato'
|
||||
});
|
||||
}
|
||||
|
||||
// Genera nuova immagine AI
|
||||
const startTime = Date.now();
|
||||
const imageUrl = await imageGenerator.generate(provider, prompt);
|
||||
const generationTime = Date.now() - startTime;
|
||||
|
||||
// Salva su filesystem
|
||||
const fileName = `${poster._id}_${assetType}_${Date.now()}.jpg`;
|
||||
const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
|
||||
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
|
||||
// Se è base64, converti
|
||||
let savedPath;
|
||||
if (imageUrl.startsWith('data:')) {
|
||||
const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, '');
|
||||
await fs.writeFile(filePath, base64Data, 'base64');
|
||||
savedPath = filePath;
|
||||
} else {
|
||||
// Se è URL, scarica
|
||||
const fetch = require('node-fetch');
|
||||
const response = await fetch(imageUrl);
|
||||
const buffer = await response.buffer();
|
||||
await fs.writeFile(filePath, buffer);
|
||||
savedPath = filePath;
|
||||
}
|
||||
|
||||
// Aggiorna asset nel poster
|
||||
const assetData = {
|
||||
sourceType: 'ai',
|
||||
url: `/uploads/ai-generated/${fileName}`,
|
||||
mimeType: 'image/jpeg',
|
||||
aiParams: {
|
||||
prompt,
|
||||
provider,
|
||||
generatedAt: new Date()
|
||||
}
|
||||
};
|
||||
|
||||
if (assetType === 'backgroundImage') {
|
||||
poster.assets.backgroundImage = assetData;
|
||||
poster.addHistory('ai_background_generated', { provider, duration: generationTime });
|
||||
} else if (assetType === 'mainImage') {
|
||||
poster.assets.mainImage = assetData;
|
||||
poster.addHistory('ai_main_generated', { provider, duration: generationTime });
|
||||
}
|
||||
|
||||
poster.status = 'draft';
|
||||
await poster.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
assetType,
|
||||
asset: assetData
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// GET /posters/:id/download/:format
|
||||
async download(req, res) {
|
||||
try {
|
||||
const { format } = req.params;
|
||||
const poster = await Poster.findById(req.params.id);
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
const outputFile = poster.renderOutput?.[format];
|
||||
if (!outputFile?.path) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: `Formato ${format} non disponibile`
|
||||
});
|
||||
}
|
||||
|
||||
// Incrementa download count
|
||||
await poster.incrementDownload();
|
||||
|
||||
const fileName = `${poster.name.replace(/[^a-z0-9]/gi, '_')}_poster.${format}`;
|
||||
|
||||
res.download(outputFile.path, fileName);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// POST /posters/:id/favorite
|
||||
async toggleFavorite(req, res) {
|
||||
try {
|
||||
const poster = await Poster.findById(req.params.id);
|
||||
|
||||
if (!poster) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Poster non trovato'
|
||||
});
|
||||
}
|
||||
|
||||
if (poster.metadata.userId.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Non autorizzato'
|
||||
});
|
||||
}
|
||||
|
||||
await poster.toggleFavorite();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
isFavorite: poster.metadata.isFavorite
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// POST /posters/quick-generate (compatibile con la tua bozza)
|
||||
async quickGenerate(req, res) {
|
||||
try {
|
||||
const {
|
||||
templateId,
|
||||
titolo,
|
||||
descrizione,
|
||||
data,
|
||||
ora,
|
||||
luogo,
|
||||
contatti,
|
||||
fotoDescrizione,
|
||||
stile,
|
||||
provider = 'hf',
|
||||
aspectRatio = '9:16'
|
||||
} = req.body;
|
||||
|
||||
// Validazione base
|
||||
if (!titolo || !data || !luogo) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Compila titolo, data e luogo'
|
||||
});
|
||||
}
|
||||
|
||||
// Usa template default o quello specificato
|
||||
let template;
|
||||
if (templateId) {
|
||||
template = await Template.findById(templateId);
|
||||
} else {
|
||||
// Template default per quick-generate
|
||||
template = await Template.findOne({
|
||||
templateType: 'quick-generate',
|
||||
isActive: true
|
||||
});
|
||||
}
|
||||
|
||||
// Genera prompt per AI background
|
||||
const aiPrompt = `Vertical event poster background, ${stile || 'modern style, vivid colors'}. Subject: ${fotoDescrizione || 'abstract artistic shapes'}. Composition: Central empty space suitable for text overlay. NO TEXT, NO LETTERS, clean illustration, high quality, 4k.`;
|
||||
|
||||
// Genera immagine AI
|
||||
const startTime = Date.now();
|
||||
const rawImageUrl = await imageGenerator.generate(provider, aiPrompt);
|
||||
const generationTime = Date.now() - startTime;
|
||||
|
||||
// Salva asset generato
|
||||
const fileName = `quick_${Date.now()}.jpg`;
|
||||
const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
|
||||
if (rawImageUrl.startsWith('data:')) {
|
||||
const base64Data = rawImageUrl.replace(/^data:image\/\w+;base64,/, '');
|
||||
await fs.writeFile(filePath, base64Data, 'base64');
|
||||
}
|
||||
|
||||
// Crea poster
|
||||
const poster = new Poster({
|
||||
templateId: template?._id,
|
||||
name: titolo,
|
||||
status: 'processing',
|
||||
content: {
|
||||
title: titolo,
|
||||
subtitle: descrizione,
|
||||
eventDate: data,
|
||||
eventTime: ora,
|
||||
location: luogo,
|
||||
contacts: contatti
|
||||
},
|
||||
assets: {
|
||||
backgroundImage: {
|
||||
sourceType: 'ai',
|
||||
url: `/uploads/ai-generated/${fileName}`,
|
||||
mimeType: 'image/jpeg',
|
||||
aiParams: {
|
||||
prompt: aiPrompt,
|
||||
provider,
|
||||
generatedAt: new Date()
|
||||
}
|
||||
}
|
||||
},
|
||||
originalPrompt: aiPrompt,
|
||||
styleUsed: stile,
|
||||
aspectRatio,
|
||||
provider,
|
||||
metadata: {
|
||||
userId: req.user._id
|
||||
}
|
||||
});
|
||||
|
||||
poster.addHistory('ai_background_generated', { provider, duration: generationTime });
|
||||
|
||||
// Render con testi sovrapposti
|
||||
await posterController._renderPoster(poster, { useQuickRender: true });
|
||||
await poster.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
posterId: poster._id,
|
||||
imageUrl: poster.renderOutput?.png?.url || rawImageUrl,
|
||||
status: poster.status
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Quick generate error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Helper interno: renderizza poster
|
||||
async _renderPoster(poster, options = {}) {
|
||||
const template = poster.templateId || poster.templateSnapshot;
|
||||
|
||||
const result = await posterRenderer.render({
|
||||
template,
|
||||
content: poster.content,
|
||||
assets: poster.assets,
|
||||
layerOverrides: Object.fromEntries(poster.layerOverrides || new Map()),
|
||||
outputDir: path.join(UPLOAD_DIR, 'posters', 'final'),
|
||||
posterId: poster._id.toString()
|
||||
});
|
||||
|
||||
poster.setRenderOutput({
|
||||
png: {
|
||||
path: result.pngPath,
|
||||
url: `/uploads/posters/final/${path.basename(result.pngPath)}`,
|
||||
size: result.pngSize
|
||||
},
|
||||
jpg: {
|
||||
path: result.jpgPath,
|
||||
url: `/uploads/posters/final/${path.basename(result.jpgPath)}`,
|
||||
size: result.jpgSize,
|
||||
quality: 95
|
||||
},
|
||||
dimensions: result.dimensions,
|
||||
duration: result.duration
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = posterController;
|
||||
41
src/controllers/posts.controller.js
Normal file
41
src/controllers/posts.controller.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// @ts-check
|
||||
const { PostModel } = require('../models/Post');
|
||||
const { pick } = require('../utils/pick');
|
||||
|
||||
async function listPosts(req, res) {
|
||||
const { limit = '3', page = '1', sort = '-date', category } = /** @type {any} */ (req.query);
|
||||
const q = {};
|
||||
if (category) q.category = category;
|
||||
|
||||
const lim = Math.min(Number(limit) || 3, 50);
|
||||
const skip = (Math.max(Number(page) || 1, 1) - 1) * lim;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
PostModel.find(q).sort(sort).skip(skip).limit(lim).lean(),
|
||||
PostModel.countDocuments(q)
|
||||
]);
|
||||
|
||||
res.set('Cache-Control', 'public, max-age=30, stale-while-revalidate=120');
|
||||
return res.json({ items, total, page: Number(page), limit: lim });
|
||||
}
|
||||
|
||||
async function createPost(req, res) {
|
||||
const data = pick(req.body, ['title','date','category','teaser','cover','to','bodyMd']);
|
||||
const doc = await PostModel.create(data);
|
||||
return res.status(201).json(doc);
|
||||
}
|
||||
|
||||
async function updatePost(req, res) {
|
||||
const data = pick(req.body, ['title','date','category','teaser','cover','to','bodyMd']);
|
||||
const doc = await PostModel.findByIdAndUpdate(req.params.id, data, { new: true });
|
||||
if (!doc) return res.status(404).json({ message: 'Post non trovato' });
|
||||
return res.json(doc);
|
||||
}
|
||||
|
||||
async function deletePost(req, res) {
|
||||
const r = await PostModel.findByIdAndDelete(req.params.id);
|
||||
if (!r) return res.status(404).json({ message: 'Post non trovato' });
|
||||
return res.status(204).send();
|
||||
}
|
||||
|
||||
module.exports = { listPosts, createPost, updatePost, deletePost };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user