1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-24 07:19:36 +02:00

User profile settings API and remove old Dj-Rest-Auth code

This commit is contained in:
Sean Morley 2024-11-30 10:24:27 -05:00
parent c65fcc2558
commit 84566b8ec1
22 changed files with 514 additions and 791 deletions

View file

@ -1,27 +0,0 @@
# Preface
AdventureLog uses DjRestAuth, a Django REST Framework authentication backend for Django Rest Framework. DjRestAuth is licensed under the MIT License.
---
## The MIT License (MIT)
Copyright (c) 2014 iMerica https://github.com/iMerica/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,4 +0,0 @@
# AdventureLog Django Backend
A demo of a possible AdventureLog 2.0 version using Django as the backend with a REST API.
Based of django-rest-framework and dj-rest-auth.

View file

@ -11,7 +11,6 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from datetime import timedelta
from os import getenv from os import getenv
from pathlib import Path from pathlib import Path
# Load environment variables from .env file # Load environment variables from .env file
@ -35,8 +34,6 @@ DEBUG = getenv('DEBUG', 'True') == 'True'
# ] # ]
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
@ -50,7 +47,7 @@ INSTALLED_APPS = (
"allauth_ui", "allauth_ui",
'allauth', 'allauth',
'allauth.account', 'allauth.account',
'allauth.mfa', # 'allauth.mfa',
'allauth.headless', 'allauth.headless',
'allauth.socialaccount', 'allauth.socialaccount',
"widget_tweaks", "widget_tweaks",
@ -108,7 +105,7 @@ DATABASES = {
} }
} }
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/ # https://docs.djangoproject.com/en/1.7/topics/i18n/
@ -123,8 +120,7 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
ALLAUTH_UI_THEME = "dark"
SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/ # https://docs.djangoproject.com/en/1.7/howto/static-files/
@ -138,6 +134,16 @@ MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media' MEDIA_ROOT = BASE_DIR / 'media'
STATICFILES_DIRS = [BASE_DIR / 'static'] STATICFILES_DIRS = [BASE_DIR / 'static']
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
}
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -154,22 +160,22 @@ TEMPLATES = [
}, },
] ]
# Authentication settings
DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True' DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True'
DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.') DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.')
STORAGES = { ALLAUTH_UI_THEME = "dark"
"staticfiles": { SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
}
AUTH_USER_MODEL = 'users.CustomUser' AUTH_USER_MODEL = 'users.CustomUser'
ACCOUNT_ADAPTER = 'users.adapters.NoNewUsersAccountAdapter' ACCOUNT_ADAPTER = 'users.adapters.NoNewUsersAccountAdapter'
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
SESSION_SAVE_EVERY_REQUEST = True
FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000') FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000')
# HEADLESS_FRONTEND_URLS = { # HEADLESS_FRONTEND_URLS = {
@ -218,9 +224,6 @@ SWAGGER_SETTINGS = {
'LOGOUT_URL': 'logout', 'LOGOUT_URL': 'logout',
} }
from os import getenv
CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()] CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
@ -253,6 +256,4 @@ LOGGING = {
}, },
} }
# https://github.com/dr5hn/countries-states-cities-database/tags # https://github.com/dr5hn/countries-states-cities-database/tags
COUNTRY_REGION_JSON_VERSION = 'v2.4' COUNTRY_REGION_JSON_VERSION = 'v2.4'
SESSION_SAVE_EVERY_REQUEST = True

View file

