Merge branch 'profile-feature'

This commit is contained in:
a-ill
2023-08-01 15:58:04 +03:00
127 changed files with 9576 additions and 680 deletions

View File

@@ -0,0 +1,180 @@
label {
font-size: 1.3rem;
font-family: var(--sans-serif);
}
.auth-pane {
position: relative;
padding: 3.4rem;
padding-top: 3.4rem;
padding-bottom: 3.4rem;
width: 30rem;
height: auto;
}
.auth-title {
position: relative;
top: 0.2rem;
margin-bottom: 2.7rem;
}
.auth-label {
display: inline-block;
margin-bottom: 0.3rem;
}
.authEmailInput, .authPasswordInput {
position: relative;
width: 100%;
border-radius: 0.34rem;
color: #353535;
height: 2.73rem;
padding-left: 0.34rem;
}
.authEmailInput {
margin-bottom: 0.682rem;
}
.auth-button {
margin-top: 1.365rem;
height: 3.412rem;
width: 100%;
font-family: var(--sans-serif,sans-serif);
font-size: 1.3rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.512rem;
filter: drop-shadow(0.068rem 0.136rem 0.068rem rgb(0 0 0 / 0.4));
}
.auth-button:active {
background-color: var(--darker-pink);
}
#email-msg,#password-msg {
display: inline;
color:red;
font-family: var(--sans-serif,sans-serif);
}
.auth-line {
margin-top: 1.5rem;
width: 100%;
height: 0.07rem;
border: 0;
border-radius: 0.1rem;
background: black;
}
.auth-methods-group {
display: grid;
grid-template-columns: auto ; /*auto auto*/
justify-content: center;
gap: 2.7rem;
margin-top: 2rem;
}
.auth-methods-group img {
height: auto;
width: 3.4rem;
}
.auth-methods-group> div {
position: relative;
border-radius: 6.8rem;
width: 3.4rem;
height: 3.4rem;
overflow: hidden;
}
#google-btn {
position: absolute;
top: -0.8rem;
left: -0.8rem;
}
#google-logo {
position: relative;
background: white;
pointer-events: none;
}
#google-btn-wrapper {
cursor: pointer;
}
#google-btn div {
position: absolute;
height: 5rem;
width: 5rem;
}
#google-btn iframe {
position: absolute;
height: 10rem;
width: 10rem;
}
.password-field {
position: relative;
}
.eye-icon {
display: block;
position: absolute;
cursor: pointer;
opacity: 0.25;
top: 2.6rem;
right: 0.8rem;
width: 1.7rem;
}
.eye-icon * {
pointer-events: none;
}
#forgot-password {
display: block;
position: relative;
margin-top: 0.5rem;
height: 2rem;
color:#5f5f5f;
margin-left: auto;
width: max-content;
font-family: var(--sans-serif);
font-size: 1.3rem;
}
#remember-me {
position: relative;
display: flex;
align-items: center;
margin-top: 1rem;
gap: 1rem;
}
#remember-me-checkbox {
min-height: 1.5rem;
min-width: 1.5rem;
flex: 0;
accent-color: var(--gray);
}
#remember-me label {
position: relative;
margin-top: -0.2rem;
color: #5f5f5f;
}
#content {
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
height: 100%;
min-height: 100vh;
}

View File

@@ -1,21 +1,7 @@
:root {
--light-blue:hsl(195, 67%, 95%);
--darker-pink:hsl(344, 60%, 47%);
--pink:hsl(344, 73%, 57%);
--dark-green:hsl(176, 63%, 25%);
--green:hsl(147, 33%, 60%);
--orange:hsl(30, 97%, 72%);
--light-orange: hsl(19, 76%, 72%);
--dark-brown:hsl(23, 47%, 20%);
--brown:hsl(23, 47%, 30%);
--light-brown: hsl(23, 47%, 50%);
--dark-pink:hsl(343, 39%, 16%);
--red:hsl(359, 72%, 61%);
--dark-blue:hsl(217, 25%, 16%);
--grey-blue:hsl(223, 13%, 22%);
--cream:hsl(34, 43%, 90%);
--dark-cream:hsl(33, 26%, 84%);
--red:#c52a28;
--gray: #5B6970;
--sans-serif: "OpenSans";
--serif: "Lora";
}
@@ -48,12 +34,10 @@ body {
#content {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
display: grid;
grid-template-rows: max-content auto max-content;
height: 100%;
min-height: 100vh;
flex-grow: 1;
}
/*---Fonts---------------------------------------------------------*/
@@ -475,9 +459,8 @@ input[type=number]::-webkit-outer-spin-button {
.pane {
background: white;
border: 0;
border: 0.1rem solid rgb(187, 187, 187);
border-radius: 0.635rem;
box-shadow: 0 0 0.314rem rgb(187, 187, 187);
}
.pane-container {

View File

@@ -0,0 +1,268 @@
/* Header */
#navbar{
position: relative;
top: 0;
width: min(100%,116rem);
z-index: 1000000000;
height: 5.26rem;
padding-left: 0rem;
padding-right: 0rem;
}
#navbar * {
font-family: var(--sans-serif, sans-serif);
}
/* Logo */
#logo-container {
display: flex;
position: absolute;
margin-left: 1rem;
height: 100%;
max-height: 5.26rem;
color: black;
z-index: 1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
}
#navbar-logo {
height: 3.5rem;
width: 3.5rem;
object-fit: contain;
border-radius: 10rem;
}
#navbar-logo-text {
position: relative;
word-wrap: normal;
height: 100%;
line-height: 400%;
font-size: 1.4rem;
color: #292222;
font-family: var(--sans-serif, sans-serif);
font-weight: 400;
padding-left: 1.2rem;
}
/* Nav menu */
#nav {
position: fixed;
width: 100%;
height: 100%;
background-color: white;
overflow: hidden;
z-index: 0;
}
#menu > li > a, .options-button {
display: block;
padding: 1.2rem;
padding-top: 1rem;
padding-bottom: 1rem;
color: black;
font-size: 1.4rem;
}
#menu > li > a:active{
}
#menu li {
list-style-type: none;
}
#nav{
max-height: 0;
/*transition: max-height .5s ease-out;*/
}
/* Menu Icon */
#hamb{
position: absolute;
cursor: pointer;
right: 0rem;
padding: 2.8rem 2rem;
z-index: 9999;
}/* Style label tag */
#hamb-line {
background: black;
display: block;
height: 2px;
position: relative;
width: 24px;
} /* Style span tag */
#hamb-line::before,
#hamb-line::after{
background: black;
content: '';
display: block;
height: 100%;
position: absolute;
transition: all .2s ease-out;
width: 100%;
}
#hamb-line::before{
top: 5px;
}
#hamb-line::after{
top: -5px;
}
#side-menu {
display: none;
} /* Hide checkbox */
/* Toggle menu icon */
#side-menu:checked ~ nav {
display: block;
max-height: 100%;
padding-top: 5.625rem;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb {
position: fixed;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb #hamb-line {
background: transparent;
}
#side-menu:checked ~ #hamb #hamb-line::before {
transform: rotate(-45deg);
top: 0;
}
#side-menu:checked ~ #hamb #hamb-line::after {
transform: rotate(45deg);
top: 0;
}
/* Options */
.options-dropdown {
position: absolute;
display: none;
top: 5.6rem;
right: 1.8rem;
border: #404040 solid 0.1rem;
background-color: white;
z-index: 10;
}
.options-dropdown button, .options-dropdown a {
display: block;
font-family: var(--sans-serif,sans-serif);
font-size: 1.2rem;
width: 100%;
padding: 1rem;
text-align: left;
}
.options-dropdown button:hover, .options-dropdown a:hover {
background-color: var(--red);
color: white;
}
.options-button {
width: 100%;
text-align: left;
}
/* Localization */
#locales {
position: relative;
}
#locales button {
width: 100%;
text-align: left;
height: 4rem;
}
#locales button:hover {
opacity: 0.5;
}
#locales-img {
position: relative;
top: 0rem;
height: 2rem;
margin-left: 1.2rem;
}
/*
#options-dropdown>:first-child {
padding-bottom: 0.5rem;
}
#options-dropdown>:nth-child(2) {
padding-top: 0.5rem;
}
*/
/* Responsiveness */
@media only screen and (min-width: 1200px) {
#navbar {
position: relative;
width: min(100%,116rem);
left: 50%;
transform: translateX(-50%);
padding-right: 4rem;
padding-left: 4rem;
}
#nav {
max-height: none;
top: 0;
position: relative;
float: right;
width: fit-content;
background-color: transparent;
overflow: visible;
}
#side-menu:checked ~ nav {
padding-top: 0;
}
#menu li {
float: left;
}
#menu > li > a:hover, .options-button:hover, #navbar-logo-text:hover {
color: rgb(127, 127, 127);
}
#menu > li > a, .options-button {
padding: 0.9rem;
padding-top: 1.9rem;
padding-bottom: 1.9rem;
}
#hamb {
display: none;
}
#locales {
position: relative;
margin-right: 1.8rem;
}
#locales-img {
top: 0.9rem;
}
}

View File

@@ -0,0 +1,55 @@
/*
#notifications-div>button {
cursor: pointer;
margin-left: 0.341rem;
}
#notifications-div>:nth-child(1) {
width: 1.706rem;
height: 1.706rem;
background-color: #ccc;
border-radius: 100%;
}
#notifications-div>:nth-child(2) {
width: 2.047rem;
height: 2.047rem;
background-color: #ccc;
border-radius: 100%;
}
#notifications-div>:nth-child(3) {
width: 2.389rem;
height: 2.389rem;
background-color: #ccc;
border-radius: 100%;
}
#notifications-div>button>div {
cursor: pointer;
margin: auto;
background-color: white;
border-radius: 100%;
}
#notifications-div>:nth-child(1)>div {
width: 1.228rem;
height: 1.228rem;
}
#notifications-div>:nth-child(2)>div {
width: 1.57rem;
height: 1.57rem;
}
#notifications-div>:nth-child(3)>div {
width: 1.843rem;
height: 1.843rem;
}
*/

View File

