forked from mirrors/pronouns.cc
feat: display timezone
This commit is contained in:
parent
309aa569f6
commit
e10db2fa09
6 changed files with 55 additions and 0 deletions
|
@ -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 (
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
|
@ -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>
|
||||||
|
|
5
scripts/migrate/019_timezones.sql
Normal file
5
scripts/migrate/019_timezones.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
-- 2023-07-30: Add user timezones
|
||||||
|
|
||||||
|
alter table users add column timezone text null;
|
Loading…
Reference in a new issue