feat: display timezone

This commit is contained in:
sam 2023-07-30 23:13:35 +02:00
parent 309aa569f6
commit e10db2fa09
No known key found for this signature in database
GPG key ID: B4EF20DDE721CAA1
6 changed files with 55 additions and 0 deletions

View file

@ -52,6 +52,7 @@ type User struct {
IsAdmin bool IsAdmin bool
ListPrivate bool ListPrivate bool
LastSIDReroll time.Time `db:"last_sid_reroll"` LastSIDReroll time.Time `db:"last_sid_reroll"`
Timezone *string
DeletedAt *time.Time DeletedAt *time.Time
SelfDelete *bool SelfDelete *bool
@ -113,6 +114,21 @@ func (u User) NumProviders() (numProviders int) {
return numProviders return numProviders
} }
// UTCOffset returns the user's UTC offset in seconds. If the user does not have a timezone set, `ok` is false.
func (u User) UTCOffset() (offset int, ok bool) {
if u.Timezone == nil {
return 0, false
}
loc, err := time.LoadLocation(*u.Timezone)
if err != nil {
return 0, false
}
_, offset = time.Now().In(loc).Zone()
return offset, true
}
type Badge int32 type Badge int32
const ( const (

View file

@ -28,12 +28,14 @@ type GetUserResponse struct {
CustomPreferences db.CustomPreferences `json:"custom_preferences"` CustomPreferences db.CustomPreferences `json:"custom_preferences"`
Flags []db.UserFlag `json:"flags"` Flags []db.UserFlag `json:"flags"`
Badges db.Badge `json:"badges"` Badges db.Badge `json:"badges"`
UTCOffset *int `json:"utc_offset"`
} }
type GetMeResponse struct { type GetMeResponse struct {
GetUserResponse GetUserResponse
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
Timezone *string `json:"timezone"`
MaxInvites int `json:"max_invites"` MaxInvites int `json:"max_invites"`
IsAdmin bool `json:"is_admin"` IsAdmin bool `json:"is_admin"`
@ -87,6 +89,10 @@ func dbUserToResponse(u db.User, fields []db.Field, members []db.Member, flags [
resp.Badges |= db.BadgeAdmin resp.Badges |= db.BadgeAdmin
} }
if offset, ok := u.UTCOffset(); ok {
resp.UTCOffset = &offset
}
resp.Members = make([]PartialMember, len(members)) resp.Members = make([]PartialMember, len(members))
for i := range members { for i := range members {
resp.Members[i] = PartialMember{ resp.Members[i] = PartialMember{
@ -195,6 +201,7 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error {
render.JSON(w, r, GetMeResponse{ render.JSON(w, r, GetMeResponse{
GetUserResponse: dbUserToResponse(u, fields, members, flags), GetUserResponse: dbUserToResponse(u, fields, members, flags),
CreatedAt: u.ID.Time(), CreatedAt: u.ID.Time(),
Timezone: u.Timezone,
MaxInvites: u.MaxInvites, MaxInvites: u.MaxInvites,
IsAdmin: u.IsAdmin, IsAdmin: u.IsAdmin,
ListPrivate: u.ListPrivate, ListPrivate: u.ListPrivate,

View file

@ -311,6 +311,8 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
// echo the updated user back on success // echo the updated user back on success
render.JSON(w, r, GetMeResponse{ render.JSON(w, r, GetMeResponse{
GetUserResponse: dbUserToResponse(u, fields, nil, flags), GetUserResponse: dbUserToResponse(u, fields, nil, flags),
CreatedAt: u.ID.Time(),
Timezone: u.Timezone,
MaxInvites: u.MaxInvites, MaxInvites: u.MaxInvites,
IsAdmin: u.IsAdmin, IsAdmin: u.IsAdmin,
ListPrivate: u.ListPrivate, ListPrivate: u.ListPrivate,

View file

@ -14,6 +14,7 @@ export interface User {
links: string[]; links: string[];
member_title: string | null; member_title: string | null;
badges: number; badges: number;
utc_offset: number | null;
names: FieldEntry[]; names: FieldEntry[];
pronouns: Pronoun[]; pronouns: Pronoun[];

View file

@ -13,7 +13,9 @@
Modal, Modal,
ModalBody, ModalBody,
ModalFooter, ModalFooter,
Tooltip,
} from "sveltestrap"; } from "sveltestrap";
import { DateTime, Duration, FixedOffsetZone, Zone } from "luxon";
import FieldCard from "$lib/components/FieldCard.svelte"; import FieldCard from "$lib/components/FieldCard.svelte";
import PronounLink from "$lib/components/PronounLink.svelte"; import PronounLink from "$lib/components/PronounLink.svelte";
import PartialMemberCard from "$lib/components/PartialMemberCard.svelte"; import PartialMemberCard from "$lib/components/PartialMemberCard.svelte";
@ -77,6 +79,24 @@
let memberNameValid = true; let memberNameValid = true;
$: memberNameValid = memberNameRegex.test(newMemberName); $: memberNameValid = memberNameRegex.test(newMemberName);
let currentTime: string | null;
let timezone: string | null;
$: setTime(data.utc_offset);
const setTime = (offset: number | null) => {
if (!offset) {
currentTime = null;
timezone = null;
return;
}
const now = DateTime.now();
const zone = FixedOffsetZone.instance(offset / 60);
currentTime = now.setZone(zone).toLocaleString(DateTime.TIME_SIMPLE);
timezone = zone.formatOffset(now.toUnixInteger(), "narrow");
};
const createMember = async () => { const createMember = async () => {
try { try {
const member = await apiFetchClient<Member>("/members", "POST", { const member = await apiFetchClient<Member>("/members", "POST", {
@ -168,6 +188,10 @@
{:else} {:else}
<h2>@{data.name} <Badges userBadges={data.badges} /></h2> <h2>@{data.name} <Badges userBadges={data.badges} /></h2>
{/if} {/if}
{#if data.utc_offset}
<Tooltip target="user-clock" placement="top">Current time</Tooltip>
<Icon id="user-clock" name="clock" aria-label="This user's current time" /> {currentTime} <span class="text-body-secondary">(UTC{timezone})</span>
{/if}
{#if profileEmpty && $userStore?.id === data.id} {#if profileEmpty && $userStore?.id === data.id}
<hr /> <hr />
<p> <p>

View file

@ -0,0 +1,5 @@
-- +migrate Up
-- 2023-07-30: Add user timezones
alter table users add column timezone text null;