@@ -0,0 +1,163 @@
import {getData, sendData} from "/js/libraries/serverTools.js"
export function getUser(user,loaded,callbackOuter) {
let callback = function(response) {
Object.assign(user,JSON.parse(response))
if(callbackOuter!=undefined) {
callbackOuter()
}
loaded.update((val) => {
return val + 1
})
}
getData("/xx/get-user",callback)
}
export function changeUser(name,value,user) {
if (user[name]!=value && user[name]!=undefined) {
user[name] = value
let data = new Object();
data[name] = value
sendData("/xx/change-user",data)
}
}
export function changePasswordVisibility(button) {
let input = button.previousElementSibling
let type = input.type
if (type=="text") {
input.type = "password";
button.style.opacity = 0.25
}
else {
input.type = "text";
button.style.opacity = 1
}
}
export function checkEmail(email,msg) {
if (email.includes("@")) {
return true
}
else {
msg.innerHTML = "must contain '@'"
return false
}
}
export function checkPassword(password,msg) {
let passwordLength = password.length
if (passwordLength<8) {
msg.innerHTML = "must be 8 characters"
return false
}
let numNumbers = password.match(/\d/g)?.length || 0;
if (numNumbers<1) {
msg.innerHTML = "mush have digits"
return false
}
let numLetters = password.match(/\D/g)?.length || 0;
if (numLetters<2) {
msg.innerHTML = "must have letters"
return false
}
return true
}
export function redirectLogged() {
let callback = function(responseText) {
let response = JSON.parse(responseText)
if (response) {
window.location.href = "/";
}
}
getData("/xx/check-login",callback)
}
export function redirectNotLogged() {
let callback = function(responseText) {
let response = JSON.parse(responseText)
if (!response) {
window.location.href = "/";
}
}
getData("/xx/check-login",callback)
}
// Redirect to the landing page
export function toLandingPage(response) {
if (response!=0) {
window.location.href = "/";
}
}
// Redirect to the dashboard page
export function toDashboard() {
window.location.href = "/";
}
// Process log in
export function processLoginResponse(response,msgs,remember) {
if (response==0) {
msgs.email.innerHTML = "not found"
}
else if (response==1) {
msgs.password.innerHTML = "is wrong"
}
else {
if (remember) {
let date = new Date()
date.setMonth(date.getMonth()+1)
date = date.toUTCString()
document.cookie = "__genierememberme=; expires=" + date + "; path=/;SameSite=Lax";
}
toDashboard()
}
}
// Log in
export function login(msgs,inputs) {
msgs.email.innerHTML = ""
msgs.password.innerHTML = ""
let data = {email: inputs.email.value, password: inputs.password.value, remember: inputs.remember.checked}
sendData('/xx/login-post', data, (response) => processLoginResponse(response,msgs,inputs.remember.checked))
}
// Process sign in
function processSignupResponse(response,msgs) {
if (response) {
toDashboard()
}
else {
msgs.email.innerHTML = "already exists"
}
}
// Sign up
export function signup(msgs,inputs) {
msgs.email.innerHTML = ""
let email = inputs.email.value
let password = inputs.password.value
if (checkEmail(email,msgs.email)==false) {
return
}
if (checkPassword(password,msgs.password)==false) {
return
}
let data = {email: email, password: password}
sendData('/xx/signup-post', data, (response) => processSignupResponse(response,msgs))
}
export function confirmEmail(msg,code,callback) {
msg.innerHTML = ""
sendData('xx/confirm-email',code,callback)
}
// Log out
export function logout() {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "/logout", false ); // false for synchronous request
xmlHttp.send( null );
window.location.href = "/";
}

View File

