Compare commits
3 Commits
7a2ccc1bd1
...
e39ac32e43
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e39ac32e43 | ||
|
|
a7ea2ce9cf | ||
|
|
1bf09a6629 |
153
src/components/ProfileModal.vue
Normal file
153
src/components/ProfileModal.vue
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
|
import SignInAtproto from "@/components/SignInAtproto.vue"
|
||||||
|
import SignInGithub from "@/components/SignInGithub.vue"
|
||||||
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
|
|
||||||
|
const { accessToken } = useGitHubLogin()
|
||||||
|
const isGitHubLoggedIn = computed(() => !!accessToken.value)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<dialog id="profile_modal" class="modal profile-modal">
|
||||||
|
<div class="modal-box profile-modal-box">
|
||||||
|
<div class="profile-modal-head">
|
||||||
|
<h3>Profile</h3>
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="profile-modal-x" aria-label="close">×</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="profile-modal-section">
|
||||||
|
<div class="profile-modal-label">Bluesky / ATProto</div>
|
||||||
|
<sign-in-atproto :with-sign-out="true" />
|
||||||
|
</div>
|
||||||
|
<hr class="profile-modal-rule" />
|
||||||
|
<div class="profile-modal-section">
|
||||||
|
<div class="profile-modal-label">GitHub</div>
|
||||||
|
<sign-in-github />
|
||||||
|
<router-link
|
||||||
|
v-if="isGitHubLoggedIn"
|
||||||
|
:to="{ name: 'RepoList' }"
|
||||||
|
class="profile-modal-link"
|
||||||
|
>Manage your repos</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button></button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.profile-modal {
|
||||||
|
--pm-rule: color-mix(
|
||||||
|
in oklch,
|
||||||
|
var(--color-base-content) 12%,
|
||||||
|
var(--color-base-200)
|
||||||
|
);
|
||||||
|
--pm-ink: var(--color-base-content);
|
||||||
|
--pm-ink-soft: color-mix(
|
||||||
|
in oklch,
|
||||||
|
var(--color-base-content) 65%,
|
||||||
|
var(--color-base-200)
|
||||||
|
);
|
||||||
|
--pm-ink-faint: color-mix(
|
||||||
|
in oklch,
|
||||||
|
var(--color-base-content) 38%,
|
||||||
|
var(--color-base-200)
|
||||||
|
);
|
||||||
|
--pm-accent: #e36598;
|
||||||
|
--pm-accent-wash: color-mix(in oklch, #e36598 12%, var(--color-base-200));
|
||||||
|
--pm-accent-deep: color-mix(
|
||||||
|
in oklch,
|
||||||
|
#e36598 75%,
|
||||||
|
var(--color-base-content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-box {
|
||||||
|
background: var(--color-base-200);
|
||||||
|
border: 1px solid var(--pm-rule);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.3);
|
||||||
|
color: var(--pm-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-x {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--pm-ink-soft);
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--pm-accent-deep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-section {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-label {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
color: var(--pm-ink-faint);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-rule {
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid var(--pm-rule);
|
||||||
|
margin: 1.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-modal-link {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 0.55rem 1rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--pm-rule);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--pm-ink-soft);
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s,
|
||||||
|
border-color 0.15s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--pm-accent-wash);
|
||||||
|
border-color: var(--pm-accent);
|
||||||
|
color: var(--pm-accent-deep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
111
src/components/UserPill.vue
Normal file
111
src/components/UserPill.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
|
|
||||||
|
const { username, accessToken } = useGitHubLogin()
|
||||||
|
const { isLoggedIn: isATProtoLoggedIn, handle, avatarUrl } = useATProtoLogin()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
click: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const isGitHubLoggedIn = computed(() => !!accessToken.value)
|
||||||
|
const isAnyUserLoggedIn = computed(
|
||||||
|
() => isGitHubLoggedIn.value || isATProtoLoggedIn.value
|
||||||
|
)
|
||||||
|
const displayUsername = computed(() => handle.value || username.value || "")
|
||||||
|
const displayInitial = computed(() =>
|
||||||
|
(displayUsername.value[0] || "?").toUpperCase()
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
v-if="isAnyUserLoggedIn"
|
||||||
|
class="profile-chip"
|
||||||
|
type="button"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="isATProtoLoggedIn && avatarUrl"
|
||||||
|
:src="avatarUrl"
|
||||||
|
class="profile-avatar-small"
|
||||||
|
alt="Profile"
|
||||||
|
/>
|
||||||
|
<span v-else class="profile-avatar-small profile-avatar-initial">
|
||||||
|
{{ displayInitial }}
|
||||||
|
</span>
|
||||||
|
<span class="profile-name">{{ displayUsername }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="profile-chip profile-chip--ghost"
|
||||||
|
type="button"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.profile-chip {
|
||||||
|
--pill-rule: color-mix(
|
||||||
|
in oklch,
|
||||||
|
var(--color-base-content) 12%,
|
||||||
|
var(--color-base-200)
|
||||||
|
);
|
||||||
|
--pill-accent: #e36598;
|
||||||
|
--pill-accent-wash: color-mix(
|
||||||
|
in oklch,
|
||||||
|
#e36598 12%,
|
||||||
|
var(--color-base-200)
|
||||||
|
);
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.25rem 0.75rem 0.25rem 0.25rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--pill-rule);
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-base-content);
|
||||||
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
background 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--pill-accent);
|
||||||
|
background: var(--pill-accent-wash);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.profile-chip--ghost {
|
||||||
|
padding: 0.35rem 0.9rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-small {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-initial {
|
||||||
|
background: var(--pill-accent, #e36598);
|
||||||
|
color: white;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,9 +4,9 @@ import { getHex } from "pastel-color"
|
|||||||
import { computed, onMounted, ref } from "vue"
|
import { computed, onMounted, ref } from "vue"
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
import SignInAtproto from "@/components/SignInAtproto.vue"
|
import ProfileModal from "@/components/ProfileModal.vue"
|
||||||
import SignInGithub from "@/components/SignInGithub.vue"
|
|
||||||
import ThemeSwap from "@/components/ThemeSwap.vue"
|
import ThemeSwap from "@/components/ThemeSwap.vue"
|
||||||
|
import UserPill from "@/components/UserPill.vue"
|
||||||
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
import { useForm } from "@/hooks/useForm.hook"
|
import { useForm } from "@/hooks/useForm.hook"
|
||||||
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
@@ -18,7 +18,7 @@ import { useFavoriteRepos } from "@/modules/repo/hooks/useFavoriteRepos.hook"
|
|||||||
import { slugify } from "@/utils/slugify"
|
import { slugify } from "@/utils/slugify"
|
||||||
|
|
||||||
const { username, accessToken } = useGitHubLogin()
|
const { username, accessToken } = useGitHubLogin()
|
||||||
const { isLoggedIn: isATProtoLoggedIn, handle, avatarUrl } = useATProtoLogin()
|
const { isLoggedIn: isATProtoLoggedIn, handle } = useATProtoLogin()
|
||||||
const { userInput, repoInput, submit } = useForm()
|
const { userInput, repoInput, submit } = useForm()
|
||||||
const { savedFavoriteRepos } = useFavoriteRepos()
|
const { savedFavoriteRepos } = useFavoriteRepos()
|
||||||
const { lastVisitedRepos } = useLastVisitedRepos()
|
const { lastVisitedRepos } = useLastVisitedRepos()
|
||||||
@@ -29,10 +29,11 @@ const isGitHubLoggedIn = computed(() => !!accessToken.value)
|
|||||||
const isAnyUserLoggedIn = computed(
|
const isAnyUserLoggedIn = computed(
|
||||||
() => isGitHubLoggedIn.value || isATProtoLoggedIn.value
|
() => isGitHubLoggedIn.value || isATProtoLoggedIn.value
|
||||||
)
|
)
|
||||||
const displayUsername = computed(() => username.value || handle.value || "")
|
const displayUsername = computed(() => handle.value || username.value || "")
|
||||||
const displayInitial = computed(() =>
|
|
||||||
(displayUsername.value[0] || "?").toUpperCase()
|
const openProfile = () => {
|
||||||
)
|
;(document.getElementById("profile_modal") as HTMLDialogElement)?.showModal()
|
||||||
|
}
|
||||||
|
|
||||||
const tileStyle = (seed: string) => {
|
const tileStyle = (seed: string) => {
|
||||||
const bg = getHex(seed)
|
const bg = getHex(seed)
|
||||||
@@ -150,29 +151,7 @@ onMounted(() => {
|
|||||||
class="navlink"
|
class="navlink"
|
||||||
>Getting started</router-link
|
>Getting started</router-link
|
||||||
>
|
>
|
||||||
<button
|
<UserPill @click="openProfile" />
|
||||||
v-if="isAnyUserLoggedIn"
|
|
||||||
class="profile-chip"
|
|
||||||
onclick="profile_modal.showModal()"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="isATProtoLoggedIn && avatarUrl"
|
|
||||||
:src="avatarUrl"
|
|
||||||
class="profile-avatar-small"
|
|
||||||
alt="Profile"
|
|
||||||
/>
|
|
||||||
<span v-else class="profile-avatar-small profile-avatar-initial">
|
|
||||||
{{ displayInitial }}
|
|
||||||
</span>
|
|
||||||
<span class="profile-name">{{ displayUsername }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
class="hw-btn hw-btn-ghost"
|
|
||||||
onclick="profile_modal.showModal()"
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -846,35 +825,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- ── Profile modal ──────────────────────────────────── -->
|
<ProfileModal />
|
||||||
<dialog id="profile_modal" class="modal hw-modal">
|
|
||||||
<div class="modal-box hw-modal-box">
|
|
||||||
<div class="hw-modal-head">
|
|
||||||
<h3>Profile</h3>
|
|
||||||
<form method="dialog">
|
|
||||||
<button class="hw-modal-x" aria-label="close">×</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="hw-modal-section">
|
|
||||||
<div class="hw-ms-label mono">Bluesky / ATProto</div>
|
|
||||||
<sign-in-atproto :with-sign-out="true" />
|
|
||||||
</div>
|
|
||||||
<hr class="hw-rule" />
|
|
||||||
<div class="hw-modal-section">
|
|
||||||
<div class="hw-ms-label mono">GitHub</div>
|
|
||||||
<sign-in-github />
|
|
||||||
<router-link
|
|
||||||
v-if="isGitHubLoggedIn"
|
|
||||||
:to="{ name: 'RepoList' }"
|
|
||||||
class="hw-btn hw-btn-ghost"
|
|
||||||
>Manage your repos</router-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button></button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -999,17 +950,6 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hw-btn-ghost {
|
|
||||||
border-color: var(--hw-rule);
|
|
||||||
color: var(--hw-ink-soft);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--hw-pink-wash);
|
|
||||||
border-color: var(--hw-pink);
|
|
||||||
color: var(--hw-pink-deep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Utility classes ────────────────────────────────────────── */
|
/* ── Utility classes ────────────────────────────────────────── */
|
||||||
.mono {
|
.mono {
|
||||||
font-family: var(--hw-mono);
|
font-family: var(--hw-mono);
|
||||||
@@ -1029,12 +969,6 @@ main {
|
|||||||
color: var(--hw-pink-deep);
|
color: var(--hw-pink-deep);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hw-rule {
|
|
||||||
border: 0;
|
|
||||||
border-top: 1px solid var(--hw-rule);
|
|
||||||
margin: 1.25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Top nav ────────────────────────────────────────────────── */
|
/* ── Top nav ────────────────────────────────────────────────── */
|
||||||
.topnav {
|
.topnav {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1086,50 +1020,6 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.25rem 0.75rem 0.25rem 0.25rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid var(--hw-rule);
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
color: var(--hw-ink);
|
|
||||||
transition:
|
|
||||||
border-color 0.15s,
|
|
||||||
background 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--hw-pink);
|
|
||||||
background: var(--hw-pink-wash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar-small {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar-initial {
|
|
||||||
background: var(--hw-pink);
|
|
||||||
color: white;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-name {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── GitHub form ────────────────────────────────────────────── */
|
/* ── GitHub form ────────────────────────────────────────────── */
|
||||||
.gh-form {
|
.gh-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -2282,62 +2172,6 @@ img {
|
|||||||
color: var(--hw-ink-faint);
|
color: var(--hw-ink-faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Profile modal ──────────────────────────────────────────── */
|
|
||||||
.hw-modal .hw-modal-box {
|
|
||||||
background: var(--hw-paper);
|
|
||||||
border: 1px solid var(--hw-rule);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.3);
|
|
||||||
color: var(--hw-ink);
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-modal-head {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
font-size: 1.3rem;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-modal-x {
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--hw-ink-soft);
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--hw-pink-deep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-modal-section {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-ms-label {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
letter-spacing: 0.14em;
|
|
||||||
color: var(--hw-ink-faint);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Responsive ─────────────────────────────────────────────── */
|
/* ── Responsive ─────────────────────────────────────────────── */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.hero-ed-inner {
|
.hero-ed-inner {
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { computed } from "vue"
|
|||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
|
||||||
import HomeButton from "@/components/HomeButton.vue"
|
import HomeButton from "@/components/HomeButton.vue"
|
||||||
|
import ProfileModal from "@/components/ProfileModal.vue"
|
||||||
import PublicNoteList from "@/components/PublicNoteList.vue"
|
import PublicNoteList from "@/components/PublicNoteList.vue"
|
||||||
import SignInAtproto from "@/components/SignInAtproto.vue"
|
import UserPill from "@/components/UserPill.vue"
|
||||||
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
import { useFollowingNoteList } from "@/hooks/useFollowingNoteList.hook"
|
import { useFollowingNoteList } from "@/hooks/useFollowingNoteList.hook"
|
||||||
import { useFollows } from "@/hooks/useFollows.hook"
|
import { useFollows } from "@/hooks/useFollows.hook"
|
||||||
@@ -29,13 +30,18 @@ const followingEnabled = computed(() => tab.value === "following")
|
|||||||
|
|
||||||
const all = usePublicNoteList()
|
const all = usePublicNoteList()
|
||||||
const following = useFollowingNoteList(follows, followingEnabled)
|
const following = useFollowingNoteList(follows, followingEnabled)
|
||||||
|
|
||||||
|
const openProfile = () => {
|
||||||
|
;(document.getElementById("profile_modal") as HTMLDialogElement)?.showModal()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="public-note-list-view">
|
<main class="public-note-list-view">
|
||||||
<div class="header">
|
<header class="header">
|
||||||
<home-button class="home-button" />
|
<home-button />
|
||||||
</div>
|
<user-pill @click="openProfile" />
|
||||||
|
</header>
|
||||||
|
|
||||||
<div v-if="isLoggedIn" role="tablist" class="tabs tabs-border">
|
<div v-if="isLoggedIn" role="tablist" class="tabs tabs-border">
|
||||||
<a
|
<a
|
||||||
@@ -52,7 +58,6 @@ const following = useFollowingNoteList(follows, followingEnabled)
|
|||||||
@click="tab = 'following'"
|
@click="tab = 'following'"
|
||||||
>Following</a
|
>Following</a
|
||||||
>
|
>
|
||||||
<sign-in-atproto class="handle" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PublicNoteList
|
<PublicNoteList
|
||||||
@@ -104,20 +109,12 @@ const following = useFollowingNoteList(follows, followingEnabled)
|
|||||||
<div v-else class="skeleton h-4 w-20"></div>
|
<div v-else class="skeleton h-4 w-20"></div>
|
||||||
</template>
|
</template>
|
||||||
</PublicNoteList>
|
</PublicNoteList>
|
||||||
|
|
||||||
|
<profile-modal />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.tabs {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.public-note-list-view {
|
.public-note-list-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -126,10 +123,11 @@ const following = useFollowingNoteList(follows, followingEnabled)
|
|||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
margin: 0.5rem auto 0;
|
margin-top: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import fontColorContrast from "font-color-contrast"
|
|||||||
import { getHex } from "pastel-color"
|
import { getHex } from "pastel-color"
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
import SignInAtproto from "@/components/SignInAtproto.vue"
|
import ProfileModal from "@/components/ProfileModal.vue"
|
||||||
import SignInGithub from "@/components/SignInGithub.vue"
|
import SignInGithub from "@/components/SignInGithub.vue"
|
||||||
|
import UserPill from "@/components/UserPill.vue"
|
||||||
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
|
||||||
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
|
||||||
import { useRepos } from "@/hooks/useRepos.hook"
|
import { useRepos } from "@/hooks/useRepos.hook"
|
||||||
@@ -13,7 +14,7 @@ import { useRepoList } from "@/modules/repo/hooks/useRepoList.hook"
|
|||||||
import type { RepoBase } from "@/modules/repo/interfaces/RepoBase"
|
import type { RepoBase } from "@/modules/repo/interfaces/RepoBase"
|
||||||
|
|
||||||
const { username, accessToken } = useGitHubLogin()
|
const { username, accessToken } = useGitHubLogin()
|
||||||
const { isLoggedIn: isATProtoLoggedIn, handle, avatarUrl } = useATProtoLogin()
|
const { isLoggedIn: isATProtoLoggedIn } = useATProtoLogin()
|
||||||
const { isReady, hasCredentialError } = useRepos()
|
const { isReady, hasCredentialError } = useRepos()
|
||||||
const {
|
const {
|
||||||
favoriteRepos,
|
favoriteRepos,
|
||||||
@@ -28,10 +29,10 @@ const isGitHubLoggedIn = computed(() => !!accessToken.value)
|
|||||||
const isAnyUserLoggedIn = computed(
|
const isAnyUserLoggedIn = computed(
|
||||||
() => isGitHubLoggedIn.value || isATProtoLoggedIn.value
|
() => isGitHubLoggedIn.value || isATProtoLoggedIn.value
|
||||||
)
|
)
|
||||||
const displayUsername = computed(() => username.value || handle.value || "")
|
|
||||||
const displayInitial = computed(() =>
|
const openProfile = () => {
|
||||||
(displayUsername.value[0] || "?").toUpperCase()
|
;(document.getElementById("profile_modal") as HTMLDialogElement)?.showModal()
|
||||||
)
|
}
|
||||||
|
|
||||||
const filterQuery = ref("")
|
const filterQuery = ref("")
|
||||||
const normalizedQuery = computed(() => filterQuery.value.trim().toLowerCase())
|
const normalizedQuery = computed(() => filterQuery.value.trim().toLowerCase())
|
||||||
@@ -102,29 +103,7 @@ const isStarred = (repo: RepoBase) => favoriteCheckboxes.value.includes(repo.id)
|
|||||||
class="navlink"
|
class="navlink"
|
||||||
>Getting started</router-link
|
>Getting started</router-link
|
||||||
>
|
>
|
||||||
<button
|
<UserPill @click="openProfile" />
|
||||||
v-if="isAnyUserLoggedIn"
|
|
||||||
class="profile-chip"
|
|
||||||
onclick="profile_modal.showModal()"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="isATProtoLoggedIn && avatarUrl"
|
|
||||||
:src="avatarUrl"
|
|
||||||
class="profile-avatar-small"
|
|
||||||
alt="Profile"
|
|
||||||
/>
|
|
||||||
<span v-else class="profile-avatar-small profile-avatar-initial">
|
|
||||||
{{ displayInitial }}
|
|
||||||
</span>
|
|
||||||
<span class="profile-name">{{ displayUsername }}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
class="hw-btn hw-btn-ghost"
|
|
||||||
onclick="profile_modal.showModal()"
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -344,29 +323,7 @@ const isStarred = (repo: RepoBase) => favoriteCheckboxes.value.includes(repo.id)
|
|||||||
</template>
|
</template>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- ── Profile modal ──────────────────────────────────── -->
|
<ProfileModal />
|
||||||
<dialog id="profile_modal" class="modal hw-modal">
|
|
||||||
<div class="modal-box hw-modal-box">
|
|
||||||
<div class="hw-modal-head">
|
|
||||||
<h3>Profile</h3>
|
|
||||||
<form method="dialog">
|
|
||||||
<button class="hw-modal-x" aria-label="close">×</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="hw-modal-section">
|
|
||||||
<div class="hw-ms-label mono">Bluesky / ATProto</div>
|
|
||||||
<sign-in-atproto :with-sign-out="true" />
|
|
||||||
</div>
|
|
||||||
<hr class="hw-rule" />
|
|
||||||
<div class="hw-modal-section">
|
|
||||||
<div class="hw-ms-label mono">GitHub</div>
|
|
||||||
<sign-in-github />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form method="dialog" class="modal-backdrop">
|
|
||||||
<button></button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -467,27 +424,10 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hw-btn-ghost {
|
|
||||||
border-color: var(--hw-rule);
|
|
||||||
color: var(--hw-ink-soft);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--hw-pink-wash);
|
|
||||||
border-color: var(--hw-pink);
|
|
||||||
color: var(--hw-pink-deep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mono {
|
.mono {
|
||||||
font-family: var(--hw-mono);
|
font-family: var(--hw-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hw-rule {
|
|
||||||
border: 0;
|
|
||||||
border-top: 1px solid var(--hw-rule);
|
|
||||||
margin: 1.25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Top nav ───────────────────────────────────────────────── */
|
/* ── Top nav ───────────────────────────────────────────────── */
|
||||||
.topnav {
|
.topnav {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -541,50 +481,6 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.25rem 0.75rem 0.25rem 0.25rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid var(--hw-rule);
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
color: var(--hw-ink);
|
|
||||||
transition:
|
|
||||||
border-color 0.15s,
|
|
||||||
background 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--hw-pink);
|
|
||||||
background: var(--hw-pink-wash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar-small {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar-initial {
|
|
||||||
background: var(--hw-pink);
|
|
||||||
color: white;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-name {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Hero ──────────────────────────────────────────────────── */
|
/* ── Hero ──────────────────────────────────────────────────── */
|
||||||
.rl-hero {
|
.rl-hero {
|
||||||
padding: 3rem 2rem 1rem;
|
padding: 3rem 2rem 1rem;
|
||||||
@@ -1015,62 +911,6 @@ main {
|
|||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Profile modal ─────────────────────────────────────────── */
|
|
||||||
.hw-modal .hw-modal-box {
|
|
||||||
background: var(--hw-paper);
|
|
||||||
border: 1px solid var(--hw-rule);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.3);
|
|
||||||
color: var(--hw-ink);
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-modal-head {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-family: var(--hw-serif);
|
|
||||||
font-size: 1.3rem;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-modal-x {
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--hw-ink-soft);
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--hw-pink-deep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-modal-section {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hw-ms-label {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
letter-spacing: 0.14em;
|
|
||||||
color: var(--hw-ink-faint);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Responsive ────────────────────────────────────────────── */
|
/* ── Responsive ────────────────────────────────────────────── */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.favs-grid {
|
.favs-grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user