@ -3,8 +3,7 @@ from django.contrib import admin
from django.views.generic import RedirectView, TemplateView from django.views.generic import RedirectView, TemplateView
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from adventures import urls as adventures from users.views import IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView, UpdateUserMetadataView
from users.views import ChangeEmailView, IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView
from .views import get_csrf_token from .views import get_csrf_token
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
@ -19,56 +18,19 @@ schema_view = get_schema_view(
urlpatterns = [ urlpatterns = [
path('api/', include('adventures.urls')), path('api/', include('adventures.urls')),
path('api/', include('worldtravel.urls')), path('api/', include('worldtravel.urls')),
path("_allauth/", include("allauth.headless.urls")),
path('auth/change-email/', ChangeEmailView.as_view(), name='change_email'),
path('auth/is-registration-disabled/', IsRegistrationDisabled.as_view(), name='is_registration_disabled'), path('auth/is-registration-disabled/', IsRegistrationDisabled.as_view(), name='is_registration_disabled'),
path('auth/users/', PublicUserListView.as_view(), name='public-user-list'), path('auth/users/', PublicUserListView.as_view(), name='public-user-list'),
path('auth/user/<uuid:user_id>/', PublicUserDetailView.as_view(), name='public-user-detail'), path('auth/user/<uuid:user_id>/', PublicUserDetailView.as_view(), name='public-user-detail'),
path('auth/update-user/', UpdateUserMetadataView.as_view(), name='update-user-metadata'),
path('auth/user-metadata/', UserMetadataView.as_view(), name='user-metadata'), path('auth/user-metadata/', UserMetadataView.as_view(), name='user-metadata'),
path('csrf/', get_csrf_token, name='get_csrf_token'), path('csrf/', get_csrf_token, name='get_csrf_token'),
re_path(r'^$', TemplateView.as_view(
template_name="home.html"), name='home'), path('', TemplateView.as_view(template_name='home.html')),
re_path(r'^signup/$', TemplateView.as_view(template_name="signup.html"),
name='signup'),
re_path(r'^email-verification/$',
TemplateView.as_view(template_name="email_verification.html"),
name='email-verification'),
re_path(r'^login/$', TemplateView.as_view(template_name="login.html"),
name='login'),
re_path(r'^logout/$', TemplateView.as_view(template_name="logout.html"),
name='logout'),
re_path(r'^password-reset/$',
TemplateView.as_view(template_name="password_reset.html"),
name='password-reset'),
re_path(r'^password-reset/confirm/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),
name='password-reset-confirm'),
re_path(r'^user-details/$',
TemplateView.as_view(template_name="user_details.html"),
name='user-details'),
re_path(r'^password-change/$',
TemplateView.as_view(template_name="password_change.html"),
name='password-change'),
re_path(r'^resend-email-verification/$',
TemplateView.as_view(
template_name="resend_email_verification.html"),
name='resend-email-verification'),
# this url is used to generate email content
re_path(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,32})/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),
name='password_reset_confirm'),
re_path(r'^auth/', include('dj_rest_auth.urls')),
re_path(r'^auth/registration/',
include('dj_rest_auth.registration.urls')),
# re_path(r'^account/', include('allauth.urls')),
re_path(r'^admin/', admin.site.urls), re_path(r'^admin/', admin.site.urls),
re_path(r'^accounts/profile/$', RedirectView.as_view(url='/', re_path(r'^accounts/profile/$', RedirectView.as_view(url='/',
permanent=True), name='profile-redirect'), permanent=True), name='profile-redirect'),
@ -78,5 +40,5 @@ urlpatterns = [
path("accounts/", include("allauth.urls")), path("accounts/", include("allauth.urls")),
# Include the API endpoints: # Include the API endpoints:
path("_allauth/", include("allauth.headless.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -1,7 +1,5 @@
Django==5.0.8 Django==5.0.8
dj-rest-auth @ git+https://github.com/iMerica/dj-rest-auth.git@master
djangorestframework>=3.15.2 djangorestframework>=3.15.2
djangorestframework-simplejwt==5.3.1
django-allauth==0.63.3 django-allauth==0.63.3
drf-yasg==1.21.4 drf-yasg==1.21.4
django-cors-headers==4.4.0 django-cors-headers==4.4.0

View file

@ -4,8 +4,8 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Django-dj-rest-auth demo" /> <meta name="description" content="AdventureLog Server" />
<meta name="author" content="iMerica, Inc." /> <meta name="author" content="Sean Morley" />
<title>AdventureLog API Server</title> <title>AdventureLog API Server</title>
@ -31,39 +31,6 @@
<body role="document"> <body role="document">
<div class="navbar navbar-inverse" role="navigation"> <div class="navbar navbar-inverse" role="navigation">
<div class="container"> <div class="container">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
>API endpoints <span class="caret"></span
></a>
<ul class="dropdown-menu" role="menu">
<!-- these pages don't require user token -->
<li><a href="{% url 'signup' %}">Signup</a></li>
<li>
<a href="{% url 'email-verification' %}">E-mail verification</a>
</li>
<li>
<a href="{% url 'resend-email-verification' %}"
>Resend E-mail verification</a
>
</li>
<li><a href="{% url 'login' %}">Login</a></li>
<li><a href="{% url 'password-reset' %}">Password Reset</a></li>
<li>
<a href="{% url 'password-reset-confirm' %}"
>Password Reset Confirm</a
>
</li>
<li class="divider"></li>
<!-- these pages require user token -->
<li><a href="{% url 'user-details' %}">User details</a></li>
<li><a href="{% url 'logout' %}">Logout</a></li>
<li><a href="{% url 'password-change' %}">Password change</a></li>
</ul>
</li>
</ul>
<div class="navbar-header"> <div class="navbar-header">
<button <button
type="button" type="button"
@ -80,20 +47,19 @@
</div> </div>
<div class="collapse navbar-collapse"> <div class="collapse navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="active"><a href="/">Demo</a></li> <li class="active"><a href="/">Server Home</a></li>
<li> <li>
<a <a target="_blank" href="http://adventurelog.app"
target="_blank"
href="http://dj-rest-auth.readthedocs.org/en/latest/"
>Documentation</a >Documentation</a
> >
</li> </li>
<li> <li>
<a target="_blank" href="https://github.com/iMerica/dj-rest-auth" <a
>Source code</a target="_blank"
href="https://github.com/seanmorley15/AdventureLog"
>Source Code</a
> >
</li> </li>
<li><a target="_blank" href="{% url 'api_docs' %}">API Docs</a></li>
</ul> </ul>
</div> </div>
<!--/.nav-collapse --> <!--/.nav-collapse -->

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>E-mail verification</h3><hr/>
{% include "fragments/email_verification_form.html" %}
</div>
{% endblock %}

View file

@ -4,7 +4,12 @@
<h1>AdventureLog API Server</h1> <h1>AdventureLog API Server</h1>
<p> <p>
<a class="btn btn-primary btn-lg" href="/admin" role="button">Admin Site</a> <a class="btn btn-primary btn-lg" href="/admin" role="button">Admin Site</a>
<a class="btn btn-secondary btn-lg" href="/docs" role="button">API Docs</a> <a
class="btn btn-secondary btn-lg"
href="/accounts/password/change"
role="button"
>Account Managment</a
>
</p> </p>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Login</h3><hr/>
{% include "fragments/login_form.html" %}
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Logout</h3><hr/>
{% include "fragments/logout_form.html" %}
</div>
{% endblock %}

View file

@ -1,39 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="form-group">
<label for="token" class="col-sm-2 control-label">User Token</label>
<div class="col-sm-4">
<input name="token" type="text" class="form-control" id="token" placeholder="Token">
<p class="help-block">Token received after login</p>
</div>
</div>
</div>
<div class="row">
<h3>Update User Details</h3><hr/>
{% include "fragments/password_change_form.html" %}
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
$().ready(function(){
$('form button[type=submit]').click(function(){
var token = $('input[name=token]').val();
var form = $('form');
$.ajax({
url: form.attr('action'),
data: $('form').serialize(),
type: "POST",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Token '+token);}
}).fail(function(data){error_response(data);})
.done(function(data){susccess_response(data);});
return false;
});
});
</script>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Password reset</h3><hr/>
{% include "fragments/password_reset_form.html" %}
</div>
{% endblock %}

View file

@ -1,26 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Password reset confirmation</h3><hr/>
{% include "fragments/password_reset_confirm_form.html" %}
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
var url_elements = window.location.pathname.split('/');
if (url_elements.length == 6){
var uid = url_elements[url_elements.length - 3];
if (uid !== undefined){
$('input[name=uid]').val(uid);
}
var token = url_elements[url_elements.length - 2];
if (token !== undefined){
$('input[name=token]').val(token);
}
}
</script>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Resend E-mail verification</h3><hr/>
{% include "fragments/resend_email_verification_form.html" %}
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Signup</h3><hr/>
{% include "fragments/signup_form.html" %}
</div>
{% endblock %}

View file

@ -1,58 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<h3>Retrieve User Details</h3><hr/>
<div class="form-group">
<label for="token" class="col-sm-2 control-label">User Token</label>
<div class="col-sm-4">
<input name="token" type="text" class="form-control" id="token" placeholder="Token">
<p class="help-block">Token received after login</p>
</div>
<button id="get-user-details" class="btn btn-primary">GET user details</button>
</div>
</div>
<div class="row">
<h3>Update User Details</h3><hr/>
{% include "fragments/user_details_form.html" %}
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">
$().ready(function(){
$('#get-user-details').click(function(){
var token = $('input[name=token]').val();
$.ajax({
url: "{% url 'rest_user_details' %}",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Token '+token);},
type: "GET",
success: function(data) {
$('input[name=username]').val(data.username);
$('input[name=email]').val(data.email);
$('input[name=first_name]').val(data.first_name);
$('input[name=last_name]').val(data.last_name);
}
});
return false;
});
$('form button[type=submit]').click(function(){
var token = $('input[name=token]').val();
var form = $('form');
$.ajax({
url: form.attr('action'),
data: $('form').serialize(),
type: "PUT",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Token '+token);}
}).fail(function(data){error_response(data);})
.done(function(data){susccess_response(data);});
return false;
});
});
</script>
{% endblock %}

View file

@ -106,6 +106,7 @@ class CustomUserDetailsSerializer(UserDetailsSerializer):
class Meta(UserDetailsSerializer.Meta): class Meta(UserDetailsSerializer.Meta):
model = CustomUser model = CustomUser
fields = UserDetailsSerializer.Meta.fields + ('profile_pic', 'uuid', 'public_profile') fields = UserDetailsSerializer.Meta.fields + ('profile_pic', 'uuid', 'public_profile')
read_only_fields = UserDetailsSerializer.Meta.read_only_fields + ('uuid',)
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)

View file

@ -97,4 +97,27 @@ class UserMetadataView(APIView):
def get(self, request): def get(self, request):
user = request.user user = request.user
serializer = PublicUserSerializer(user) serializer = PublicUserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
class UpdateUserMetadataView(APIView):
"""
Update user metadata using fields from the PublicUserSerializer.
Using patch opposed to put allows for partial updates, covers the case where it checks the username and says it's already taken. Duplicate uesrname values should not be included in the request to avoid this.
"""
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
request_body=PublicUserSerializer,
responses={
200: openapi.Response('User metadata updated'),
400: 'Bad Request'
},
operation_description="Update user metadata."
)
def patch(self, request):
user = request.user
serializer = PublicUserSerializer(user, data=request.data, partial=True, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View file

@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View file

@ -1,415 +1,417 @@
{ {
"navbar": { "navbar": {
"adventures": "Adventures", "adventures": "Adventures",
"collections": "Collections", "collections": "Collections",
"worldtravel": "World Travel", "worldtravel": "World Travel",
"map": "Map", "map": "Map",
"users": "Users", "users": "Users",
"search": "Search", "search": "Search",
"profile": "Profile", "profile": "Profile",
"greeting": "Hi", "greeting": "Hi",
"my_adventures": "My Adventures", "my_adventures": "My Adventures",
"my_tags": "My Tags", "my_tags": "My Tags",
"tag": "Tag", "tag": "Tag",
"shared_with_me": "Shared With Me", "shared_with_me": "Shared With Me",
"settings": "Settings", "settings": "Settings",
"logout": "Logout", "logout": "Logout",
"about": "About AdventureLog", "about": "About AdventureLog",
"documentation": "Documentation", "documentation": "Documentation",
"discord": "Discord", "discord": "Discord",
"language_selection": "Language", "language_selection": "Language",
"support": "Support", "support": "Support",
"theme_selection": "Theme Selection", "theme_selection": "Theme Selection",
"themes": { "themes": {
"light": "Light", "light": "Light",
"dark": "Dark", "dark": "Dark",
"night": "Night", "night": "Night",
"forest": "Forest", "forest": "Forest",
"aestheticLight": "Aesthetic Light", "aestheticLight": "Aesthetic Light",
"aestheticDark": "Aesthetic Dark", "aestheticDark": "Aesthetic Dark",
"aqua": "Aqua" "aqua": "Aqua"
} }
}, },
"about": { "about": {
"about": "About", "about": "About",
"license": "Licensed under the GPL-3.0 License.", "license": "Licensed under the GPL-3.0 License.",
"source_code": "Source Code", "source_code": "Source Code",
"message": "Made with ❤️ in the United States.", "message": "Made with ❤️ in the United States.",
"oss_attributions": "Open Source Attributions", "oss_attributions": "Open Source Attributions",
"nominatim_1": "Location Search and Geocoding is provided by", "nominatim_1": "Location Search and Geocoding is provided by",
"nominatim_2": "Their data is liscensed under the ODbL license.", "nominatim_2": "Their data is liscensed under the ODbL license.",
"other_attributions": "Additional attributions can be found in the README file.", "other_attributions": "Additional attributions can be found in the README file.",
"close": "Close" "close": "Close"
}, },
"home": { "home": {
"hero_1": "Discover the World's Most Thrilling Adventures", "hero_1": "Discover the World's Most Thrilling Adventures",
"hero_2": "Discover and plan your next adventure with AdventureLog. Explore breathtaking destinations, create custom itineraries, and stay connected on the go.", "hero_2": "Discover and plan your next adventure with AdventureLog. Explore breathtaking destinations, create custom itineraries, and stay connected on the go.",
"go_to": "Go To AdventureLog", "go_to": "Go To AdventureLog",
"key_features": "Key Features", "key_features": "Key Features",
"desc_1": "Discover, Plan, and Explore with Ease", "desc_1": "Discover, Plan, and Explore with Ease",
"desc_2": "AdventureLog is designed to simplify your journey, providing you with the tools and resources to plan, pack, and navigate your next unforgettable adventure.", "desc_2": "AdventureLog is designed to simplify your journey, providing you with the tools and resources to plan, pack, and navigate your next unforgettable adventure.",
"feature_1": "Travel Log", "feature_1": "Travel Log",
"feature_1_desc": "Keep track of your adventures with a personalized travel log and share your experiences with friends and family.", "feature_1_desc": "Keep track of your adventures with a personalized travel log and share your experiences with friends and family.",
"feature_2": "Trip Planning", "feature_2": "Trip Planning",
"feature_2_desc": "Easily create custom itineraries and get a day-by-day breakdown of your trip.", "feature_2_desc": "Easily create custom itineraries and get a day-by-day breakdown of your trip.",
"feature_3": "Travel Map", "feature_3": "Travel Map",
"feature_3_desc": "View your travels throughout the world with an interactive map and explore new destinations." "feature_3_desc": "View your travels throughout the world with an interactive map and explore new destinations."
}, },
"adventures": { "adventures": {
"collection_remove_success": "Adventure removed from collection successfully!", "collection_remove_success": "Adventure removed from collection successfully!",
"collection_remove_error": "Error removing adventure from collection", "collection_remove_error": "Error removing adventure from collection",
"collection_link_success": "Adventure linked to collection successfully!", "collection_link_success": "Adventure linked to collection successfully!",
"no_image_found": "No image found", "no_image_found": "No image found",
"collection_link_error": "Error linking adventure to collection", "collection_link_error": "Error linking adventure to collection",
"adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.", "adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.",
"open_details": "Open Details", "open_details": "Open Details",
"edit_adventure": "Edit Adventure", "edit_adventure": "Edit Adventure",
"remove_from_collection": "Remove from Collection", "remove_from_collection": "Remove from Collection",
"add_to_collection": "Add to Collection", "add_to_collection": "Add to Collection",
"delete": "Delete", "delete": "Delete",
"not_found": "Adventure not found", "not_found": "Adventure not found",
"not_found_desc": "The adventure you were looking for could not be found. Please try a different adventure or check back later.", "not_found_desc": "The adventure you were looking for could not be found. Please try a different adventure or check back later.",
"homepage": "Homepage", "homepage": "Homepage",
"adventure_details": "Adventure Details", "adventure_details": "Adventure Details",
"collection": "Collection", "collection": "Collection",
"adventure_type": "Adventure Type", "adventure_type": "Adventure Type",
"longitude": "Longitude", "longitude": "Longitude",
"latitude": "Latitude", "latitude": "Latitude",
"visit": "Visit", "visit": "Visit",
"visits": "Visits", "visits": "Visits",
"create_new": "Create New...", "create_new": "Create New...",
"adventure": "Adventure", "adventure": "Adventure",
"count_txt": "results matching your search", "count_txt": "results matching your search",
"sort": "Sort", "sort": "Sort",
"order_by": "Order By", "order_by": "Order By",
"order_direction": "Order Direction", "order_direction": "Order Direction",
"ascending": "Ascending", "ascending": "Ascending",
"descending": "Descending", "descending": "Descending",
"updated": "Updated", "updated": "Updated",
"name": "Name", "name": "Name",
"date": "Date", "date": "Date",
"activity_types": "Activity Types", "activity_types": "Activity Types",
"tags": "Tags", "tags": "Tags",
"add_a_tag": "Add a tag", "add_a_tag": "Add a tag",
"date_constrain": "Constrain to collection dates", "date_constrain": "Constrain to collection dates",
"rating": "Rating", "rating": "Rating",
"my_images": "My Images", "my_images": "My Images",
"add_an_activity": "Add an activity", "add_an_activity": "Add an activity",
"no_images": "No Images", "no_images": "No Images",
"upload_images_here": "Upload images here", "upload_images_here": "Upload images here",
"share_adventure": "Share this Adventure!", "share_adventure": "Share this Adventure!",
"copy_link": "Copy Link", "copy_link": "Copy Link",
"image": "Image", "image": "Image",
"upload_image": "Upload Image", "upload_image": "Upload Image",
"url": "URL", "url": "URL",
"fetch_image": "Fetch Image", "fetch_image": "Fetch Image",
"wikipedia": "Wikipedia", "wikipedia": "Wikipedia",
"add_notes": "Add notes", "add_notes": "Add notes",
"warning": "Warning", "warning": "Warning",
"my_adventures": "My Adventures", "my_adventures": "My Adventures",
"no_linkable_adventures": "No adventures found that can be linked to this collection.", "no_linkable_adventures": "No adventures found that can be linked to this collection.",
"add": "Add", "add": "Add",
"save_next": "Save & Next", "save_next": "Save & Next",
"end_date": "End Date", "end_date": "End Date",
"my_visits": "My Visits", "my_visits": "My Visits",
"start_date": "Start Date", "start_date": "Start Date",
"remove": "Remove", "remove": "Remove",
"location": "Location", "location": "Location",
"search_for_location": "Search for a location", "search_for_location": "Search for a location",
"clear_map": "Clear map", "clear_map": "Clear map",
"search_results": "Searh results", "search_results": "Searh results",
"no_results": "No results found", "no_results": "No results found",
"wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.", "wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.",
"generate_desc": "Generate Description", "generate_desc": "Generate Description",
"public_adventure": "Public Adventure", "public_adventure": "Public Adventure",
"location_information": "Location Information", "location_information": "Location Information",
"link": "Link", "link": "Link",
"links": "Links", "links": "Links",
"description": "Description", "description": "Description",
"sources": "Sources", "sources": "Sources",
"collection_adventures": "Include Collection Adventures", "collection_adventures": "Include Collection Adventures",
"filter": "Filter", "filter": "Filter",
"category_filter": "Category Filter", "category_filter": "Category Filter",
"category": "Category", "category": "Category",
"select_adventure_category": "Select Adventure Category", "select_adventure_category": "Select Adventure Category",
"clear": "Clear", "clear": "Clear",
"my_collections": "My Collections", "my_collections": "My Collections",
"open_filters": "Open Filters", "open_filters": "Open Filters",
"close_filters": "Close Filters", "close_filters": "Close Filters",
"archived_collections": "Archived Collections", "archived_collections": "Archived Collections",
"share": "Share", "share": "Share",
"private": "Private", "private": "Private",
"public": "Public", "public": "Public",
"archived": "Archived", "archived": "Archived",
"edit_collection": "Edit Collection", "edit_collection": "Edit Collection",
"unarchive": "Unarchive", "unarchive": "Unarchive",
"archive": "Archive", "archive": "Archive",
"no_collections_found": "No collections found to add this adventure to.", "no_collections_found": "No collections found to add this adventure to.",
"not_visited": "Not Visited", "not_visited": "Not Visited",
"archived_collection_message": "Collection archived successfully!", "archived_collection_message": "Collection archived successfully!",
"unarchived_collection_message": "Collection unarchived successfully!", "unarchived_collection_message": "Collection unarchived successfully!",
"delete_collection_success": "Collection deleted successfully!", "delete_collection_success": "Collection deleted successfully!",
"delete_collection_warning": "Are you sure you want to delete this collection? This will also delete all of the linked adventures. This action cannot be undone.", "delete_collection_warning": "Are you sure you want to delete this collection? This will also delete all of the linked adventures. This action cannot be undone.",
"cancel": "Cancel", "cancel": "Cancel",
"delete_collection": "Delete Collection", "delete_collection": "Delete Collection",
"delete_adventure": "Delete Adventure", "delete_adventure": "Delete Adventure",
"adventure_delete_success": "Adventure deleted successfully!", "adventure_delete_success": "Adventure deleted successfully!",
"visited": "Visited", "visited": "Visited",
"planned": "Planned", "planned": "Planned",
"duration": "Duration", "duration": "Duration",
"all": "All", "all": "All",
"image_removed_success": "Image removed successfully!", "image_removed_success": "Image removed successfully!",
"image_removed_error": "Error removing image", "image_removed_error": "Error removing image",
"no_image_url": "No image found at that URL.", "no_image_url": "No image found at that URL.",
"image_upload_success": "Image uploaded successfully!", "image_upload_success": "Image uploaded successfully!",
"image_upload_error": "Error uploading image", "image_upload_error": "Error uploading image",
"dates": "Dates", "dates": "Dates",
"wiki_image_error": "Error fetching image from Wikipedia", "wiki_image_error": "Error fetching image from Wikipedia",
"start_before_end_error": "Start date must be before end date", "start_before_end_error": "Start date must be before end date",
"activity": "Activity", "activity": "Activity",
"actions": "Actions", "actions": "Actions",
"no_end_date": "Please enter an end date", "no_end_date": "Please enter an end date",
"see_adventures": "See Adventures", "see_adventures": "See Adventures",
"image_fetch_failed": "Failed to fetch image", "image_fetch_failed": "Failed to fetch image",
"no_location": "Please enter a location", "no_location": "Please enter a location",
"no_start_date": "Please enter a start date", "no_start_date": "Please enter a start date",
"no_description_found": "No description found", "no_description_found": "No description found",
"adventure_created": "Adventure created", "adventure_created": "Adventure created",
"adventure_create_error": "Failed to create adventure", "adventure_create_error": "Failed to create adventure",
"adventure_updated": "Adventure updated", "adventure_updated": "Adventure updated",
"adventure_update_error": "Failed to update adventure", "adventure_update_error": "Failed to update adventure",
"set_to_pin": "Set to Pin", "set_to_pin": "Set to Pin",
"category_fetch_error": "Error fetching categories", "category_fetch_error": "Error fetching categories",
"new_adventure": "New Adventure", "new_adventure": "New Adventure",
"basic_information": "Basic Information", "basic_information": "Basic Information",
"adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!", "adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!",
"no_adventures_found": "No adventures found", "no_adventures_found": "No adventures found",
"mark_region_as_visited": "Mark region {region}, {country} as visited?", "mark_region_as_visited": "Mark region {region}, {country} as visited?",
"mark_visited": "Mark Visited", "mark_visited": "Mark Visited",
"error_updating_regions": "Error updating regions", "error_updating_regions": "Error updating regions",
"regions_updated": "regions updated", "regions_updated": "regions updated",
"visited_region_check": "Visited Region Check", "visited_region_check": "Visited Region Check",
"visited_region_check_desc": "By selecting this, the server will check all of your visited adventures and mark the regions they are located in as visited in world travel.", "visited_region_check_desc": "By selecting this, the server will check all of your visited adventures and mark the regions they are located in as visited in world travel.",
"update_visited_regions": "Update Visited Regions", "update_visited_regions": "Update Visited Regions",
"update_visited_regions_disclaimer": "This may take a while depending on the number of adventures you have visited.", "update_visited_regions_disclaimer": "This may take a while depending on the number of adventures you have visited.",
"link_new": "Link New...", "link_new": "Link New...",
"add_new": "Add New...", "add_new": "Add New...",
"transportation": "Transportation", "transportation": "Transportation",
"note": "Note", "note": "Note",
"checklist": "Checklist", "checklist": "Checklist",
"collection_archived": "This collection has been archived.", "collection_archived": "This collection has been archived.",
"visit_link": "Visit Link", "visit_link": "Visit Link",
"collection_completed": "You've completed this collection!", "collection_completed": "You've completed this collection!",
"collection_stats": "Collection Stats", "collection_stats": "Collection Stats",
"keep_exploring": "Keep Exploring!", "keep_exploring": "Keep Exploring!",
"linked_adventures": "Linked Adventures", "linked_adventures": "Linked Adventures",
"notes": "Notes", "notes": "Notes",
"checklists": "Checklists", "checklists": "Checklists",
"transportations": "Transportations", "transportations": "Transportations",
"day": "Day", "day": "Day",
"itineary_by_date": "Itinerary by Date", "itineary_by_date": "Itinerary by Date",
"nothing_planned": "Nothing planned for this day. Enjoy the journey!", "nothing_planned": "Nothing planned for this day. Enjoy the journey!",
"days": "days", "days": "days",
"activities": { "activities": {
"general": "General 🌍", "general": "General 🌍",
"outdoor": "Outdoor 🏞️", "outdoor": "Outdoor 🏞️",
"lodging": "Lodging 🛌", "lodging": "Lodging 🛌",
"dining": "Dining 🍽️", "dining": "Dining 🍽️",
"activity": "Activity 🏄", "activity": "Activity 🏄",
"attraction": "Attraction 🎢", "attraction": "Attraction 🎢",
"shopping": "Shopping 🛍️", "shopping": "Shopping 🛍️",
"nightlife": "Nightlife 🌃", "nightlife": "Nightlife 🌃",
"event": "Event 🎉", "event": "Event 🎉",
"transportation": "Transportation 🚗", "transportation": "Transportation 🚗",
"culture": "Culture 🎭", "culture": "Culture 🎭",
"water_sports": "Water Sports 🚤", "water_sports": "Water Sports 🚤",
"hiking": "Hiking 🥾", "hiking": "Hiking 🥾",
"wildlife": "Wildlife 🦒", "wildlife": "Wildlife 🦒",
"historical_sites": "Historical Sites 🏛️", "historical_sites": "Historical Sites 🏛️",
"music_concerts": "Music & Concerts 🎶", "music_concerts": "Music & Concerts 🎶",
"fitness": "Fitness 🏋️", "fitness": "Fitness 🏋️",
"art_museums": "Art & Museums 🎨", "art_museums": "Art & Museums 🎨",
"festivals": "Festivals 🎪", "festivals": "Festivals 🎪",
"spiritual_journeys": "Spiritual Journeys 🧘‍♀️", "spiritual_journeys": "Spiritual Journeys 🧘‍♀️",
"volunteer_work": "Volunteer Work 🤝", "volunteer_work": "Volunteer Work 🤝",
"other": "Other" "other": "Other"
} }
}, },
"worldtravel": { "worldtravel": {
"country_list": "Country List", "country_list": "Country List",
"num_countries": "countries found", "num_countries": "countries found",
"all": "All", "all": "All",
"partially_visited": "Partially Visited", "partially_visited": "Partially Visited",
"not_visited": "Not Visited", "not_visited": "Not Visited",
"completely_visited": "Completely Visited", "completely_visited": "Completely Visited",
"all_subregions": "All Subregions", "all_subregions": "All Subregions",
"clear_search": "Clear Search", "clear_search": "Clear Search",
"no_countries_found": "No countries found" "no_countries_found": "No countries found"
}, },
"auth": { "auth": {
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"forgot_password": "Forgot Password?", "forgot_password": "Forgot Password?",
"signup": "Signup", "signup": "Signup",
"login_error": "Unable to login with the provided credentials.", "login_error": "Unable to login with the provided credentials.",
"login": "Login", "login": "Login",
"email": "Email", "email": "Email",
"first_name": "First Name", "first_name": "First Name",
"last_name": "Last Name", "last_name": "Last Name",
"confirm_password": "Confirm Password", "confirm_password": "Confirm Password",
"registration_disabled": "Registration is currently disabled.", "registration_disabled": "Registration is currently disabled.",
"profile_picture": "Profile Picture", "profile_picture": "Profile Picture",
"public_profile": "Public Profile", "public_profile": "Public Profile",
"public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page." "public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page."
}, },
"users": { "users": {
"no_users_found": "No users found with public profiles." "no_users_found": "No users found with public profiles."
}, },
"settings": { "settings": {
"update_error": "Error updating settings", "update_error": "Error updating settings",
"update_success": "Settings updated successfully!", "update_success": "Settings updated successfully!",
"settings_page": "Settings Page", "settings_page": "Settings Page",
"account_settings": "User Account Settings", "account_settings": "User Account Settings",
"update": "Update", "update": "Update",
"password_change": "Change Password", "password_change": "Change Password",
"new_password": "New Password", "new_password": "New Password",
"confirm_new_password": "Confirm New Password", "confirm_new_password": "Confirm New Password",
"email_change": "Change Email", "email_change": "Change Email",
"current_email": "Current Email", "current_email": "Current Email",
"no_email_set": "No email set", "no_email_set": "No email set",
"new_email": "New Email", "new_email": "New Email",
"change_password": "Change Password", "change_password": "Change Password",
"login_redir": "You will then be redirected to the login page.", "login_redir": "You will then be redirected to the login page.",
"token_required": "Token and UID are required for password reset.", "token_required": "Token and UID are required for password reset.",
"reset_password": "Reset Password", "reset_password": "Reset Password",
"possible_reset": "If the email address you provided is associated with an account, you will receive an email with instructions to reset your password!", "possible_reset": "If the email address you provided is associated with an account, you will receive an email with instructions to reset your password!",
"missing_email": "Please enter an email address", "missing_email": "Please enter an email address",
"submit": "Submit", "submit": "Submit",
"password_does_not_match": "Passwords do not match", "password_does_not_match": "Passwords do not match",
"password_is_required": "Password is required", "password_is_required": "Password is required",
"invalid_token": "Token is invalid or has expired", "invalid_token": "Token is invalid or has expired",
"about_this_background": "About this background", "about_this_background": "About this background",
"photo_by": "Photo by", "photo_by": "Photo by",
"join_discord": "Join the Discord", "join_discord": "Join the Discord",
"join_discord_desc": "to share your own photos. Post them in the #travel-share channel." "join_discord_desc": "to share your own photos. Post them in the #travel-share channel.",
}, "current_password": "Current Password",
"collection": { "change_password_error": "Unable to change password. Invalid current password or invalid new password."
"collection_created": "Collection created successfully!", },
"error_creating_collection": "Error creating collection", "collection": {
"new_collection": "New Collection", "collection_created": "Collection created successfully!",
"create": "Create", "error_creating_collection": "Error creating collection",
"collection_edit_success": "Collection edited successfully!", "new_collection": "New Collection",
"error_editing_collection": "Error editing collection", "create": "Create",
"edit_collection": "Edit Collection" "collection_edit_success": "Collection edited successfully!",
}, "error_editing_collection": "Error editing collection",
"notes": { "edit_collection": "Edit Collection"
"note_deleted": "Note deleted successfully!", },
"note_delete_error": "Error deleting note", "notes": {
"open": "Open", "note_deleted": "Note deleted successfully!",
"failed_to_save": "Failed to save note", "note_delete_error": "Error deleting note",
"note_editor": "Note Editor", "open": "Open",
"editing_note": "Editing note", "failed_to_save": "Failed to save note",
"content": "Content", "note_editor": "Note Editor",
"save": "Save", "editing_note": "Editing note",
"note_public": "This note is public because it is in a public collection.", "content": "Content",
"add_a_link": "Add a link", "save": "Save",
"invalid_url": "Invalid URL" "note_public": "This note is public because it is in a public collection.",
}, "add_a_link": "Add a link",
"checklist": { "invalid_url": "Invalid URL"
"checklist_deleted": "Checklist deleted successfully!", },
"checklist_delete_error": "Error deleting checklist", "checklist": {
"failed_to_save": "Failed to save checklist", "checklist_deleted": "Checklist deleted successfully!",
"checklist_editor": "Checklist Editor", "checklist_delete_error": "Error deleting checklist",
"editing_checklist": "Editing checklist", "failed_to_save": "Failed to save checklist",
"item": "Item", "checklist_editor": "Checklist Editor",
"items": "Items", "editing_checklist": "Editing checklist",
"add_item": "Add Item", "item": "Item",
"new_item": "New Item", "items": "Items",
"save": "Save", "add_item": "Add Item",
"checklist_public": "This checklist is public because it is in a public collection.", "new_item": "New Item",
"item_cannot_be_empty": "Item cannot be empty", "save": "Save",
"item_already_exists": "Item already exists" "checklist_public": "This checklist is public because it is in a public collection.",
}, "item_cannot_be_empty": "Item cannot be empty",
"transportation": { "item_already_exists": "Item already exists"
"transportation_deleted": "Transportation deleted successfully!", },
"transportation_delete_error": "Error deleting transportation", "transportation": {
"provide_start_date": "Please provide a start date", "transportation_deleted": "Transportation deleted successfully!",
"transport_type": "Transport Type", "transportation_delete_error": "Error deleting transportation",
"type": "Type", "provide_start_date": "Please provide a start date",
"transportation_added": "Transportation added successfully!", "transport_type": "Transport Type",
"error_editing_transportation": "Error editing transportation", "type": "Type",
"new_transportation": "New Transportation", "transportation_added": "Transportation added successfully!",
"date_time": "Start Date & Time", "error_editing_transportation": "Error editing transportation",
"end_date_time": "End Date & Time", "new_transportation": "New Transportation",
"flight_number": "Flight Number", "date_time": "Start Date & Time",
"from_location": "From Location", "end_date_time": "End Date & Time",
"to_location": "To Location", "flight_number": "Flight Number",
"edit": "Edit", "from_location": "From Location",
"modes": { "to_location": "To Location",
"car": "Car", "edit": "Edit",
"plane": "Plane", "modes": {
"train": "Train", "car": "Car",
"bus": "Bus", "plane": "Plane",
"boat": "Boat", "train": "Train",
"bike": "Bike", "bus": "Bus",
"walking": "Walking", "boat": "Boat",
"other": "Other" "bike": "Bike",
}, "walking": "Walking",
"transportation_edit_success": "Transportation edited successfully!", "other": "Other"
"edit_transportation": "Edit Transportation", },
"start": "Start", "transportation_edit_success": "Transportation edited successfully!",
"date_and_time": "Date & Time" "edit_transportation": "Edit Transportation",
}, "start": "Start",
"search": { "date_and_time": "Date & Time"
"adventurelog_results": "AdventureLog Results", },
"public_adventures": "Public Adventures", "search": {
"online_results": "Online Results" "adventurelog_results": "AdventureLog Results",
}, "public_adventures": "Public Adventures",
"map": { "online_results": "Online Results"
"view_details": "View Details", },
"adventure_map": "Adventure Map", "map": {
"map_options": "Map Options", "view_details": "View Details",
"show_visited_regions": "Show Visited Regions", "adventure_map": "Adventure Map",
"add_adventure_at_marker": "Add New Adventure at Marker", "map_options": "Map Options",
"clear_marker": "Clear Marker", "show_visited_regions": "Show Visited Regions",
"add_adventure": "Add New Adventure" "add_adventure_at_marker": "Add New Adventure at Marker",
}, "clear_marker": "Clear Marker",
"share": { "add_adventure": "Add New Adventure"
"shared": "Shared", },
"with": "with", "share": {
"unshared": "Unshared", "shared": "Shared",
"share_desc": "Share this collection with other users.", "with": "with",
"shared_with": "Shared With", "unshared": "Unshared",
"no_users_shared": "No users shared with", "share_desc": "Share this collection with other users.",
"not_shared_with": "Not Shared With", "shared_with": "Shared With",
"no_shared_found": "No collections found that are shared with you.", "no_users_shared": "No users shared with",
"set_public": "In order to allow users to share with you, you need your profile set to public.", "not_shared_with": "Not Shared With",
"go_to_settings": "Go to settings" "no_shared_found": "No collections found that are shared with you.",
}, "set_public": "In order to allow users to share with you, you need your profile set to public.",
"languages": { "go_to_settings": "Go to settings"
"en": "English", },
"de": "German", "languages": {
"es": "Spanish", "en": "English",
"fr": "French", "de": "German",
"it": "Italian", "es": "Spanish",
"nl": "Dutch", "fr": "French",
"sv": "Swedish", "it": "Italian",
"zh": "Chinese", "nl": "Dutch",
"pl": "Polish" "sv": "Swedish",
}, "zh": "Chinese",
"profile": { "pl": "Polish"
"member_since": "Member since", },
"user_stats": "User Stats", "profile": {
"visited_countries": "Visited Countries", "member_since": "Member since",
"visited_regions": "Visited Regions" "user_stats": "User Stats",
}, "visited_countries": "Visited Countries",
"categories": { "visited_regions": "Visited Regions"
"manage_categories": "Manage Categories", },
"no_categories_found": "No categories found.", "categories": {
"edit_category": "Edit Category", "manage_categories": "Manage Categories",
"icon": "Icon", "no_categories_found": "No categories found.",
"update_after_refresh": "The adventure cards will be updated once you refresh the page.", "edit_category": "Edit Category",
"select_category": "Select Category", "icon": "Icon",
"category_name": "Category Name" "update_after_refresh": "The adventure cards will be updated once you refresh the page.",
} "select_category": "Select Category",
"category_name": "Category Name"
}
} }

View file

@ -13,7 +13,7 @@ export const load: PageServerLoad = async (event) => {
if (!sessionId) { if (!sessionId) {
return redirect(302, '/'); return redirect(302, '/');
} }
let res = await fetch(`${endpoint}/auth/user/`, { let res = await fetch(`${endpoint}/auth/user-metadata/`, {
headers: { headers: {
Cookie: `sessionid=${sessionId}` Cookie: `sessionid=${sessionId}`
} }
@ -50,7 +50,7 @@ export const actions: Actions = {
let profile_pic = formData.get('profile_pic') as File | null | undefined; let profile_pic = formData.get('profile_pic') as File | null | undefined;
let public_profile = formData.get('public_profile') as string | null | undefined | boolean; let public_profile = formData.get('public_profile') as string | null | undefined | boolean;
const resCurrent = await fetch(`${endpoint}/auth/user/`, { const resCurrent = await fetch(`${endpoint}/auth/user-metadata/`, {
headers: { headers: {
Cookie: `sessionid=${sessionId}` Cookie: `sessionid=${sessionId}`
} }
@ -60,12 +60,12 @@ export const actions: Actions = {
return fail(resCurrent.status, await resCurrent.json()); return fail(resCurrent.status, await resCurrent.json());
} }
// Gets the boolean value of the public_profile input
if (public_profile === 'on') { if (public_profile === 'on') {
public_profile = true; public_profile = true;
} else { } else {
public_profile = false; public_profile = false;
} }
console.log(public_profile);
let currentUser = (await resCurrent.json()) as User; let currentUser = (await resCurrent.json()) as User;
@ -83,6 +83,7 @@ export const actions: Actions = {
} }
let formDataToSend = new FormData(); let formDataToSend = new FormData();
if (username) { if (username) {
formDataToSend.append('username', username); formDataToSend.append('username', username);
} }
@ -99,7 +100,7 @@ export const actions: Actions = {
let csrfToken = await fetchCSRFToken(); let csrfToken = await fetchCSRFToken();
let res = await fetch(`${endpoint}/auth/user/`, { let res = await fetch(`${endpoint}/auth/update-user/`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`, Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`,
@ -111,8 +112,6 @@ export const actions: Actions = {
let response = await res.json(); let response = await res.json();
if (!res.ok) { if (!res.ok) {
// change the first key in the response to 'message' for the fail function
response = { message: Object.values(response)[0] };
return fail(res.status, response); return fail(res.status, response);
} }
@ -130,19 +129,23 @@ export const actions: Actions = {
if (!sessionId) { if (!sessionId) {
return redirect(302, '/'); return redirect(302, '/');
} }
console.log('changePassword');
const formData = await event.request.formData(); const formData = await event.request.formData();
const password1 = formData.get('password1') as string | null | undefined; const password1 = formData.get('password1') as string | null | undefined;
const password2 = formData.get('password2') as string | null | undefined; const password2 = formData.get('password2') as string | null | undefined;
const current_password = formData.get('current_password') as string | null | undefined;
if (password1 !== password2) { if (password1 !== password2) {
return fail(400, { message: 'Passwords do not match' }); return fail(400, { message: 'Passwords do not match' });
} }
if (!current_password) {
return fail(400, { message: 'Current password is required' });
}
let csrfToken = await fetchCSRFToken(); let csrfToken = await fetchCSRFToken();
let res = await fetch(`${endpoint}/auth/password/change/`, { let res = await fetch(`${endpoint}/_allauth/browser/v1/account/password/change`, {
method: 'POST', method: 'POST',
headers: { headers: {
Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`, Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`,
@ -150,12 +153,18 @@ export const actions: Actions = {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
new_password1: password1, current_password,
new_password2: password2 new_password: password1
}) })
}); });
if (!res.ok) { if (!res.ok) {
return fail(res.status, await res.json()); let error_message = await res.text();
if (res.status === 400) {
// get the message key of the object
// {"status": 400, "errors": [{"message": "Please type your current password.", "code": "enter_current_password", "param": "current_password"}]}
error_message = JSON.parse(error_message).errors[0].message;
}
return fail(res.status, { message: error_message });
} }
return { success: true }; return { success: true };
}, },

View file

@ -34,16 +34,6 @@
} }
} }
// async function exportAdventures() {
// const url = await exportData();
// const a = document.createElement('a');
// a.href = url;
// a.download = 'adventure-log-export.json';
// a.click();
// URL.revokeObjectURL(url);
// }
async function checkVisitedRegions() { async function checkVisitedRegions() {
let res = await fetch('/api/reverse-geocode/mark_visited_region/', { let res = await fetch('/api/reverse-geocode/mark_visited_region/', {
method: 'POST', method: 'POST',
@ -137,7 +127,15 @@
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">{$t('settings.password_change')}</h1> <h1 class="text-center font-extrabold text-xl mt-4 mb-2">{$t('settings.password_change')}</h1>
<div class="flex justify-center"> <div class="flex justify-center">
<form action="?/changePassword" method="post" class="w-full max-w-xs"> <form action="?/changePassword" method="post" class="w-full max-w-xs" use:enhance>
<input
type="password"
name="current_password"
placeholder={$t('settings.current_password')}
id="current_password"
class="block mb-2 input input-bordered w-full max-w-xs"
/>
<br />
<input <input
type="password" type="password"
name="password1" name="password1"
@ -202,9 +200,7 @@
</div> --> </div> -->
<small class="text-center" <small class="text-center"
><b>For Debug Use:</b> Server PK={user.pk} | Date Joined: {user.date_joined ><b>For Debug Use:</b> UUID={user.uuid} | Staff user: {user.is_staff}</small
? new Date(user.date_joined).toDateString()
: ''} | Staff user: {user.is_staff}</small
> >
<svelte:head> <svelte:head>