@@ -10,7 +10,7 @@ export function translate(content, x) {
}
}
function addMarkersToLayer(g,layer,content,locale,addPinContent,markerColor) {
function addMarkersToLayer(g,layer,content,locale,addPinContent,markerColor,options) {
let {text,coordinates} = addPinContent(g,content,locale)
var markerIcon = new L.Icon({
iconUrl: 'https://www.libsoc.org/img/common/markers/marker-' + markerColor + '.png',
@@ -21,57 +21,69 @@ function addMarkersToLayer(g,layer,content,locale,addPinContent,markerColor) {
shadowSize: [41, 41]
})
let marker = L.marker(coordinates, {icon: markerIcon})
marker.id = g.id
marker.members = g.members
marker.contact = g.contact
marker.addTo(layer).bindPopup(text)
if (options.pinCallback!=undefined) {
marker.on('click', (event) => options.pinCallback(marker,event))
}
}
export function addMarkersEntries(entries,entriesByCountry,map,content,locale,addPinContent,markerColor) {
export function addMarkersEntries(entries,entriesByCountry,map,content,locale,addPinContent,markerColor,options) {
let entriesMarkersLayer = L.layerGroup()
let entriesMarkersLayerOut = L.layerGroup()
let entriesMarkersLayerIn = L.layerGroup()
for (let g of entries) {
if (g.country!="Online" && g.country!="Worldwide") {
addMarkersToLayer(g,entriesMarkersLayerIn,content,locale,addPinContent,markerColor)
addMarkersToLayer(g,entriesMarkersLayerIn,content,locale,addPinContent,markerColor,options)
}
}
for (let gs of Object.values(entriesByCountry)) {
if (gs.length==1) {
let g = {...gs[0]}
g.country = [g.country]
if (g.country!="Online" && g.country!="Worldwide") {
addMarkersToLayer(g,entriesMarkersLayerOut,content,locale,addPinContent,markerColor)
if (options.enableCountryGrouping) {
for (let gs of Object.values(entriesByCountry)) {
if (gs.length==1) {
let g = {...gs[0]}
g.country = [g.country]
if (g.country!="Online" && g.country!="Worldwide") {
addMarkersToLayer(g,entriesMarkersLayerOut,content,locale,addPinContent,markerColor,options)
}
}
}
else {
if (gs[0].country!="Online" && gs[0].country!="Worldwide") {
let locationName = gs[0].country
let locationCoordinates = [0,0]
let members = 0
let contact = gs[0].contact
for (let g of gs) {
locationCoordinates[0] += g.latitude
locationCoordinates[1] += g.longitude
members += g.members
if (g.contact[0]!=gs[0].contact[0]) {
contact = contactGeneral
else {
if (gs[0].country!="Online" && gs[0].country!="Worldwide") {
let locationName = gs[0].country
let locationCoordinates = [0,0]
let members = 0
let contact = gs[0].contact
for (let g of gs) {
locationCoordinates[0] += g.latitude
locationCoordinates[1] += g.longitude
members += g.members
if (g.contact[0]!=gs[0].contact[0]) {
contact = contactGeneral
}
}
locationCoordinates[0] = locationCoordinates[0]/gs.length
locationCoordinates[1] = locationCoordinates[1]/gs.length
let gNew = {
country: locationName,
latitude: locationCoordinates[0],
longitude: locationCoordinates[1],
members: members,
contact: contact
}
addMarkersToLayer(gNew,entriesMarkersLayerOut,content,locale,addPinContent,markerColor,options)
}
locationCoordinates[0] = locationCoordinates[0]/gs.length
locationCoordinates[1] = locationCoordinates[1]/gs.length
let gNew = {
country: locationName,
latitude: locationCoordinates[0],
longitude: locationCoordinates[1],
members: members,
contact: contact
}
addMarkersToLayer(gNew,entriesMarkersLayerOut,content,locale,addPinContent,markerColor)
}
}
entriesMarkersLayerOut.addTo(entriesMarkersLayer)
map.on("zoomend", () => onZoomEnd(map,entriesMarkersLayer,entriesMarkersLayerOut,entriesMarkersLayerIn))
}
else {
entriesMarkersLayerIn.addTo(entriesMarkersLayer)
}
entriesMarkersLayerOut.addTo(entriesMarkersLayer)
entriesMarkersLayer.addTo(map)
map.on("zoomend", () => onZoomEnd(map,entriesMarkersLayer,entriesMarkersLayerOut,entriesMarkersLayerIn))
return entriesMarkersLayer
}

View File

@@ -7,5 +7,7 @@
"communes": "Communes",
"cooperatives": "Cooperatives",
"parties": "Parties",
"partners": "Partners"
"partners": "Partners",
"login": "Login",
"profile": "Profile"
}

View File

@@ -10,6 +10,7 @@ import watch from "rollup-plugin-watch";
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;

View File

@@ -0,0 +1,129 @@
<svelte:options tag="auth-component" />
<script>
// Import statements
import { onMount, setContext,getContext } from 'svelte'
import { sendText } from "/js/libraries/serverTools.js"
import * as AuthTools from "/js/libraries/authTools.js"
import "/js/components/login-component.js"
import "/js/components/signup-component.js"
// Main code
AuthTools.redirectLogged()
let loginComponent
let signupComponent
let context = {
googleInit: false
}
setContext("auth",context)
function switchFocus(component) {
if (component==loginComponent) {
loginComponent.focused = true
signupComponent.focused = false
}
else {
loginComponent.focused = false
signupComponent.focused = true
}
}
function callbackGoogle(data) {
console.log(data)
sendText("/signup-google",data.credential,(response) => AuthTools.processLoginResponse(response,context.msgs,context.remember.checked))
}
function initGoogle() {
if (typeof google != 'undefined') {
google.accounts.id.initialize({
client_id: '93612176787-sr8qjqem4e3kok4msrnj8s1illt85a9g.apps.googleusercontent.com',
callback: callbackGoogle,
auto_select: true,
context: "signin"
})
context.googleInit = true
}
else {
setTimeout(initGoogle,100)
}
}
initGoogle()
</script>
<div id="auth-group">
<div id="auth-grid-group">
<login-component bind:this={loginComponent} on:click={() => switchFocus(loginComponent)} on:keydown={() => ""}></login-component>
<signup-component bind:this={signupComponent} on:click={() => switchFocus(signupComponent)} on:keydown={() => ""}></signup-component>
</div>
<div id="auth-or" class="pane">
<span>OR</span>
</div>
</div>
<style>
@import '/css/common.css';
@import '/css/auth.css';
span {
font-size: 1.4rem;
font-family: var(--sans-serif,sans-serif);
}
#auth-group {
margin: auto;
width: auto;
margin-bottom: 3rem;
}
#auth-grid-group {
display: grid;
grid-template-columns: 30rem 30rem;
justify-content: center;
gap: 1.37rem;
width: 100%;
}
#auth-or {
display: flex;
position: absolute;
margin: auto;
top: 40%;/*40%;*/
left: 50%;
transform: translate(-50%, -50%);
width: 5.4rem;
height: 5.4rem;
border-radius: 6.8rem;
background-color: white;
align-items:center;
justify-content:center;
font-family: var(--sans-serif,sans-serif);
font-weight: 500;
}
@media only screen and (max-width: 1200px) {
#auth-grid-group {
display: grid;
grid-template-columns: 30rem;
grid-template-rows: auto auto;
justify-content: center;
gap: 1.37rem;
width: 100%;
}
#auth-or {
top: 40rem;/*46.4rem;*/
}
#auth-group {
margin-top: 2rem;
margin-bottom: 3rem;
}
}
</style>

View File

@@ -0,0 +1,131 @@
<svelte:options tag="confirmation-component" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Export statements
// Main code
let confirmationInputs = []
let confirmationMsg
let confirmationButton
function onlyNumberKey(ind,evt) {
// Only ASCII character in that range allowed
var value = evt.data
if (value in ["0","1","2","3","4","5","6","7","8","9"]) {
if (ind<4) {
confirmationInputs[ind+1].focus()
}
else {
AuthTools.confirmEmail(confirmationMsg,getCode(),callback)
}
}
else {
confirmationInputs[ind].value = ""
}
}
function getCode() {
let code = ""
for (let input of confirmationInputs) {
code += input.value
}
return parseInt(code)
}
function callback(response) {
if (response=="true") {
AuthTools.toDashboard()
}
else {
confirmationMsg.innerHTML = "Wrong code"
}
}
onMount(() => {
})
</script>
<div class="pane auth-pane">
<h2 class="auth-title title-highlight">CONFIRMATION CODE</h2>
<div id="confirmationInputs">
<input bind:this={confirmationInputs[0]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(0,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[1]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(1,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[2]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(2,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[3]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(3,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[4]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(4,evt)}>
</div>
<span bind:this={confirmationMsg} id="confirmation-msg"></span>
<button bind:this={confirmationButton} class="auth-button" on:click="{() => AuthTools.confirmEmail(confirmationMsg,getCode(),callback)}">Confirm</button>
</div>
<style>
@import '/css/common.css';
.auth-pane {
position: relative;
padding: 3.4rem;
padding-top: 5.5rem;
padding-bottom: 5.5rem;
width: 33rem;
height: auto;
margin: auto;
}
.auth-title {
position: relative;
left: 0.7rem;
top: 0.2rem;
margin-bottom: 1.4rem;
}
.authConfirmationInput {
position: relative;
width: 3.16rem;
font-family: var(--serif,serif);
font-size: 3rem;
border-radius: 0.34rem;
margin-bottom: 0.7rem;
text-align: center;
padding-left: 0;
padding-bottom: 0.3 rem;
}
.dash {
display: block;
font-size: 3rem;
font-family: var(--serif,serif);
}
#confirmationInputs {
margin: auto;
display: grid;
justify-content: space-between;
grid-auto-flow: column;
}
.auth-button {
margin-top: 1.4rem;
height: 3.4rem;
width: 100%;
font-family: var(--sans-serif,sans-serif);
font-size: 1.6rem;
color: white;
background-color: var(--pink);
border-color: var(--pink);
border-radius: 0.5rem;
filter: drop-shadow(0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
}
#confirmation-msg {
display: inline;
color:red;
}
</style>

View File

@@ -0,0 +1,110 @@
<svelte:options tag="login-component" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Export statements
export let focused = false
// Main code
let emailInput
let passwordInput
let inputs
let passwordVisibilityButton
let emailMsg
let passwordMsg
let msgs
let rememberMe
let googleButton
let parentProps = getContext("auth")
function renderGoogle() {
if (parentProps.googleInit) {
google.accounts.id.renderButton(googleButton,{
theme: 'outline',
size: 'large'
})
let iframe = googleButton.getElementsByTagName('iframe')[0]
iframe.style.height = "5rem"
iframe.style.width = "5rem"
}
else {
setTimeout(renderGoogle,100)
}
}
onMount(() => {
rememberMe.checked = true
inputs = {email: emailInput, password: passwordInput, remember: rememberMe}
msgs = {email: emailMsg, password: passwordMsg}
parentProps.msgs = msgs
parentProps.remember = rememberMe
parentProps.loginGoogle = googleButton
document.addEventListener("keypress", function(event) {
if (event.code == "Enter") {
if (focused) {
AuthTools.login(msgs,inputs)
}
}
})
renderGoogle()
})
</script>
<div id="login-group"class="pane auth-pane">
<h2 class="auth-title">LOG IN</h2>
<label class="auth-label" for="emailInput">Email&nbsp;</label><span bind:this={emailMsg} id="email-msg"></span>
<input bind:this={emailInput} id="emailInput" class="authEmailInput" type="email">
<div class="password-field">
<label class="auth-label" for="passwordInput">Password&nbsp;</label><span bind:this={passwordMsg} id="password-msg"></span>
<input bind:this={passwordInput} id="passwordInput" class="authPasswordInput" type="password">
<button bind:this={passwordVisibilityButton} class="eye-icon" on:click="{() => AuthTools.changePasswordVisibility(passwordVisibilityButton)}">
<object type="image/svg+xml" data="/img/auth/eye_icon.svg" title="eye icon"></object>
</button>
</div>
<div id="remember-me">
<input bind:this={rememberMe} type="checkbox" id="remember-me-checkbox"><label id="remember-me-label" for="passwordInput">remember me</label>
</div>
<button class="auth-button" on:click="{() => AuthTools.login(msgs,inputs)}">Log in</button>
<a id="forgot-password" href="forgot-password">Forgot password?</a>
<!--
<hr class="auth-line">
<div class="auth-methods-group">
<div id="google-btn-wrapper">
<div bind:this={googleButton} id="google-btn"></div>
<img src="/img/auth/google_icon.svg" id="google-logo" alt="google icon">
</div>
<button on:click={openGoogleWindow}>
<img src="img/auth/google_icon.svg" id="navbar-logo" alt="google icon">
</button>
<button onclick="">
<img src="img/auth/facebook_icon.svg" id="navbar-logo" alt="facebook icon">
</button>
<button onclick="">
<img src="img/auth/linkedin_icon.svg" id="navbar-logo" alt="linkedin icon">
</button>
</div>
-->
</div>
<style>
@import '/css/common.css';
@import '/css/auth.css';
</style>

View File

@@ -0,0 +1,243 @@
<svelte:options tag="signup-component" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Export statements
export let focused = false
// Main code
let signupGroup
let emailInput
let passwordInput
let passwordVisibilityButton
let inputs
let googleButton
let emailMsg
let passwordMsg
let msgs
let rememberMe
let dialog
let signUp
let signUpField
let parentProps = getContext("auth")
function removeMsg(msg) {
if (msg.innerHTML!="") {
msg.innerHTML = ""
}
}
function showDialog() {
dialog.style.display = "block"
}
function hide() {
if (dialog!=null) {
dialog.style.display = "none";
}
}
function sendEmail() {
let email = signUpField.value
if (email.includes("@")) {
sendText("/get-email",email)
signUpField.value = ""
signUpField.placeholder = "Subscribed!"
signUpField.style.setProperty("--c", "hsl(147, 33%, 60%)")
}
else {
signUpField.value = ""
signUpField.placeholder = "must contain '@'"
signUpField.style.setProperty("--c", "hsl(0, 100%, 60%)")
}
}
function clearField() {
signUpField.placeholder = ""
}
function renderGoogle() {
if (parentProps.googleInit) {
google.accounts.id.renderButton(googleButton,{
theme: 'outline',
size: 'large'
})
}
else {
setTimeout(renderGoogle,100)
}
}
onMount(() => {
rememberMe.checked = true
inputs = {email: emailInput, password: passwordInput}
msgs = {email: emailMsg, password: passwordMsg}
document.addEventListener("keypress", function(event) {
if (event.code == "Enter") {
if (focused) {
AuthTools.signup(msgs,inputs,toLandingPage)
}
}
})
//renderGoogle()
})
</script>
<div id="signup-group" class="pane auth-pane" bind:this={signupGroup}>
<h2 class="auth-title">SIGN UP</h2>
<label class="auth-label" for="emailInput">Email&nbsp;</label><span bind:this={emailMsg} id="email-msg" on:change={() => removeMsg(emailMsg)}></span>
<input bind:this={emailInput} id="emailInput" class="authEmailInput" type="email">
<div class="password-field">
<label class="auth-label" for="emailInput">Password&nbsp;</label><span bind:this={passwordMsg} id="password-msg"></span>
<input bind:this={passwordInput} id="passwordInput" class="authPasswordInput" type="password" on:change={() => removeMsg(passwordMsg)}>
<button bind:this={passwordVisibilityButton} class="eye-icon" on:click="{() => AuthTools.changePasswordVisibility(passwordVisibilityButton)}">
<object type="image/svg+xml" data="/img/auth/eye_icon.svg" title="eye-icon"></object>
</button>
</div>
<div id="remember-me">
<input bind:this={rememberMe} type="checkbox" id="remember-me-checkbox"><label id="remember-me-label" for="passwordInput">remember me</label>
</div>
<button class="auth-button" on:click="{showDialog}">Sign up</button> <!--() => AuthTools.signup(msgs,inputs,AuthTools.toLandingPage)-->
<p id="forgot-password"></p>
<!--
<hr class="auth-line">
<div class="auth-methods-group">
<button on:click="{showDialog}">
<img src="/img/auth/google_icon.svg" id="navbar-logo" alt="google icon">
</button>
<button onclick="">
<img src="img/auth/facebook_icon.svg" id="navbar-logo" alt="facebook icon">
</button>
<button onclick="">
<img src="img/auth/linkedin_icon.svg" id="navbar-logo" alt="linkedin icon">
</button>
</div>
-->
</div>
<div bind:this={dialog} id="dialog">
<button id="shadow" on:click={hide}></button>
<div id="wrapper" class="pane">
<h2>Registration is closed</h2>
<p>We are still in the process of opening.</p>
<p>Sign up for updates to know when it becomes available:</p>
<div id="newsletter-container">
<input bind:this={signUpField} on:click={clearField} id="newsletterEmailInput" type="text">
<button bind:this={signUp} on:click={sendEmail} id="newsletterEmailButton">sign up</button>
</div>
<button id="no-button" on:click={hide}>No thanks</button>
</div>
</div>
<style>
@import '/css/common.css';
@import '/css/auth.css';
#dialog {
display: none;
}
#wrapper p {
text-align: justify;
}
#wrapper h2 {
text-align: center;
margin-bottom: 1rem;
}
#shadow {
position: fixed;
cursor: default;
top: 50%; right: 50%;
transform: translate(50%,-50%);
width: 100vw;
height: 100vh;
background:rgb(0, 0, 0, 0.2);
z-index: 999999;
}
#newsletter-container {
position: relative;
height: 3rem;
width: 100%;
margin-top: 1rem;
display: flex;
flex-direction: row;
}
#newsletterEmailInput {
height: 2.5rem;
border-radius: 0.2rem 0 0 0.2rem;
filter: drop-shadow( 0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
flex-grow: 1;
}
#newsletterEmailInput::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: var(--c,gray);
opacity: 1; /* Firefox */
}
#newsletterEmailButton {
width: 6.8rem;
height: 2.5rem;
background: var(--pink);
color: #ffffff;
font-family: var(--sans-serif,sans-serif);
font-size: 1.4rem;
border-radius: 0 0.2rem 0.2rem 0;
filter: drop-shadow( 0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
}
#newsletterEmailButton:active {
background: var(--darker-pink);
}
#wrapper {
top: 50%; right: 50%;
transform: translate(50%,-50%);
position: fixed;
max-width: 36rem;
width: 90vw;
padding: 2rem 4rem;
z-index: 1999999;
}
#wrapper * {
font-family: var(--sans-serif);
}
#no-button {
position: relative;
left: 50%;
transform: translateX(-50%);
width: 13rem;
height: 3rem;
margin-top: 2rem;
margin-bottom: 0.5rem;
background: #ffffff;
border: 0.2rem solid var(--pink);
font-family: var(--sans-serif,sans-serif);
font-size: 1.4rem;
border-radius: 0.5rem;
filter: drop-shadow( 0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
}
#no-button:active {
background: hsl(343, 23%, 82%);
}
</style>

