From 3e3ccd971bd8491f8594688e93c39ca7ab09d042 Mon Sep 17 00:00:00 2001
From: sam
Date: Wed, 2 Aug 2023 23:24:38 +0200
Subject: [PATCH] feat: add timezone settings
---
backend/db/user.go | 10 +++-
backend/routes/user/patch_user.go | 16 ++++-
frontend/src/lib/api/entities.ts | 1 +
frontend/src/routes/edit/profile/+page.svelte | 59 ++++++++++++++++++-
frontend/vite.config.ts | 1 +
scripts/seeddb/main.go | 2 +-
6 files changed, 85 insertions(+), 4 deletions(-)
diff --git a/backend/db/user.go b/backend/db/user.go
index dd222a9..7f6834e 100644
--- a/backend/db/user.go
+++ b/backend/db/user.go
@@ -555,9 +555,10 @@ func (db *DB) UpdateUser(
memberTitle *string, listPrivate *bool,
links *[]string,
avatar *string,
+ timezone *string,
customPreferences *CustomPreferences,
) (u User, err error) {
- if displayName == nil && bio == nil && links == nil && avatar == nil && memberTitle == nil && listPrivate == nil && customPreferences == nil {
+ if displayName == nil && bio == nil && links == nil && avatar == nil && memberTitle == nil && listPrivate == nil && timezone == nil && customPreferences == nil {
sql, args, err := sq.Select("*").From("users").Where("id = ?", id).ToSql()
if err != nil {
return u, errors.Wrap(err, "building sql")
@@ -593,6 +594,13 @@ func (db *DB) UpdateUser(
builder = builder.Set("member_title", *memberTitle)
}
}
+ if timezone != nil {
+ if *timezone == "" {
+ builder = builder.Set("timezone", nil)
+ } else {
+ builder = builder.Set("timezone", *timezone)
+ }
+ }
if links != nil {
builder = builder.Set("links", *links)
}
diff --git a/backend/routes/user/patch_user.go b/backend/routes/user/patch_user.go
index a5c634c..a07f6e9 100644
--- a/backend/routes/user/patch_user.go
+++ b/backend/routes/user/patch_user.go
@@ -25,6 +25,7 @@ type PatchUserRequest struct {
Pronouns *[]db.PronounEntry `json:"pronouns"`
Fields *[]db.Field `json:"fields"`
Avatar *string `json:"avatar"`
+ Timezone *string `json:"timezone"`
ListPrivate *bool `json:"list_private"`
CustomPreferences *db.CustomPreferences `json:"custom_preferences"`
Flags *[]xid.ID `json:"flags"`
@@ -91,6 +92,19 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
}
}
+ // validate timezone
+ if req.Timezone != nil {
+ if *req.Timezone != "" {
+ _, err := time.LoadLocation(*req.Timezone)
+ if err != nil {
+ return server.APIError{
+ Code: server.ErrBadRequest,
+ Details: fmt.Sprintf("%q is not a valid timezone", *req.Timezone),
+ }
+ }
+ }
+ }
+
// validate links
if req.Links != nil {
if len(*req.Links) > db.MaxUserLinksLength {
@@ -224,7 +238,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
}
}
- u, err = s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.MemberTitle, req.ListPrivate, req.Links, avatarHash, req.CustomPreferences)
+ u, err = s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.MemberTitle, req.ListPrivate, req.Links, avatarHash, req.Timezone, req.CustomPreferences)
if err != nil && errors.Cause(err) != db.ErrNothingToUpdate {
log.Errorf("updating user: %v", err)
return err
diff --git a/frontend/src/lib/api/entities.ts b/frontend/src/lib/api/entities.ts
index 523cc0e..d4e6b7a 100644
--- a/frontend/src/lib/api/entities.ts
+++ b/frontend/src/lib/api/entities.ts
@@ -57,6 +57,7 @@ export interface MeUser extends User {
fediverse_instance: string | null;
list_private: boolean;
last_sid_reroll: string;
+ timezone: string | null;
}
export interface Field {
diff --git a/frontend/src/routes/edit/profile/+page.svelte b/frontend/src/routes/edit/profile/+page.svelte
index da01fe3..b4b7673 100644
--- a/frontend/src/routes/edit/profile/+page.svelte
+++ b/frontend/src/routes/edit/profile/+page.svelte
@@ -21,14 +21,16 @@
CardBody,
CardHeader,
FormGroup,
+ InputGroup,
Icon,
Input,
Popover,
TabContent,
TabPane,
+ InputGroupText,
} from "sveltestrap";
import { encode } from "base64-arraybuffer";
- import { DateTime } from "luxon";
+ import { DateTime, FixedOffsetZone } from "luxon";
import { apiFetchClient } from "$lib/api/fetch";
import { PUBLIC_SHORT_BASE } from "$env/static/public";
import IconButton from "$lib/components/IconButton.svelte";
@@ -60,6 +62,7 @@
let flags: PrideFlag[] = window.structuredClone(data.user.flags);
let list_private = data.user.list_private;
let custom_preferences = window.structuredClone(data.user.custom_preferences);
+ let timezone = data.user.timezone;
let avatar: string | null;
let avatar_files: FileList | null;
@@ -98,6 +101,7 @@
member_title,
list_private,
custom_preferences,
+ timezone,
);
$: getAvatar(avatar_files).then((b64) => (avatar = b64));
@@ -114,6 +118,7 @@
member_title: string,
list_private: boolean,
custom_preferences: CustomPreferences,
+ timezone: string | null,
) => {
if (bio !== (user.bio || "")) return true;
if (display_name !== (user.display_name || "")) return true;
@@ -126,6 +131,7 @@
if (!customPreferencesEqual(custom_preferences, user.custom_preferences)) return true;
if (avatar !== null) return true;
if (list_private !== user.list_private) return true;
+ if (timezone !== user.timezone) return true;
return false;
};
@@ -208,6 +214,24 @@
return uri;
};
+ let currentTime = "";
+ let displayTimezone = "";
+ $: setTime(timezone);
+
+ const setTime = (timezone: string | null) => {
+ if (!timezone) {
+ currentTime = "";
+ displayTimezone = "";
+ return;
+ }
+
+ const offset = DateTime.now().setZone(timezone).offset;
+ const zone = FixedOffsetZone.instance(offset);
+
+ currentTime = now.setZone(zone).toLocaleString(DateTime.TIME_SIMPLE);
+ displayTimezone = zone.formatOffset(now.toUnixInteger(), "narrow");
+ };
+
const moveName = (index: number, up: boolean) => {
if (up && index == 0) return;
if (!up && index == names.length - 1) return;
@@ -361,6 +385,7 @@
fields,
member_title,
list_private,
+ timezone: timezone || "",
custom_preferences,
flags: flags.map((flag) => flag.id),
});
@@ -403,6 +428,10 @@
addToast({ body: "Copied the short link to your clipboard!", duration: 2000 });
};
+ const detectTimezone = () => {
+ timezone = DateTime.local().zoneName;
+ };
+
interface SnapshotData {
bio: string;
display_name: string;
@@ -413,6 +442,7 @@
fields: Field[];
flags: PrideFlag[];
list_private: boolean;
+ timezone: string | null;
custom_preferences: CustomPreferences;
avatar: string | null;
@@ -432,6 +462,7 @@
fields,
flags,
list_private,
+ timezone,
custom_preferences,
avatar,
newName,
@@ -448,6 +479,7 @@
fields = value.fields;
flags = value.flags;
list_private = value.list_private;
+ timezone = value.timezone;
custom_preferences = value.custom_preferences;
avatar = value.avatar;
newName = value.newName;
@@ -787,6 +819,31 @@
pronouns.cc/@{data.user.name}/[member-name]
.
+
+
+
+ You can optionally set your timezone, which will show your current local time on your
+ profile.
+
+
+
+
+ Detect
+ (timezone = null)}>Reset
+
+
+ {#if timezone}
+ This will show up on your profile like this:
+
+ {currentTime} (UTC{displayTimezone})
+
+ {/if}
+
+ Your timezone is never shared directly, only the difference between UTC and your
+ current timezone is.
+
+
+
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 7d8ed95..5f528ea 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -5,6 +5,7 @@ import { plugin as markdown, Mode as MarkdownMode } from "vite-plugin-markdown";
export default defineConfig({
plugins: [sveltekit(), markdown({ mode: [MarkdownMode.HTML] })],
server: {
+ host: "127.0.0.1",
proxy: {
"/api": {
target: "http://localhost:8080",
diff --git a/scripts/seeddb/main.go b/scripts/seeddb/main.go
index 3755617..d18b354 100644
--- a/scripts/seeddb/main.go
+++ b/scripts/seeddb/main.go
@@ -48,7 +48,7 @@ func run(c *cli.Context) error {
return err
}
- _, err = pg.UpdateUser(ctx, tx, u.ID, ptr("testing"), ptr("This is a bio!"), nil, ptr(false), &[]string{"https://pronouns.cc"}, nil, nil)
+ _, err = pg.UpdateUser(ctx, tx, u.ID, ptr("testing"), ptr("This is a bio!"), nil, ptr(false), &[]string{"https://pronouns.cc"}, nil, nil, nil)
if err != nil {
fmt.Println("error setting user info:", err)
return err