mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 16:38:23 +00:00
* Imports are back!! * Settings UI v3 * Use Inertia forms for heartbeat imports * Update app/javascript/pages/Users/Settings/Data.svelte Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update Bundle * Fix broken Form/Button markup in Data.svelte settings page * Update JS deps * Greptile fixes * Remove dead code --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
282 lines
8.5 KiB
Svelte
282 lines
8.5 KiB
Svelte
<script lang="ts">
|
|
import { Checkbox, RadioGroup } from "bits-ui";
|
|
import { onMount } from "svelte";
|
|
import Button from "../../../components/Button.svelte";
|
|
import Select from "../../../components/Select.svelte";
|
|
import SectionCard from "./components/SectionCard.svelte";
|
|
import SettingsShell from "./Shell.svelte";
|
|
import type { ProfilePageProps } from "./types";
|
|
|
|
let {
|
|
active_section,
|
|
section_paths,
|
|
page_title,
|
|
heading,
|
|
subheading,
|
|
settings_update_path,
|
|
username_max_length,
|
|
user,
|
|
options,
|
|
badges,
|
|
errors,
|
|
}: ProfilePageProps = $props();
|
|
|
|
let csrfToken = $state("");
|
|
let selectedTheme = $state("gruvbox_dark");
|
|
let allowPublicStatsLookup = $state(false);
|
|
|
|
$effect(() => {
|
|
selectedTheme = user.theme || "gruvbox_dark";
|
|
allowPublicStatsLookup = user.allow_public_stats_lookup;
|
|
});
|
|
|
|
onMount(() => {
|
|
csrfToken =
|
|
document
|
|
.querySelector("meta[name='csrf-token']")
|
|
?.getAttribute("content") || "";
|
|
});
|
|
</script>
|
|
|
|
<SettingsShell
|
|
{active_section}
|
|
{section_paths}
|
|
{page_title}
|
|
{heading}
|
|
{subheading}
|
|
{errors}
|
|
>
|
|
<SectionCard
|
|
id="user_region"
|
|
title="Region and Timezone"
|
|
description="Use your local region and timezone for accurate dashboards and leaderboards."
|
|
>
|
|
<form
|
|
id="profile-region-form"
|
|
method="post"
|
|
action={settings_update_path}
|
|
class="space-y-4"
|
|
>
|
|
<input type="hidden" name="_method" value="patch" />
|
|
<input type="hidden" name="authenticity_token" value={csrfToken} />
|
|
|
|
<div>
|
|
<label
|
|
for="country_code"
|
|
class="mb-2 block text-sm text-surface-content"
|
|
>
|
|
Country
|
|
</label>
|
|
<Select
|
|
id="country_code"
|
|
name="user[country_code]"
|
|
value={user.country_code || ""}
|
|
items={[
|
|
{ value: "", label: "Select a country" },
|
|
...options.countries,
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
<div id="user_timezone">
|
|
<label for="timezone" class="mb-2 block text-sm text-surface-content">
|
|
Timezone
|
|
</label>
|
|
<Select
|
|
id="timezone"
|
|
name="user[timezone]"
|
|
value={user.timezone}
|
|
items={options.timezones}
|
|
/>
|
|
</div>
|
|
</form>
|
|
|
|
{#snippet footer()}
|
|
<Button type="submit" variant="primary" form="profile-region-form">
|
|
Save region settings
|
|
</Button>
|
|
{/snippet}
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
id="user_username"
|
|
title="Username"
|
|
description="This username is used in links and public profile pages."
|
|
>
|
|
<form
|
|
id="profile-username-form"
|
|
method="post"
|
|
action={settings_update_path}
|
|
class="space-y-3"
|
|
>
|
|
<input type="hidden" name="_method" value="patch" />
|
|
<input type="hidden" name="authenticity_token" value={csrfToken} />
|
|
|
|
<div>
|
|
<label for="username" class="mb-2 block text-sm text-surface-content">
|
|
Username
|
|
</label>
|
|
<input
|
|
id="username"
|
|
name="user[username]"
|
|
value={user.username || ""}
|
|
maxlength={username_max_length}
|
|
placeholder="your-name"
|
|
class="w-full rounded-md border border-surface-200 bg-darker px-3 py-2 text-sm text-surface-content focus:border-primary focus:outline-none"
|
|
/>
|
|
{#if errors.username.length > 0}
|
|
<p class="mt-2 text-xs text-red">{errors.username[0]}</p>
|
|
{/if}
|
|
</div>
|
|
</form>
|
|
|
|
{#if badges.profile_url}
|
|
<p class="text-sm text-muted">
|
|
Public profile:
|
|
<a
|
|
href={badges.profile_url}
|
|
target="_blank"
|
|
class="text-primary underline"
|
|
>
|
|
{badges.profile_url}
|
|
</a>
|
|
</p>
|
|
{/if}
|
|
|
|
{#snippet footer()}
|
|
<Button type="submit" variant="primary" form="profile-username-form">
|
|
Save username
|
|
</Button>
|
|
{/snippet}
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
id="user_privacy"
|
|
title="Privacy"
|
|
description="Control whether your coding stats can be used by public APIs."
|
|
>
|
|
<form
|
|
id="profile-privacy-form"
|
|
method="post"
|
|
action={settings_update_path}
|
|
class="space-y-3"
|
|
>
|
|
<input type="hidden" name="_method" value="patch" />
|
|
<input type="hidden" name="authenticity_token" value={csrfToken} />
|
|
|
|
<label class="flex items-center gap-3 text-sm text-surface-content">
|
|
<input type="hidden" name="user[allow_public_stats_lookup]" value="0" />
|
|
<Checkbox.Root
|
|
bind:checked={allowPublicStatsLookup}
|
|
name="user[allow_public_stats_lookup]"
|
|
value="1"
|
|
class="inline-flex h-4 w-4 min-w-4 items-center justify-center rounded border border-surface-200 bg-darker text-on-primary transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary"
|
|
>
|
|
{#snippet children({ checked })}
|
|
<span class={checked ? "text-[10px]" : "hidden"}>✓</span>
|
|
{/snippet}
|
|
</Checkbox.Root>
|
|
Allow public stats lookup
|
|
</label>
|
|
</form>
|
|
|
|
{#snippet footer()}
|
|
<Button type="submit" variant="primary" form="profile-privacy-form">
|
|
Save privacy settings
|
|
</Button>
|
|
{/snippet}
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
id="user_theme"
|
|
title="Theme"
|
|
description="Pick how Hackatime looks for your account."
|
|
wide
|
|
>
|
|
<form
|
|
id="profile-theme-form"
|
|
method="post"
|
|
action={settings_update_path}
|
|
class="space-y-4"
|
|
>
|
|
<input type="hidden" name="_method" value="patch" />
|
|
<input type="hidden" name="authenticity_token" value={csrfToken} />
|
|
|
|
<RadioGroup.Root
|
|
name="user[theme]"
|
|
bind:value={selectedTheme}
|
|
class="grid grid-cols-1 gap-4 md:grid-cols-2"
|
|
>
|
|
{#each options.themes as theme}
|
|
<RadioGroup.Item
|
|
value={theme.value}
|
|
class="block cursor-pointer rounded-xl border p-4 text-left outline-none transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-surface-100 data-[state=unchecked]:border-surface-200 data-[state=unchecked]:bg-darker/40 data-[state=unchecked]:hover:border-surface-300"
|
|
>
|
|
{#snippet children({ checked })}
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div>
|
|
<p class="text-sm font-semibold text-surface-content">
|
|
{theme.label}
|
|
</p>
|
|
<p class="mt-1 text-xs text-muted">{theme.description}</p>
|
|
</div>
|
|
{#if checked}
|
|
<span
|
|
class="rounded-full bg-primary/20 px-2 py-0.5 text-xs font-medium text-primary"
|
|
>
|
|
Selected
|
|
</span>
|
|
{/if}
|
|
</div>
|
|
|
|
<div
|
|
class="mt-3 rounded-lg border p-2"
|
|
style={`background:${theme.preview.darker};border-color:${theme.preview.darkless};color:${theme.preview.content};`}
|
|
>
|
|
<div
|
|
class="flex items-center justify-between rounded-md px-2 py-1"
|
|
style={`background:${theme.preview.dark};`}
|
|
>
|
|
<span class="text-[11px] font-semibold">Dashboard</span>
|
|
<span class="text-[10px] opacity-80">2h 14m</span>
|
|
</div>
|
|
|
|
<div class="mt-2 grid grid-cols-[1fr_auto] items-center gap-2">
|
|
<span
|
|
class="h-2 rounded"
|
|
style={`background:${theme.preview.primary};`}
|
|
></span>
|
|
<span
|
|
class="h-2 w-8 rounded"
|
|
style={`background:${theme.preview.darkless};`}
|
|
></span>
|
|
</div>
|
|
|
|
<div class="mt-2 flex gap-1.5">
|
|
<span
|
|
class="h-1.5 w-6 rounded"
|
|
style={`background:${theme.preview.info};`}
|
|
></span>
|
|
<span
|
|
class="h-1.5 w-6 rounded"
|
|
style={`background:${theme.preview.success};`}
|
|
></span>
|
|
<span
|
|
class="h-1.5 w-6 rounded"
|
|
style={`background:${theme.preview.warning};`}
|
|
></span>
|
|
</div>
|
|
</div>
|
|
{/snippet}
|
|
</RadioGroup.Item>
|
|
{/each}
|
|
</RadioGroup.Root>
|
|
</form>
|
|
|
|
{#snippet footer()}
|
|
<Button type="submit" variant="primary" form="profile-theme-form">
|
|
Save theme
|
|
</Button>
|
|
{/snippet}
|
|
</SectionCard>
|
|
</SettingsShell>
|