View File

@@ -43,7 +43,10 @@
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addCommunePinContent,"red")
let options = {
enableCountryGrouping: true,
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addCommunePinContent,"red",options)
}
function getCountry(x) {

View File

@@ -9,6 +9,7 @@
// Export statements
export let callback = null
export let colors = null
export let map = null
// Main code
let mapContainer

View File

@@ -71,17 +71,17 @@
<div bind:this={root} id="root" class="pane-centering">
<div class="pane-container">
<div id="sidebars-left" class="sidebar">
<div bind:this={sidebarLeft} id="sidebar-left" class="pane">
<div bind:this={sidebarLeft} id="sidebar-left">
<slot name="sidebar-left"></slot>
</div>
<div bind:this={sidebarLeft2} id="sidebar-left2" class="pane">
<div bind:this={sidebarLeft2} id="sidebar-left2">
<slot name="sidebar-left2"></slot>
</div>
</div>
<div bind:this={sidebarRight} id="sidebar-right" class="pane sidebar">
<slot name="sidebar-right"></slot>
</div>
<div bind:this={mainPane} id="main-pane" class="pane">
<div bind:this={mainPane} id="main-pane">
<slot name="main" id="main-slot"></slot>
</div>
</div>
@@ -95,7 +95,10 @@
}
#root {
position: relative;
margin-top: auto;
min-height: var(--min-height,auto);
height: 100%;
}
#main-pane {
@@ -105,10 +108,8 @@
padding-top: var(--padding-top,0rem);
padding-bottom: var(--padding-bottom,0rem);
text-align: justify;
background: var(--background,white);
box-shadow: var(--box-shadow,0 0 0.314rem rgb(187, 187, 187));
margin: auto;
height: min-content;
height: 100%;
max-width: var(--width-main,66rem);
width: var(--width-main,66rem);
z-index: 1;
@@ -124,13 +125,14 @@
flex-direction: column;
gap: 1rem;
margin-left: calc(-1*var(--width-left,22.5rem) - 1rem - 4rem);
width: calc(var(--width-left,22.5rem) + 4rem);
width: max-content;
max-width: 30rem;
}
#sidebar-left,#sidebar-left2 {
position: relative;
background-color: white;
padding: 2rem 2rem;
padding: 0rem 0rem;
}
#sidebar-left {
@@ -149,16 +151,16 @@
padding: 2rem 2rem;
}
@media only screen and (max-width: 1880px) {
@media only screen and (max-width: 1340px) {
#main-pane {
max-width: initial;
width: 100%;
max-width: var(--width-main,66rem);
padding-left: var(--padding-left-mobile,1.8rem);
padding-right: var(--padding-right-mobile,1.8rem);
padding-top: var(--padding-top-mobile,1.8rem);
padding-bottom: var(--padding-bottom-mobile,1.8rem);
padding-left: var(--padding-left-mobile,0rem);
padding-right: var(--padding-right-mobile,0rem);
padding-top: var(--padding-top-mobile,0rem);
padding-bottom: var(--padding-bottom-mobile,0rem);
}
#sidebars-left, #sidebar-right {

View File

@@ -43,7 +43,10 @@
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addCoopPinContent,"blue")
let options = {
enableCountryGrouping: true,
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addCoopPinContent,"blue",options)
}
function getCountry(x) {

View File

@@ -32,8 +32,8 @@
<button on:click={() => {location.href='#'}} id="footer-up" aria-label="go up">
<svg xmlns="http://www.w3.org/2000/svg" width="42.545" height="72.601" viewBox="0 0 42.545 72.601">
<g id="Group_268" data-name="Group 268" transform="translate(-6.177 -2.399)">
<rect id="Rectangle_146" data-name="Rectangle 146" width="11" height="51" rx="5.5" transform="translate(22 24)" fill="#cb1816"/>
<path id="Path_1145" data-name="Path 1145" d="M23.814,4.021a5,5,0,0,1,7.372,0l16.134,17.6c2.94,3.207,1.046,10.4-3.686,8.379S28.02,14.081,28.391,13.524,16.544,27.976,11.366,30,4.741,24.828,7.68,21.621Z" fill="#DD1C1A"/>
<rect id="Rectangle_146" data-name="Rectangle 146" width="11" height="51" rx="5.5" transform="translate(22 24)" fill="var(--red)"/>
<path id="Path_1145" data-name="Path 1145" d="M23.814,4.021a5,5,0,0,1,7.372,0l16.134,17.6c2.94,3.207,1.046,10.4-3.686,8.379S28.02,14.081,28.391,13.524,16.544,27.976,11.366,30,4.741,24.828,7.68,21.621Z" fill="var(--red)"/>
</g>
</svg>
</button>
@@ -54,8 +54,8 @@ footer {
bottom: 0;
width: 100%;
height: auto;
background: #5B6970;/*var(--dark-green);*/
border-top: #cb1816 solid 0.5rem;
background: var(--gray);
border-top: var(--red) solid 0.5rem;
}
footer p, footer a {

View File

@@ -2,19 +2,26 @@
<script>
// Import statements
import { onMount } from 'svelte'
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store';
import { loadLocaleContent, getData, sendData } from "/js/libraries/serverTools.js"
import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
// Import components
import "/js/components/map-component.js"
// Export statements
export let map = null
// Main code
let loaded = writable(0)
let content = writable({})
let entries
let entriesByCountry
let userData
let buttonsGroupMember
let buttonsNotGroupMember
let callback = (response) => {
entries = JSON.parse(response)
@@ -41,11 +48,32 @@
let confirmationMsg
let addressInput
let contactInput
let addressVec
let userPinLat = 0
let membersInput
let addressVec = ["","",""]
let userPinData = {
}
let userPinLng = 0
let userPin = createPin(0,0)
userPin.setOpacity(0)
let modeButtons = []
let context = getContext("profile-component")
let closeGroupsAdd = context.closeGroupsAdd
let maps = context.maps
let onLoadedGroups = context.onLoadedGroups
let userGroups = context.userGroups
let user = context.user
let has_group = userGroups.length!=0
let mode = has_group ? 2 : 0
let pendingGroup
if (has_group) {
pendingGroup= userGroups[0].status!=undefined
if (pendingGroup) {
mode = 3
}
}
let locale = loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"countries",loaded)
@@ -105,23 +133,25 @@
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
if (address!=undefined) {
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressVec = [country,state,city]
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressVec = [country,state,city]
}
getData(url,callback)
}
@@ -153,85 +183,180 @@
return {text,coordinates}
}
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green")
map = createMap([22, 0],2)
maps["groupsAdd"] = map
let options = {
enableCountryGrouping: false,
pinCallback: pinCallback
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green",options)
userPin.addTo(map)
map.on('click', function(event) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinLat = lat
userPinLng = lng
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
if (mode==0) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinData["latitude"] = lat
userPinData["longitude"] = lng
userPinData["id"] = null
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
}
})
}
function updateConfirmationMsg(response) {
if (response!==false) {
confirmationMsg.innerHTML = "You have been added to our database! Now go to our Discord to verify yourself."
if (mode==0 && !user.verified) {
confirmationMsg.innerHTML = "You have been added to our database! Now go to our Discord to verify yourself."
}
else {
confirmationMsg.innerHTML = "Success!"
}
confirmationMsg.style.color = "green"
if (mode==0 || mode==1) {
userGroups[0] = {}
}
userGroups[0].country = addressVec[0]=="" ? null : addressVec[0]
userGroups[0].state = addressVec[1]=="" ? null : addressVec[1]
userGroups[0].town = addressVec[2]=="" ? null : addressVec[2]
userGroups[0].members = userPinData["members"]
onLoadedGroups()
}
else {
confirmationMsg.innerHTML = "Something went wrong."
confirmationMsg.style.color = "red"
}
}
function submitLocation() {
if (addressVec!=undefined) {
let data = {
if (addressVec[0]!="" || mode==3) {
let membersVal, contactVal
if (mode==0) { // Create
membersVal = membersInput.value
contactVal = contactInput.value
}
else if (mode==1) { // Join
contactVal = contactInput.value
}
else if (mode==2 || mode==3) { // Move
membersVal = ""
contactVal = ""
}
else if (mode==3) { // Leave
membersVal = ""
contactVal = ""
addressVec = [null,null,null]
userPinData["latitude"] = null
userPinData["longitude"] = null
}
userData = {
country: addressVec[0],
state: addressVec[1],
town: addressVec[2],
latitude: userPinLat,
longitude: userPinLng,
contact: contactInput.value
latitude: userPinData["latitude"],
longitude: userPinData["longitude"],
contact: contactVal=="" ? null : contactVal,
members: membersVal=="" ? null : parseInt(membersVal),
group_id: userPinData["id"],
mode: mode
}
if (data.state=="") {
data.state = null
if (userData.state=="") {
userData.state = null
}
if (data.town=="") {
data.town = null
}
if (data.contact=="") {
data.contact = null
if (userData.town=="") {
userData.town = null
}
let url = "/" + locale + "/groups-add-post/"
sendData(url,data,updateConfirmationMsg)
sendData(url,userData,updateConfirmationMsg)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
function pinCallback(marker,event) {
if (mode==1) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinData["latitude"] = lat
userPinData["longitude"] = lng
userPinData["id"] = marker.id
userPinData["members"] = marker.members
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
}
}
function chooseButton(index) {
for (let b of modeButtons) {
if (b!=undefined) {
b.style.background = "rgba(197, 43, 40, 0.319)"
b.style.color = "black"
}
}
modeButtons[index].style.background = "rgb(197, 43, 40)"
modeButtons[index].style.color = "white"
mode = index
}
function getAddress(g) {
if (g!=undefined) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
else {
return "Create or join group"
}
}
function onLoaded() {
if ($loaded==3) {
chooseButton(mode)
if (mode==2 || mode==3) {
addressInput.value = getAddress(userGroups[0])
}
}
else {
let f = () => onLoaded()
setTimeout(f, 100)
}
}
onMount(() => {
onLoaded()
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<button class="close-button" on:click={closeGroupsAdd}></button>
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
<h1>Add a Group</h1>
<img id="groups-img" src="/img/common/groups.svg" alt="groups">
<p class="description">If there are no groups in your town with whom you can organize then do the following:</p>
<ol>
<li>Click on the map to show us where you are located;</li>
<li>Add a way to contact you or leave blank for a pin to point to our discord;</li>
<li>Press "Submit" to add yourself to our map;</li>
<li>Verify yourself by having a chat with us at our Discord server to show on the map;</li>
</ol>
{#if !has_group}
<div bind:this={buttonsNotGroupMember} id="button-line">
<button bind:this={modeButtons[0]} on:click={() => chooseButton(0)}>Create</button>
<button bind:this={modeButtons[1]} on:click={() => chooseButton(1)}>Join</button>
</div>
{:else if has_group && !pendingGroup}
<div bind:this={buttonsGroupMember} id="button-line">
<button bind:this={modeButtons[2]} on:click={() => chooseButton(2)} style={"display: " + (pendingGroup ? "none" : "initial")}>Move</button>
<button bind:this={modeButtons[3]} on:click={() => chooseButton(3)}>Leave</button>
</div>
{:else}
<div bind:this={buttonsGroupMember} id="button-line">
<button bind:this={modeButtons[3]} on:click={() => chooseButton(3)}>Leave</button>
</div>
{/if}
<div id="address-input-wrapper" class="input-label-wrapper">
<label for="address-input">Location: </label>
<div class="input-wrapper">
@@ -239,16 +364,30 @@
<div class="ghost-input"></div>
</div>
</div>
<div class="input-label-wrapper">
<label for="contact-input">Contact: </label>
<div class="input-wrapper">
<input bind:this={contactInput} on:input={() => resizeInput(contactInput)} id="contact-input" type="text">
<div class="ghost-input"></div>
</div>
</div>
{#key mode}
{#if mode==0}
<div id="members-input-wrapper" class="input-label-wrapper">
<label for="members-input">Members: </label>
<div class="input-wrapper">
<input bind:this={membersInput} id="members-input" type="text" value={1}>
</div>
</div>
{/if}
{#if mode==0 || mode==1}
<div class="input-label-wrapper">
<label for="contact-input">Contact: </label>
<div class="input-wrapper">
<input bind:this={contactInput} on:input={() => resizeInput(contactInput)} id="contact-input" type="text">
<div class="ghost-input"></div>
</div>
</div>
{/if}
{/key}
<button id="submit-button" on:click={submitLocation}>Submit</button>
<p id="confirmation-msg" bind:this={confirmationMsg}></p>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
{#if !(has_group && pendingGroup)}
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
{/if}
</div>
</div>
{/if}
@@ -257,13 +396,75 @@
<style>
@import '/css/common.css';
#button-line {
position: relative;
width: fit-content;
margin: auto;
margin-top: 1.5rem;
}
#button-line button{
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
padding: 1rem 0;
width: 7rem;
}
#button-line :first-child {
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
margin-right: 0.1rem;
}
#button-line :last-child {
margin-left: 0.1rem;
border-top-right-radius: 1rem;
border-bottom-right-radius: 1rem;
}
.close-button {
position: absolute;
top: 2rem;
right: 0rem;
width: 2rem;
height: 2rem;
border: none;
cursor: pointer;
z-index: 2;
}
.close-button:hover {
opacity: 0.7;
}
.close-button::before,
.close-button::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 0.2rem;
height: 2rem;
background-color: #000;
border-radius: 1rem;
}
.close-button::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.close-button::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
#confirmation-msg {
margin-top: 0.5rem;
margin-bottom: 2rem;
}
ol li {
margin-left: 3rem;
margin-left: 1rem;
margin-bottom: 0.5rem;
}
@@ -273,7 +474,7 @@
font-size: 1.15rem;
line-height: 160%;
color: #222222;
width: 5.5rem;
width: 6rem;
}
input, .ghost-input {
@@ -296,6 +497,14 @@
margin-bottom: 1rem;
}
#members-input-wrapper {
margin-bottom: 1rem;
}
#members-input {
width: 5rem;
}
.ghost-input {
display: block;
visibility: hidden;
@@ -339,42 +548,6 @@
color: white;
}
#add-prompt {
margin-bottom: 2rem;
}
#groups-img {
position: absolute;
width: 14rem;
left: 50%;
transform: translate(-50%);
z-index: 0;
opacity: 0.2;
}
#text-container>:nth-child(3) {
margin-top: 8rem;
}
.country-name {
margin-bottom: 0.5rem;
}
.country-block {
margin-bottom: 2rem;
}
.location-info {
margin-bottom: 0.75rem;
}
.location-info p {
margin-bottom: 0;
}
a {
color: #DD1C1A;
}
#map {
--height: 30rem;
@@ -389,16 +562,6 @@
margin: auto;
}
h1 {
margin-bottom: 1rem;
font-size: 2.2rem;
text-align: center;
}
h3 {
margin-bottom: 1rem;
}
#container {
margin: auto;
max-width: 800px;

View File

@@ -62,10 +62,13 @@
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
let groupsMarkersLayer = addMarkersEntries(entries["groups"],entriesByCountry["groups"],map,content,locale,addGroupPinContent,"green")
let communesMarkersLayer = addMarkersEntries(entries["communes"],entriesByCountry["communes"],map,content,locale,addCommunePinContent,"red")
let coopsMarkersLayer = addMarkersEntries(entries["cooperatives"],entriesByCountry["cooperatives"],map,content,locale,addCoopPinContent,"blue")
let partiesMarkersLayer = addMarkersEntries(entries["parties"],entriesByCountry["parties"],map,content,locale,addPartyPinContent,"gold")
let options = {
enableCountryGrouping: true,
}
let groupsMarkersLayer = addMarkersEntries(entries["groups"],entriesByCountry["groups"],map,content,locale,addGroupPinContent,"green",options)
let communesMarkersLayer = addMarkersEntries(entries["communes"],entriesByCountry["communes"],map,content,locale,addCommunePinContent,"red",options)
let coopsMarkersLayer = addMarkersEntries(entries["cooperatives"],entriesByCountry["cooperatives"],map,content,locale,addCoopPinContent,"blue",options)
let partiesMarkersLayer = addMarkersEntries(entries["parties"],entriesByCountry["parties"],map,content,locale,addPartyPinContent,"gold",options)
let overlayMaps = {}
overlayMaps[content.groups] = groupsMarkersLayer
@@ -163,7 +166,7 @@
font-family: var(--sans-serif,sans-serif);
width: 14rem;
line-height: 4rem;
background: #cb1816;
background: var(--red);
color: white;
text-align: center;
}

View File

@@ -0,0 +1,151 @@
<svelte:options tag="navbar-logged" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store'
import { loadLocaleContent, locales } from "/js/libraries/serverTools.js"
// Main code
let hambInput
let navbar
let localesDropdown
let initiativesDropdown
let loaded = writable(0)
let content = writable({})
let logoText
let locale = loadLocaleContent(content,"navbar-component",loaded)
function changeNavbar() {
if (hambInput.checked) {
navbar.style.background = "white"
//navbar.style.boxShadow = "0 0 0.314rem rgb(187, 187, 187)"
}
else {
setTimeout(()=> {
navbar.style.position = "relative"
navbar.style.background = ""
navbar.style.boxShadow = ""
}
,510)
}
}
function showDropdown(dropdown) {
let state = dropdown.style.display
initiativesDropdown.style.display = "none"
localesDropdown.style.display = "none"
if (state=="block") {
dropdown.style.display = "none"
}
else {
dropdown.style.display = "block"
}
}
function changeLocale(lang) {
localStorage.setItem("locale",lang)
let locSplit = location.href.split("/")
let localesSymbols = Object.keys(locales)
locSplit = locSplit.filter(x => !localesSymbols.includes(x))
let loc = locSplit.slice(0,locSplit.length-1).join("/") + "/" + lang + "/" + locSplit[locSplit.length-1]
location.href = loc
}
function fixHeading() {
if (locale=="ru") {
let func = () => {
if (logoText==undefined) {
setTimeout(func,100)
}
else {
if (((window.innerWidth < 1700 && window.innerWidth > 1400) || window.innerWidth < 400) && logoText.style.lineHeight!="100%") {
logoText.style.lineHeight = "120%"
logoText.style.top = "1rem"
logoText.style.width = "16rem"
}
else if (((window.innerWidth > 1700) || (window.innerWidth > 400 && window.innerWidth < 1400)) && logoText.style.lineHeight!="400%") {
logoText.style.lineHeight = "400%"
logoText.style.top = "0rem"
logoText.style.width = "auto"
}
}
}
func()
addEventListener("resize", func)
}
}
function hide(dropdown) {
let callback = () => {
dropdown.style.display = "none"
}
setTimeout(callback,100)
}
onMount(() => {
fixHeading()
})
</script>
<!-- Navigation bar -->
{#key loaded}
{#if Object.keys($content).length!=0}
<header bind:this={navbar} id="navbar">
<!-- Hamburger icon -->
<input bind:this={hambInput} type="checkbox" id="side-menu" on:click={changeNavbar}>
<label id="hamb" for="side-menu"><span id="hamb-line"></span></label>
<!-- Logo -->
<a id=logo-container href={"/" + locale + "/"}>
<img src="/img/common/flag.png" id="navbar-logo" alt="logo">
<span bind:this={logoText} id="navbar-logo-text" >{@html $content.orgName}</span>
</a>
<!-- Menu -->
<nav id="nav">
<ul id="menu">
<li><a href={"/"+locale+"/join-us"}>{$content.joinUs}</a></li>
<li><a href={"/"+locale+"/manifesto"}>{$content.manifesto}</a></li>
<!-- Options dropdown -->
<!-- A list of links pointing to different pages of the website. Implemented as a div opened on :hover-->
<li id="options-container">
<button on:click={() => showDropdown(initiativesDropdown)} on:focusout={() => hide(initiativesDropdown)} class="options-button">{$content.initiatives}</button>
<div bind:this={initiativesDropdown} class="options-dropdown">
<a href={"/"+locale+"/groups"}>{$content.groups}</a>
<a href={"/"+locale+"/communes"}>{$content.communes}</a>
<a href={"/"+locale+"/cooperatives"}>{$content.cooperatives}</a>
<a href={"/"+locale+"/parties"}>{$content.parties}</a>
<a href={"/"+locale+"/partners"}>{$content.partners}</a>
</div>
</li>
<li><a href={"/"+locale+"/profile"}>{$content.profile}</a></li>
<li id="locales">
<button on:click={() => showDropdown(localesDropdown)} on:focusout={() => hide(localesDropdown)}>
<picture>
<source srcset="/img/common/globe.webp">
<source srcset="/img/common/globe.png">
<img id="locales-img" alt="globe">
</picture>
</button>
</li>
<div bind:this={localesDropdown} class="options-dropdown">
{#each Object.entries(locales) as [loc,name]}
<button on:click={() => changeLocale(loc)}>{name}</button>
{/each}
</div>
</ul>
</nav>
</header>
{/if}
{/key}
<style>
@import '/css/common.css';
@import '/css/navbar.css';
</style>

View File

@@ -1,4 +1,4 @@
<svelte:options tag="navbar-component" />
<svelte:options tag="navbar-not-logged" />
<script>
@@ -121,6 +121,7 @@
<a href={"/"+locale+"/partners"}>{$content.partners}</a>
</div>
</li>
<li><a href={"/"+locale+"/auth"}>{$content.login}</a></li>
<li id="locales">
<button on:click={() => showDropdown(localesDropdown)} on:focusout={() => hide(localesDropdown)}>
<picture>
@@ -146,274 +147,5 @@
<style>
@import '/css/common.css';
/* Header */
#navbar{
position: relative;
top: 0;
width: min(100%,116rem);
z-index: 1000000000;
height: 5.26rem;
padding-left: 0rem;
padding-right: 0rem;
}
#navbar * {
font-family: var(--sans-serif, sans-serif);
}
/* Logo */
#logo-container {
display: flex;
position: absolute;
margin-left: 1rem;
height: 100%;
max-height: 5.26rem;
color: black;
z-index: 1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
}
#navbar-logo {
height: 3.5rem;
width: 3.5rem;
object-fit: contain;
border-radius: 10rem;
}
#navbar-logo-text {
position: relative;
word-wrap: normal;
height: 100%;
line-height: 400%;
font-size: 1.4rem;
color: #292222;
font-family: var(--sans-serif, sans-serif);
font-weight: 400;
padding-left: 1.2rem;
}
/* Nav menu */
#nav {
position: fixed;
width: 100%;
height: 100%;
background-color: white;
overflow: hidden;
z-index: 0;
}
#menu > li > a, .options-button {
display: block;
padding: 1.2rem;
padding-top: 1rem;
padding-bottom: 1rem;
color: black;
font-size: 1.4rem;
}
#menu > li > a:active{
background-color: #f7aec0;
}
#menu li {
list-style-type: none;
}
#nav{
max-height: 0;
/*transition: max-height .5s ease-out;*/
}
/* Menu Icon */
#hamb{
position: absolute;
cursor: pointer;
right: 0rem;
padding: 2.8rem 2rem;
z-index: 9999;
}/* Style label tag */
#hamb-line {
background: black;
display: block;
height: 2px;
position: relative;
width: 24px;
} /* Style span tag */
#hamb-line::before,
#hamb-line::after{
background: black;
content: '';
display: block;
height: 100%;
position: absolute;
transition: all .2s ease-out;
width: 100%;
}
#hamb-line::before{
top: 5px;
}
#hamb-line::after{
top: -5px;
}
#side-menu {
display: none;
} /* Hide checkbox */
/* Toggle menu icon */
#side-menu:checked ~ nav {
display: block;
max-height: 100%;
padding-top: 5.625rem;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb {
position: fixed;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb #hamb-line {
background: transparent;
}
#side-menu:checked ~ #hamb #hamb-line::before {
transform: rotate(-45deg);
top: 0;
}
#side-menu:checked ~ #hamb #hamb-line::after {
transform: rotate(45deg);
top: 0;
}
/* Options */
.options-dropdown {
position: absolute;
display: none;
top: 5.6rem;
right: 1.8rem;
border: #404040 solid 0.1rem;
background-color: white;
z-index: 10;
}
.options-dropdown button, .options-dropdown a {
display: block;
font-family: var(--sans-serif,sans-serif);
font-size: 1.2rem;
width: 100%;
padding: 1rem;
text-align: left;
}
.options-dropdown button:hover, .options-dropdown a:hover {
background-color: rgb(187 53 52 / 96%);
color: white;
}
.options-button {
width: 100%;
text-align: left;
}
/* Localization */
#locales {
position: relative;
}
#locales button {
width: 100%;
text-align: left;
height: 4rem;
}
#locales button:hover {
opacity: 0.5;
}
#locales-img {
position: relative;
top: 0rem;
height: 2rem;
margin-left: 1.2rem;
}
/*
#options-dropdown>:first-child {
padding-bottom: 0.5rem;
}
#options-dropdown>:nth-child(2) {
padding-top: 0.5rem;
}
*/
/* Responsiveness */
@media only screen and (min-width: 1200px) {
#navbar {
position: relative;
width: min(100%,116rem);
left: 50%;
transform: translateX(-50%);
padding-right: 4rem;
padding-left: 4rem;
}
#nav {
max-height: none;
top: 0;
position: relative;
float: right;
width: fit-content;
background-color: transparent;
overflow: visible;
}
#side-menu:checked ~ nav {
padding-top: 0;
}
#menu li {
float: left;
}
#menu > li > a:hover, .options-button:hover, #navbar-logo-text:hover {
color: rgb(127, 127, 127);
}
#menu > li > a, .options-button {
padding: 0.9rem;
padding-top: 1.9rem;
padding-bottom: 1.9rem;
}
#hamb {
display: none;
}
#locales {
position: relative;
margin-right: 1.8rem;
}
#locales-img {
top: 0.9rem;
}
}
@import '/css/navbar.css';
</style>

View File

@@ -0,0 +1,29 @@
<svelte:options tag="profile-communes" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Main code
onMount(() => {
})
</script>
<h3>Under development</h3>
<p style=" position: relative; margin-top: 2rem;">Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your commune to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>

View File

@@ -0,0 +1,284 @@
<svelte:options tag="profile-component" />
<script>
// Import libraries
import { onMount, afterUpdate, setContext } from 'svelte'
import { writable } from 'svelte/store'
import * as AuthTools from "/js/libraries/authTools.js"
import {svgFromObject} from "/js/libraries/miscTools.js"
//Import components
import "/js/components/pane-aligner.js"
import "/js/components/profile-general.js"
import "/js/components/profile-groups.js"
import "/js/components/profile-communes.js"
import "/js/components/profile-coops.js"
import "/js/components/profile-parties.js"
import "/js/components/groups-add-component.js"
// Main code
AuthTools.redirectNotLogged()
let root
let general
let groups
let communes
let coops
let parties
let panes
let groupsAdd
let generalButton
let groupsButton
let communesButton
let coopsButton
let partiesButton
let buttons
let currentPaneIndex = 0
let locationPopup
let maps = {}
let user = {}
let loaded = writable(0)
let reloadTriggerVal = writable(0)
AuthTools.getUser(user,loaded)
function changePane(pane,button) {
for (let p of panes) {
p.style.display = "none"
}
for (let b of buttons) {
styleField(b,400,"#636363")
}
pane.style.display = "initial"
styleField(button,500,"#c52a28")
}
function styleField(div,weight,color) {
let svgObject = div.querySelector("object")
if (svgObject==null) {
let f = () => styleField(div,weight,color)
setTimeout(f,100)
}
else {
let svgItem = svgFromObject(svgObject)
if (svgItem==null) {
let f = () => styleField(div,weight,color)
setTimeout(f,100)
}
else {
div.style.fontWeight = weight
svgItem.setAttribute("fill", color)
}
}
}
function fillFields() {
if (Object.keys(user).length!=0 && root!=undefined) {
for (let b of buttons) {
styleField(b,400,"#636363")
}
styleField(buttons[currentPaneIndex],500,"#c52a28")
}
else {
setTimeout(fillFields, 100)
}
}
function valid(el) {
return (el!=undefined) && (el!=null)
}
function init() {
panes = [general,groups,communes,coops,parties]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton]
if ($loaded==1 && panes.every(x => valid(x)) && buttons.every(x => valid(x))) {
panes = [general,groups,communes,coops,parties]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton]
fillFields()
general.style.display = "initial"
}
else {
let f = () => init()
setTimeout(f,100)
}
}
function reloadTrigger() {
reloadTriggerVal.update((val) => {
return val + 1
})
}
setContext("profile-component",{user,maps,reloadTrigger})
onMount(() => {
init()
})
</script>
<!--
<div bind:this={locationPopup} class="overlay" style="display: none">
<div id="location-overlay-content">
</div>
<button class="overlay-button" on:click={() => locationPopup.style.display = "none"}></button>
</div>
-->
<pane-aligner>
<div id="left-column" class="pane" slot="sidebar-left" bind:this={root}>
<button bind:this={generalButton} on:click={() => changePane(general,generalButton)}>
<object id="general-img" class="icons" type="image/svg+xml" data="/img/profile/icons/general.svg" title="general"></object>
<span>general</span>
</button>
<button bind:this={groupsButton} on:click={() => changePane(groups,groupsButton)}>
<object id="groups-img" class="icons" type="image/svg+xml" data="/img/common/groups.svg" title="groups"></object>
<span>groups</span>
</button>
<button bind:this={communesButton} on:click={() => changePane(communes,communesButton)}>
<object id="communes-img" class="icons" type="image/svg+xml" data="/img/common/communes.svg" title="communes"></object>
<span>communes</span>
</button>
<button bind:this={coopsButton} on:click={() => changePane(coops,coopsButton)}>
<object id="coops-img" class="icons" type="image/svg+xml" data="/img/common/coops.svg" title="coops"></object>
<span>cooperatives</span>
</button>
<button bind:this={partiesButton} on:click={() => changePane(parties,partiesButton)}>
<object id="parties-img" class="icons" type="image/svg+xml" data="/img/common/parties.svg" title="parties"></object>
<span>parties</span>
</button>
<button on:click={AuthTools.logout} id="logout-button">
<object id="logout-img" class="icons" type="image/svg+xml" data="/img/profile/icons/logout.svg" title=""></object>
<span>logout</span>
</button>
</div>
<div id="main-column" slot="main">
{#key $loaded}
{#if $loaded==1}
<profile-general bind:this={general} style="display: none;"></profile-general>
<profile-groups bind:this={groups} style="display: none;"></profile-groups>
<profile-communes bind:this={communes} style="display: none;"></profile-communes>
<profile-coops bind:this={coops} style="display: none;"></profile-coops>
<profile-parties bind:this={parties} style="display: none;"></profile-parties>
{/if}
{/key}
</div>
</pane-aligner>
<style>
@import '/css/common.css';
#general-img {
top: 0rem;
}
#groups-img {
top: 0.3rem;
}
#coops-img {
top: 0rem;
}
#parties-img {
top: 0rem;
}
#logout-img {
width: 1.5rem;
}
#logout-button {
padding-top: 1rem;
padding-left: 0.1rem;
}
#left-column {
position: relative;
display: flex;
flex-direction: column;
width: 15.2rem;
padding: 2rem;
border-radius: 0.64rem 0.64rem 0.64rem 0.64rem;
gap: 1rem;
}
.icons {
position: relative;
width: 1.8rem;
}
#left-column button span {
position: absolute;
padding-left: 3.4rem;
margin-top: 0rem;
font-family: var(--sans-serif,sans-serif);
}
#left-column button {
display: flex;
flex-direction: row;
}
#main-column {
padding: 1rem 2rem 1rem 2rem;
height: 100%;
width: 100%;
border-radius: 0 0.64rem 0.64rem 0;
flex-grow: 1;
flex-shrink: 1;
min-height: 20rem;
}
pane-aligner {
--width-main: 800px;
--width-left: 10.5rem;
}
@media only screen and (max-width: 1340px) {
#left-column {
position: relative;
margin-left: 0rem;
width: 100%;
border-radius: 0.64rem 0.64rem 0rem 0;
}
#main-column {
border-radius: 0.64rem;
padding: 3rem 0.5rem;
padding-bottom: 1.5rem;
border-radius: 0rem 0rem 0.64rem 0.64rem;
width: 100%;
}
#logout-button {
position: relative;
bottom: 0;
}
#left-column button {
margin-left: auto;
margin-right: auto;
width: 10rem;
}
#logout-button {
padding-top: 1rem;
margin-bottom: 0rem;
}
}
</style>

View File

@@ -0,0 +1,29 @@
<svelte:options tag="profile-coops" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Main code
onMount(() => {
})
</script>
<h3>Under development</h3>
<p style=" position: relative; margin-top: 2rem;">Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your cooperative to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>

View File

@@ -0,0 +1,433 @@
<svelte:options tag="profile-general" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
//Import components
import "/js/components/select-component.js"
import "/js/components/switch-component.js"
//Export statements
// Main code
let emailInput
let section
let saveEmailButton
let changePasswordInputDiv
let changePasswordMsg
let savePasswordButton
let passwordInput
let changePasswordDiv
let passwordVisibilityButton
let emailMsg
let passwordDiv
let emailDiv
let emailInputDiv
let prevEmail
let context = getContext("profile-component")
let user = context.user
function showSaveButton(button) {
prevEmail = emailInput.value
button.style.display = "initial"
emailMsg.style.display = "inline"
let windowWidth = window.innerWidth
if (windowWidth<1100) {
emailInputDiv.style.marginTop = "1rem"
emailDiv.style.flexDirection = "column"
}
else {
//emailInput.style.width = "19rem"
}
}
function saveEmail() {
let email = emailInput.value
if (AuthTools.checkEmail(email,emailMsg)) {
if (email!=user.email) {
AuthTools.changeUser("email",email,user)
}
resetEmailField()
}
}
function resetEmailField() {
if (prevEmail!=undefined) {
emailInput.value = prevEmail
}
emailInput.style.width = "100%"
emailMsg.style.display = "none"
emailDiv.style.flexDirection = "row"
emailInputDiv.style.marginTop = "0rem"
saveEmailButton.style.display = "none"
emailMsg.innerHTML = ""
}
function launchChangePassword() {
let windowWidth = window.innerWidth
if (windowWidth<1100) {
changePasswordInputDiv.style.display = "flex";
}
else {
changePasswordInputDiv.style.display = "initial";
}
changePasswordDiv.style.display = "none";
passwordInput.focus()
}
function savePassword() {
let password = passwordInput.value
if (AuthTools.checkPassword(password,changePasswordMsg)) {
if (password!=user.password) {
AuthTools.changeUser("password",password,user)
}
changePasswordMsg.innerHTML = ""
resetPasswordField()
}
}
function resetPasswordField() {
changePasswordInputDiv.style.display = "none";
changePasswordDiv.style.display = "initial";
changePasswordMsg.innerHTML = ""
}
function fillFields() {
if (user!=null && Object.keys(user).length!=0 && section!=undefined) {
emailInput.value = user.email
}
else {
setTimeout(fillFields, 10)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
onMount(() => {
fillFields()
document.addEventListener("click", function(event) {
if (passwordDiv.focused) {
resetEmailField()
}
else if (emailDiv.focused) {
resetPasswordField()
}
else {
resetEmailField()
resetPasswordField()
}
})
})
</script>
<section bind:this={section} id="general-section">
<h2 class="title-highlight">General</h2>
<div bind:this={emailDiv} on:mouseenter={emailDiv.focused=true} on:mouseleave={emailDiv.focused=false}>
<div class="title-msg">
<span>Email:</span>
<span bind:this={emailMsg} id="signup-email-msg"></span>
</div>
<div bind:this={emailInputDiv} id="emailInputDiv">
<button bind:this={saveEmailButton} id="save-email" class="save-button" on:click={saveEmail}>save</button>
<div class="input-wrapper">
<input bind:this={emailInput} id="emailInput" class="text-input" type="text" on:click={() => showSaveButton(saveEmailButton)} on:input={() => resizeInput(emailInput)}>
<div class="ghost-input"></div>
</div>
</div>
</div>
<div bind:this={passwordDiv} on:mouseenter={passwordDiv.focused=true} on:mouseleave={passwordDiv.focused=false} id="change-password-line-wrapper">
<div id="change-password-line">
<div class="title-msg">
<span>Password:</span>
<span bind:this={changePasswordMsg} id="signup-password-msg"></span>
</div>
<div bind:this={changePasswordDiv} id="change-password-div">
<button id="change-password" on:click={launchChangePassword}>change
<object type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon"></object>
</button>
</div>
</div>
<div bind:this={changePasswordInputDiv} id="change-password-input-div">
<button bind:this={savePasswordButton} id="save-password" class="save-button" on:click={savePassword}>save</button>
<input bind:this={passwordInput} id="passwordInput" class="text-input" type="password">
<button bind:this={passwordVisibilityButton} class="eye-icon" on:click="{() => AuthTools.changePasswordVisibility(passwordVisibilityButton)}">
<object type="image/svg+xml" data="/img/auth/eye_icon.svg" title="eye icon"></object>
</button>
</div>
</div>
<div>
<div id="verifiedDiv">
<span>Verified:</span>
<span style="color: {user.verified ? "green" : "red"}">{user.verified}</span>
</div>
</div>
</section>
<style>
@import '/css/common.css';
#verifiedDiv {
display: inline;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 2rem;
width: 100%;
}
/*---General section-----------------------------------------------------------*/
.ghost-input {
display: block;
visibility: hidden;
height: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.input-wrapper {
display: inline-block;
max-width: calc(100% - 10rem);
min-width: 0rem;
height: 2.5rem;
position: relative;
right: 0
}
span {
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
}
#general-section {
display: flex;
flex-direction: column;
}
#general-section h2 {
margin: auto;
margin-top: 0;
margin-bottom: 0;
}
#general-section >div {
height: 3.5rem;
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0.14rem solid;
border-color: #cdcdcd;
}
#general-section >div >:first-child {
font-family: var(--sans-serif,sans-serif);
}
/* add padding to every line to center the diving line*/
#general-section >div:last-child {
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0;
}
#general-section >div div,
#general-section >div input,
#general-section >div :not(:first-child) input {
font-weight: 500;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
color: #292222;
border: 0;
}
#general-section >div>:last-child {
padding-right: 1.35rem;
}
.text-input {
position: relative;
width: 20.475rem;
direction: rtl;
border: 0;
outline: none;
bottom: 0.341rem;
}
/*---Email-------------------------------------------------------------------*/
#emailInput {
position: relative;
right: 0;
top: 0.1rem;
width: 100%;
}
#save-email {
display: none;
margin-top: 0.5rem;
}
#signup-email-msg,
#signup-password-msg {
position: relative;
display: inline-block;
color:red;
font-weight: 400;
white-space: nowrap;
}
#signup-email-msg {
display: none;
}
#general-section >div:nth-child(2) {
display: flex;
flex-direction: row;
}
#emailInputDiv {
display: flex;
flex-direction: row;
justify-content: right;
align-items: center;
height: 2rem;
width: 100%;
}
.title-msg {
display: flex;
gap: 0.5rem;
}
.title-msg * {
text-align: left;
}
/*---Change password-------------------------------------------------------------------*/
#change-password-line {
display: flex;
justify-content: space-between;
}
#change-password-div {
width: 9.3rem;
left: 0;
}
#change-password {
position: absolute;
cursor: pointer;
width: 8rem;
height: 2.73rem;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
font-weight: 500;
text-align: right;
padding-right: 2rem;
margin-top: -0.55rem;
background-color: transparent;
}
#change-password > object {
pointer-events: none;
position: absolute;
width: 1.5rem;
right: 0.0rem;
}
#change-password-input-div {
display: none;
float: right;
position: relative;
margin-top: -1.7rem;
}
#passwordInput {
width: 15rem;
right: 0.65rem;
margin-left: 1.5rem;
}
.save-button {
position: relative;
bottom: 0.34rem;
margin-right: 0.6rem;
height: 2.73rem;
width: 4.778rem;
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.512rem;
}
#save-password {
bottom: 0.6rem
}
.eye-icon {
display: inline-block;
position: relative;
cursor: pointer;
opacity: 0.25;
height: 2.2rem;
width: 1.7rem;
}
.eye-icon * {
pointer-events: none;
}
@media only screen and (max-width: 1100px) {
#change-password-line-wrapper {
display: flex;
flex-direction: column;
height: auto;
min-height: 4rem;
}
#change-password-input-div {
margin-top: 1rem;
justify-content: flex-end;
}
#general-section >div:nth-child(2) {
height: auto;
min-height: 4rem;
position: relative;
}
#passwordInput {
width: 100%;
bottom: 0;
}
#emailInput {
width: 100%;
}
#save-password {
bottom: 0rem
}
}
</style>

View File

@@ -0,0 +1,455 @@
<svelte:options tag="profile-groups" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store'
import { getData, sendData } from "/js/libraries/serverTools.js"
//Import components
import "/js/components/select-component.js"
import "/js/components/switch-component.js"
//Export statements
// Main code
let section
let userGroups = []
let groupsRequests = []
let content = writable({})
let loaded = writable(0)
let keyRequests = 0
let numLoaded = 2
let mainPane
let groupsAdd
let membersInput
let saveMembersButton
let membersInputDiv
let contactInput
let saveContactButton
let contactInputDiv
let locale = "en"
let inputLocation
let inputContact
let inputMembers
let pencilMembers
let pencilContact
let pencilButtonMembers
let pencilButtonContact
let myGroupLocation
let myGroupStatus
let context = getContext("profile-component")
let maps = context.maps
function groups_callback(response) {
userGroups = JSON.parse(response)
context["userGroups"] = userGroups
loaded.update((val) => {
return val + 1
})
}
getData("/xx/get-user-groups",groups_callback)
function requests_callback(response) {
let parsed = JSON.parse(response)
groupsRequests.push(...parsed)
loaded.update((val) => {
return val + 1
})
}
getData("/xx/get-group-requests",requests_callback)
function getAddress(g) {
if (g!=undefined) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
else {
return "Create or join group"
}
}
function getContact(c) {
if (c==null) {
return "https://discord.gg/Qk8KUk787z"
}
else {
return c
}
}
function launchChangeLocation() {
showLocationOverlay()
}
function launchChangeMembers() {
}
function showSaveButton(button,input) {
if (!input.readOnly) {
button.style.display = "initial"
}
}
function resetMembersField() {
saveMembersButton.style.display = "none"
}
function resetContactField() {
saveContactButton.style.display = "none"
}
function saveMembers() {
let email = emailInput.value
if (AuthTools.checkEmail(email,emailMsg)) {
if (email!=user.email) {
AuthTools.changeUser("email",email,user)
}
resetMembersField()
}
}
function saveContact() {}
function updateUserGroup(newInfo) {
if (newInfo!=undefined) {
myGroupLocation.innerHTML = getAddress(newInfo)
}
}
function onLoadedGroups() {
let els = [saveMembersButton,saveContactButton,membersInputDiv,contactInputDiv]
if ($loaded==numLoaded && els.every(x => x!=undefined && x!=null)) {
document.addEventListener("click", function(event) {
let activeEl
let shadowRoot = this.activeElement.shadowRoot
if (shadowRoot!=null) {
activeEl = shadowRoot.activeElement
shadowRoot = activeEl.shadowRoot
if (shadowRoot!=null) {
activeEl = shadowRoot.activeElement
}
}
if (activeEl == membersInput || activeEl == saveMembersButton) {
resetContactField()
}
else if (activeEl == contactInput || activeEl == saveContactButton) {
resetMembersField()
}
else {
resetMembersField()
resetContactField()
}
})
context["updateUserGroup"] = updateUserGroup
inputLocation = getAddress(userGroups[0])
if (userGroups.length==0) {
inputContact = ""
inputMembers = ""
}
else {
let group = userGroups[0]
inputContact = getContact(group.contact)
inputMembers = group.members
let status = group.status
if (status!=undefined) {
if (status==0) {
myGroupStatus.innerHTML = "(pending)"
myGroupStatus.style.color = "#FFC90E"
}
else if (status==2) {
myGroupStatus.innerHTML = "(rejected)"
myGroupStatus.style.color = "#c52a28"
}
pencilMembers.style.display = "none"
pencilContact.style.display = "none"
pencilButtonContact.style.cursor = "default"
pencilButtonMembers.style.cursor = "default"
membersInput.readOnly = true
contactInput.readOnly = true
}
}
}
else {
let f = () => onLoadedGroups()
setTimeout(f, 100)
}
}
function focus(el) {
el.focus()
el.click()
}
function approveRequest(ind,user_id) {
sendData("/xx/group-approve-request",{user_id: user_id})
groupsRequests.splice(ind,1)
keyRequests = keyRequests + 1
}
function rejectRequest(ind,user_id) {
sendData("/xx/group-reject-request",{user_id: user_id})
groupsRequests.splice(ind,1)
keyRequests = keyRequests + 1
}
function launchGroupsAdd() {
groupsAdd.style.display = "block"
mainPane.style.display = "none"
if (maps["groupsAdd"]!=undefined) {
maps["groupsAdd"].invalidateSize()
}
}
function closeGroupsAdd() {
groupsAdd.style.display = "none"
mainPane.style.display = "block"
}
context["onLoadedGroups"] = onLoadedGroups
context["launchGroupsAdd"] = launchGroupsAdd
context["closeGroupsAdd"] = closeGroupsAdd
onMount(() => {
onLoadedGroups()
})
</script>
{#key $loaded}
{#if $loaded==numLoaded}
<div bind:this={mainPane}>
<h2>Groups</h2>
<div>
<h3 class="group-heading">My group</h3>
<span bind:this={myGroupStatus} class="status"></span>
</div>
<section bind:this={section} class="entries-section">
<div>
<div class="change-field-line">
<span>Location:</span>
<div class="change-field-div">
<button class="change-field-button" bind:this={myGroupLocation} on:click={launchGroupsAdd}>{inputLocation}
<object type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon" class="pencil"></object>
</button>
</div>
</div>
</div>
<div>
<div class="change-field-line">
<span>Members:</span>
<div bind:this={membersInputDiv} class="change-field-div input-pencil">
<div class="save-button-wrapper">
<button bind:this={saveMembersButton} on:click={saveMembers} class="save-button" style="display: none">save</button>
</div>
<input bind:this={membersInput} id="membersInput" class="text-input" type="text" bind:value={inputMembers} on:click={() => showSaveButton(saveMembersButton,membersInput)}>
<button bind:this={pencilButtonMembers} class="text-input-pencil-button" on:click={() => {focus(membersInput)}}>
<object bind:this={pencilMembers} type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon" class="pencil"></object>
</button>
</div>
</div>
</div>
<div>
<div class="change-field-line">
<span>Contact:</span>
<div bind:this={contactInputDiv} class="change-field-div input-pencil">
<div class="save-button-wrapper">
<button bind:this={saveContactButton} on:click={saveContact} class="save-button" style="display: none">save</button>
</div>
<input bind:this={contactInput} id="contactInput" class="text-input" type="text" bind:value={inputContact} on:click={() => showSaveButton(saveContactButton,contactInput)}>
<button bind:this={pencilButtonContact} class="text-input-pencil-button" on:click={focus(contactInput)}>
<object bind:this={pencilContact} type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon" class="pencil"></object>
</button>
</div>
</div>
</div>
</section>
<h3>Requests</h3>
<section bind:this={section} class="entries-section">
{#key keyRequests}
{#each groupsRequests as req,ind}
<div>
<div class="change-field-line">
<span>{req.email}</span>
<div class="request-button-wrapper">
<button on:click={() => approveRequest(ind,req.user_id)} class="approve-button">approve</button>
<button on:click={() => rejectRequest(ind,req.user_id)} class="approve-button" style="display:visible">reject</button>
</div>
</div>
</div>
{/each}
{/key}
</section>
</div>
<!--Helper panes-->
<groups-add-component bind:this={groupsAdd} style="display: none;"></groups-add-component>
{/if}
{/key}
<style>
@import '/css/common.css';
.request-button-wrapper {
display: flex;
gap: 1rem;
}
.approve-button {
height: 2.7rem;
padding: 0rem 1rem;
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.5rem;
margin-top: -0.5rem;
}
.group-heading {
display: inline-block;
}
.status {
display: inline-block;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
margin-left: 0.5rem;
}
input {
font-family: var(--sans-serif,sans-serif)
}
.text-input-pencil-button {
display: inline-block;
position: relative;
height: 2.7rem;
width: 2rem;
}
.text-input-pencil-button object {
top: 0rem;
}
.pencil {
pointer-events: none;
position: absolute;
width: 1.5rem;
right: 0.0rem;
}
.change-field-div input.text-input {
position: relative;
width: 20.475rem;
direction: rtl;
border: 0;
outline: none;
height: 2.7rem;
font-style: var(--sans-serif,sans-serif);
background: transparent;
margin-top: -0.5rem;
}
#membersInput {
width: 5rem;
}
#contactInput {
max-width: 18rem;
}
.save-button {
position: absolute;
right: 0;
top: -0.4rem;
margin-right: 0.6rem;
height: 2.7rem;
width: 5rem;
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.5rem;
}
.save-button-wrapper {
display: inline-block;
position: relative;
height: 2rem;
}
h2 {
text-align: center;
margin-bottom: 0.5rem;
}
.entries-section {
margin-bottom: 1rem;
}
.entries-section >div {
height: 3.5rem;
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0.14rem solid;
border-color: #cdcdcd;
}
/* add padding to every line to center the diving line*/
.entries-section >div:last-child {
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0;
}
/*---Change field-------------------------------------------------------------------*/
.change-field-line {
display: flex;
justify-content: space-between;
}
.change-field-div {
width: max-content;
position: relative;
display: flex;
}
.change-field-button {
position: relative;
cursor: pointer;
height: 2.7rem;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
font-weight: 500;
text-align: right;
padding-right: 1.9rem;
margin-top: -0.55rem;
background-color: transparent;
width: 100%;
}
/*---General section-----------------------------------------------------------*/
h3 {
margin-bottom: 0.5rem;
}
span {
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
}
</style>

View File

@@ -0,0 +1,29 @@
<svelte:options tag="profile-parties" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Main code
onMount(() => {
})
</script>
<h3>Under development</h3>
<p style=" position: relative; margin-top: 2rem;">Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your party to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>